《你不知道的javascript》這本書讀了有好幾遍了,似乎每一次讀都有新發現,有些內容並不是一下子可以弄懂的,每次讀似乎都能明白一些概念。再重讀一下this關鍵字。這個概念非常靈活,也非常難掌握,所以我覺得經常讀讀沒有壞處。期待javascript一桶江湖,這樣學習的成本就低啦!參考本書的第二部分 ...
《你不知道的javascript》這本書讀了有好幾遍了,似乎每一次讀都有新發現,有些內容並不是一下子可以弄懂的,每次讀似乎都能明白一些概念。
再重讀一下this
關鍵字。這個概念非常靈活,也非常難掌握,所以我覺得經常讀讀沒有壞處。期待javascript一桶江湖,這樣學習的成本就低啦!
參考本書的第二部分的第一章,第二章。
this關鍵字是js中最最複雜的機制之一。他被自動定義到所有函數的作用域中。
在學習這個關鍵字的過程中似乎也走了很長時間的彎路。你要問我為什麼走了很長時間的彎路,關鍵的地方還是沒有對核心的概念徹底學習和領會,這一點和小學生學習新知識沒有任何區別。要想掌握this這個關鍵字,需要緊扣關鍵概念,不要憑空想象這到底是怎麼一回事。
關鍵概念:js中的函數在調用的時候,一定,一定,一定會綁定在一個對象上,在分析this關鍵字的時候,一定要知道函數在調用的時候這個對象到底是誰?。
切記:js中函數的調用和定義是沒有任何關係的,函數所綁定的對象直到他被調用的時候才能知道。
this關鍵字的不確定定是把雙刃劍,一是函數調用時的對象不確定性,是js中函數的使用具有很大靈活性,每個對象都可以借用其他函數來完成功能。二是這也造成了this學習的一些困擾。所以在學習的時候先要理解this關鍵字的優點,然後再去學習造成困擾的地方
首先看看第一段代碼
page 75
//註意只是定義了一個函數,並未調用,這時候函數是沒有綁定任何對象
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"
};
//通過call方式把函數identify分別綁定到兩個對象上
//這時的this是指向me對象,和you對象
identify.call( me ); // KYLE
identify.call( you ); // READER
//通過call方式把函數call分別綁定到兩個對象上
//這時的this是指向me對象,和you對象
speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER
在javascript中定義函數的時候,函數是不屬於任何對象的。這一點非常的關鍵,非常的關鍵,非常的關鍵。這是理解this關鍵字的第一個障礙。
this關鍵字在js函數定義的時候的不確定性使得js函數使用有極大的靈活性,任何對象都可以使用他。
this到底是什麼?
this的綁定和函數定義的位置沒有任何關係,只取決於函數調用的方式
.
javascript中當一個函數被調用的時候,會創建一個活動記錄(有時也稱上下文)。這個記錄包括函數在哪裡被調用,函數的調用方法,傳入的參數。this就是記錄中的一個屬性。
這樣在學習javascript關鍵字的首要問題是要解決怎麼知道到函數的調用位置
.
js對象綁定規則
每個js函數在調用的時候一定要找到一個對象,綁定
以後才能使用。 這裡是理解了js函數的定義和調用的區別以後需要掌握的一個規模最龐大的概念,在js中一共有四種綁定方式.就我個人來看,綁定規則並不難,難點還是在js的函數作用域的理解
. 尤其是預設綁定
.這個綁定方式有極大的迷惑性。
預設綁定
這個是函數的獨立調用,也就是在一個函數直接調用的時候,似乎是沒有綁定到對象上的,但是根據前面的介紹,js中函數調用時必須要綁定到一個對象上。
看下麵代碼 page 83
function foo() { //這是函數的定義位置
console.log( this.a );
}
var a = 2;//這個變數定義的含義是什麼呢?僅僅是賦值給a嗎?
foo(); // 2 //這是函數的調用位置。為什麼會列印出2呢?
很多函數都是這麼調用的,照貓畫虎也可以寫出來,但是理解了具體的含義就不一樣了。
foo這個函數定義在全局作用域中(window作用域中),巧合的是他的調用也是在全局作用域中,註意這僅僅是巧合,巧合。 那麼foo()調用的時候為什麼會列印出變數 a的值呢?儘管使用了var這個關鍵字,但是分析作用域可以知道,a這個變數實際是全局變數,說的再明白一點,a實際是window這個全局對象的一個屬性,2是這個屬性的屬性值。
foo()調用的時候是一絲不掛的全裸狀態,僅僅是函數本身,沒有任何修飾符,這個時候他也沒有任何函數包裹,處在全局作用域下麵,所以foo()裡面的this是指向全局對象的,當要列印this.a的時候,尋找foo()調用位置會找到全局作用域,找全局作用域的屬性this.a的時候會列印出2這個屬性值。
我們在使用setTimeout,setInterval函數的時候,實際這兩個函數就是一絲不掛的,同樣綁定在window對象上。
隱式綁定
函數在調用的時候被添加了修飾符。看下麵這個代碼
page 85
function foo() { //定義在全局作用下的函數,僅僅是定義,不是調用位置
console.log( this.a );
}
var obj = { //定義一個對象
a: 2,
foo: foo
};
obj.foo(); // 2 給foo()函數找了一個對象,this就指向這個對象了
這是最常見的方式了,如果不寫前面的obj是不是就是上面的預設綁定了?
隱式丟失
經常在js代碼的嵌套回調函數中看到在外層函數開始的一句
var that=this; //這是什麼含義
或許你已經會用了,但是理解了其中意義用起來會更加得心應手啊
看下麵段代碼.這段代碼其實以前我也不太理解,問題還是沒有徹底領悟js函數定義和調用之間是沒有關係的這一點。
page 86
function foo() { //定義了一個函數
console.log( this.a );
}
var obj = { //定義了一個對象字面量
a: 2,
foo: foo //函數作為對對象的屬性
};
var bar = obj.foo; //把obj對象的函數foo屬性賦值給bar變數
//這裡就是理解這個問題的關鍵,如果你現在認為調用bar()的時候綁定的對象
//是obj那就完全搞錯了。這個時候僅僅是把函數foo賦值給了var變數,
//並沒有把對象也給bar變數,因為這裡還不是foo()函數的調用位置,現在
//foo函數還沒有綁定對象,那麼調用bar()的時候對象到底是誰?不知道。
//調用的時候才知道。
var a = "oops, global"; // 任然是全局對象的屬性
bar(); // "oops, global" 這裡執行的是預設綁定,this就是去全局對象啦
下麵這段代碼就是使用var that=this的場景
在使用回調函數的時候要留心。js中函數是一等對象,可以作為另一個函數的參數傳入函數。 問題就出在這裡了,函數一旦作為實參代替形參的時候,實際也執行了和上面代碼一樣的賦值過程,實際只是傳遞了函數本身,原先的對象就沒有了。
page 86
function foo() { //定義一個函數
console.log( this.a );
}
function doFoo(fn) { //fn是形參
// 如果函數作為實參傳入相當於代碼 var fn=obj.foo
//和上面一段代碼是完全一樣的,只是函數本身,並沒有綁定任何對象
fn(); // 在這裡調用的時候,由於fn只代表foo()函數,被綁定到全局對象上了
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"不要被obj.foo迷惑了
//沒有實際執行函數的調用,此時obj.foo僅僅代表沒有綁定任何對象的函數
//這個代碼塊看著眼熟麽?這就是javascript中回調函數的樣子,當
//一個函數作為參數傳遞進另一個函數的時候,這個參數函數就找不到自己綁定的對象是誰了,
//所以就預設綁定到全局對象上了。但是我們既然在一個函數里調用另一個函數,肯定是要用這個函數操作當前的對象,那麼既然找不到了,我們就手動給他指定一個對象吧。這就是為什麼要使用
//var that=this的原因。我覺得理解這個概念,js的功力至少會增加5%