JS实现一个bind

第一层 - 绑定在原型上的方法

由于 function xxx 的原型链 指向的是 Function.prototype , 因此我们在调用 xxx.bind 的时候,调用的是  Function.prototype 上的方法。

Function.prototype._bind = function() {}

第二层 - 改变 this 的指向

Function.prototype._bind = function(thisObj) {
 const self = this;
 return function () {
    self.apply(thisObj);
  }
}

测试

var obj = {a:1}
function myname() {console.log(this.a)}
myname._bind(obj)(); // 1

第三层 - 支持柯里化

通过获取当前外部函数的  arguments ,并且去除了绑定的对象,保存成变量 args,最后 return 的方法,再一次获取当前函数的  arguments, 最终用 finalArgs 进行了一次合并

Function.prototype._bind = function(thisObj) {
 const self = this;
  const args = [...arguments].slice(1)
 return function () {
    const finalArgs = [...args, ...arguments]
    self.apply(thisObj, finalArgs);
  }
}

测试

var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
myFun1(3); // 7

第四层 - 考虑 new 的调用

通过 bind 绑定之后,依然是可以通过 new 来进行实例化的, new 的优先级会高于 bind

// 原生
var obj = { i: 1}
function myFun(a, b, c) {
  // 此处用new方法,this指向的是当前函数 myFun 
  console.log(this.i + a + b + c);
}
var myFun1 = myFun.bind(obj, 1, 2);
new myFun1(3); // NAN



// 第四层的 bind
var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
new myFun1(3); // 7

注意,这里使用的是 bind方法

因此我们需要在 bind 内部,对 new 的进行处理。而 new.target 属性,正好是用来检测构造方法是否是通过 new 运算符来被调用的。

而根据 MDNnew 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(设置该对象的constructor)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this
Function.prototype._bind = function(thisObj) {
 const self = this;
  const args = [...arguments].slice(1);
 return function () {
    const finalArgs = [...args, ...arguments];
  // new.target 用来检测是否是被 new 调用
    if(new.target !== undefined) {
      // this 指向的为构造函数本身
      var result = self.apply(this, finalArgs);
      // 判断改函数是否返回对象
      if(result instanceof Object) {
        return reuslt;
      }
      // 没有返回对象就返回 this
      return this;
    } else {
      // 如果不是 new 就原来的逻辑
      return self.apply(thisArg, finalArgs);
    }
  }
}

第五层 - 保留函数原型

当我们的构造函数有 prototype 属性的时候,就出问题啦。因此我们需要给 prototype 补上,还有就是调用对象必须为函数

Function.prototype._bind = function (thisObj) {
  // 判断是否为函数调用
  if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {
    throw new TypeError(this + ' must be a function');
  }
  const self = this;
  const args = [...arguments].slice(1);
  var bound = function () {
    var finalArgs = [...args, ...arguments];
    // new.target 用来检测是否是被 new 调用
    if (new.target !== undefined) {
      // 说明是用new来调用的
      var result = self.apply(this, finalArgs);
      if (result instanceof Object) {
        return result;
      }
      return this;
    } else {
      return self.apply(thisArg, finalArgs);
    }
  };
  if (self.prototype) {
    // 为什么使用了 Object.create? 因为我们要防止,bound.prototype 的修改而导致self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会导致原函数的原型被修改。
    bound.prototype = Object.create(self.prototype);
    bound.prototype.constructor = self;
  }
  return bound;
};

参考:https://github.com/Raynos/function-bind

posted @ 2021-08-06 15:03  盼星星盼太阳  阅读(113)  评论(0)    收藏  举报