Javascript基礎(chǔ)系列之this

前言

本文翻譯自this

概述

很多程序員習(xí)慣的將this和面向?qū)ο缶o密緊密連續(xù)在一起潦嘶,this指向了構(gòu)造函數(shù)中新建的對(duì)象床佳。雖然在這個(gè)說(shuō)法在ECMAScript也是成立的怕品,但是this不僅僅是指向構(gòu)造函數(shù)的實(shí)例

定義

this是執(zhí)行上下文的一個(gè)屬性

activeExecutionContext = {
  VO: {...},
  this: thisValue
};

this 與上下文的可執(zhí)行代碼類(lèi)型緊密相關(guān)峡钓,其值在進(jìn)入上下文階段就確定了槐雾,并且在執(zhí)行代碼階段不能被改變

全局代碼中的this

局代碼中的 this 非常簡(jiǎn)單夭委,this 始終是全局對(duì)象自身,因此募强,可以間接獲取引用

// 顯式定義全局對(duì)象的屬性
this.a = 10; // global.a = 10
alert(a);    // 10

// 通過(guò)賦值給不受限的標(biāo)識(shí)符來(lái)進(jìn)行隱式定義
b = 20;
alert(this.b); // 20

// 通過(guò)變量聲明來(lái)進(jìn)行隱式定義
// 因?yàn)槿稚舷挛闹械淖兞繉?duì)象就是全局對(duì)象本身
var c = 30;
alert(this.c); // 30

函數(shù)代碼中的 this

this 在函數(shù)代碼中的時(shí)候株灸,事情就變得有趣多了。這種情況下是最復(fù)雜的擎值,并且會(huì)引發(fā)很多的問(wèn)題慌烧。

函數(shù)代碼中的 this的第一個(gè)(同時(shí)也是最主要)的特性就是:它并非靜態(tài)綁定在函數(shù)上。

如上所述幅恋,this 的值是進(jìn)入執(zhí)行上下文階段確定的杏死,函數(shù)代碼中的 this 的值可能每次都不一樣。

而且捆交,一旦進(jìn)入代碼執(zhí)行階段淑翼,其值就維持不變了。也就是說(shuō)品追,要給 this 賦一個(gè)新值是不可能的玄括,因?yàn)?this 根本就不是一個(gè)變量:

var foo = {x: 10};

var bar = {
  x: 20,
  test: function () {
    alert(this === bar); // true
    alert(this.x);       // 20
    this = foo;    // error, 不能更改this的值
    alert(this.x); // 如果沒(méi)有錯(cuò)誤,則其值為10而不是20
  }
};
// 在進(jìn)入上下文的時(shí)候肉瓦,this 的值就確定了是“bar”對(duì)象
bar.test(); // true, 20
foo.test = bar.test;

// 但是遭京,這個(gè)時(shí)候,this的值又會(huì)變成“foo”,雖然我們調(diào)用的是同一個(gè)函數(shù)
foo.test(); // false, 10

那么泞莉,在函數(shù)代碼中有哪些因數(shù)會(huì)影響this值的變化哪雕?有如下因數(shù):

首先,在通常的函數(shù)調(diào)用時(shí)鲫趁,this是由激活上下文代碼的調(diào)用者(caller)決定的斯嚎,即調(diào)用函數(shù)的父級(jí)上下文。并且this的值是由調(diào)用表達(dá)式的形式?jīng)Q定的(換句話說(shuō)就是挨厚,由調(diào)用函數(shù)的語(yǔ)法決定)堡僻。

了解并記住這點(diǎn)非常重要,這樣才能在任何上下文中都能準(zhǔn)確判斷this的值疫剃。更確切地講钉疫,調(diào)用表達(dá)式的形式(或者說(shuō),調(diào)用函數(shù)的方式)影響了 this 的值巢价,而不是其他因素

