bind实现

bind功能

  1. 函数调用,改变this
  2. 返回一个绑定this的函数
  3. 接收多个参数
  4. 支持柯里化形式传参 fn(1)(2)
 Function.prototype.mybind = function(context) {
   if(typeof this !== 'function'){
      throw new TypeError(this + 'must be a function');
    }

    //返回一个绑定this的函数,我们需要在此保存this
    let self = this
      // 可以支持柯里化传参,保存参数
    let arg = [...arguments].slice(1) // 可改成let args = [].slice.call(arguments, 1);
      // 返回一个函数
    var bound =  function() {
      //同样因为支持柯里化形式传参我们需要再次获取存储参数
      let newArg = [...arguments] // 可改成let newArg = [].slice.call(arguments);
      console.log(newArg)
        // 返回函数绑定this,传入两次保存的参数
        //考虑返回函数有返回值做了return
      return self.apply(context, arg.concat(newArg))
    }
     return bound;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

测试demo

let Person = {
  name: 'Tom',
  say(age) {
      console.log(this)
      console.log(`我叫${this.name}我今年${age}`)
  }
}
Person1 = {
    name: 'Tom1'
}

let fn = Person.say.mybind(Person1)
fn()
fn(18)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

若要继续深入可往下看

var obj = {
    name: '若川',
};
function original(a, b){
    console.log('this', this); // original {}
    console.log('typeof this', typeof this); // object
    this.name = b;
    console.log('name', this.name); // 2
    console.log('this', this);  // original {name: 2}
    console.log([a, b]); // 1, 2
}
var bound = original.bind(obj, 1);
var newBoundResult = new bound(2);
console.log(newBoundResult, 'newBoundResult'); // original {name: 2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

从此例可以看出this指向了new bound()生成的新对象,bind原先指向obj的失效了,其他参数有效。所以相当于new调用时,bind的返回值函数bound内部要模拟实现new实现的操作。

Function.prototype.mybind = function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' must be a function');
    }
    // 存储调用bind的函数本身
    var self = this;
    // 去除thisArg的其他参数 转成数组
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        // bind返回的函数 的参数转成数组
        var boundArgs = [].slice.call(arguments);
        var finalArgs = args.concat(boundArgs);
        // new 调用时,其实this instanceof bound判断也不是很准确。es6 new.target就是解决这一问题的。
        if(this instanceof bound){
            // 1.创建一个全新的对象
            // 2.并且执行[[Prototype]]链接
            // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
            // self可能是ES6的箭头函数,没有prototype,所以就没必要再指向做prototype操作。
            if(self.prototype){
                // ES5 提供的方案 Object.create()
                // bound.prototype = Object.create(self.prototype);
                // 但 既然是模拟ES5的bind,那浏览器也基本没有实现Object.create()
                // 所以采用 MDN ployfill方案 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
                function Empty(){}
                Empty.prototype = self.prototype;
                bound.prototype = new Empty();
            }
            // 生成的新对象会绑定到函数调用的`this`。
            var result = self.apply(this, finalArgs);

            // 如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),
            // 那么`new`表达式中的函数调用会自动返回这个新的对象。
            var isObject = typeof result === 'object' && result !== null;
            var isFunction = typeof result === 'function';
            if(isObject || isFunction){
                return result;
            }
            return this;
        }
        else{
            // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
            return self.apply(thisArg, finalArgs);
        }
    };
    return bound;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

参考