javascript-This全面解析

cover.jpg

《你不知道的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)做了更新!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們現(xiàn)在知道了每個(gè)函數(shù)的this是在運(yùn)行的時(shí)候進(jìn)行綁定的,完全取決于函數(shù)的調(diào)用位置,也就是該函數(shù)的調(diào)用方法。 關(guān)于...
    FeRookie閱讀 1,088評(píng)論 0 5
  • 特別說明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 818評(píng)論 0 1
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,697評(píng)論 18 399
  • 雨直泄而下
    嵐之英閱讀 267評(píng)論 0 0
  • 電影《我是路人甲》講述了一批真實(shí)生活橫店漂泊的演員,為了自己的演員夢(mèng)想,從四海五湖來到橫店。在橫店,他們從群眾演員...
    利利VS櫻木花道閱讀 449評(píng)論 0 1

友情鏈接更多精彩內(nèi)容