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 實際上是在函數被調用時綁定的,它指向什麼完全取決於函數在哪被調用。