《手写bind》

380 阅读3分钟

1. 确定输入输出

第一个参数是this, 第二个及以后的参数是函数参数

返回值是一个新的函数

function bind(asThis, p1, p2){
  
  return function(){}
}

export default bind

如果当前环境下,函数的原型上没有bind, 就绑定上去

if(!Function.prototype.bind) {
  Function.prototype.bind = bind
}

2.返回一个绑定this的函数

实现思路:返回一个函数,在这个函数里,给调用者函数传递this, 绑定参数,然后返回调用者函数

测试用例:

const bind = require("../src")

Function.prototype.bind2 = bind // 绑定自己写的bind

const fn1 = function () {
    return this
}

const newFn1 = fn1.bind2({'name': 'lisa'})  // newFn1 是一个返回this的新函数
console.assert(newFn1().name === 'lisa')

最简单的实现:

function bind(asThis) {
    const fn = this
    
    return function () {
        return fn.call(asThis)
    }
}

module.exports = bind

3.可以传this和其他参数

测试用例:

fn2 接受两个参数p1, p2, 并且返回[this, p1, p2]

const fn2 = function (p1, p2) {
    return [this, p1, p2]
}
const newFn2 = fn2.bind({'name':'lisa'}, 123, 456)
console.assert(newFn2()[0].name === 'lisa')
console.assert(newFn2()[1] === 123)
console.assert(newFn2()[2] === 456)

解法很简单,只需要读取函数参数即可

function bind(asThis, ...args) {
    const fn = this

    return function (...args2) {
        return fn.call(asThis, ...args, ...args2)
    }
}

4.更加兼容的写法

一个不支持Bind浏览器,肯定也不会支持const和...扩展符

我们需要用var 代替 const

用slice 代替 ...

最后用apply 来代替 return fn.call(asThis, ...args, ...args2)。因为apply才能接受参数是数组

var slice = Array.prototype.splice

function bind(asThis) {
    var fn = this
    // 因为arguments 不是数组,没有slice方法,所以用slice.call(arguments)
    var args = slice.call(arguments, 1) // 获取除了第0个参数this,之外的所有其他参数
    if (typeof fn !== "function") {
        throw new Error("bind必须调用在函数上")
    }


    return function () {
        var args2 = slice.call(arguments, 0) // 获取所有参数
        // 用apply传数组,合并两次的参数数组,用apply传
        return fn.apply(asThis, args.concat(args2))
    }
}

5.支持new

5.1 new的实现

首先来看看new的用法:

new fn1('x')之后得到了一个对象,{a:'x'}

new的实现有四步

new fn1('x') //等价于下面四句话

function createNew(fn, ...args) {
    var temp = {}  // 创建临时对象
    temp.__proto__ = fn.prototype // 给临时对象绑定原型
    fn.call(temp, ...args)  // 把临时对象作为this,调用函数,并传递参数
    return temp // 返回这个临时对象
}

5.2 原版Bind里关于new的用法

var fn1 = function(a) {
  this.a = a
}
var fn2 = fn1.bind(undefined, 'y')
new fn2() // 得到 {a:'y '}

实现可以使用new的关键在于,new会重新改变this, 会重新覆盖掉之前通过bind绑定的this

所以关键点之一就是:判断是否使用了new, 如果使用了new ,就用new的新this, 如果没有用new, 就用之前绑定的this

怎么判断是否用了new?

  1. resultFn.prototype.isPrototypeOf(this);
  2. 或者 this instanceof resultFn

并且一般来说let o2 = new fn2() 之后,o2.__proto__=fn2.prototype 即o2是fn2构造出来的,

  • o2 instanceof fn2 o2是fn2的一个实例
  • fn2.prototype.isPrototypeOf(o2)

但是我们希望 o2.__proto__=fn1.prototype 而不是fn2的原型,所以关键点之二就是重新绑定原型


5.3 最后实现

function _bind(asThis, ...args) {
    // this 就是函数
    var fn = this;
    function resultFn(...args2) {
        // resultFn.prototype.isPrototypeOf(this); 也可以用这句话判断是否用了new
        // 判断是否用了new, 对this做不同处理
        return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2);
    }
  // 重新绑定原型
    resultFn.prototype = fn.prototype;
    return resultFn;
}

5.4 更兼容的实现

var slice = Array.prototype.slice;
function bind(asThis) {
  // this 就是函数
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") {
    throw new Error("bind 必须调用在函数身上");
  }
  function resultFn() {
    var args2 = slice.call(arguments, 0);
    return fn.apply(
      resultFn.prototype.isPrototypeOf(this) ? this : asThis,
      args.concat(args2)
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}

5.5 测试用例

function test6(message) {
  // new 的时候绑定了 p1, p2,并且 fn 有 prototype.sayHi
  Function.prototype.bind2 = bind;
  const fn = function(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
  };
  fn.prototype.sayHi = function() {};
  const fn2 = fn.bind2(undefined, "x", "y");
  const object = new fn2();
  console.assert(object.p1 === "x", "x");
  console.assert(object.p2 === "y", "y");
  // console.assert(object.__proto__ === fn.prototype);
  console.assert(fn.prototype.isPrototypeOf(object));
  console.assert(typeof object.sayHi === "function");
}

本文为fjl的原创文章,著作权归本人和饥人谷所有,转载务必注明来源