一直以來對this的理解只在可以用,會用,卻沒有去深究其本質。這次,借着《JavaScript The Good Parts》,作了一次深刻的理解。(所有調試都可以在控制台中看到,瀏覽器F12鍵)
下面我們一起來看看這個this吧。
在我們聲明一個函數時,每個函數除了有定義時的parameters(形參),自身還會有額外的兩個參數,一個是this,一個是arguments(實參)。arguments就是函數實際接受到的參數,是一個類數組。arguments我只做個簡略的介紹,重點我們放在this指針上。
在面向對象變成中,this十分重要,它的值取決於調用的模式。而在JavaScript中,一共有4中調用模式:方法調用模式、函數調用模式、構造函數調用模式、apply調用模式。
-
方法調用模式
當一個函數是作為一個對象的屬性時,我們通常稱這個函數是這個對象的一個方法。當這個方法被調用時,this就會指向該方法所屬對象。
<script type="text/javascript"> var people = { name : "Yika", sayName : function(){ console.log(this.name); //"Yika" //this已經綁定在了people對象上了 } } people.sayName(); </script>
如栗子所示,this指向了sayName對象,這種通過this取得所屬對象上下文的方法,就是公共方法。(publice method)
-
函數調用模式
當一個函數被調用時並非某個對象上的方法,那么它就是作為一個函數被調用的。
這種模式調用,this會指向window對象,即使這個函數或許是在外部函數里調用的,我們來看栗子。
1 <script type="text/javascript"> 2 var name = "window-Yika"; 3 var people = { 4 name : "people-Yika", 5 student : function(){ 6 console.log(this); //這里的this綁定的是對象people 7 function sayName(){ 8 var name = "sayName-Yika"; 9 console.log(this.name); //window-Yika 10 //即使sayName函數本身和它所在的people對象都有name值,但是this是指向window的 11 }; 12 sayName(); 13 } 14 } 15 16 people.student(); 17 </script>
這樣一看,是不是大概知道該怎么解決JavaScript這個“設計錯誤"了呢。
是的只要在student函數里面,也就是第6行,將this緩存起來。然后再將this通過變量轉移到sayName函數里就可以解決啦!
var people = { name : "people-Yika", student : function(){ var self = this; //將this緩存起來 function sayName(){ var name = "sayName-Yika"; console.log(self.name); //"people-Yika",此時的self指向的是people對象 }; sayName(); } }
-
構造函數調用模式
JavaScript里一講到構造函數,腦海里就會有:“函數名大寫!調用的時候要用new操作符!” 函數名大寫好理解,是為了規范統一構造函數的命名。可是你有沒有深究過為什么要用new呢?如果在一個函數前面帶上new來調用,那么函數后台會創建一個指向該函數prototype的新對象,同時this也綁定在新對象上。JavaScript是一門基於原型繼承的語言,對原型prototype不是很清楚的同學可以自己去查一下資料,我重點放在this上面。
我們先來看看構造函數一般長什么樣子。
<script type="text/javascript"> function People(name){ this.name = name; //這里的this,用new調用后便指向了新對象Yika
this.sayName = function(){ console.log(this.name); //輸出 } }
var Yika = new People("Yika");
Yika.sayName(); //輸出“Yika" ,因為Yika是通過new調用得來的,this都綁定在了Yika對象上。 </script>
乍一看,好像不是好懂,怎么剛才在函數里的this是指向window,現在不用緩存就可以指向People函數呢?
沒關系,剛才不是說函數通過new調用,會在背地里自己做“做壞事”么,我們一起看看到底做了哪些事。
<script type="text/javascript"> function People(name){ var that = {}; //壞事一:自己生成一個對象 that.name = name; that.sayName = function(){ console.log(that.name); }; return that; //壞事二,自己會改變return的行為,return剛生成的對象 } var Yika = People("Yika"); //這里可以省略new,模仿調用new操作符 Yika.sayName(); //和剛才一樣輸出"Yika" </script>
這樣看就明白清楚了吧,new不僅會生成一個對象,而且還會自動return這個對象,這樣自然this便指向了這個新對象。
千萬記得要用 new 去調用構造函數,不然出了問題,是沒有警告的,所有大寫約定還是十分有必要的。
-
Apply調用模式
apply方法讓我們構建一個參數數組傳遞給調用函數,也允許我們改變this值。
function.apply(this綁定的值, arguments參數數組)
apply可以說的東西太多了,我這里只舉個的栗子來幫助大家理解:
<script type="text/javascript"> function People(name){ this.name = name; this.sayName = function(){ console.log(this.name); //sayName這個方法是屬於People構造函數的 } } function Student(name){ People.apply(this, arguments);//借用構造函數的集成方式,就是在Student構造函數里,通過apply調用People構造函數,並改變People的this值 //這樣每次創建Student實例時,都會調用People構造函數 } var student = new Student("Yika"); student.sayName(); //輸出“Yika” </script>
我們可以通過apply輕易的修改函數的this綁定對象,和apply相似的方法call也有一樣的效果,有興趣的同學可以自己搜索學習一下。
好了,總算講完改變this的四種調用模式了,方法調用模式和構造函數調用模式會用的更多,也會更重要一點,而函數調用模式,我們則要學會避開其中的陷阱。
若有錯誤,請及時反映,我會盡快糾正,以防誤導他人,謝謝!