javascript棄坑之路之原來是這樣的this

LT_bear發表於2018-01-05

說this是js程式設計中最重要的變數也不為過了,而在遇到this時,也曾經傻傻分不清楚this指代的到底是什麼,所以現在對曾踩過this的坑做一下總結(注:本文中的this值適用場景為執行環境為瀏覽器時,執行環境為node時會有不同表現)。

this的場景

雖然this很令人迷惑,但其實總會發現,在瀏覽器環境中,this的指代不外乎兩種,要麼是window,要麼是某個特定的物件(window之外的物件),而涉及到具體的場景中this的取值,可以歸納為五種。

全域性作用域中直接呼叫this

var global=4;
console.log(this.global);
複製程式碼

如上例中,在全域性作用域下定義了變數global,同樣的,當我們呼叫this.global時,會把之前定義的變數列印出來。這是因為,全域性作用域中定義的變數相當於為window物件新增了屬性,而全域性作用域下直接呼叫this,同樣指代了window物件,因此能夠取到之前定義的this值。

物件方法中的this

物件方法,也即物件的成員函式,物件方法中的this指代的是物件本身。

var obj={
  value:2,
  func:function(){
    console.log(this.value);//2
  }
}
複製程式碼

如上例中obj是一個物件,value和func分別為它的成員變數和成員方法,此時在方法中取到的this.value值即為obj.value值,this指代的是obj本身。

new操作符中的this

new操作符通常和建構函式共同出現,往往用於建立一個新物件,而此時建構函式中的this指代的正是所建立的新物件。

function Car(){
 this.color="red";
 this.size="large";
 this.getColor=function(){
   console.log(this.color)
 }
}

var car=new Car();
console.log(car.getColor())//red
複製程式碼

如上例所示,這裡可以正確的把red列印出來,其中函式Car是一個最基本的建構函式,當用new 操作符來建立一個新的Color物件時,建構函式執行過程中首先會新建一個物件,然後將this賦值給這個新物件,之後所有對this的賦值操作都等同於對新物件賦值了。

普通函式中的this

在呼叫普通函式表示式時,呼叫this,往往指代的是window物件。

var value=3;
var func=function(){
  console.log(this.value);
}
func();
複製程式碼

如上所示,func是一個普通的指代函式的變數,在該函式中呼叫this,指代的是window物件。

繫結this

繫結this也是很常見的一種場景,call,apply,bind方法均用於繫結this到指定的物件。

this常見的坑

以上我們把this的相關場景分成了五種,但即使這樣,也常常有令人迷惑的地方,下面來看下常見的兩個題目:

  • 題目一
var obj={
 a:3;
 getValue:function(){
   return this.a;
 }
}
var func=obj.getValue;
console.log(func());
複製程式碼

看到上例,可能會毫不猶豫的說答案當然是3了,很簡單嘛,obj.getValue,所屬物件是obj,裡面的this指代的當然是obj了,返回的this.a當然就是obj.a,是3沒問題,這時候,看了控制檯的列印就會大吃一驚了,答案是undefined,這是因為var func=obj.getValue這一句賦值語句被忽略了,但這是至關重要的,經過這個賦值語句之後,this指代的不再是obj本身了,而是全域性物件window,因為wiondow中不含有a變數,this.a當然是undefined了。

  • 題目二
var obj={
  a:4,
  getValue:function(){
    var innerfunc=function(){
     return this.a;
    }
    return innerfunc();
  }
}

console.log(obj.func());
複製程式碼

看到這裡,可能又會脫口而出,列印出的值是4沒錯,這次明顯就是直接呼叫的obj物件的func函式,this總該指向obj了吧,但是當然了,答案依然是undefined,不要忽略,getValue函式中this是經過innerfunc呼叫的,而innerfunc的呼叫物件並不是obj,所以在執行innerfunc函式時,其中的this是指代window的,a值是undefined。

this和作用域

以上的題目二之所以迷惑我們,關鍵的一點是,有時候不自覺得將this和作用域關聯起來,因此,不要將this和作用域等同,不要自然而然的找innerfunc中的this時,會去找其包含作用域getValue的this,因為每個函式天生就有this變數,所以某個函式本身的this與其包含作用域中的this無直接關係。

快捷判斷this

有了以上的五種場景和常見的兩個this題目坑,或許可以作如下總結,this的指代值只有兩種:

  • 指定方法的呼叫物件,this是呼叫物件 指定方法的呼叫物件物件也有多種方法,向第一節提到的,直接的呼叫物件方法,如obj.xxx(),new建構函式中指定的呼叫物件為新物件,apply,call,bind方法也相當於繫結了呼叫物件;
  • 呼叫函式表示式的值或直接呼叫函式表示式時,this是window 直接呼叫函式表示式或其值的場景也有很多,如
var a=function(){
  return this.value;
}
a();//呼叫函式表示式的值
(function(){
  return this.value;
})();//呼叫函式表示式
~~function(){
  return this.value;
}();//呼叫函式表示式;

複製程式碼

第一節中提到的全域性作用域中的this也可以等同於在一個全域性函式中呼叫,this,是window. 有了以上兩種判斷方法就會更簡單了,甚至只要區分出一種場景,就能快速判斷了,如只要確定this所在的函式指定(繫結)了呼叫物件,this就相當於該物件,否則就是window.

this這麼複雜好嗎

上述的兩個坑只是稍微列舉了一下,實際開發中this的坑真的很多,尤其對我這種小白來說,總結髮現this的這麼多坑的原因往往與一個特性有關,就是this的後繫結,在看到涉及this的函式定義時,往往不自覺的在這裡就給this賦值了,如上節中的坑一,正因為我看到的this所在的函式是屬於一個物件的,所以未理會var func=obj.getValue,變不由得將this和obj繫結了,但這種明顯是錯誤的,由於this的後繫結,我們不應隨意在看到this時就將其賦值,而應好好分析在呼叫時this的所指才是正解。 那麼為什麼this要後繫結呢,給我們帶來了這麼多坑,這應該設計到語言的設計問題了,這裡無法深入,但毋庸置疑的是,js的極其重要的原型繼承機制就是依賴於this後繫結的,想象一下this不是後繫結,後果很可怕。。。。

相關文章