bind 用法及简单实现原理

前言

bind 和 call/apply 一样,都是用来改变上下文 this 指向的,不同的是,call/apply 是直接使用在函数上,而 bind 绑定 this 后返回一个函数(闭包),如下:

var obj = {
    init: 1,
    add: function(a, b) {
        return a + b + this.init;
    }
}
obj.add(1, 2); // 4

var plus = obj.add;
plus(3, 4); // NaN,因为 this.init 不存在,这里的 this 指向 window/global

plus.call(obj, 3, 4) // 8
plus.apply(obj, [3, 4]); // 8, apply 和 call 的区别就是第二个参数为数组
plus.bind(obj, 3, 4); // 返回一个函数,这里就是 bind 和 call/apply 的区别之一,bind 的时候不会立即执行
plus.bind(obj, 3, 4)(); // 8 

有趣的测试

测试一

假如,call、apply、bind 第一个参数是 null 或者是 undefined 会是什么结果?

function test() {
    console.log(this);
}
test(); // this === window

// call
test.call(null); // this === window
test.call(undefined); // this === window

// apply
test.apply(null); // this === window
test.apply(undefined); // this === window

// bind
test.bind(null)(); // this === window
test.bind(undefined)(); // this === window

上述测试只是在非严格的模式下进行,感兴趣的话可以测试一下严格模式。

测试二

前面知道 bind 是返回一个函数,它的执行很像函数柯里化的一个过程,如下:

// 柯里化
function test(x) {
    return function(y){
        return x + y;
    }
};
test(1)(2); // 3

// bind
function test2(a, b) {
    return a + b;
}
test2.bind(null, 1)(2); // 3

这是是 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 返回一个函数,那么这个函数当做构造函数会发生什么?

function Test3(a, b) {
    this.a = a;
    this.b = b;
}
Test3.prototype.add = function () {
    return this.a + this.b;
}

// 如果不用 bind,正常来说这样处理
var t1 = new Test3(1, 2);
t1.add(); // 3, this 指向 t1

// 使用 bind
var NewTest3 = Test3.bind(null, 3);
var t2 = new NewTest3(4);
t2.add(); // 7, this 指向 t2

将绑定函数当做构造函数使用,bind 提供的 this 指向无效,但是还是可以预设参数。

bind 的实现

通过以上测试,知道 bind 的用法及原理,还是希望能动手一步一步实现一下:

// 利用 apply 改变 this 指向
Function.prototype.bind = function(context) {
    var that = this;

    return function() {
       return that.apply(context, arguments);
    }
}

上述实现只能传递一个参数(context),原生的 bind 方法可以传入多个参数,作如下修改:

// 将 bind 方法的参数提取出来拼接到返回的闭包函数中
Function.prototype.bind = function(context) {
    var that = this;
    var args = Array.prototype.slice.call(arguments, 1);


    return function() {
       // 预设参数一定是 args 在前拼接
       return that.apply(context, args.concat(Array.prototype.slice.call(arguments))) ;
    }
}

修改之后可以传多个参数了,测试一和测试二的场景也满足,但是测试三的场景不满足(不能作为构造函数):

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

    var bound = function() {
       if(this instanceof bound) { // 如果是作为构造函数,那么第一个参数 context 就不需要了
            return that.apply(this, args.concat(Array.prototype.slice.call(arguments)));
       } else {
            return that.apply(context, args.concat(Array.prototype.slice.call(arguments))) ;
       }
    }

    // 维护原型关系
    if(this.prototype) {
        bound.prototype = this.prototype;
    }

    return bound;
}

好了,一个简单版的 bind 完成,满足上面三个测试项。

总结

平时我用到的最多的场景,就是简单的使用 bind 来改变 this 指向,那样做和 call/apply 没什么区别,只是一个延迟执行的作用,以为 bind 很简单,现在看来,其实并不简单。。。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值