如何理解,javascript bind

阅读 7.2k
4 个回答

举个栗子:

你(son)和你爸(papa)在一块的时候,大家都会知道你爸爸的名字(不知道可以直接问啊!),用代码来说就是:

var papa = {
    name: "li gang",
    son: function() {
        return this.name;
    }
};
alert( papa.son() );

但是你不和你爸在一块的时候呢?就成了这样:

var son = papa.son;
alert( son() );

你会发现你大家没办法知道你爸的名字了,然后你就不能为非作歹了。好在你还记得你爸的电话号码,打个电话就又可以兴风作浪又知道了:

alert( son.call(papa) );

然后你想了想,不行啊,不能我每次兴风作浪的时候都得打我爸电话吧,我爸还不得弄死我。诶!这么笨,我随身带着我爸的身份证复印件不就好了嘛,所以你就成了这样:

son = son.bind( papa );
alert( son() );

哈哈,爸爸再也不用担心我出门被打叻(๑´ㅂ`๑)!

就这样又过了半年你爸爸大人作死收了一个养子!什么!遗产全部都是我的!养子的中文名叫 二狗子,英文名叫 er~Gou~Zi:

function erGouZi() {
    return this.name;
}

这个二狗子养子也想知道你爸的名字好出去泡妞把妹歌功颂德,哀求了半天之后,老爸也给了他一份复印件:

erGouZi = erGouZi.bind( papa );
alert( erGouZi() );

二狗子这货啊表示非常高兴,决定每次报出爸爸的大名的时候都要加上敬爱的三个字,就像我们经常说敬爱的 x 总理 一样,于是他机智的做了一次小手术:

erGouZi = function() {
    var prefix = arguments[0] || "";
    return prefix + this.name;
}
erGouZi = erGouZi.bind( papa, "great " );
alert( erGouZi() );

又没过多久,这事被老爸知道了。老爸对于二狗子这种舔脚后跟的行为表示非常满意,对他越来越恩爱亲切,对你越来越冷板凳。最后终于老爸把所有的遗产都给了二狗子而你因为平常只会坑蒙拐骗啥技能也不会只能上街乞讨最后被城管暴打而死。

终!

就是函数式编程里的 curry / partial。

你把他给的第一个代码示例里稍作调整如下:

javascriptvar getX = module.getX;                 // #1

var boundGetX_a = getX.bind(module);    // #2

var boundGetX_b = getX;                 // #3

然后分别调用三个函数得到的结果如下:

javascriptgetX();           // 9
boundGetX_a();    // 81
boundGetX_b();    // 9

那么,为什么会这样?

首先你要分清 “此 getX 非 彼 getX 这一事实。怎么说?

看 #1,等号左边的 getX 是定义在全局的一个变量,然后它引用则是定义在 module 对象里的 getX 方法(也是函数)。

于是对于左边的 getX,实际上它应该是 window.getX,那么当它被调用的时候,其内的 this 指向的是 window(这就是常说的上下文:context)。因为 window.x 是 9,所以 getX() 返回的结果就是 9;

同样的 #3 的结果也是 9,分析的过程和 #1 是一样的,boundGetX_bgetX 等价,它们都是 window 对象下的一个属性,所以它们的上下文 this 也是一样的。

最后剩下 #2,bind 在这里所起到的作用就是告诉 boundGetX_a,当你被调用的时候,甭管你属于哪个对象(或者说,甭管你的命名空间是什么),你得给我把 module 当成你的执行上下文,也就是把 this bind(绑定)成 module。这样一来,因为 module.x 是 81,所以结果就成了 81。

bind 这样的功能在动态上下文切换的语言里(Javascript 就是一个典型代表)是非常有用的。

因为一个方法定义在哪里是不可改的(至少在运行中不能改),比如说 module.actionaction 方法,它自身就是 module 的方法,它不能是别人的方法;如果其他的对象要引用它,就需要通过引用传递,如:window.action = module.action。然而这只是把方法体的引用传递了出去,但是方法在执行的那一刻,其内部的 this 究竟是谁那就要看到底是哪个 action 被执行了。如果是 window.actionthis 就是 window;反之,this 就是 module

于是,window.action 里就不能通过 this 去访问属于 module 对象里的其他属性(上下文被动态改变了),这个时候 bind 就可以登场了,如同上面的例子里描述的那样,去明确限定要绑定的上下文是谁。这样,方法体内的 this 才能访问你设想中的目标对象。

在理解 bind 之前,首先要理解 this

this 指代调用(invoke)当前函数的那个对象(若直接调用函数,this 则为全局对象 global)。

var Foo = function() {
    return this;
}

Foo(); // global

var foo = {
    func: Foo
}

foo.func(); // foo

var bar = {
    func: Foo
}

bar.func(); // bar

当无法确定函数由谁执行且函数与运行的上下文紧密关联时,可以通过 Function.bind(object) 为函数指定 this 对象。

这在工具函数或事件的回调函数中非常常用,例如 underscore.js ,下面举个例子

var Foo = function(somethings) {
    this.ret = [];
    _.each(somethings, function(val){
        this.ret.push(val); // ERROR ret is undefined
    });
}

var foo = new Foo([1, 2, 3]);

在上面的例子中,回调函数中的 this 已经不再指代构造函数中的 this 了。可能是全局对象,或者其它一些什么(由 _.each 的实现方式决定)。但是我们希望这个回调函数与构造函数的上下文产生关联。有两种方法解决这个问题。

方法一:闭包

var Foo = function(somethings) {
    this.ret = [];

    var that = this;
    _.each(somethings, function(val){
        that.ret.push(val);
    });
}

var foo = new Foo([1, 2, 3]);

回调函数能通过闭包访问到 that,也就是原先上下文的 this。

方法二:bind

var Foo = function(somethings) {
    this.ret = [];
    _.each(somethings, function(val){
        this.ret.push(val);
    }.bind(this));
}

var foo = new Foo([1, 2, 3]);

使用 bind 将构造函数中的 this 与回调函数绑定,也可以达成目的。

至于用哪个方法好,欢迎讨论。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