1. 程式人生 > >js--this指向的相關問題

js--this指向的相關問題

前言

  關於this的指向問題是前端面試中常考的知識點,也是我們開發學習中較難理解的問題。作為JavaScript的基礎,需要我們徹底理解這一關鍵詞。this作為JavaScript中非常複復雜的機制,值得我們付出更大的代價來學習理解。這裡分享一下我的學習筆記。

正文

  1.this是什麼?this指向什麼?

  學習this之前首先要知道this永遠指向一個物件,this就是函式執行時候所在的環境,我們在學習執行上下文的時候提到,每一個執行上下文都包含三個重要物件,分別是變數物件、作用域鏈和 this,JavaScript支援執行環境動態切換,也就是說,this的指向是動態的,並不是固定的指向一個物件,this的指向完全取絕於函式呼叫的位置。接下來我們看下下面的程式碼:

        var a=1
        function foo(){
            var a=3
            console.log(a)
        }
        foo()//3 

  上面的程式碼執行用到函式的作用域問題,foo()函式執行的時候,函式內部定義一個變數a等於3,然後輸出列印a,此時foo上下文中存在a變數,因此輸出3,若函式foo()內部沒有定義a這個變數,在執行列印這句程式碼的時候,函式內部找不到該變數,此時會沿著作用域鏈向上次查詢,即全域性作用域,此時列印結果便為1。

        var a=1
        function foo(){
            var a=3
            console.log(this.a)
        }
        foo()//1

  上面的程式碼執行結果會打印出1,對比第一段程式碼你會發現就這裡多了this這一個關鍵詞,為什麼加了this之後輸出結果會發生改變呢?莫非此時this指向window全域性物件?

        var a=1
        function foo(){
            var a=3
            console.log(this.a)
        }
        var obj={a:100,foo:foo}
        obj.foo()//100

 

  再來看下上面的程式碼,列印結果變為100,對比前面兩段程式碼,foo函式增加了obj物件的一層引用,輸出的this.a結果再次發生改變?難道此時this指向obj物件?

    this的指向為什麼會發生改變,this的指向到底什麼時候發生的?是因為函式在JavaScript中既可以當作值傳遞和返回,也可以當作物件和建構函式。所有函式在執行的時候才能確定其當前的執行環境,所以this就產生了,this會根據函式的執行環境的改變而改變,同時,函式中的this也只能在函式執行時最終確定其執行環境。

  2.this不指向它自身。

    function foo(num) { 
        console.log( "foo: " + num ); // 記錄 foo 被呼叫的次數
        this.count++; 
    }
    foo.count = 0;
    for (var i=0; i<5; i++) {
            foo( i ); 
    }
    console.log("foo.count:"+foo.count)
    //foo: 0
    //foo: 1
    //foo: 2
    //foo: 3
    //foo: 4
    //foo.count:0

  上面的程式碼列印輸出foo:01234,說明foo函式被呼叫了五次,然後列印foo.count輸出還是0,說明this.count中的this並不是指向的函式foo本身,foo.count只是函式foo的一個屬性,this無法指向他本身。我們可以通過arguments.callee來引用當前正在執行的函式物件,即物件本身。

  3.this不指向它的作用域。

  另一種常見的誤解就是this指向函式的作用域。我們需要牢記,this在任何情況下都不指向函式的詞法作用域,this的指向完全取決於函式呼叫的位置。因此不能使用this來引用一個函式詞法作用域內部的東西。

  4.this的繫結規則。

  this是什麼?當一個函式被呼叫時,會建立一個活動記錄(有時候也稱為執行上下文)。這個記錄會包含函式在哪裡被呼叫(呼叫棧)、函式的呼叫方法、傳入的引數等資訊。this 就是記錄的 其中一個屬性,會在函式執行的過程中用到。         如何尋找函式的呼叫位置,從而判斷函式在執行過程中會如何繫結 this?首先找到呼叫位置:使用開發者工具得到呼叫棧,然後找到棧中第二個元素,這就是真正的呼叫位置。

  通過函式呼叫的位置來確定函式繫結的this物件,需要遵守以下四條規則:

  (1)預設繫結

            function foo() { 
                    console.log( this.a ); 
             }
             var a = 2; 
             foo(); // 2    

  上面的程式碼中,foo()函式直接呼叫,不帶任何修飾的函式引用進行呼叫,因此採用預設繫結,此時this指向window物件,無法應用其他規則。在非 strict mode 下時,預設繫結才能繫結到全域性物件,嚴格模式下 this 繫結在undefined。

  (2)隱式繫結--需要考慮的規則是呼叫位置是否有上下文物件,或者說是否被某個物件擁有或者包含。

                function foo() { 
                    console.log( this.a ); 
                }
                var obj = { a: 2, foo: foo };
                obj.foo(); // 2

  上面的程式碼中,foo函式被呼叫時 obj 物件 “ 擁有 ” 或者 “ 包含 ” 它。當函式引 用有上下文物件時,隱式繫結規則會把函式呼叫中的  this繫結到這個上下文物件,因為調 用 foo() 時 this 被繫結到 obj,因此 this.a 和 obj.a 是一樣的。

            function foo() {
                    console.log(this.a);
                }
                var obj2 = {
                    a: 2,
                    fn: foo
                };
                var obj1 = {
                    a: 1,
                    o1: obj2
                };
                obj1.o1.fn();//2

  上面的程式碼需要值得注意的是:物件屬性引用鏈中只有最頂層或者說最後一層會影響呼叫位置。

              function foo() { 
                    console.log( this.a ); 
                }
                var obj = { a: "local", foo: foo };
                var bar = obj.foo; // 函式別名!
                var a = " global"; // a 是全域性物件的屬性 
                bar(); // "global"

  上面的程式碼,雖然 bar 是 obj.foo 的一個引用,但是實際上,它引用的是 foo 函式本身,因此此時的 bar() 其實是一個不帶任何修飾的函式呼叫,因此應用了預設繫結。

          function foo(){
                    console.log(this.a)
                }
                var a="global"
                var obj={a:"local",foo:foo}
                var bar=obj.foo
                function doFnc(fn){
                    fn()
                }
                doFnc(bar)//global

  上面的程式碼需要注意的是:被隱式繫結的函式會丟失繫結物件。

  (3)顯示繫結或者硬繫結--call,apply,bind直接指定this的繫結物件 

  call() apply() bind()直接指定 this 的繫結物件,返回一個硬編碼的新函式,它會把引數設定為 this 的上下文並呼叫原始函式。但是如果你把 null 或者 undefined 作為 this 的繫結物件傳入 call、apply 或者 bind,這些值 在呼叫時會被忽略,實際應用的是預設繫結規則。foo.call( obj );以在呼叫 foo 時強制把它的 this 繫結到 obj 上。

  (4)new繫結

  JavaScript 中 new 的機制實 際上和麵向類的語言完全不同。

  在 JavaScript 中,建構函式只是一些 使用 new 操作符時被呼叫的函式。它們並不會屬於某個類,也不會例項化一個類。實際上, 它們甚至都不能說是一種特殊的函式型別,它們只是被 new 操作符呼叫的普通函式而已。包括內建物件函式(比如 Number(..))在內的所有函式都可 以用 new 來呼叫,這種函式呼叫被稱為建構函式呼叫。

       使用 new 來呼叫函式,或者說發生建構函式呼叫時,會自動執行下面的操作。 

                1. 建立(或者說構造)一個全新的物件。

                2. 這個新物件會被執行 [[ 原型 ]] 連線。 

                3. 這個新物件會繫結到函式呼叫的 this。

                4. 如果函式沒有返回其他物件,那麼 new 表示式中的函式呼叫會自動返回這個新物件

            new 來呼叫 foo(..) 時,我們會構造一個新物件並把它繫結到 foo(..) 呼叫中的 this 上。

  5.繫結的優先和一些特殊情況。

  判斷優先順序:             (1)函式是否在 new 中呼叫(new 繫結)?如果是的話 this 繫結的是新建立的物件。 var bar = new foo()              (2)函式是否通過 call、apply(顯式繫結)或者硬繫結呼叫?如果是的話,this 繫結的是 指定的物件。 var bar = foo.call(obj2)              (3)函式是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,this 繫結的是那個上 下文物件。 var bar = obj1.foo()              (4)如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到 undefined,否則繫結到 全域性物件。 var bar = foo()   特殊情況:

     (1)間接繫結

         function foo() { console.log( this.a ); }
                var a = 2;
                var o = { a: 3, foo: foo };
                var p = { a: 4 }; 
                o.foo(); // 3 
                (p.foo = o.foo)(); // 2
                賦值表示式 p.foo = o.foo 的返回值是目標函式的引用

    對比隱式繫結

          function foo() { console.log( this.a ); }
                var a = 2;
                var o = { a: 3, foo: foo };
                var p = { a: 4 ,fo:o.foo};
                p.fo() //4   對比間接引用

    (2)事件繫結中的this

          1.行內繫結 <input type='button' onclick="this">
                          <input onclick="clickFuc"> function(){  this ...}
                          //this指向window物件
                2.事件監聽與動態繫結
                        <input type="button" value="按鈕" id="btn">
                        <script> var btn = document.getElementById('btn');
                            btn.onclick = function(){
                                this ;  // this指向本節點物件
                            }</script>
                 //動態繫結的事件本身就是對應節點的一個屬性,重新賦值為一個匿名函式,this自然就指向了本節點物件

    (3)箭頭函式

    ES6 中的箭頭函式並不會使用四條標準的繫結規則,而是根據當前的詞法作用域來決定this,具體來說,箭頭函式會繼承外層函式呼叫的 this 繫結(無論 this 繫結到什麼)。這其實和 ES6 之前程式碼中的 self = this 機制一樣。

總結

  以上就是本文的全部內容,希望給讀者帶來些許的幫助和進步,方便的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。

 

 

&n