JavaScript中的this

前言

總括:詳解JavaScript中的this的一篇總結,不懂this這個難點,很多時候會造成一些困擾,寫出一些bug不知如何收場,所以一起來寫bug吧,不對,一起來寫代碼吧。

人生得意須盡歡,莫使金樽空對月

正文

? JavaScript中的this格外的不一樣,比如Java語言中的this是在代碼的執行階段是不可更改,而JavaScript的this是在調用階段進行綁定。??因為這一性質所以給了this很大的發揮空間。但其在嚴格模式和非嚴格模式下又有些不一樣,在函數的不同調用方式也導致this有些區別。??

What's this?

??首先對this的下個定義:this是在執行上下文創建時確定的一個在執行過程中不可更改的變量。

所謂執行上下文,就是JavaScript引擎在執行一段代碼之前將代碼內部會用到的一些變量函數this提前聲明然后保存在變量對象中的過程。這個'代碼片段'包括:全局代碼(script標簽內部的代碼)、函數內部代碼eval內部代碼。而我們所熟知的作用域鏈也會在保存在這里,以一個類數組的形式存儲在對應函數的[[Scopes]]屬性中。

this只在函數調用階段確定,也就是執行上下文創建的階段進行賦值,保存在變量對象中。這個特性也導致了this的多變性:??即當函數在不同的調用方式下都可能會導致this的值不同。

????上面我們說過了在嚴格模式下和非嚴格模式下this表現不同:

var a = 1;
function fun() {
   'use strict';
    var a = 2;
    return this.a;
}
fun();//??報錯 Cannot read property 'a' of undefined

??嚴格模式下,this指向undefined;

var a = 1;
function fun() {
    var a = 2;
    return this.a;
}
fun();//1

??非嚴格模式下this指向window;

上面同一段代碼,在不同模式下之所以有不同表現,就是因為this在嚴格模式,非嚴格模式下的不同。

結論:當函數獨立調用的時候,在嚴格模式下它的this指向undefined,在非嚴格模式下,當this指向undefined的時候,自動指向全局對象(瀏覽器中就是window)

多提一句,在全局環境下,this就是指向自己,再看??:

this.a = 1;
var b = 1;
c = 1;
console.log(this === window)//true
//這三種都能得到想要的結果,全局上下文的變量對象中存在這三個變量

再多提一句,當this不在函數中用的時候會怎樣?看??:

var a = 1000;
var obj = {
    a: 1,
    b: this.a + 1
}
function fun() {
    var obj = {
        a: 1,
        c: this.a + 2 //嚴格模式下這塊報錯 Cannot read property 'a' of undefined
    }
    return obj.c;
}
console.log(fun());//1002
console.log(obj.b);//1001

這種情況下this還是指向了window。那么我們可以單獨下個結論:

當obj在全局聲明的時候,obj內部屬性中的this指向全局對象,當obj在一個函數中聲明的時候,嚴格模式下this會指向undefined,非嚴格模式自動轉為指向全局對象。

??好了,剛剛小試牛刀下,知道了嚴格模式和非嚴格模式下this的區別,然而我們日常應用最多的還是在函數中用this,上面也說過了this在函數的不同調用方式還有區別,那么函數的調用方式都有哪些呢?四種:

  • 在全局環境或是普通函數中直接調用
  • 作為對象的方法
  • 使用apply和call
  • 作為構造函數

下面分別就四種情況展開:

直接調用

上面的??其實就是直接調用的,不過我決定再寫????:

var a = 1;
var obj  =  {
    a: 2,
    b: function () {
        function fun() {
          return this.a
        }
       console.log(fun());
    }
} 
obj.b();//1

fun函數雖然在obj.b方法中定義,但它還是一個普通函數,直接調用在非嚴格模式下指向undefined,又自動指向了全局對象,正如預料,嚴格模式會報錯undefined.a不成立,a未定義。

重要的事情再說一遍:當函數獨立調用的時候,在嚴格模式下它的this指向undefined,在非嚴格模式下,當this指向undefined的時候,自動指向全局對象(瀏覽器中就是window)。??

作為對象的方法

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
console.log(obj.b())//2

??b所引用的匿名函數作為obj的一個方法調用,這時候this指向調用它的對象。這里也就是obj。那么如果b方法不作為對象方法調用呢?啥意思呢,就是這樣??:

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
var t = obj.b;
console.log(t());//1

如上,t函數執行結果竟然是全局變量1,為啥呢?這就涉及Javascript的內存空間了,就是說,obj對象的b屬性存儲的是對該匿名函數的一個引用,可以理解為一個指針。當賦值給t的時候,并沒有單獨開辟內存空間存儲新的函數,而是讓t存儲了一個指針,該指針指向這個函數。相當于執行了這么一段偽代碼:

var a = 1;
function fun() {//此函數存儲在堆中
    return this.a;
}
var obj = {
  a: 2,
  b: fun //b指向fun函數
}
var t = fun;//變量t指向fun函數
console.log(t());//1

此時的t就是一個指向fun函數的指針,調用t,相當于直接調用fun,套用以上規則,打印出來1自然很好理解了。

使用apply,call

關于apply和call是干什么的怎么用本文不涉及,請移駕:applycall

這是個萬能公式,實際上上面直接調用的代碼,我們可以看成這樣的:

function fun() {
    return this.a;
}
fun();//1
//嚴格模式
fun.call(undefined)
//非嚴格模式
fun.call(window)

