深入學習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 實際上是在函式被呼叫時繫結的,它指向什麼完全取決於函式在哪被呼叫。