
《你不知道的javascript》這本書讀了有好幾遍了,似乎每一次讀都有新發(fā)現(xiàn),有些內(nèi)容并不是一下子可以弄懂的,每次讀似乎都能明白一些概念。
再重讀一下
this關(guān)鍵字。這個(gè)概念非常靈活,也非常難掌握,所以我覺得經(jīng)常讀讀沒有壞處。期待javascript一桶江湖,這樣學(xué)習(xí)的成本就低啦!參考本書的第二部分的第一章,第二章。
this關(guān)鍵字是js中最最復(fù)雜的機(jī)制之一。他被自動(dòng)定義到所有函數(shù)的作用域中。
在學(xué)習(xí)這個(gè)關(guān)鍵字的過程中似乎也走了很長(zhǎng)時(shí)間的彎路。你要問我為什么走了很長(zhǎng)時(shí)間的彎路,關(guān)鍵的地方還是沒有對(duì)核心的概念徹底學(xué)習(xí)和領(lǐng)會(huì),這一點(diǎn)和小學(xué)生學(xué)習(xí)新知識(shí)沒有任何區(qū)別。要想掌握this這個(gè)關(guān)鍵字,需要緊扣關(guān)鍵概念,不要憑空想象這到底是怎么一回事。
關(guān)鍵概念:js中的函數(shù)在調(diào)用的時(shí)候,一定,一定,一定會(huì)綁定在一個(gè)對(duì)象上,在分析this關(guān)鍵字的時(shí)候,一定要知道函數(shù)在調(diào)用的時(shí)候這個(gè)對(duì)象到底是誰?。
切記:js中函數(shù)的調(diào)用和定義是沒有任何關(guān)系的,函數(shù)所綁定的對(duì)象直到他被調(diào)用的時(shí)候才能知道。
this關(guān)鍵字的不確定定是把雙刃劍,一是函數(shù)調(diào)用時(shí)的對(duì)象不確定性,是js中函數(shù)的使用具有很大靈活性,每個(gè)對(duì)象都可以借用其他函數(shù)來完成功能。二是這也造成了this學(xué)習(xí)的一些困擾。所以在學(xué)習(xí)的時(shí)候先要理解this關(guān)鍵字的優(yōu)點(diǎn),然后再去學(xué)習(xí)造成困擾的地方
首先看看第一段代碼
page 75
//注意只是定義了一個(gè)函數(shù),并未調(diào)用,這時(shí)候函數(shù)是沒有綁定任何對(duì)象
function identify() {
return this.name.toUpperCase();
}
//同上面的函數(shù),但是這個(gè)函數(shù)內(nèi)部有點(diǎn)復(fù)雜,如果下面的代碼看不懂
//可以只看上面的函數(shù)
function speak() {
var greeting = "Hello, I'm " + identify.call( this );
console.log( greeting );
}
var me = { //定義了一個(gè)字面量對(duì)象
name: "Kyle"
};
var you = {//定義了一個(gè)字面量對(duì)象
name: "Reader"
};
//通過call方式把函數(shù)identify分別綁定到兩個(gè)對(duì)象上
//這時(shí)的this是指向me對(duì)象,和you對(duì)象
identify.call( me ); // KYLE
identify.call( you ); // READER
//通過call方式把函數(shù)call分別綁定到兩個(gè)對(duì)象上
//這時(shí)的this是指向me對(duì)象,和you對(duì)象
speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER
在javascript中定義函數(shù)的時(shí)候,函數(shù)是不屬于任何對(duì)象的。這一點(diǎn)非常的關(guān)鍵,非常的關(guān)鍵,非常的關(guān)鍵。這是理解this關(guān)鍵字的第一個(gè)障礙。
this關(guān)鍵字在js函數(shù)定義的時(shí)候的不確定性使得js函數(shù)使用有極大的靈活性,任何對(duì)象都可以使用他。
this到底是什么?
this的綁定和函數(shù)定義的位置沒有任何關(guān)系,只取決于函數(shù)調(diào)用的方式.
javascript中當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(有時(shí)也稱上下文)。這個(gè)記錄包括函數(shù)在哪里被調(diào)用,函數(shù)的調(diào)用方法,傳入的參數(shù)。this就是記錄中的一個(gè)屬性。
這樣在學(xué)習(xí)javascript關(guān)鍵字的首要問題是要解決怎么知道到函數(shù)的調(diào)用位置.
js對(duì)象綁定規(guī)則
每個(gè)js函數(shù)在調(diào)用的時(shí)候一定要找到一個(gè)對(duì)象,綁定以后才能使用。 這里是理解了js函數(shù)的定義和調(diào)用的區(qū)別以后需要掌握的一個(gè)規(guī)模最龐大的概念,在js中一共有四種綁定方式.就我個(gè)人來看,綁定規(guī)則并不難,難點(diǎn)還是在js的函數(shù)作用域的理解. 尤其是默認(rèn)綁定.這個(gè)綁定方式有極大的迷惑性。
默認(rèn)綁定
這個(gè)是函數(shù)的獨(dú)立調(diào)用,也就是在一個(gè)函數(shù)直接調(diào)用的時(shí)候,似乎是沒有綁定到對(duì)象上的,但是根據(jù)前面的介紹,js中函數(shù)調(diào)用時(shí)必須要綁定到一個(gè)對(duì)象上。
看下面代碼 page 83
function foo() { //這是函數(shù)的定義位置
console.log( this.a );
}
var a = 2;//這個(gè)變量定義的含義是什么呢??jī)H僅是賦值給a嗎?
foo(); // 2 //這是函數(shù)的調(diào)用位置。為什么會(huì)打印出2呢?
很多函數(shù)都是這么調(diào)用的,照貓畫虎也可以寫出來,但是理解了具體的含義就不一樣了。
foo這個(gè)函數(shù)定義在全局作用域中(window作用域中),巧合的是他的調(diào)用也是在全局作用域中,注意這僅僅是巧合,巧合。 那么foo()調(diào)用的時(shí)候?yàn)槭裁磿?huì)打印出變量 a的值呢?盡管使用了var這個(gè)關(guān)鍵字,但是分析作用域可以知道,a這個(gè)變量實(shí)際是全局變量,說的再明白一點(diǎn),a實(shí)際是window這個(gè)全局對(duì)象的一個(gè)屬性,2是這個(gè)屬性的屬性值。
foo()調(diào)用的時(shí)候是一絲不掛的全裸狀態(tài),僅僅是函數(shù)本身,沒有任何修飾符,這個(gè)時(shí)候他也沒有任何函數(shù)包裹,處在全局作用域下面,所以foo()里面的this是指向全局對(duì)象的,當(dāng)要打印this.a的時(shí)候,尋找foo()調(diào)用位置會(huì)找到全局作用域,找全局作用域的屬性this.a的時(shí)候會(huì)打印出2這個(gè)屬性值。
我們?cè)谑褂胹etTimeout,setInterval函數(shù)的時(shí)候,實(shí)際這兩個(gè)函數(shù)就是一絲不掛的,同樣綁定在window對(duì)象上。
隱式綁定
函數(shù)在調(diào)用的時(shí)候被添加了修飾符。看下面這個(gè)代碼
page 85
function foo() { //定義在全局作用下的函數(shù),僅僅是定義,不是調(diào)用位置
console.log( this.a );
}
var obj = { //定義一個(gè)對(duì)象
a: 2,
foo: foo
};
obj.foo(); // 2 給foo()函數(shù)找了一個(gè)對(duì)象,this就指向這個(gè)對(duì)象了
這是最常見的方式了,如果不寫前面的obj是不是就是上面的默認(rèn)綁定了?
隱式丟失
經(jīng)常在js代碼的嵌套回調(diào)函數(shù)中看到在外層函數(shù)開始的一句
var that=this; //這是什么含義
或許你已經(jīng)會(huì)用了,但是理解了其中意義用起來會(huì)更加得心應(yīng)手啊
看下面段代碼.這段代碼其實(shí)以前我也不太理解,問題還是沒有徹底領(lǐng)悟js函數(shù)定義和調(diào)用之間是沒有關(guān)系的這一點(diǎn)。
page 86
function foo() { //定義了一個(gè)函數(shù)
console.log( this.a );
}
var obj = { //定義了一個(gè)對(duì)象字面量
a: 2,
foo: foo //函數(shù)作為對(duì)對(duì)象的屬性
};
var bar = obj.foo; //把obj對(duì)象的函數(shù)foo屬性賦值給bar變量
//這里就是理解這個(gè)問題的關(guān)鍵,如果你現(xiàn)在認(rèn)為調(diào)用bar()的時(shí)候綁定的對(duì)象
//是obj那就完全搞錯(cuò)了。這個(gè)時(shí)候僅僅是把函數(shù)foo賦值給了var變量,
//并沒有把對(duì)象也給bar變量,因?yàn)檫@里還不是foo()函數(shù)的調(diào)用位置,現(xiàn)在
//foo函數(shù)還沒有綁定對(duì)象,那么調(diào)用bar()的時(shí)候?qū)ο蟮降资钦l?不知道。
//調(diào)用的時(shí)候才知道。
var a = "oops, global"; // 任然是全局對(duì)象的屬性
bar(); // "oops, global" 這里執(zhí)行的是默認(rèn)綁定,this就是去全局對(duì)象啦
下面這段代碼就是使用var that=this的場(chǎng)景
在使用回調(diào)函數(shù)的時(shí)候要留心。js中函數(shù)是一等對(duì)象,可以作為另一個(gè)函數(shù)的參數(shù)傳入函數(shù)。 問題就出在這里了,函數(shù)一旦作為實(shí)參代替形參的時(shí)候,實(shí)際也執(zhí)行了和上面代碼一樣的賦值過程,實(shí)際只是傳遞了函數(shù)本身,原先的對(duì)象就沒有了。
page 86
function foo() { //定義一個(gè)函數(shù)
console.log( this.a );
}
function doFoo(fn) { //fn是形參
// 如果函數(shù)作為實(shí)參傳入相當(dāng)于代碼 var fn=obj.foo
//和上面一段代碼是完全一樣的,只是函數(shù)本身,并沒有綁定任何對(duì)象
fn(); // 在這里調(diào)用的時(shí)候,由于fn只代表foo()函數(shù),被綁定到全局對(duì)象上了
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"不要被obj.foo迷惑了
//沒有實(shí)際執(zhí)行函數(shù)的調(diào)用,此時(shí)obj.foo僅僅代表沒有綁定任何對(duì)象的函數(shù)
//這個(gè)代碼塊看著眼熟么?這就是javascript中回調(diào)函數(shù)的樣子,當(dāng)
//一個(gè)函數(shù)作為參數(shù)傳遞進(jìn)另一個(gè)函數(shù)的時(shí)候,這個(gè)參數(shù)函數(shù)就找不到自己綁定的對(duì)象是誰了,
//所以就默認(rèn)綁定到全局對(duì)象上了。但是我們既然在一個(gè)函數(shù)里調(diào)用另一個(gè)函數(shù),肯定是要用這個(gè)函數(shù)操作當(dāng)前的對(duì)象,那么既然找不到了,我們就手動(dòng)給他指定一個(gè)對(duì)象吧。這就是為什么要使用
//var that=this的原因。我覺得理解這個(gè)概念,js的功力至少會(huì)增加5%??。至于具體使用,我想寫出來其實(shí)沒有什么必要了。這樣的代
//碼隨處可見.
最后我們會(huì)返回來看看怎么解決這個(gè)問題。
顯示綁定
直接使用apply()和call()方法來給函數(shù)指定一個(gè)對(duì)象
page 88
function foo() { //定義函數(shù)
console.log( this.a );
}
var obj = { //對(duì)象字面量定義
a: 2
};
foo.call( obj ); // 2 強(qiáng)制綁定到obj對(duì)象上
使用顯示綁定還不能解決this的丟失問題,所以可以創(chuàng)建一個(gè)包裹函數(shù)
page 89
function foo(something) { //定義函數(shù)
console.log( this.a, something );
return this.a + something;
}
var obj = { //對(duì)象字面量
a: 2
};
var bar = function() { 包裹函數(shù),顯示綁定
return foo.apply( obj, arguments );
//返回綁定了對(duì)象和傳入?yún)?shù)的函數(shù)調(diào)用
//這個(gè)語句在js的代碼中非常的常見
};
var b = bar( 3 ); // 2, 3
console.log( b ); // 5
//可以把綁定函數(shù)獨(dú)立出來
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
new 綁定
js中的new關(guān)鍵字和java中完全不同,js中沒有類,只有對(duì)象,在js中使用new 關(guān)鍵字的時(shí)候只是被調(diào)用的普通函數(shù)
function foo(a) { //定義函數(shù)
this.a = a;
}
var bar = new foo( 2 ); //僅僅是調(diào)用了一個(gè)函數(shù)
console.log( bar.a ); // 2
好了以上就是js this綁定的四種方式。 解決的關(guān)鍵問題是js中在函數(shù)調(diào)用的時(shí)候到底是屬于哪個(gè)對(duì)象的問題。
后面還有一點(diǎn)內(nèi)容,但是上面的內(nèi)容是最重要的。 2017年1月17日
的確包裹函數(shù)那里好像是沒有講清楚,我也忘了當(dāng)初是怎么理解的,等我再看看,然后更新。有些地方已經(jīng)做了更新!