function identify(){
return this.name.toUpperCase();
}
function speak(){
var greeting = "Hello, I'm " + identify.call(this);
console.log(greeting);
}
var me = {
name: "Kyle"
}
var you = {
name: "Reader"
}
identify.call(me); // KYLE
identify.call(you); // READER
speak.call(me); // Hello,我是KYLE
speak.call(you); // Hello,我是READER
複製代碼
這段代碼能夠在不一樣的上下文對象(me和you)中重複使用函數identify()和speak(),不用針對每一個對象編寫不一樣版本的函數。javascript
若是不使用this,那就須要給identify()和speak()顯式傳入一個上下文對象。java
function identify(context){
return context.name.toUppperCase();
}
function speak(context){
var greeting = "Hello, I'm " + identify(context);
console.log(greeting);
}
identify(you); // READER
speak(me); // hello,I'm + identify(context)
複製代碼
然而,this提供了一種更優雅的方式來隱式「傳遞」一個對象引用,所以能夠將API設計得更加簡潔而且易於複用。ide
隨着你的使用模式愈來愈複雜,顯式傳遞上下文對象會讓代碼變得愈來愈混亂,使用this則不會這樣。函數
function foo(num){
console.log("foo:" + num);
// 記錄foo被調用的次數
this.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
if (i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被調用了多少次?
console.log(foo.count); // 0 -- 什麼?!
複製代碼
執行foo.count = 0時,的確向函數對象foo添加了一個屬性count。可是函數內部代碼this.count中的this並非指向那個函數對象,因此雖然屬性名相同,根對象卻並不相同,困惑隨之產生。ui
實際上,若是深刻探索,就會發現這段代碼無心中建立了一個全局變量count,其值爲NaN。this
遇到這樣的問題,大多數人會選擇下面的解決方案,建立另外一個帶有count屬性的對象。spa
function foo(num){
console.log("foo: " + num);
// 記錄foo被調用的次數
data.count++
}
var data = {
count: 0
}
var i;
for (i = 0; i < 10; i++){
if (i > 5){
foo(i);
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被調用了多少次?
console.log(data.count);
複製代碼
上面使用詞法做用域「解決」了問題,可是忽視了真正的問題--沒法理解this的含義和工做原理。設計
若是要從函數對象內部引用它自身,那隻使用this是不夠的。通常來講你須要經過一個指向函數對象的詞法標識符(變量)來引用它。code
思考下面的這兩個函數對象
function foo(){
foo.count = 4; // foo指向它自身
}
setTimeout(function (){
// 匿名(沒有名字的)函數沒法指向自身
},10)
複製代碼
第一個函數被稱爲具名函數,在它內部可使用foo來引用自身。
第二個例子中,傳入setTimeout(...)的回調函數沒有名稱標識符(這種函數被稱爲匿名函數),所以沒法從函數內部引用自身。
因此,對於咱們的例子來講,另外一種解決方法是使用foo標識符替代this來引用函數對象:
function foo(num){
console.log("foo:" + num);
// 記錄foo被調用的次數
foo.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
if (i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被調用了多少次?
console.log(foo.count); // 4
複製代碼
然而,這種方法一樣迴避了this的問題,而且徹底依賴於變量foo的詞法做用域。
另外一種方法是強制this指向foo函數對象:
function foo(num){
console.log("foo:" + num);
// 記錄foo被調用的次數
// 注意,在當前的調用方式下,this確實指向foo
this.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
if (i > 5){
foo.call(foo,i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被調用了多少次?
console.log(foo.count); // 4
複製代碼
第二種常見的誤解是,this指向函數的做用域。這個問題有點複雜,由於在某種狀況下它是正確的,可是在其餘狀況下它倒是錯誤的。
this在任何狀況下都不指向函數的詞法做用域。在JavaScript內部,做用域確實和對象相似,可見的標識符都是它的屬性。可是做用域「對象」沒法經過JavaScript代碼訪問,它存在於引擎內部。
function foo(){
var a = 2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo(); // ReferenceError: a is not defined
複製代碼
首先,這段代碼試圖經過this.bar()來引用bar()函數。這樣調用能成功純屬意外,咱們以後會解釋緣由。調用bar()最天然的方法是省略前面的this,直接使用詞法引用標識符。
此外,編寫這段代碼的開發者還試圖使用this聯通foo()和bar()的詞法做用域,從而讓bar()能夠訪問foo()做用域裏的變量a。這是不可能實現的,使用this不可能在詞法做用域中查到什麼。
每當你想要把this和詞法做用域的查找混合使用時,必定要提醒本身,這是沒法實現的。
this是在運行時進行綁定的,並非在編寫時綁定,它的上下文取決於函數調用時的各類條件。this的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方式、傳入的參數等信息。this就是這個記錄的一個屬性,會在函數執行的過程當中用到。
this既不指向自身也不指向函數的詞法做用域。
this其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。
關於懶
我原本想寫的是關於習慣,後來想一想仍是不要美化本身了,其實就是懶,也不必找藉口,這個的原由是提交代碼時不作確認,其實不少次我都作了,可是一遇到忙,或者提交的文件確實多的時候,我就懶得去檢查了,這樣就天然而然會致使不少問題,好比,以前就犯過API文檔的接口名和實際的接口名不一致而被懟的狀況,被教育一番,本身也非常羞愧,寫了紙條提醒必定不要再犯,可是吧,仍是那句話,忙起來就忘了,這也是我想把它歸到習慣的緣由,此次又是一樣的問題,直接把代碼粘貼過來改了一下,也沒有檢查,就提交了,天然又被批評了一番,心中的懊悔自沒必要說,意識到了本身須要改正的兩個地方,1.提交的東西要嚴謹,2.儘可能不粘貼代碼,此次應該確定不會由於什麼就忘了,由於教訓是慘痛的!