搞懂JavaScript的Function.prototype.bind[译]

搞懂JavaScript的Function.prototype.bind[译]

Ben Howdle

binding多是初学Javascript的人最不体贴的函数,当你意想到须要『坚持this在其他函数中的高低文』,实际上你须要的是Function.prototype.bind()。

你第一次碰到题目标时刻,你能够倾向于把this赋值给一个变量,你就能够在高低文转变的时刻,也能够运用。许多人挑选self,_this或许context来定名。这些都不会被用到,如许做也没什么题目,然则这里有更好的方法,特地处置惩罚这个题目。

我愿意为作用域做任何事,但我不会that = this

— Jake Archibald (@jaffathecake) February 20, 2013

我们真正在追求处置惩罚的题目是什么?

看看这段代码,把高低文赋值给一个变量:

var myObj = {

    specialFunction: function () {

    },

    anotherSpecialFunction: function () {

    },

    getAsyncData: function (cb) {
        cb();
    },

    render: function () {
        var that = this;
        this.getAsyncData(function () {
            that.specialFunction();
            that.anotherSpecialFunction();
        });
    }
};

myObj.render();

假如上面直接用this.specialFunction(),结果是一个错误信息:

Uncaught TypeError: Object [object global] has no method ‘specialFunction’

当回调的时刻,我们须要坚持myObj的高低文援用。运用that.specialFunction(),让我们用that的高低文且准确实行函数。但是,用Function.prototype.bind()能够简化一些。

重写例子:

render: function () {

    this.getAsyncData(function () {

        this.specialFunction();

        this.anotherSpecialFunction();

    }.bind(this));

}

我们刚做了什么?

.bind()就是创建了一个新函数,当我们呼唤时,把他的this赋值。所以我们能够通报我们的高低文,this(指向myObj),通报进.bind()函数。当回调实行的时刻,this指向myObj。

假如我们对Function.prototype.bind()的内部完成有兴趣,请看下面的例子:

Function.prototype.bind = function (scope) {
    var fn = this;
    return function () {
        return fn.apply(scope);
    };
}

一个简朴的例子:

var foo = {
    x: 3
}

var bar = function(){
    console.log(this.x);
}

bar(); // undefined

var boundFunc = bar.bind(foo);

boundFunc(); // 3

浏览器兼容性

BrowserVersion support
Chrome7
Firefox (Gecko)4.0 (2)
IE9
Opera11.60
Safari5.1.4

如你所见,不幸的是,不支持ie8以下(啥也不说了)。
荣幸的是,MDN为那些原生不支持.bind()的浏览器供应了处置惩罚:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

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

    return fBound;
  };
}

运用体式格局

进修东西时刻,我发明有用的体式格局不是仔细的去进修观点,而是去看怎样运用到如今的工作中。假如顺遂的话,下面某些例子能够被用到你的代码中处置惩罚你面临的题目。

点击事宜处置惩罚

个中一个用途是追踪点击(点击后实行一个行动),须要我们在一个对象中贮存信息:

var logger = {
    x: 0,
    updateCount: function(){
        this.x++;
        console.log(this.x);
    }
}

我们写click事宜处置惩罚,然后呼唤logger中的updateCount():

document.querySelector('button').addEventListener('click',logger.updateCount);

但我们造了一个不必要的匿名函数,坚持this的准确指向。
简化一下:

document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));

适才我们用了.bind()制造一个新函数然后把作用域绑定到logger对象上。

时候距离函数

假如你之前用过模板引擎(handlebars)或许MV*框架,那你应当意想到一个题目标发作,当你呼唤衬着模板,马上想进入新的DOM节点。
假定我们尝试实例一个jQuery插件:

var myView = {

    template: '/* a template string containing our <select /> */',

    $el: $('#content'),

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        this.afterRender();
    }
}

myView.render();

你会发明这可用,但并不老是可用的。这就是题目所在。这就像是老鼠竞走:不论发作什么,第一个抵达获得胜利。有时刻是render,有时刻是插件的实例(instantiation)。

如今,一个不为人知,我们能够用小hack—setTimeout()。
须要重写一下,一旦Dom节点涌现,我们就能够平安的实例我们的JQuery插件。

//

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        setTimeout(this.afterRender, 0);
    }

//

但是,我们会看到.afterRender()没有被找到。
咋办?把我们.bind()加进去:

//

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        setTimeout(this.afterRender.bind(this), 0);
    }

//

如今afterRender()能够在准确的高低文中实行了。

整合事宜绑定和QUERYSELECTORALL

DOM API一个严重进步就是querySelector,querySelectorAll和classList API等等。

但是,并没有原生增加事宜到多个节点(nodeList)的体式格局。所以,我们终究偷盗了forEach函数,来自Array.prototype,以下:

Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){
    el.addEventListener('click', someFunction);
});

更好一点,用.bind():

var unboundForEach = Array.prototype.forEach,
    forEach = Function.prototype.call.bind(unboundForEach);

forEach(document.querySelectorAll('.klasses'), function (el) {
    el.addEventListener('click', someFunction);
});

如今我们有了玲珑的方法来轮回多个dom节点。

总结

如你所见,.bind()函数能够用来完成种种目标,同时简化代码。愿望这个概述能让你的代码能运用.bind(),利用好变化的this这个特性。

『才能有限,若有疑问,马虎,速指出,谢谢你』

    原文作者:wee911
    原文地址: https://segmentfault.com/a/1190000004640916
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