改变this指向的三个函数call, apply, bind的实现
(一)call源码解析
先上一个call使用
1 function add(c, d) { 2 return this.a + this.b + c + d; 3 } 4 5 const obj = { a: 1, b: 2 }; 6 7 console.error(add.call(obj, 3, 4)); // 10
大统上的说法就是,call改变了this的指向。然后,介绍this xxx什么一大堆名词,反正不管你懂不懂,成功绕晕你就已经ok了,但是实际发生的过程,可以看成下面的样子。
1 const o = { 2 a: 1, 3 b: 2, 4 add: function(c, d) { 5 return this.a + this.b + c + d 6 } 7 };
- 给o对象添加一个add属性,这个时候 this 就指向了 o,
- o.add(5,7)得到的结果和add.call(o, 5, 6)相同。
- 但是给对象o添加了一个额外的add属性,这个属性我们是不需要的,所以可以使用delete删除它。
so, 基本为以下三部。
1 // 1. 将函数设为对象的属性 2 o.fn = bar 3 // 2. 执行该函数 4 o.fn() 5 // 3. 删除该函数 6 delete o.fn
//基于ES3 实现call
1 Function.prototype.es3Call = function (context) { 2 var content = context || window; 3 content.fn = this; 4 var args = []; 5 // arguments是类数组对象,遍历之前需要保存长度,过滤出第一个传参 6 for (var i = 1, len = arguments.length ; i < len; i++) { 7 // 避免object之类传入 8 args.push('arguments[' + i + ']'); 9 } 10 var result = eval('content.fn('+args+')'); 11 delete content.fn; 12 return result; 13 } 14 console.error(add.es3Call(obj, 3, 4)); // 10 15 console.log(add.es3Call({ a: 3, b: 9 }, 3, 4)); // 19 16 console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4
//基于ES6 实现call, 其实差别不大,es6新增… rest,这就可以放弃eval的写法
1 // ES6 call 实现 2 Function.prototype.es6Call = function (context) { 3 var context = context || window; 4 context.fn = this; 5 var args = []; 6 for (var i = 1, len = arguments.length; i < len; i++) { 7 args.push('arguments[' + i + ']'); 8 } 9 const result = context.fn(...args); 10 return result; 11 } 12 13 console.error(add.es6Call(obj, 3, 4)); 14 console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4
(二)apply源码解析
apply和call区别在于apply第二个参数是Array,而call是将一个个传入
// 基于es3实现
1 Function.prototype.es3Apply = function (context, arr) { 2 var context = context || window; 3 context.fn = this; 4 var result; 5 if (!arr) { 6 result = context.fn(); 7 } else { 8 // 获取参数 9 var args = []; 10 for (var i = 0, len = arr.length; i < len; i++) { 11 args.push('arr[' + i + ']'); 12 } 13 // 执行函数 14 result = eval('context.fn(' + args + ')') 15 } 16 delete context.fn; 17 return result 18 } 19 20 console.log(add.es3Apply(obj, [1, 'abc', '2'])); // 4abc 21 // console.log(add.apply(obj, [1, 2]));
// 基于es6实现
1 Function.prototype.es6Apply = function (context, arr) { 2 var context = context || window; 3 context.fn = this; 4 var result; 5 if (!arr) { 6 result = context.fn(); 7 } else { 8 if (!(arr instanceof Array)) throw new Error('params must be array'); 9 result = context.fn(...arr); 10 } 11 delete context.fn; 12 return result 13 } 14 15 console.error(add.es6Apply(obj, [1, 2])); // 6
(三)bind源码解析
bind() 方法会创建一个新函数。
当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,
之后的一序列参数将会在传递的实参前传入作为它的参数。
//ES3实现
1 Function.prototype.es3Bind = function (context) { 2 if (typeof this !== "function") throw new TypeError('what is trying to be bound is not callback'); 3 var self = this; 4 var args = Array.prototype.slice.call(arguments, 1); 5 const fBound = function () { 6 // 获取函数的参数 7 var bindArgs = Array.prototype.slice.call(arguments); 8 // 返回函数的执行结果 9 // 判断函数是作为构造函数还是普通函数 10 // 构造函数this instanceof fNOP返回true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值。 11 // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context 12 return self.apply(this instanceof fNOP ? this: context, args.concat(bindArgs)); 13 } 14 // 创建空函数 15 var fNOP = function () {}; 16 // fNOP函数的prototype为绑定函数的prototype 17 fNOP.prototype = this.prototype; 18 // 返回函数的prototype等于fNOP函数的实例实现继承 19 fBound.prototype = new fNOP(); 20 // 以上三句相当于Object.create(this.prototype) 21 return fBound; 22 } 23 24 var func = foo.es3Bind({a: 1}, '1st'); 25 func('2nd'); // 1 100 1st 2nd 26 func.call({a: 2}, '3rd'); // 1 100 1st 3rd 27 new func('4th'); //undefined 100 1st 4th
//es6实现
1 Function.prototype.es6Bind = function(context, ...rest) { 2 if (typeof this !== 'function') throw new TypeError('invalid invoked!'); 3 var self = this; 4 return function F(...args) { 5 if (this instanceof F) { 6 return new self(...rest, ...args) 7 } 8 return self.apply(context, rest.concat(args)) 9 } 10 } 11 12 var func = foo.es3Bind({a: 1}, '1st'); 13 func('2nd'); // 1 100 1st 2nd 14 func.call({a: 2}, '3rd'); // 1 100 1st 3rd 15 new func('4th'); //undefined 100 1st 4th
1 var obj ={ 2 value: 1 3 } 4 function bar(yeah, ok){ 5 this.age = "18" 6 console.log(yeah) 7 console.log(ok) 8 console.log(this.value) 9 } 10 var fbind = bar.bind(obj,'我很棒') 11 var fBind = new fbind('是的') 12 console.log(fBind.age) //18 13 //fbind('是的') // 1 14 15 // 实现 1、保存this返回一个函数 2、可以传入参数 3、一个绑定函数也能使用new操作符创建对象:这种行为就像把函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数 16 Function.prototype.bind2 = function(context){ 17 var self = this 18 //console.log(this) // this指向bar函数 接下来让this指向obj 19 var args = Array.prototype.slice.call(arguments,1) // 将类数组arguments转为真正的数组 20 // console.log(args) 21 var fResult = function(){ 22 var args2 = Array.prototype.slice.call(arguments) 23 // console.log(args2) 24 // console.log(this) 25 self.apply(this instanceof self? this : context,args.concat(args2)) // 三元表达式处解读 this是bar函数的实例吗 是返回this 不是返回context 为了兼容 2 、3点需求 26 } 27 fResult.prototype =bar.prototype // bar函数的原型赋给fResult函数 就拥有bar函数属性与方法了 28 return fResul 29 } 30 var fbind2 = bar.bind2(obj,'孤独的人会走的更远') 31 var fBind2 = new fbind2('脑子不用就会生锈') 32 console.log(fBind2.age) 33 // fbind2('脑子不用就会生锈')
原文链接:https://blog.csdn.net/u010377383/java/article/details/80646415