Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端手写系列02-手写bind的三重境界 #14

Open
venaissance opened this issue Jul 23, 2020 · 0 comments
Open

前端手写系列02-手写bind的三重境界 #14

venaissance opened this issue Jul 23, 2020 · 0 comments

Comments

@venaissance
Copy link
Owner

venaissance commented Jul 23, 2020

本文是前端手写系列的第二篇,讲如何手写一个 bind,会从 bind 用法开始,逐渐带你领略手写 bind 的三种境界。

bind 用法

bind 是用来干嘛的?跟 bind 发音其实很像,是用来绑定 this 的。

具体怎么用呢?让我们先来看一个 bind 的简单用法:

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // unboundGetX 声明为全局函数
// expected output: undefined

const boundGetX = unboundGetX.bind(module); // 把 unboundGetX 的 this 绑定成module
console.log(boundGetX());
// expected output: 42

语法很简单,就是函数绑定 this —— function.bind(thisArg[, arg1[, arg2[, ...]]]),它返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

既然 bind 用法不难,大家有没有想过要怎么实现一个 bind 呢?

三重境界实现 bind

  1. 初阶:ES6
    • 优点:代码简洁,使用 const... 操作符
    • 缺点:不兼容 IE,不支持 new
  2. 中阶:ES5
    • 优点:兼容 IE
    • 缺点:参数获取复杂,不支持 new
  3. 高阶:成年人全都要
    • 优点:兼容性最高,且支持 new
    • 缺点:最复杂

初阶

这里直接上代码了,不熟悉ES6的可以试着用用展开操作符...,很好很强大

function bind1(asThis, ...args) {
  const fn = this; // 这里的 this 就是调用 bind 的函数 func
  return function (...args2) {
    return fn.apply(asThis, ...args, ...args2);
  };
}

中阶

进阶中阶的话,我们需要注意参数不能再用 ...args 拿了,要使用 Array.prototype.slice(arguments)

function bind2(asThis) {
  var slice = Array.prototype.slice;
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") { // 加入了对调用函数类型的判断
    throw new Error("cannot bind non_function");
  }
  return function () {
    var args2 = slice.call(arguments, 0);
    return fn.apply(asThis, args.concat(args2));
  };
}

高阶

终于来到高阶了,写之前,让我们先来看下应当如何判断一个对象实例是否是通过 new 构造函数() 创建出来的。

const temp = new fn(args) 其实等价于如下代码:

const temp = {}
temp.__proto__ = fn.prototype
fn.call(temp, ...args)
return temp

其中最核心的是第二句:temp.__proto__ = fn.prototype,�因为 new 出来的对象实例必定满足这个条件,我们便知道可以用 fn.prototype 是否为对象实例的原型来处理类似 new (funcA.bind(thisArg, args)) 的情况。

function bind3(asThis) {
  var slice = Array.prototype.slice;
  var args1 = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") {
    throw new Error("Must accept function");
  }
  function resultFn() {
    var args2 = slice.call(arguments, 0);
    return fn.call(
      resultFn.prototype.isPrototypeOf(this) ? this : asThis, // new 的情况下 this 改绑成 new 出来的对象实例
      args1.concat(args2)
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}
@venaissance venaissance changed the title 前端手写系列02-手写bind的三种境界 前端手写系列02-手写bind的三重境界 Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant