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

bind的模拟实现 #23

Open
songning0605 opened this issue Aug 12, 2020 · 0 comments
Open

bind的模拟实现 #23

songning0605 opened this issue Aug 12, 2020 · 0 comments
Labels

Comments

@songning0605
Copy link
Owner

songning0605 commented Aug 12, 2020

参考

bind

一句话介绍 bind:

bind()方法会创建一个新的函数。当这个新函数被调用时,bind的第一个参数会作为它运行时的this,其余的参数将会在传递的实参前传入,作为它的参数。

由此我们可以首先得出 bind 函数的两个特点:

  1. 可以指定this
  2. 返回一个函数
  3. 可以传入参数
  4. 柯里化

返回函数的模拟实现

从第一个特点开始,我们举个例子:

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

// 返回了一个函数
var bindFoo = bar.bind(foo); 

bindFoo(); // 1

模拟实现第一步(指定this,返回函数)

对于第 1 点,使用 call / apply 指定 this 。

对于第 2 点,使用 return 返回一个函数。

Function.prototype.bind2 = function (context) {
    var self = this; // this 指向调用者
    return function () { // 实现第 2点
        return self.apply(context); // 实现第 1 点
    }

}

之所以 return self.apply(context),是考虑到绑定函数可能是有返回值的,依然是这个例子

var foo = {
    value: 1
};

function bar() {
	return this.value;
}

var bindFoo = bar.bind(foo);

console.log(bindFoo()); // 1

模拟实现第二步(传参数的模拟实现)

对于第3点,可以传入参数,使用 arguments 获取参数数组并作为 self.apply() 的第二个参数,实现参数传递的功能。

// 第二版
Function.prototype.bind2 = function (context) {

    var self = this;
    // 实现第3点,因为第1个参数是指定的this,所以只截取第1个之后的参数
	// arr.slice(begin); 即 [begin, end]
    var args = Array.prototype.slice.call(arguments, 1); 

    return function () {
        // 实现第4点,这时的arguments是指bind返回的函数传入的参数
        // 即 return function 的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply( context, args.concat(bindArgs) );
    }
}

模拟实现第三步(构造函数效果的模拟实现)

到现在已经完成大部分了,但是还有一个难点,bind 有以下一个特性

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

上面例子中,运行结果this.value 输出为 undefined,这不是全局value 也不是foo对象中的value,这说明 bind 的 this 对象失效了,new 的实现中生成一个新的对象,这个时候的 this指向的是 obj。

所以我们可以通过修改返回的函数的原型来实现,让我们写一下:

Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
        // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
        return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
    fBound.prototype = this.prototype;
    return fBound;
}

构造函数效果的优化实现

但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:

Function.prototype.bind2 = function (context) {

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

异常处理

到这里其实已经差不多了,但有一个问题是调用 bind 的不是函数,这时候需要抛出异常。

if (typeof this !== "function") {
  throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

所以完整版模拟实现代码如下:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant