改变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 };
  1. 给o对象添加一个add属性,这个时候 this 就指向了 o,
  2. o.add(5,7)得到的结果和add.call(o, 5, 6)相同。
  3. 但是给对象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

原文地址:https://www.cnblogs.com/xfcao/p/12978977.html