美文网首页
分析 MDN bind方法的Polyfill

分析 MDN bind方法的Polyfill

作者: 回调的幸福时光 | 来源:发表于2019-05-10 23:09 被阅读0次

一、bind 方法介绍

  1. bind 方法创建一个新的绑定函数
  2. bind 方法重新绑定原函数的 this 值
  3. 在调用绑定函数时,将bind 中的给定参数作为原函数的参数
function.bind(thisArg, arg1, arg2, ...)
  • thisArg
    调用绑定函数时,将原函数的 this 绑定为 thisArg
  1. 如果将绑定函数作为构造函数,通过关键字 new 调用,则忽略参数 thisArg

二、从简单到复杂

版本 1: apply 模拟 bind

版本一不考虑那么多,仅仅使用 apply 来实现一个简单的 bind。调用语法和 bind 相似:

function.myBind(context, arg1, arg2, ...)

这里核心就是在 myBind 函数中如何获取原函数?
利用 this 关键字即可达到目的,具体请看 梳理 this 关键字的指向 中的对象方法

Function.prototype.myBind = function (context) {
  const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
  // 新的绑定函数
  function fBound() {
    // 说明:这里返回的是原函数执行 apply 后返回的结果
    return fToBind.apply(context);
  }
  // 返回绑定函数
  return fBound;
}

调用示例:

demo 版本一
版本 2: 考虑参数

这里参数有两种情况:

  • myBind 的预设参数
  • 新的绑定函数 fBound 的参数
Function.prototype.myBind = function (context) {
  const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
  const outerArgs   = [].slice.call(arguments, 1);
  // 返回新的绑定函数
  function fBound () {
    const innerArgs = [].slice.call(arguments);
    // 说明:这里返回的是原函数执行 apply 后返回的结果
    return fToBind.apply(context, outerArgs.concat(innerArgs));
  }
  // 返回绑定函数
  return fBound;
}

调用示例:

在例子中利用 myBind 实现了偏函数,新的绑定函数 add 在调用时,只传入了一个参数 2, 但是结果是 1 + 2

demo 版本二
版本 3: 考虑构造函数的影响

3.1 如果将绑定函数作为构造函数,通过关键字 new 调用,则忽略参数 context。

通过 instanceof 即可判断是否通过 new 关键字调用构造函数。

Function.prototype.myBind = function (context) {
  const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
  const outerArgs   = [].slice.call(arguments, 1);
  // 返回新的绑定函数
  function fBound () {
    const innerArgs = [].slice.call(arguments);
    // 说明:这里返回的是原函数执行 apply 后返回的结果
    return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
  }
  // 返回绑定函数
  return fBound;
}

运行示例:
从以下例子中可以看出,Person 是通过 myBind 生成的新的绑定函数:

  • Person 作为构造函数,通过 new 关键字调用时,this 指向实例对象,obj 被忽略了。
  • Person 作为普通函数调用时, this 指向 obj。
demo 3.1

3.2 原型链的影响
上述的仍旧不够完美,Person 当作构造函数时,如果在函数 person 中执行语句 this.say(),就会报错。
因为 myBind 函数返回的绑定函数 fBound 的 prototype 和原函数的 prototype 并不相同。

那能不能直接简单粗暴地执行语句fBound.prototype = fToBind.prototype,将原函数的 prototype 赋值给 fBound 呢?

很明显这样的操作把 fBound 和 原函数的 prototype 强关联起来了,如果 fBound 函数的 prototype 将会影响到原函数的 prototype。

所以可以联想到通过 fBound.prototype = Object.create(fToBind.prototype) ,以原函数的 prototype 为模板,生成一个新的实例对象,并赋值给 fBound.prototype。

Function.prototype.myBind = function (context) {
  const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
  const outerArgs   = [].slice.call(arguments, 1);
  // 返回新的绑定函数
  function fBound () {
    const innerArgs = [].slice.call(arguments);
    // 说明:这里返回的是原函数执行 apply 后返回的结果
    return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
  }

  fBound.prototype = Object.create(fToBind.prototype);
  // 返回绑定函数
  return fBound;
}

运行示例:
从以下例子中可以发现,eat 函数之后被添加到 fBound 的原型上,而不会影响到原函数。

demo 3.2

3.3 mdn 中并未使用 Object.create()
因为 Object.create()bind 都是 ES5 规范提出的,如果不支持 bind, 那么bind 的 polyfill 里面自然不支持 Object.create()。

Object.create 的实现:

原型式继承:(参见 js 高级程序设计)

function object(o) {
  function F() {};
  F.prototype = o;
  return new F();
}

应用到 myBind 中:

Function.prototype.myBind = function (context) {
  const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
  const fNop = function () {};
  const outerArgs   = [].slice.call(arguments, 1);
  // 返回新的绑定函数
  function fBound () {
    const innerArgs = [].slice.call(arguments);
    // 说明:这里返回的是原函数执行 apply 后返回的结果
    return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
  }
   
  // 这里为什么需要判断?
  if (fToBind.prototype) {
    fNop.prototype = fToBind.prototype;
  }
  fBound.prototype = new fNop();
  // 返回绑定函数
  return fBound;
}

疑惑:为什么需要对 fToBind.prototype 进行判断?
因为 Function.prototype 是一个函数,而且它没有 prototype。
即防止有人这么调用 bind 方法: Function.prototype.bind()

3.4 instanceof 不准确

这个使用 new.target 即可。

参考

MDN bind
从一道面试题的进阶,到“我可能看了假源码”
stackoverflow
MDN Object.create

更深入的分析文章推荐

从一道面试题的进阶,到“我可能看了假源码”(2)
面试官问:能否模拟实现JS的bind方法
不用call和apply方法模拟实现ES5的bind方法

相关文章

  • 分析 MDN bind方法的Polyfill

    一、bind 方法介绍 bind 方法创建一个新的绑定函数 bind 方法重新绑定原函数的 this 值 在调用绑...

  • 手写bind函数

    参考文章:手写bind()函数,理解MDN上的标准Polyfill

  • 手写bind()函数

    MDN上的标准Polyfill

  • vue学习1

    Vue源码学习 基本函数 bind的polyfill方案 once方法 generateComponentTrac...

  • JS-手动实现bind、call方法

    实现bind方法 MDN文档指出:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 t...

  • bind Polyfill 详解javascript bind

    前端面试经常会遇到的一个面试题:手写bind方法 这篇文章就ECMAScript中bind Polyfill来详解...

  • bind理解

    MDN的解释是:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 ...

  • bind的-polyfill

    ////////////////// Function.prototype.bind = Function.pro...

  • 关于bind方法的polyfill问题

    见MDN的代码段, 我很疑惑: 问题1:此处为什么要判断 if (this.prototype) { 而不是直接指...

  • bind和call和apply

    bind() 方法与 apply 和 call 很相似,也是可以改变函数体内 this 的指向。MDN的解释是:b...

网友评论

      本文标题:分析 MDN bind方法的Polyfill

      本文链接:https://www.haomeiwen.com/subject/fofovqtx.html