bind関数の役割、シーンの適用、シミュレーションの実装

9319 ワード

bind関数
Bind関数はFunctionのプロトタイプに掛けられています
Function.prototype.bind
作成した関数はbindを直接呼び出すことができます.
    function func(){
        console.log(this)
    }
    func.bind(); //       

bindの役割:
bind()メソッドが呼び出されると、新しい関数が作成されます.この新しい関数が呼び出されるとbind()の最初のパラメータは、新しい関数として実行されるthisの値となり、その後のシーケンスパラメータは、渡された実パラメータの前に新しい関数としてのパラメータに渡されます.
bind受信パラメータ
func.bind(thisArg[,arg1,arg2...argN])
  • 最初のパラメータthisArgは、func関数が呼び出されるとfunc関数の実行時のthisとして指定されます.newオペレータを使用してバインド関数を呼び出すと、このパラメータは無効です.
  • [,arg 1,arg 2…argN]は実パラメータとしてfunc関数に渡される.

  • bind戻り値
    新しい関数を返す
    注:これは、関数呼び出しcall/applyがthisの指向を変更するのとは異なります.コール/applyを呼び出すと、元の関数が直接実行されます.
    例を挙げて説明します.
    function func(){
        console.log(this)
    }
    
    //  call
    func.call({a:1});  // func      ,  :{a:1}
    
    //  bind
    let newFunc = func.bind({});   //      
    
    newFunc(); //            ,func       
    

    以上より、以下の情報が得られた.
  • bindは、関数呼び出し
  • によって呼び出される.
  • は、新しい関数
  • を返します.
  • 関数thisは
  • を指す
  • は、パラメータ
  • に入力することができる.
    深くbind使用
    以上bind関数の役割と使い方を知り,次にbind関数の使用に深く入り込み,具体的に3つの面での使用を紹介したが,これも後シミュレーションによるbind関数の実現のポイントである.
  • 関数の実行を変更するthisは
  • を指します.
  • 伝達パラメータ
  • が返す新しい関数は、コンストラクション関数
  • として扱われる.
    関数の実行時にthisの方向を変更
    bind関数を呼び出すと、bind関数の最初のパラメータは、元の関数の役割ドメインでthisが指す値です.
    function func(){
        console.log(this); 
    }
    
    let newFunc = func.bind({a:1});
    newFunc(); //   :{a:1}
    
    let newFunc2 = func.bind([1,2,3]);
    newFunc2(); //   :[1,2,3]
    
    let newFunc3 = func.bind(1);
    newFunc3(); //   :Number:{1}
    
    let newFunc4 = func.bind(undefined/null);
    newFunc4(); //   :window
    

    以上、nullまたはundefinedとして入力されると、非厳格モードではwindowを指すことに注意してください.
    単純値として入力されると、内部では単純値が対応するタイプのオブジェクトにパッケージされ、数値はNumberメソッドパッケージを呼び出します.文字列はStringメソッドパッケージを呼び出します.true/falseはBooleanメソッドパッケージを呼び出します.元の値を取得するには、valueOfメソッドを呼び出します.
    Number(1).valueOf(); // 1
    String("hello").valueOf(); // hello
    Boolean(true).valueOf(); // true

    bind関数を複数回呼び出す場合、bind関数を最初に呼び出す変更thisが指す値に準じる.
    function func(){
        console.log(this);
    }
    
    let newFunc = func.bind({a:1}).bind(1).bind(['a','b','c']);
    newFunc(); //   :{a: 1}

    渡されたパラメータ
    bindの2番目のパラメータから,元の関数に伝達される実パラメータである.bindが返す新しい関数呼び出し時にも、元の関数に実パラメータを渡すことができます.ここでは、順序の問題に関連します.
    function func(a,b,c){
        console.log(a,b,c); //        
    }
    
    let newFunc = func.bind({},1,2);
    
    newFunc(3)
    

    印刷結果は1,2,3です.bindで渡されるパラメータは、元の関数に先に渡されることがわかります.
    返される新しい関数は構造関数として扱われます
    bind関数を呼び出して返される新しい関数は、コンストラクション関数としても使用できます.新しい関数で作成されたインスタンスでは、元の関数のプロトタイプを見つけることができます.
    //    
    function func(name){
    console.log(this); //   :  {name:'wy'}
    this.name = name;
    }
    func.prototype.hello = function(){
        console.log(this.name)
    }
    let obj = {a:1}
    //   bind,     
    let newFunc = func.bind(obj);
    
    //           ,    
    
    let o = new newFunc('seven');
    
    console.log(o.hello()); //   :'seven'
    console.log(obj); //   :{a:1}
    

    新しい関数は構造関数として扱われ,元の関数funcのthisはbindに伝達される最初のパラメータではなくnewで作成されたインスタンスを指す.インスタンスoによってプロトタイプ上のメソッドhelloを探す場合,元の関数funcプロトタイプ上のメソッドを見つけることができる.
    シミュレーション実装bindでは特にこの実装に注意し,これも面接の重点であり,継承にかかわる.
    bind関数適用シーン
    以上bind関数をどのように使用するかを述べただけで、使用をマスターし、ビジネスシーンに置いて現実的な問題を解決します.
    シーン1
    まずレイアウトを1つください.
    • 1
    • 1
    • 1

    需要:各li要素をクリックし、1000 ms遅延した後、li要素の色を変更します.
    let lis = document.querySelectorAll('#list li');
    for(var i = 0; i < lis.length; i++){
        lis[i].onclick = function(){
            setTimeout(function(){
                this.style.color = 'red'
            },1000)
        }
    }

    以上のコードは各liをクリックしても色は変わりません.タイマコールバック関数のthisはクリックしたliではなくwindowを指しているからです(もちろん矢印関数、letなどを使って解決することもできますが、ここでは主にbindで解決します).この場合、コールバック関数のthis指向を変更する必要があります.関数を変更できるthisは、call、apply、bindを指します.では、どちらを選びますか.シーンによっては、ここのシーンは1000 ms後にコールバック関数を実行するので、call、applyを使用することは選択できません.すぐに関数を実行するので、bindを使用して解決する必要があります.
    setTimeout(function(){
        this.style.color = 'red'
    }.bind(this),1000)

    シーン2
    オブジェクト向けの方法でコードを整理する場合があり、イベント処理関数をプロトタイプに分割し、プロトタイプに掛けたメソッドをイベントに割り当てる場合があります.この場合の関数は、イベントトリガ時にthisが要素を指し、さらに関数でインスタンス上の属性にアクセスする必要がある場合に見つかりません.
    function Modal(options){
        this.options = options;
    }
    
    Modal.prototype.init = function(){
        this.el.onclick = this.clickHandler; //         
    }
    Modal.prototype.clickHandler = function(){
        console.log(this.left);  //            ,this    ,    left
    }
    
    let m = new Modal({
        el: document.querySelector('#list'),
        left: 300
    })
    
    m.init(); //     

    以上のコードは、init関数では、要素にイベントをバインドし、イベント処理関数をプロトタイプに掛け、thisを使用してアクセスします.要素をクリックすると、clickHandler関数ではインスタンスのleft属性を取得する必要がありますが、clickHandler関数のthisはインスタンスではなく要素を指しているので取得できません.clickHandler関数thisの指向を変更するにはbindが必要です.
    Modal.prototype.init = function(){
        this.el.onclick = this.clickHandler.bind(this)
    }

    以上のシーンはbindが使用する氷山の一角にすぎず、本質的にやるべきことはthisの指向を変え、予想された目的を達成することである.bindの役割や応用シーンを把握すると、thisの指向を変える必要があり、すぐに関数を実行しない場合、bindを思い浮かべるという印象が頭に残ります.
    シミュレーション実装
    どうして自分でbind関数を実現するのですか?
    bind()関数はECMA-262第5版で追加された.すべてのブラウザで実行できない場合があります(ie 8以下).
    面接用で、面接官があなたを拒絶する理由が見つからないようにする
    bindが使用するいくつかの特徴をつかんで、これらの点を一つ一つ実現すればOKで、具体的な点:
  • 関数呼び出し
  • は、新しい関数
  • を返します.
  • 伝達パラメータ
  • 関数の実行を変更するthisは
  • を指します.
  • 新しい関数がコンストラクション関数として扱われる場合の処理
  • 関数呼び出しにより、Functionのプロトタイプに直接掛けることができ、サポートされていないブラウザを補充するために、サポートされているブラウザに追加する必要がなく、以下の判断ができます.
    if(!Function.prototype.bind) {
        Function.prototype.bind = function(){
            
        }
    }

    この動作はpolyfillとも呼ばれ、サポートされていないブラウザに機能を追加してブラウザ間の差を平らにします.
    注意:ブラウザがサポートされている場合は、自分でテストするのに便利です.
    if条件を外すか、
    bindは名前を変えた.以下に名前を変更する予定です.
    bind 2、テストしやすいです.
    bindを呼び出すと新しい関数が返され、新しい関数が呼び出されると元の関数も呼び出されます.
    Function.prototype.bind2 = function(thisArg,...args){
        let funcThis = this; //     bind,this     
        //      
        return function (...rest) {
            return funcThis.apply(thisArg,[...args,...rest]/*bind2              */)
        }
    }
    
    //   
    function func(a,b,c){
        console.log(this)
        console.log(a,b,c)
    }
    
    let newFunc = func.bind2({a:1},1,2);
    
    newFunc(3);
     //   :{a: 1}
     //   :1 2 3

    以上の関数は,元の関数thisの指向を変更し,正しい順序のパラメータを伝達することができる.次に,新しい関数が構造関数として扱われる場合の理解が困難である.
    2つの場所を変更する必要があります.
  • 新しく返された関数は、元の関数プロトタイプの属性
  • を継承します.
  • 元の関数はthis問題を変更します.newで呼び出されると、元の関数thisは新しい関数のthisであるべき値を指します.それ以外の場合は、渡されたthis Argの値です.

  • まず継承を行い,新しい関数に元の関数のプロトタイプを継承させ,元のプロトタイプ関係を維持する.匿名関数は参照できないので、新しい関数に名前を付けます.
    Function.prototype.bind2 = function(thisArg,...args){
        let funcThis = this; //     bind,this     
        
        //        
        let fBound = function (...rest) {
            return funcThis.apply(thisArg,[...args,...rest]/*bind2              */)
        }
        
        //         prototype  ,   Function.prototype   。
        if(funcThis.prototype){
            //   Object.create,    prototype            
            fBound.prototype = Object.create(funcThis.prototype);
        }
        return fBound;
    }
    
    //   
    function func(name){
        console.log(this); // {a: 1}
        this.name = name;
    }
    
    func.prototype.hello = function(){
        console.log(this.name); // undefined
    }
    
    let newFunc = func.bind2({a:1});
    let o = new newFunc('seven')
    
    o.hello();
    //   :{a: 1}
    //   :undefined

    以上のコードは,新しいインスタンスoがhelloというメソッドを呼び出すことができ,継承が実現され,新しい関数上のプロトタイプメソッドにアクセスできることを示している.
    次に、this指向性の問題について説明します.上記の例では、new演算子を使用して関数を呼び出すと、元の関数では、thisはインスタンスを指向する必要があります.したがって、thisが指すapplyを変更してnewオペレータ呼び出しを使用しているかどうかを判断する必要があります.
    使用するオペレータはinstanceofで、1つの関数のプロトタイプが1つのオブジェクトのプロトタイプチェーンにあるかどうかを判断する役割を果たし、そうであればtrueを返し、そうでなければfalseを返します.テストは次のとおりです.
    function Person(){}
    let p = new Person();
    console.log(p instanceof Person); // true
    console.log(p instanceof Object); // true
    console.log(p instanceof Array); // fasle

    構造関数でnewによって呼び出されたかどうかをinstanceofで判断することもできます.newで呼び出されると、関数のthisオブジェクトのプロトタイプチェーンに関数のプロトタイプが存在することを示すとtrueが返されます.
    function Person(){
        console.log(this instanceof Person); // true
    }
    
    new Person();

    我々のbind 2関数に戻ると,bind 2が呼び出されて新しい関数fBoundが返され,newを用いてコンストラクション関数が呼び出されると,実際にはfBoundという関数が呼び出されるので,fBound関数でinstanceofを用いてnewで呼び出されるか否かを判断するだけでよい.
    Function.prototype.bind2 = function(thisArg,...args){
        let funcThis = this; //     bind,this     
        
        //        
        let fBound = function (...rest) {
            //    new   ,   this             
            //   new  ,     bind2        
            thisArg = this instanceof fBound ? this : thisArg;
            return funcThis.apply(thisArg,[...args,...rest]/*bind2              */)
        }
        
        //         prototype  ,   Function.prototype   。
        if(funcThis.prototype){
            //   Object.create,    prototype            
            fBound.prototype = Object.create(funcThis.prototype);
        }
        return fBound;
    }
    //   
    function func(name){
        console.log(this); // {a: 1}
        this.name = name;
    }
    
    func.prototype.hello = function(){
        console.log(this.name); // undefined
    }
    
    
    let newFunc = func.bind2({a:1});
    let o = new newFunc('seven')
    
    o.hello();
    //   :{name:'seven'}
    //   :'seven'

    bind関数のソースコードが完了しました.助けてほしいです.
    偏差があれば指正学習を歓迎します.ありがとうございます.