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

js bind方法实现 #7

Open
Vxee opened this issue Jun 30, 2018 · 0 comments
Open

js bind方法实现 #7

Vxee opened this issue Jun 30, 2018 · 0 comments

Comments

@Vxee
Copy link
Owner

Vxee commented Jun 30, 2018

bind方法可以用来给一个方法绑定上下文环境对象,以及重新给方法传参数。
bind的另一个简单使用是使一个函数拥有预设的初始参数。我们称为偏函数

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

由于bind方法在并不是在所有的浏览器上都支持,因此我们考虑自己实现bind方法。
首先我们可以给目标函数指定作用域来简单实现bind

Function.prototype.bind = function(context){
    self = this;
    return function(){
        return self.apply(context, arguments);
    }
}

这样实现的bind方法就只能接受一个上下文环境变量的参数,不能同时接受参数。因此我们修改一下。

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this;
    return function(){
        var _inargs = slice.call(arguments);
        return self.apply(context, _args.concat(_inargs));
    }
}

现在bind可以绑定对象,同时也能在绑定对象时传递参数。
但是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

我们可以通过修改函数的返回原型来实现,代码如下:

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this,
        fBound = function(){
            var _inargs = 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(_inargs));
        };
        // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
        fBound.prototype = self.prototype;
        return fBound;
}

bound.prototype = this.prototype这么写的话,修改返回函数原型对象(bound.prototype)的同时把绑定函数的原型对象(this.prototype也同时修改了。因此用匿名函数做中转,this.protptype 就安全了。
还有几个小问题的解决:

  • 调用bind不是函数
  • bind兼容性的问题
    最终的完整代码如下:
fakeBind = function (context) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var slice = Array.prototype.slice,
        _args = slice.call(arguments, 1),
        self = this,
        F = function () {},
        bound = function () {
            var _inargs = slice.call(arguments);
            return self.apply((this instanceof F ? this : context), _args.concat(_inargs));
        };
    F.prototype = self.prototype;
    bound.prototype = new F();
    return bound;
}

Function.prototype.bind = Function.prototype.bind || fakeBind;
ES6版实现
Function.prototype.fakeBindES6 = function(context, ...rest) {
  if (typeof this !== "function") {
    throw new Error("Bind must be called on a function");
  }
  var self = this;
  return function inner(...args) {
    if (this instanceof inner) {
      // 当返回的内层函数作为构造函数使用,bind 时绑定的 this 失效。
      // 即此处直接执行绑定函数,而不使用 apply 对 this 进行绑定 
      return new self(...rest, ...args);
    }
    // 当作为普通函数调用,this 指向传入的对象
    return self.apply(context, rest.concat(args));
  };
};
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