深入解析 bind 原理并自己实现 bind 和 apply 方法

151 阅读3分钟

一. bind原理

  1. 我们都知道apply,call,bind这三个方法都可以改变上下文this指向,bind与call/apply最大的不同就是call/apply会立即执行函数,直接返回结果,而bind会返回一个新的函数,调用的时候才会执行。
  2. 既然所有的函数都可以调用call,apply,bind,说明他们是Function原型上的方法,所以我们新写的newBind方法的挂载位置就是Function.prototype。
  3. 根据隐式绑定规则,谁调用函数,this就指向谁,比如obj.foo(),foo中的this就指向obj。
  4. arguments内置对象(因为我后面的代码会用到它,在这里先介绍一下)
    • 作用:可以获取所有实参
    • 特点:是个'伪数组',不是真正的数组,有length,有索引,但是没有数组方法pop,push等。 arguments数组

二. 原生bind方法使用及执行结果

我们可以先写一下原生的bind方法,观察一下执行结果,this会绑定成我们设置的obj对象,并且调用bind()时它没有立即执行,需要主动调用一下。

// 使用原生bind 运行结果
function testFn(arg1, arg2) {
  console.log("this", this); //{name: 'zhangsan'}
  console.log("name", this.name); // zhangsan
  return arg1 + arg2;
}
const testObj = {
  name: "zhangsan",
};
const fn = testFn.bind(testObj, 1, 2);
const result = fn();
console.log("result", result); //3

三. 自定义newBind

  1. 将newBind挂载到Function的原型上,这样所有函数都能调用该新方法
  2. 参数:使用arguments处理入参, 数组第一位是this要指向的对象,第二个到最后一个是函数传参
  3. 返回:返回一个新的函数,返回原函数的结果并继承参数
// 1. 挂载到Function.prototype
Function.prototype.newBind = function () {
  // 记录当前执行态的this
  const _this = this;
  // 2. 入参: args特点 => 第一个参数是要绑定this的对象, 第二项-最后一项是函数传参
  // 2.1 因为arguments对象是伪数组,先转成真正的数组
  const args = Array.prototype.slice.call(arguments);
  // 2.2 shift()方法获取this对象:删除数组第一项,并返回该项
  const newThis = args.shift();
  // 3. 返回:返回的是一个函数 => 构造一个函数 => 返回原函数的结果且传参继承,需要调用才会执行
  return function () {
    // 4. 内部原理和apply一致, 可以直接调用apply,也可以手写一个
    // return _this.apply(newThis, args);
    return _this.newApply(newThis, args);
  };
};

从上面代码我们可以看到,如果除去注释,并在返回的函数中使用原生的apply,手写bind代码很简单,就是拿到要绑定的this对象,返回一个函数,具体这个返回的函数内部原理是和apply一致的,接下来我们就实现一下apply。

四. 自定义newApply

// 自定义newApply
Function.prototype.newApply = function (context, args) {
  // 边缘检测,判断调用对象是不是函数.
  if (typeof this !== "function") {
    return TypeError("type error");
  }

  // 兜底,如果没有传入上下文对象,默认为window
  context = context || window;

  // 其实就是把老的this换成新的this
  // 1. 引入一个临时的函数,执行函数替换
  context.tempFn = this;
  // 2. 把老的fn,拿到新的下去执行
  const result = args?.length ? context.tempFn(...args) : context.tempFn();
  // 销毁临时挂载fn
  delete context.tempFn;
  return result;
};

五. 自定义newBind的使用

我们使用和上面测试原生bind时一样的对象及函数,调用newBind(), 可以发现打印的结果和上面的是一样的,我们自定义newBind可以正常使用。

// 自定义bind使用
function testFn(arg1, arg2) {
  console.log("this", this); //{name: 'zhangsan'}
  console.log("name", this.name); // zhangsan
  return arg1 + arg2;
}
const testObj = {
  name: "zhangsan",
};
const fn = testFn.newBind(testObj, 1, 2);
const result = fn();
console.log("result", result); //3

成功啦!撒花❀❀❀❀❀