這時候我們就可以解釋下,為啥說在非嚴格模式下,當函數this指向undefined的時候,會自動指向全局對象,如上,在非嚴格模式下,當調用fun.call(undefined)的時候打印出來的依舊是1,就是最好的證據。

為啥說是萬能公式呢?再看函數作為對象的方法調用:

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
obj.b()
obj.b.call(obj)

如上,是不是很強大,可以理解為其它兩種都是這個方法的語法糖罷了,那么apply和call是不是真的萬能的呢?并不是,ES6的箭頭函數就是特例,因為箭頭函數的this不是在調用時候確定的,這也就是為啥說箭頭函數好用的原因之一,因為它的this固定不會變來變去的了。關于箭頭函數的this我們稍后再說。

作為構造函數

何為構造函數?所謂構造函數就是用來new對象的函數,像FunctionObjectArrayDate等都是全局定義的構造函數。其實每一個函數都可以new對象,那些批量生產我們需要的對象的函數就叫它構造函數罷了。注意,構造函數首字母記得大寫。

function Fun() {
  this.name = 'Damonre';
  this.age = 21;
  this.sex = 'man';
  this.run = function () {
    return this.name + '正在跑步';
  }
}
Fun.prototype = {
  contructor: Fun,
  say: function () {
    return this.name + '正在說話';
  }
}
var f = new Fun();
f.run();//Damonare正在跑步
f.say();//Damonare正在說話

如上,如果函數作為構造函數用,那么其中的this就代表它即將new出來的對象。為啥呢?new做了啥呢?

偽代碼如下:

function Fun() {
  //new做的事情
  var obj = {};
  obj.__proto__ = Fun.prototype;//Base為構造函數
  obj.name = 'Damonare';
  ...//一系列賦值以及更多的事
  return obj
}

也就是說new做了下面這些事:

  • 創建一個臨時對象
  • 給臨時對象綁定原型
  • 給臨時對象對應屬性賦值
  • 將臨時對象return

也就是說new其實就是個語法糖,this之所以指向臨時對象還是沒逃脫上面說的幾種情況。

當然如果直接調用Fun(),如下:

function Fun() {
  this.name = 'Damonre';
  this.age = 21;
  this.sex = 'man';
  this.run = function () {
    return this.name + '正在跑步';
  }
}
Fun();
console.log(window)

其實就是直接調用一個函數,this在非嚴格模式下指向window,你可以在window對象找到所有的變量。

另外還有一點,prototype對象的方法的this指向實例對象,因為實例對象的__proto__已經指向了原型函數的prototype。這就涉及原型鏈的知識了,即方法會沿著對象的原型鏈進行查找。

箭頭函數

剛剛提到了箭頭函數是一個不可以用call和apply改變this的典型。

我們看下面這個??:

var a = 1;
var obj = {
  a: 2
};
var fun = () => console.log(this.a);
fun();//1
fun.call(obj)//1

以上,兩次調用都是1。

那么箭頭函數的this是怎么確定的呢?箭頭函數會捕獲其所在上下文的 this 值,作為自己的 this,也就是說箭頭函數的this在詞法層面就完成了綁定。apply,call方法只是傳入參數,卻改不了this。

var a = 1;
var obj = {
  a: 2
};
function fun() {
    var a = 3;
    let f = () => console.log(this.a);
    f();
};
fun();//1
fun.call(obj);//2

如上,fun直接調用,fun的上下文中的this值為window,注意,這個地方有點繞。fun的上下文就是此箭頭函數所在的上下文,因此此時f的this為fun的this也就是window。當fun.call(obj)再次調用的時候,新的上下文創建,fun此時的this為obj,也就是箭頭函數的this值。

再來一個??:

function Fun() {
    this.name = 'Damonare';
}
Fun.prototype.say = () => {
    console.log(this);
}
var f = new Fun();
f.say();//window

有的同學看到這個??會很懵逼,感覺上this應該指向f這個實例對象啊。不是的,此時的箭頭函數所在的上下文是__proto__所在的上下文也就是Object函數的上下文,而Object的this值就是全局對象。

那么再來一個??:

function Fun() {
    this.name = 'Damonare';
    this.say = () => {
        console.log(this);
    }
}
var f = new Fun();
f.say();//Fun的實例對象

如上,this.say所在的上下文,此時箭頭函數所在的上下文就變成了Fun的上下文環境,而因為上面說過當函數作為構造函數調用的時候(也就是new的作用)上下文環境的this指向實例對象。

后記

文中定義均為個人總結,不妥之處還請雅正。

轉載請注明出處。

以上。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 導語 不得不說,作為一名初級的前端開發者,this關鍵字這個問題對于我來說一直是一個痛點,什么是this?什么是函...
    Nicole_tiny閱讀 536評論 0 4
  • this在JavaScript中似乎可以視而不見,但不去正視它學到的就只是殘缺的JS。拋開這些“形而上”的意義不說...
    棕小漸閱讀 314評論 0 2
  • 不論是面向對象,還是基于對象的語言,都會有this,我更喜歡叫他this指針,如果你不理解指針,認為它是個引用也無...
    faremax閱讀 685評論 2 1
  • 本來不想寫this的東西,因為實在是頭暈啊,講不清楚,JavaScript中的this真是讓人抓狂,好在我們調試的...
    轉角遇見一直熊閱讀 1,891評論 11 39
  • 距離3017還有兩天,覺得自己不能在這么胖下去了,我的青春要這么胖下去嗎,我不要我也不想,我要減肥,我要減肥,我要...
    顧陌涵閱讀 212評論 0 0