1. 程式人生 > >深入學習JavaScript之初識this

深入學習JavaScript之初識this

  JavaScript是一個詞法作用域的程式語言,詞法作用域和動態作用域的區別我也說過了,具體在我的《深入理解JavaScript之詞法域》https://blog.csdn.net/qq_41889956/article/details/83061472中有介紹,

  總的來說

  動態作用域是根據呼叫棧關係來確定變數值的,比如在當前的函式找不到,那麼就會到它呼叫的函式中找。

  詞法作用域是在詞法分析階段(程式碼編寫時)就已經決定的了。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

今天要學習的  this機制   與   動態作用域差不多。

  this是JavaScript中最為重要的機制之一,同時也是最為複雜的機制之一,在大型專案裡,this來this去會讓你感到一頭霧水。這個  this  與英語單詞中理解的  this  不太一樣,並不是單純的指這個函式物件的本身。

 1.1為什麼要用this

  接下來請看看例子,讓我們來看看this的用處


        function identify() {
            return this.name.toUpperCase();

        }
        function speack() {
            var greeting="Hello,I'm"+identify.call(this);
            console.log(greeting);
        }
        var me={
            name:"Kyle"
        };
        var you={
            name:"Reader"
        };

       console.log(identify.call(me));    //KYLE
       console.log( identify.call( you ));      //READER

        speack.call(me);      //Hello,I'm KYLE
        speack.call(you);     //Hello,I'm RAEDER
    

  在理解這篇程式碼之前,讓我們先來看看,.call()是什麼,這是一個能夠修改" this"指標的方法,作用是將函式內的"this"指標物件轉移到"()"內的物件中

  接下來我們看看程式碼,程式碼中,identify(...)函式將"this.name.toUpperCase()"作為返回值,此函式是修改傳入物件的name值,使其輸出大寫。

   這段程式碼可以在不同的上下文物件(me和you)中重複使用函式identify(...)以及speack(...)來輸出不同的結果,而不用對不同的物件編寫不同的結果。

 

如果不使用  this  的話,我們就需要顯示的建立一個上下文物件,


        function identify(context) {
        return context.name.toUpperCase();
    }
    function speak(context) {
        var greeting = "Hello, I'm " + identify(context);
        console.log( greeting );
    }
    var me = {
        name: "Kyle"
    };
    var you = {
        name: "Reader"
    };
   console.log(identify(me));    //KYLE
   console.log( identify( you ));      //READER
    speak( me ); // Hello, 我是 KYLE
    speak( you ); // Hello, 我是 READER

  使用  this  能夠隱式的傳遞物件的引用,讓你的程式碼變得更加的優美,簡潔,易於複用。

  隨著你使用的模式越來越複雜,顯式傳遞上下文物件會讓程式碼變得越來越混亂,使用  this  能夠很好的解決這個問題

 

1.2  誤解

   在我們正式瞭解  this  的作用機制的時候,先讓我們來消除一些對  this  常有的誤解。

   太拘泥於  this  的字面意思會產生一個誤解。有兩種對  this  的誤解,它們都是錯誤的

 

1.2.1  誤解一:指向自身

   在英語字面上解釋  this  的話,通常是指向函式本身,在函式作用域中使用它,目的也是指向自己(函式內部呼叫自己),那麼在函式內部指向自己(執行自己)是在什麼情況呢?

  一般是在遞迴的情況下,比如:階乘函式,就需要不斷的呼叫自己、或者在第一次呼叫時自己解綁的事件處理器。

 在JavaScript一些開發者很容易混淆儲存狀態的位置(儲存狀態=屬性的值),因為JavaScript中有多種模式可以儲存狀態,有一部分人認為,函式是一個物件(在JavaScript中,函式看作物件),那麼就可以在呼叫函式時儲存狀態 這是可行的,但是你要記住,儲存狀態不止這一個位置。

   接下來我們看看這個程式碼


        function foo(num) {
            console.log("foo"+num);

            this.count++;
        }
        foo.count=0;
        var i;
        for(i=0;i<10;i++){
            if(i>5){
                foo(i);     //6-7-8-9
            }
        }
        console.log("foo被呼叫:"+foo.count+"次");    //0
    

  console.log(...)輸出了四條語句,證明foo(...)函式確實被呼叫了四次,但是foo(...)函式的計數器並沒有發生計數,很明顯,是foo(...)中this  物件的錯誤。

  執行  foo.count=0  時,確實是向函式物件  foo  添加了一個屬性  count。但是函式內部程式碼  this.count依然是0,那就證明  this.count中的  this  並不是指向  foo  這個物件,屬性相同但是物件不同。

     實際上如果深究的話,foo(...)中  this.conut  是建立了一個全域性變數conut,它的值為NaN。

    為什麼呢?還記得我們之前講過的查詢方式吧,this.count++  使用的是LHS查詢,在當前作用域中查詢不到count變數,於是它返回到上一層查詢,這時,在上一層(詞法作用域)中同樣沒有查詢到,因為進行的是LHS查詢,所以它會自動在全域性作用域中建立一個變數count,它的值為NaN

-------------------------------------------這充分說明了  this  並不是指向函式本身

  要解決上面的問題有多種方法

  • 建立全域性變數count,此方法沒有用到  this  而是用到了詞法作用域
  • 利用foo代替this,此方法同樣沒有用到  this  用到的是foo(...)的函式作用域
  • 利用call強制foo(...)中的  this  與foo進行繫結,在foo(i)------->>>foo.call(foo,i)   call正確用法是:this存在的函式.call(需要this繫結的函式,傳入this存在的函式的引數)

1.2.2  誤解二:它的作用域

  第二種常見的誤區是將this指向它的作用域。這個問題涉及到了很多東西,在某些情況下,它是正確的,在某些情況下,它是錯誤的。

 

    this在任何情況下都不會指向函式的詞法作用域,在JavaScript內部,作用域和物件類似,可見的識別符號都是它的屬性。但是作用域物件無法通過JavaScript程式碼訪問,它存在於JavaScript引擎內部。

 

我們看看以下的程式碼,

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

  這個程式碼中有很多個錯誤,這段程式碼似圖跨過邊界,使用 this 來隱式引用函式的詞法作用域。但這是無法實現的

  首先,這段程式碼似圖通過  this.bar()  來引用bar(...)函式。這個是不可能成功的,要呼叫bar(...)最好的方法是直接使用詞法引用識別符號bar(...),不要  this。

  此外這段程式碼還試圖用  this  溝通foo()、bar()的詞法域,從而讓bar()能夠訪問foo()中的變數a,這是不可能實現的,

因為你無法使用this來引用詞法作用域裡面的東西。

 

1.3  this到底是什麼?

    我們之前說過  this  是在執行時進行繫結的,就同動態作用域一樣,它的上下文取決於函式呼叫的各種條件

    this的繫結和函式宣告的位置沒有任何的關係,只取決於函式呼叫的方式

 簡單說下  this  。當一個函式被呼叫時,會建立一個活動記錄(執行上下文)。這個記錄會包含函式在哪被呼叫(呼叫棧),函式呼叫的方法,傳入的引數等等。this就是記錄其中的一個屬性而已,會在函式執行時用到。

 

總結:this 機制是JavaScript中最為複雜以及最為重要的機制之一,它的用處非常廣。

          要認識this機制首先要知道,this並不是指向函式自身,也不是指向函式的作用域。

          this  實際上是在函式被呼叫時繫結的,它指向什麼完全取決於函式在哪被呼叫。