这些都是js基础进阶的必备了,有时候可能一会儿想不起来是什么,时不时就回头看看基础,加强硬实力。javascript
谁最后调用,就指向谁
先简单复习一次,this指向就那么几种:java
指向new 建立的对象git
function F() { this.name = 1 } var f = new F()
指向传入函数的第一个参数。a.call(b),函数a内部若是是要用到this。则这个this指向bgithub
对象内部的方法指向对象自己数组
var obj = { value: 5, printThis: function () { console.log(this); } };
指向全局缓存
var obj = { value: 5, printThis: function () { console.log(this); } }; var f = obj.printThis f()
5.箭头函数
指向箭头函数定义时外层上下文app
var obj = { value: 5, printThis: function () { return function(){ console.log(this)} } }; obj.printThis()//window var obj = { value: 5, printThis: function () { return () => console.log(this) } }; obj.printThis()()//obj
前二者都是同样,只是参数表现形式不一样,bind表示的是静态的前二者,须要手动调用a.call(b,args)
让函数a执行上下文指向b,也就是b的属性就算没有a函数,也能像b.a(args)这样子调用函数
方法你们都知道,咱们不妨来本身实现一下这三个:this
再看一次概念,b没有a方法,也就是没有b.a,若是想要这个效果,那就利用这三个函数来改变执行上下文。因而咱们就能够想到,要是本身实现一个,大概就是,给b强行加上这个a 的方法,而后拿到argument去调用:spa
Function.prototype.mycall = function(){ var ctx = arguments[0]||window||global//获取上下文,call的第一个参数 var len = arguments.length var hash = new Date().getTime()//避免名字重复 ctx[hash] = this//将this缓存,this就是那个想在另外一个上下文利用的函数 var result if(len === 1){ result = ctx[hash]()//若是后面没有其余参数直接运行 } else{ var i = 1 var args = [] for(;i<len;i++){ args.push(arguments[i]) } args = args.join(',') result = eval('ctx[hash](' + args + ')')//将参数传递进去调用 } delete ctx[hash]//删除临时增长的属性 return result }
apply也是同理,并且少了数组这一步,更加简单接下来咱们看一下bind怎么实现:
Function.prototype.mybind = function(){ var ctx = arguments[0]||window||global var f = this var args1 = [] if(arguments.length>1){//预先填入的参数 var i = 1 for(;i < arguments.length;i++){ args1.push(arguments[i]) } } return function(){ var args2 = Array.prototype.slice.call(arguments)//call和apply咱们均可以实现,这里就再也不重复 return f.apply(ctx,args1.concat(args2))//将预先填入的参数和执行时的参数合并 } }
此外,须要注意的,一个函数被bind后,之后不管怎么用call、apply、bind,this指向都不会变,都是第一次bind的上下文
首先,js没有严格意义上的子类父类,实现继承是依靠原型链来实现相似于所谓的类的效果。
咱们但愿G继承F,或者是说,开发的时候,因为G有不少属性继承F咱们想偷懒,那么就能够这样
function F(name,age){ this.name = name this.age = age } function G(name,age,a) { F.call(this,...arguments) this.a = a } var g = new G('a',12,1) //G {name: "a", age: 12, a: 1}
这个方法特别之处是,子类能够向父类构造函数传参。可是,没法获取F的原型上的属性。
另外,方法也是写在内部
this.f = function(){}
也注定没法实现函数复用了,每个实例都有一个函数,浪费内存。
要想子类得到父类的属性,若是是经过原型来实现继承,那么就是父类的一个实例是子类的原型:
function F(){ this.a = [1,2,3,4] this.b = 2 } var f = new F() function G(){} G.prototype = f var g = new G() var h = new G() g.a //[1,2,3,4] g.b //2 //对于引用类型,若是咱们修改g.a(不是用=赋值,用=不会操做到原型链) g.a.push(123) g.a//[1,2,3,4,123] //并且其余的实例也会变化 h.a //[1,2,3,4,123] g.b = 666 //只是在实例里面对b属性进行改写,不会影响原形链
能够看见,对于父类的引用类型,某个值是引用类型的属性被改写后,子类的全部的实例继承过来的属性都会变,主要的是,子类均可以改变父类。可是=赋值操做至关于直接在某一个实例上面改写。由于属性查找是按照原型链查找,先查找自身再查找原型链,找到为止。用了等号,先给自身赋值,因此自身赋值成功了也不会继续去原型链查找。
由于都有各自的缺陷,因此就有一种组合继承,将构造函数继承和prototype继承混合起来,方法写在父类的prototype上,是比较常见的方法。可是实例化都会调用两次构造函数,new和call
这样子,能够在两个prototype中间加上一个中介F类,使得子类不会污染父类,子类A是父类B继承而来,并且还能够在中间给他定义属性
function A() {} function B() {} A.prototype = Object.create(B.prototype,{father:{value:[1,2,3]}}); //Object.create的hack Object.create =Object.create|| function (o) { var F = function () {}; F.prototype = o; return new F(); } //其实create函数内部的原理就是这样子,看回去上面的A和B,这些操做至关于 var F = function () {}; F.prototype = B.prototype;//原型被重写,a.__proto__.constructor是B而不是F A.prototype = new F() //create方法,第二个参数相似于defineProperty,并且定义的属性能够自行配置,默认是不能够从新赋值 var a = new A() a.father //[1,2,3] a.father = 1 a.father //[1,2,3]
在不须要动用构造函数的时候,只是想看到让子类父类这种继承关系,create基本上是完美选择
利用一个封装好继承过程的函数来实现继承,不须要另外定义一个子类,直接把子类的方法写在函数里面
function createobj (obj) { var temp = Object.create(obj) temp.f = function () { console.log('this is father') } return temp } function B() {} var b = createobj (B.prototype) b.f() //this is father
可是,不能作到函数复用,每个实例都要写一份,并且写了一个createobj就是写死了,也不能获取B类的内部属性
对于上面的仅仅依靠Object.create继承,a.__proto__原型对象被重写,他的构造函数是B,而不是中间量F,对于这种中间类F无心义,并且只是依靠中间原型对象,咱们能够用比较完美的寄生组合式继承:
function A() {} function B() {} var prototype = Object.create(B.prototype)//建立 prototype.constructor = A//加强 A.prototype = prototype//指定,这下a.__proto__.constructor 就是A了 var a = new A()
不用建立中间类F,并且构造函数A的确是造出a的(a.__proto__.constructor == A),而不是像create那样改写原型链,构造函数是B
附上原型链图解:(注意终点是null,中间的都是正常new构造,没有改写prototype)