(一些關(guān)于 JavaScript的文章和書(shū)籍中指出:“this的值取決于函數(shù)定義的方式牲阁,如果是全局函數(shù)固阁,那么this的值就是全局對(duì)象,如果函數(shù)是某個(gè)對(duì)象的方法咨油,那么this的值就是該對(duì)象” -- 這絕對(duì)不正確)您炉。下面我們將看到,即便是全局函數(shù)役电,this的值也會(huì)因?yàn)檎{(diào)用函數(shù)的方式不同而不同

function foo() {
  alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// 然而赚爵,同樣的函數(shù),以另外一種調(diào)用方式的話法瑟,this的值就不同了
foo.prototype.constructor(); // foo.prototype

同樣冀膝,調(diào)用對(duì)象中的方法,this值也可能不是該對(duì)象

var foo = {
  bar: function () {
    alert(this);
    alert(this === foo);
  }
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true

// 同樣地霎挟,相同的函數(shù)以不同的調(diào)用方式窝剖,this的值也就不同了
exampleFunc(); // global, false

那么,究竟調(diào)用方式是如何影響this的值酥夭?為了完全弄懂其中的奧妙赐纱,首選需要了解一種內(nèi)部類(lèi)型 - 引用(Reference)類(lèi)型

引用類(lèi)型

引用類(lèi)型可以用偽代碼表示為擁有兩個(gè)屬性的對(duì)象:base(即擁有屬性的那個(gè)對(duì)象),和base中的 propertyName 熬北。

var valueOfReferenceType = {
  base: ,
  propertyName: 
};

引用類(lèi)型的值只有可能是以下兩種情況:

  • 當(dāng)處理一個(gè)標(biāo)識(shí)的時(shí)候
  • 進(jìn)行屬性訪問(wèn)的時(shí)候

標(biāo)示符的處理過(guò)程在作用域鏈中討論;在這里我們只需要知道疙描,使用這種處理方式的返回值總是一個(gè)引用類(lèi)型的值

標(biāo)識(shí)符是變量名,函數(shù)名讶隐,函數(shù)參數(shù)名和全局對(duì)象中未識(shí)別的屬性名起胰,如下:

var foo = 10;
function bar() {}

中間過(guò)程中,對(duì)應(yīng)的引用類(lèi)型的值如下所示:

var fooReference = {
  base: global,
  propertyName: 'foo'
};

var barReference = {
  base: global,
  propertyName: 'bar'
};

要從引用類(lèi)型的值中獲取一個(gè)對(duì)象實(shí)際的值需要GetValue方法巫延,該方法用偽代碼可以描述成如下形式:

function GetValue(value) {
  if (Type(value) != Reference) {
    return value;
  }
  var base = GetBase(value);
  if (base === null) {
    throw new ReferenceError;
  }
  return base.[[Get]](GetPropertyName(value));
}

上述代碼中的 [[Get]] 方法返回了對(duì)象屬性實(shí)際的值效五,包括從原型鏈中繼承的屬性:

GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"

對(duì)于屬性訪問(wèn)來(lái)說(shuō)洼滚,有兩種方式:點(diǎn)符號(hào)(這時(shí)屬性名是正確的標(biāo)識(shí)符并且提前已經(jīng)知道了)或者中括號(hào)符號(hào):

foo.bar();
foo['bar']();

中間過(guò)程中蝎宇,得到如下的引用類(lèi)型的值:

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

GetValue(fooBarReference); // function object "bar"

那么,引用類(lèi)型的值與函數(shù)上下文中this的值是如何關(guān)聯(lián)起來(lái)的呢踢代?這很重要疼阔,也是本文的核心內(nèi)容瓜客。總體來(lái)說(shuō)竿开,確定函數(shù)上下文中this值的一般規(guī)則如下:

函數(shù)上下文中this的值由調(diào)用者(caller)提供,并由調(diào)用表達(dá)式的形式確定(函數(shù)調(diào)用的語(yǔ)法)玻熙。

如果在調(diào)用括號(hào) () 的左邊是引用類(lèi)型否彩,那么this的值就是該引用類(lèi)型值的 base 對(duì)象。

在其他情況下(非引用類(lèi)型)嗦随,this的值總是 null列荔。然而敬尺,null 對(duì)于 this 來(lái)說(shuō)沒(méi)有任何意義,因此為隱式轉(zhuǎn)換為全局對(duì)象

function foo() {
  return this;
}

foo(); // global

上面代碼中贴浙,調(diào)用括號(hào)左側(cè)是引用類(lèi)型(因?yàn)?foo 是標(biāo)識(shí)符):

var fooReference = {
  base: global,
  propertyName: 'foo'
};

相應(yīng)的砂吞,this的值會(huì)設(shè)置為引用類(lèi)型值的base對(duì)象,這里就是全局對(duì)象崎溃。

同樣蜻直,使用屬性訪問(wèn)器:

var foo = {
  bar: function () {
    return this;
  }
};

foo.bar(); 

同樣,bar也是引用類(lèi)型的值袁串,它的base對(duì)象是foo對(duì)象概而,當(dāng)激活bar函數(shù)的時(shí),this的值就設(shè)置為 foo 對(duì)象:

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

然而囱修,同樣的函數(shù)以不同的激活方式的話赎瑰,this 的值就完全不同了:

var test = foo.bar;
test(); // global

因?yàn)閠est也是標(biāo)識(shí)符,這樣就產(chǎn)生了其他引用類(lèi)型的值破镰,該值的 base(全局對(duì)象)被設(shè)置為this的值

var testReference = {
  base: global,
  propertyName: 'test'
};

函數(shù)調(diào)用和非引用類(lèi)型

正如此前提到過(guò)的餐曼,當(dāng)調(diào)用括號(hào)左側(cè)為非引用類(lèi)型的時(shí)候,this的值會(huì)設(shè)置為 null鲜漩,并最終成為全局對(duì)象

(function  () {
  alert(this); // null => global
})();

在這個(gè)例子中源譬,我們有一個(gè)函數(shù)對(duì)象但不是引用類(lèi)型的對(duì)象(因?yàn)樗皇菢?biāo)示符,也不是屬性訪問(wèn)器)宇整,因此 this 的值最終被設(shè)為全局對(duì)象瓶佳。

