面试题手写bind,原来没有我想得那么简单

24 阅读2分钟

导语:bind不仅是绑定上下文和默认参数,还需要应对一些如new调用这样的特殊场景,需要在内部判断调用环境改变上下文并设置原型

先来简单回顾一下bind是干啥的:

bind - 返回绑定上下文和参数的新函数,可用于生产偏函数

语法:let bound = func.bind(context, [arg1], [arg2], ...);

我马上懂了,这不就是用apply构建的语法糖,而bind的作用就是让你不用每次都写apply去绑定上下文和默认参数

唉╮(╯▽╰)╭,今天的手写代码每日一题又结束了

吭吭施工:

Function.prototype.myBind = function (context, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("The binding object must be a function");
  }
  const originFunc = this; // (*)
  return function () {
    return originFunc.apply(context,[...args, ...arguments]); 
  };
};

浅注意一下在(*)处要保存一下this,不能直接在返回的函数里写this,因为返回函数中this的值是该函数调用的时候才决定的

OK,大功告成,出于严谨,看看别人写的(其实也没什么好看的对吧)

// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
      fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context, // (*)
      args.concat(...arguments)
    );
  };
};

谢!(*)是啥?为嘛要整个这?

遇事不觉,MDN里学!

哦哦!是new,当我们用new调用绑定函数时...我自己也有点迷糊了w(゚Д゚)w

自己整个例子测测,哼

function Rabbit(name) {
  this.name = name;
}

const fake = {};

// myBind
let Rabbit_lee = Rabbit.myBind(fake, "lee");
let rab = new Rabbit_lee();

console.log(fake); //  { name: 'lee' }
console.log(rab); // {}
console.log(rab instanceof Rabbit); // false

// bind
Rabbit_lee = Rabbit.bind(fake, "lee");
rab = new Rabbit_lee();

console.log(fake); // {}
console.log(rab); // Rabbit { name: 'lee' }
console.log(rab instanceof Rabbit); // true

如上,JS的bind希望的是使用new调用时,原先绑定的上下文被忽略,而只使用绑定的默认参数

回顾一下,new关键字会进行如下操作:

  1. 创建一个空的简单JavaScript对象(即{})
  2. 将这个空对象的原型指向构造函数的prototype属性
  3. 将步骤1新创建的对象作为this的上下文
  4. 执行构造函数内部的代码
  5. 如果该函数没有返回对象,则返回this

所以说,针对这个特殊调用场景,我们要能自动改变绑定的上下文,来吧

因此,我们需要判断绑定函数bound是否在被new调用

因为new会先将this的原型指向构造函数的原型,所以此时的this是可以使用instanceof来判断是不是new新创造的实例的

那么代码来咯

Function.prototype.myBind = function (context, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("The binding object must be a function");
  }
  const originFunc = this;
  function bound() {
    return originFunc.apply(this instanceof bound ? this : context, [
      ...args,
      ...arguments,
    ]);
  }
  bound.prototype = Object.create(originFunc.prototype); // (*)
  return bound;
};

需要注意的是在(*),为了完整性,使实例可以用instanceof判断原型,我们也要为bound设置原型,因为他才是真的构造函数呀(有点隔壁老王的意思O(∩_∩)O~)