javascriptのFunction.prototye.bindを深く理解する

11437 ワード

関数バインディング(Function binding)はJavaScriptを使用し始めた時に最も関心のある点かもしれませんが、他の関数でthisコンテキストを維持するには解決策が必要だと認識したとき、本当に必要なのはFuntions.prototype.bindです.
この問題が初めて発生した時には、変数にthisを設定する傾向があります.これはコンテキストを変えてからも引用できます.多くの人がselfを使います.thisまたはcontextを変数名として使用します.これらの方法はすべて役に立ちます.もちろん問題はありません.しかし、より良い、より専用の方法があります.
本当に解決したい問題は何ですか?
次の例のコードでは、名前を付けて、コンテキストを変数にキャッシュすることができます.
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.special Functionを使ってメソッドを呼び出すと、次のエラーが発生します.
Uncaught TypeError: Object [object global] has no method 'specialFunction'
回転関数の実行のためには、myObjオブジェクトコンテキストへの参照を維持する必要があります.that.special Functionを呼び出して、私たちはスコープコンテキストを維持し、正確に関数を実行することができます.しかしながら、Funtions.prototype.bindを使用すると、より簡潔で清潔な方法があり得る.
render: function () {
 
    this.getAsyncData(function () {
 
        this.specialFunction();
 
        this.anotherSpecialFunction();
 
    }.bind(this));
 
}
私たちは先ほど何をしましたか?
bind()は、この関数が呼び出されたときに、そのthisキーワードは着信された値(ここではbind()を呼び出したときに入ってくるパラメータ)に設定されます.したがって、私たちは希望の文脈に入ってきました.そして、コールバック関数が実行されると、thisはmyObjオブジェクトを指す.
もし興味があれば、Funtion.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
私たちは新しい関数を作成しました.実行されると、そのthisはfooに設定されます.私たちがbar()を呼び出した時のようなグローバルスコープではありません.
ブラウザのサポート
Browser
Version support
Chrome
7
Firefox(Gecko)
4.0(2)
インターネットExplorer
9
Opera
11.60
Safari
5.1.4
あなたが見ているように、残念ながら、Funtions.prototype.bindはIE 8および以下のバージョンではサポートされていませんので、予備案がないと運行中に問題が発生する可能性があります.
ラッキーなことに、Mozila Developer Networkは、自身が実現していないために、ブラウザ絶対的に信頼できる代替案を提供した。:
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;
  };
}
適用モード
技術のポイントを学ぶ時、役立つのは徹底的に概念を学ぶことだけではなくて、更に手元の仕事の中でそれを適用するところがあるかどうかを見てみます.あるいはそれに近いものです.私は次のいくつかの例があなたのコードに適用されることを望んでいます.あるいはあなたが直面している問題を解決します.
CLICK HANDLERS(クリック処理関数)
一つの用途はクリックイベントを記録することです.(またはクリックした後に操作を実行することです.)
var logger = {
    x: 0,       
    updateCount: function(){
        this.x++;
        console.log(this.x);
    }
}
以下のようにクリック処理関数を指定して、ロゴオブジェクトのudateCount()を呼び出すかもしれません.
document.querySelector('button').addEventListener('click', function(){
    logger.updateCount();
});
しかし、私たちはudateCount関数のthisキーワードが正しい値を持つことを確認するために、余分な匿名関数を作成しなければなりません.
私たちは次のようなきれいな方法を使うことができます.
document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));
便利なBind()関数を巧みに使用して新しい関数を作成し,その作用領域をloggerオブジェクトとして結びつけた.
SETTIMEOUT
テンプレートエンジン(例えば、Handlebars)を使ったことがある場合、またはいくつかのMV*フレーム(私の経験からBackbone.jsしか話せません)を使ったことがある場合、次のような議論はテンプレートをレンダリングした直後に新しいDOMノードを訪問する時に発生する問題について知っているかもしれません.
もし私たちがjQueryプラグインを実装したいなら、
var myView = {
 
    template: '/*      <select />       */',
 
    $el: $('#content'),
 
    afterRender: function () {
        this.$el.find('select').myPlugin();
    },
 
    render: function () {
        this.$el.html(this.template());
        this.afterRender();
    }
}
 
myView.render();
正常に仕事ができるということを発見したかもしれませんが、毎回できるわけではありません.中に問題がありますから.これは競争の問題です.先に到着してこそ勝ちとなります.先にレンダリングする場合もありますが、プラグインの実装が先に来ます.【翻訳者注:レンダリングプロセスがまだ完成していないと、findは対応するノードを見つけて実行できなくなります.】
今は多くの人に知られていないかもしれませんが、setTimeout()に基づくものが使えます.  slight hackは問題を解決する.
私達はコードを少し書き換えて、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);        
    }
 
//
原文の出典:http://blog.jobbole.com/58032/
参考資料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Object/Function/bind