var foo = {
  bar: function () {
    alert(this);
  }
};

foo.bar();   // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo

(foo.bar = foo.bar)(); // global?
(false || foo.bar)();  // global?
(foo.bar, foo.bar)();  // global?

那么,為什么明明是屬性訪問(wèn)鳞青,而最終this不是引用類(lèi)型的base對(duì)象(foo)霸饲,而是全局對(duì)象呢?

問(wèn)題出在后面三個(gè)調(diào)用臂拓,在執(zhí)行一定的操作運(yùn)算之后厚脉,在調(diào)用括號(hào)的左邊的值不再是引用類(lèi)型。

第一種情況胶惰,很明顯是引用類(lèi)型this的值為 base 對(duì)象傻工,即 foo。

第二種情況孵滞,分組操作符沒(méi)有實(shí)際意義中捆,分組操作符返回的仍是一個(gè)引用類(lèi)型,這就是 this的值為什么再次被設(shè)為 base 對(duì)象坊饶,即 foo泄伪。

第三種情況,賦值操作符(assignment operator)與組操作符不同匿级,它會(huì)觸發(fā)調(diào)用GetValue方法蟋滴。最后返回的時(shí)候就是一個(gè)函數(shù)對(duì)象了(而不是引用類(lèi)型的值了)染厅,這就意味著 this的值會(huì)設(shè)置為 null,最終會(huì)變成全局對(duì)象津函。

第四和第五種情況也類(lèi)似肖粮,逗號(hào)操作符和 OR 邏輯表達(dá)式都會(huì)觸發(fā)調(diào)用GetValue 方法,于是相應(yīng)地就會(huì)丟失原先的引用類(lèi)型值尔苦,變成了函數(shù)類(lèi)型涩馆,this 的值就變成了全局對(duì)象了。

引用類(lèi)型和 this 為 null

有一種情況蕉堰,當(dāng)調(diào)用表達(dá)式左側(cè)是引用類(lèi)型的值凌净,但是 this的值卻是null,最終變?yōu)槿謱?duì)象屋讶。發(fā)生這種情況的條件是當(dāng)引用類(lèi)型值的base對(duì)象恰好為活動(dòng)對(duì)象

function foo() {
  function bar() {
    alert(this); // global
  }
  bar(); // 和AO.bar()是一樣的
}

活躍對(duì)象總是會(huì)返回this值為null(用偽代碼來(lái)表示冰寻, AO.bar() 就相當(dāng)于 null.bar())。然后皿渗,如此前描述的斩芭,this 的值最終會(huì)由null變?yōu)槿謱?duì)象

作為構(gòu)造器調(diào)用的函數(shù)中的 this

這里介紹另外一種情況,當(dāng)函數(shù)作為構(gòu)造器被調(diào)用的時(shí)候:

function A() {
  alert(this); // newly created object, below - "a" object
  this.x = 10;
}

var a = new A();
alert(a.x); // 10

在這種情況下乐疆,new 操作符會(huì)調(diào)用"A" 函數(shù)的內(nèi)部 [[Construct]]划乖。在對(duì)象創(chuàng)建之后,會(huì)調(diào)用內(nèi)部的 [[Call]] 函數(shù)挤土,然后所有 “A” 函數(shù)中this的值會(huì)設(shè)置為新創(chuàng)建的對(duì)象琴庵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仰美,隨后出現(xiàn)的幾起案子迷殿,更是在濱河造成了極大的恐慌,老刑警劉巖咖杂,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆寺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诉字,警方通過(guò)查閱死者的電腦和手機(jī)懦尝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)壤圃,“玉大人陵霉,你說(shuō)我怎么就攤上這事∥樯” “怎么了撩匕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)墨叛。 經(jīng)常有香客問(wèn)我止毕,道長(zhǎng),這世上最難降的妖魔是什么漠趁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任扁凛,我火速辦了婚禮,結(jié)果婚禮上闯传,老公的妹妹穿的比我還像新娘谨朝。我一直安慰自己,他們只是感情好甥绿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布字币。 她就那樣靜靜地躺著,像睡著了一般共缕。 火紅的嫁衣襯著肌膚如雪洗出。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天图谷,我揣著相機(jī)與錄音翩活,去河邊找鬼。 笑死便贵,一個(gè)胖子當(dāng)著我的面吹牛菠镇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承璃,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼利耍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盔粹?” 一聲冷哼從身側(cè)響起隘梨,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玻佩,沒(méi)想到半個(gè)月后出嘹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咬崔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年税稼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垮斯。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡郎仆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兜蠕,到底是詐尸還是另有隱情扰肌,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布熊杨,位于F島的核電站曙旭,受9級(jí)特大地震影響盗舰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桂躏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一钻趋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剂习,春花似錦蛮位、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至们何,卻和暖如春萄焦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垂蜗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工楷扬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贴见。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓烘苹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親片部。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镣衡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容