js thisバインドを深く理解する(暗記する必要はなく、末尾に総括と面接問題の解析がある)


jsのthisバインド問題は、多くの初心者を混乱させ、一部のベテランが気持ち悪いと感じているのは、thisのバインドが「つかみにくい」ためで、間違いを犯すときはなぜか分からないことが多く、かなり逆論理だ.次のコードを考えてみましょう.
var people = {
    name : "    ",
    getName : function(){
        console.log(this.name);
    }
};
window.onload = function(){
    xxx.onclick =  people.getName;
};

普段レンガを運ぶときによくあるthisバインドの問題は、xxxに書いたり出会ったりするかもしれません.onclickがトリガーされたとき、何を出力しますか?
テストを容易にするために、コードを簡略化します.
var people = {
    Name: "    ",
    getName : function(){
        console.log(this.Name);
    }
};
var bar = people.getName;

bar();    // undefined

この小さな例を通して、thisの気持ち悪いところを感じてみましょう.私が最初にこの問題に遭遇したときも愚かな顔をしていました.コードのthisは作成時に非常に明らかな指向をしていましたから.自分のpeopleのオブジェクトを指していましたが、実際にはwindowのペアを指していました.これは私がすぐに皆さんに言うthisのバインドルールです.
1 . this thisとは?thisバインドについて議論する前に、thisが何を表しているのかを明らかにしなければなりません.
  • thisはJavaScriptのキーワードの一つです.オブジェクトが自動的に生成する内部オブジェクトであり、オブジェクトの内部でのみ使用できます.関数の使用状況によってthisの値が変化します.
  • thisが何を指しているかは、作成時ではなく、どこでどのように呼び出されるかにかかっています.(多くの人が誤解しているところ)
  • 2 . thisバインド規則
    以下に説明する4つのバインドのルールを把握すると、関数呼び出しを見るだけでthisの指向を判断することができます.
    2.1デフォルトバインド
    次のコードを考慮します.
    function foo(){
        var a = 1 ;
        console.log(this.a);    // 10
    }
    var a = 10;
    foo();

    これが典型的なデフォルトバインドです.foo呼び出しの位置を見てみましょう.「ライトレバー司令官」というように、修飾されていない関数呼び出しを直接使用すると、デフォルトでデフォルトバインドしか適用できません.
    では、デフォルトはどこにバインドされていますか.一般的にはwindowで、厳格なモードではundefinedです.
    2.隠性バインディング
    コード:
    function foo(){
        console.log(this.a);
    }
    var obj = {
        a : 10,
        foo : foo
    }
    foo();                // ?
    
    obj.foo();            // ?
    

    答え:undefined 10foo()のこの書き方はよく知っていますか.私たちが書いたばかりのデフォルトのバインドは、window.aの印刷に等しいので、undefinedを出力します.次のobj.foo()はよく書くべきです.これは実は私たちがすぐに議論する隠れたバインドです.
    関数fooが実行されると、コンテキストオブジェクト、すなわちobjが存在する.この場合、関数のthisはコンテキストオブジェクトにデフォルトでバインドされ、印刷obj.aに等価であるため、10が出力される.
    チェーン性の関係、例えばxx.yy.obj.foo();であれば、コンテキストは関数の直接的な上位、すなわち隣接するもの、あるいはオブジェクトチェーンの最後のものを取ります.
    2.3顕性バインディング
    2.3.1暗黙的バインドの制限
    私たちのさっきの暗黙的なバインドには致命的な制限があります.コンテキストには私たちの関数が含まれなければなりません.例:var obj = { foo : foo }です.コンテキストに私たちの関数が含まれていない場合は、暗黙的なバインドで明らかにエラーが発生します.すべてのオブジェクトにこの関数を追加することはできません.そうすると、拡張性が悪くなります.メンテナンス性が悪いです.次にお話しするのは、関数にthisを強制的にバインドすることです.
    2 .3 .2 call apply bind
    ここではjsが提供する関数callとapplyを使用します.これらの役割は関数のthis指向を変更し、最初のパラメータはthisオブジェクトを設定することです.
    2つの関数の違い:
  • callは、2番目のパラメータからすべてのパラメータが元の関数のパラメータです.
  • applyは2つのパラメータのみを受け入れ、2番目のパラメータは配列でなければなりません.この配列は元の関数のパラメータリストを表します.

  • 例:
    function foo(a,b){
        console.log(a+b);
    }
    foo.call(null,'  ','  ');        //         this       null 
    foo.apply(null, ['  ','  '] );     //     

    call,apply関数のほかにthisを変える関数bindがあり,call,applyとは異なる.
    bindには1つの関数しかなく、すぐに実行されません.ただ、1つの値を関数のthisにバインドし、バインドされた関数を返します.例:
    function foo(){
        console.log(this.a);
    }
    var obj = { a : 10 };
    
    foo = foo.bind(obj);
    foo();                    // 10

    (bind関数はとても特別で、今度みんなと一緒にそのソースコードを議論します)
    2.3.2顕性バインド
    本題を開始し、コードを上げると、上の暗黙的なバインドの例を使用します.
    function foo(){
        console.log(this.a);
    }
    var obj = {
        a : 10            //     foo
    }
    foo.call(obj);        // 10

    暗黙的なバインド例のコンテキストオブジェクトの関数を削除しました.明らかに . という形式で関数を呼び出すことはできません.コードの明示的なバインドコードfoo.call(obj)を見てください.変に見えますが、私たちが以前知っていた関数呼び出しとは違います.
    実はcallはfoo上の関数で、thisの指向を変えながらこの関数を実行します.
    (深く理解したい[call apply bind this , , ]など、より多くのブラックテクノロジーのパートナーは私や本稿のコメントに注目してください.最近、私は単独で1期を作って一緒に文章を書きます)(見たくないパートナーは心配しないで、本稿の理解に影響しません)
    2.4 newバインド
    2.4.1 newとは
    オブジェクト向けを学んだパートナーはnewに慣れていないに違いない.jsのnewと伝統的なオブジェクト向け言語のnewの役割は新しいオブジェクトを作成することだが、彼らのメカニズムはまったく異なる.
    新しいオブジェクトを作成するには、 という概念が欠かせません.従来のオブジェクト向けコンストラクション関数はクラス内の特殊な関数であり、オブジェクトを作成するときにnew ()の形式でクラス内のコンストラクション関数を呼び出しますが、jsでは異なります.
    jsにおけるnewで修飾された関数は「構造関数」であり、正確には関数の である.jsにはいわゆる「構造関数」は存在しないからである.
    ではnewで関数の を作った後、jsは私たちにどんな仕事をしてくれましたか.
  • 新しいオブジェクトを作成します.
  • この新しいオブジェクトの__proto__属性を元の関数のprototype属性に向けます.(すなわち、元の関数を継承するプロトタイプ)
  • この新しいオブジェクトをこの関数のthisにバインドします.
  • は、この関数が他のオブジェクトを返さない場合、新しいオブジェクトを返します.

  • 3つ目は次のnewバインドです
    2.4.2 newバインド
    ピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッピッ
    function foo(){
        this.a = 10;
        console.log(this);
    }
    foo();                    // window  
    console.log(window.a);    // 10       
    
    var obj = new foo();      // foo{ a : 10 }                
                              //       foo { a : 10 };  var obj = foo;
    console.log(obj.a);       // 10    new  

    newを使用して関数を呼び出すと、関数は独自の名前で名前を付けて新しいオブジェクトを作成し、戻ります.
    特に注意:元の関数がオブジェクトタイプを返すと、新しいオブジェクトを返すことができなくなり、thisにバインドされた新しいオブジェクトが失われます.例:
    function foo(){
        this.a = 10;
        return new String("   ");
    }
    var obj = new foo();
    console.log(obj.a);       // undefined
    console.log(obj);         // "   "

    2.thisバインド優先度
    プロセスは退屈なコードテストで、私は直接優先度を書きました.
    new    >      >      >     
    
    

    3 . まとめ
  • 関数がnewによって修飾する場合
       this          , :var bar = new foo();     foo    this      foo        ,          bar ,          new   .
  • .
  • 関数がcall,apply,bindを使用して呼び出す
       this     call,apply,bind       . : foo.call(obj); , foo    this    obj ,               .
  • である場合.
  • 関数がコンテキストオブジェクトの下で呼び出された場合
       this           ,  : var obj = { foo : foo };    obj.foo();  foo    this    obj .               .
  • .
  • そうでない場合は、デフォルトバインディング
        :function foo(){...} foo() ,foo    this    window.(          undefined).
                     .
    
  • を使用します.
    4 . 面接問題の解析
    1.
    var x = 10;
    var obj = {
        x: 20,
        f: function(){
            console.log(this.x);        // ?
            var foo = function(){ 
                console.log(this.x);    
                }
            foo();                      // ?
        }
    };
    obj.f();

    -----------------------------------------------------------------------------このデフォルトバインド2.この隠しバインド
    var x = 10;
    var obj = {
        x: 20,
        f: function(){
            console.log(this.x);    // 20
                                    //        ,   f  this      obj ,    20
            function foo(){ 
                console.log(this.x); 
                }
            foo();       // 10
                         //                foo     f  ,   f    ,
                         //  this      obj   ,         this     ,        
                         //    '    ',              ,  this    window
        }
    };
    obj.f();             

    2.
    function foo(arg){
        this.a = arg;
        return this
    };
    
    var a = foo(1);
    var b = foo(10);
    
    console.log(a.a);    // ?
    console.log(b.a);    // ?

    ---------------------------------------
    答え:undefined 10解析:ポイント1.全局汚染2.デフォルトのバインド
    この問題はとても面白くて、問題は基本的に第1 undefinedに集中して、これは実は問題の小さい罠ですが、スタックを追う過程は絶対にすばらしくて、私たちにここで何が起こったのかを一歩一歩分析させます.
  • foo(1)実行は、デフォルトバインドであることがわかりやすいでしょう.これはwindowを指し、関数の中でwindowに等価です.a = 1,return window;
  • var a=foo(1)はwindowに等しい.a=window、var aがwindowであることを無視する人が多い.a,与えられたばかりの1を置き換えた.
  • だからここのaの値はwindowで、a.aもwindowで、つまりwindowです.a = window ; window.a.a = window;
  • foo(10)は初めてと同じデフォルトバインドで、このときwindow.aは10に値をつけて、ここが肝心なことに注意して、もとはwindow.a=windowは、現在10に割り当てられ、値タイプになっているので、現在a.a=undefinedとなっています.(これを検証するにはvar b=foo(10)を必要とします.削除、ここのa.aはやはりwindow)
  • var b = foo(10); Windowsに等価です.b = window;

  • 本題のすべての変数の値、a=window.a = 10 , a.a = undefined , b = window , b.a = window.a = 10;
    3.
    var x = 10;
    var obj = {
        x: 20,
        f: function(){ console.log(this.x); }
    };
    var bar = obj.f;
    var obj2 = {
        x: 30,
        f: obj.f
    }
    obj.f();
    bar();
    obj2.f();

    ------------------------------------------------------------------
    var x = 10;
    var obj = {
        x: 20,
        f: function(){ console.log(this.x); }
    };
    var bar = obj.f;
    var obj2 = {
        x: 30,
        f: obj.f
    }
    obj.f();    // 20
                //    ,this obj,    
    bar();      // 10
                //'    '       ( obj.f           )
    obj2.f();   //30
                //   f       ,this            ,          
                

    4.圧巻の問題
    function foo() {
        getName = function () { console.log (1); };
        return this;
    }
    foo.getName = function () { console.log(2);};
    foo.prototype.getName = function () { console.log(3);};
    var getName = function () { console.log(4);};
    function getName () { console.log(5);}
     
    foo.getName ();                // ?
    getName ();                    // ?
    foo().getName ();              // ?
    getName ();                    // ?
    new foo.getName ();            // ?
    new foo().getName ();          // ?
    new new foo().getName ();      // ?

    ------------------------------------------------------------------newバインド2.隠性バインド3.デフォルトのバインド4.へんすうおせん
    function foo() {
        getName = function () { console.log (1); }; 
                //   getName       window 
        return this;
    }
    foo.getName = function () { console.log(2);};   
            //  getName      ,      foo  
    foo.prototype.getName = function () { console.log(3);}; 
            //   getName     foo    ,  new                 
    var getName = function () { console.log(4);}; 
            //  foo    getName  ,       window 
    function getName () { console.log(5);}    
            //   ,           ,              ,               
            //       ,              getName(),      ,           
            //   ,           
            
            //       getName             
    
    foo.getName ();                // 2
                                   //       ,            getName  
                                   //            2   3   ,     3 ,        
                                   // foo.prototype    ,                ,    
                                   //    ,  foo.prototype.getName()      ,        
                                   // 3       2,      (     new      ,   
                                   // Prototype           ,  new           )
                                   
    getName ();                    // 4 
                                   //             ,             5    4   ,
                                   //    5   4    ,   js           ,       
                                   //              
                                   
    foo().getName ();              // 1 
                                   //    foo          , 1.  window.getName   1,
                                   // 2.   window ,      window.getName();    1
    getName ();                    // 1
                                   //          window.getName   1,       1
                                   
    new foo.getName ();            // 2
                                   // new             ,   foo.getName ,         
                                   //        ,         ,   2 (         
                                   //              ,        foo.getName    
                                   //  __proto__      getName  ,       3   )
                                   
    new foo().getName ();          // 3
                                   //           ,new             ,        
                                   //      ,foo(),         ,    var obj = new foo();
                                   // obj.getName();        ,         prototype  
                                   //   getName  3 ,    new      prototype       
                                   
    new new foo().getName ();      // 3
                                   //   ,        ,        :
                                   // var obj = new foo();
                                   // var obj1 = new obj.getName();
                                   //   ,    ,            ,obj  getName 3,    3
                                   // obj         foo   ,obj1       obj.getName   
    

    5 . 矢印関数のthisバインド(2017.9.18更新)
    矢印関数は、functionのキーワードではなく、=>、学名 (2333)を使用します.通常の関数とは異なります.
  • 矢印関数は、上述した4つのバインドを使用するのではなく、外部の役割ドメインに完全に基づいてthisを決定します.(その親は私たちのルールを使っていますよ)
  • 矢印関数のthisバインドは修正できません(この特性は非常に爽やかです)
  • まずコードを見て強化します.
    function foo(){
        return ()=>{
            console.log(this.a);
        }
    }
    foo.a = 10;
    
    // 1.            this
    
    var bar = foo();            // foo    
    bar();                      // undefined   ,           
    
    var baz = foo.call(foo);    // foo     
    baz();                      // 10 
    
    // 2.     this    
    //              foo   baz
    var obj = {
        a : 999
    }
    baz.call(obj);              // 10

    さあ、実戦で、前の最初の例を覚えていますか.矢印関数の形式に変更します(気持ち悪いthisバインドの問題を徹底的に解決できます):
    var people = {
        Name: "    ",
        getName : function(){
            console.log(this.Name);
        }
    };
    var bar = people.getName;
    
    bar();    // undefined

    =================================
    var people = {
        Name: "    ",
        getName : function(){
            return ()=>{
                console.log(this.Name);
            }
        }
    };
    var bar = people.getName(); //        people   ,   this ,      ?
    
    bar();    //      

    なぜ矢印関数の外にもう1つセットして、直接書けばいいのか分からない人もいるかもしれませんが、こんなに面倒なことをして何をしているのか、実はこれも矢印関数の多くの人が使いにくいところです.
    var obj= {
        that : this,
        bar : function(){
            return ()=>{
                console.log(this);
            }
        },
        baz : ()=>{
            console.log(this);
        }
    }
    console.log(obj.that);  // window
    obj.bar()();            // obj
    obj.baz();              // window
  • objの現在の役割ドメインはwindow、例えばobjであることを明らかにしなければならない.that === window.
  • function(functionには独自の関数役割ドメインがある)でラップしない場合、デフォルトでバインドされている親役割ドメインはwindowです.
  • をfunctionで包む目的は、矢印関数を現在のオブジェクトにバインドすることです.関数の役割ドメインは現在のオブジェクトであり、矢印関数は関数が存在する役割ドメインのthis、すなわちobjを自動的にバインドします.

  • 滋養に満ちて抜けてしまった
    参考書:あなたが知らないJavaScriptKYLE SIMPSON著(おすすめ)