一文詳解令人抓狂的JS賦值順序
前言
你可能以為自己已經(jīng)非常熟悉 JavaScript 的賦值操作了,但這一行代碼:
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
卻讓無(wú)數(shù)開(kāi)發(fā)者陷入沉思:到底是先給 a.x 賦值,還是先給 a 重新賦值?為什么 console.log(b.x) 是一個(gè)對(duì)象,而 a.x 卻是 undefined?這不是語(yǔ)法問(wèn)題,而是 JavaScript 底層執(zhí)行機(jī)制的奇妙。
下面就簡(jiǎn)單講述一下。
JavaScript 賦值操作基礎(chǔ)
運(yùn)算符優(yōu)先級(jí)與結(jié)合性
賦值運(yùn)算符(=、+=、-=) 在 JavaScript 中是 右結(jié)合(right-to-left)的。這意味著表達(dá)式 a = b = c 會(huì)先計(jì)算右側(cè)的 b = c,再把結(jié)果賦給 a。
| 運(yùn)算符 | 優(yōu)先級(jí) | 結(jié)合性 |
|---|---|---|
= | 3 | 右到左 |
| +, - | 13 | 左到右 |
. | 20 | 左到右 |
左值(L-value)與右值(R-value)
- 左值:表達(dá)式的“賦值目標(biāo)”,例如變量、對(duì)象屬性、數(shù)組元素。
- 右值:表達(dá)式的“計(jì)算結(jié)果”,例如字面量、函數(shù)返回值、其他表達(dá)式。
賦值時(shí),JS 引擎要分別評(píng)估左值(確定要寫(xiě)到哪里)和評(píng)估右值(計(jì)算要寫(xiě)什么),然后才執(zhí)行寫(xiě)入。
連續(xù)賦值語(yǔ)義
為什么 a = b = c 能連續(xù)寫(xiě)?
因?yàn)樵?JS 中,賦值表達(dá)式本身會(huì)返回被賦的值,所以可以寫(xiě)成鏈?zhǔn)剑?/p>
// 步驟拆解: b = c // 返回 c a = (b = c) // 再把 c 賦給 a
賦值表達(dá)式的返回值
- 表達(dá)式 x = y 的求值結(jié)果就是“被賦的值 y”。
- 因此 a = b = 5 相當(dāng)于 a = 5; b = 5;,且整個(gè)表達(dá)式的值為 5。
a = b = 5; console.log(a, b, (a = b = 5)); // 5 5 5
ECMAScript 規(guī)范視角
如何評(píng)估賦值表達(dá)式
根據(jù) ECMAScript® Language Specification(ECMA-262):
- 評(píng)估 AssignmentExpression:先識(shí)別運(yùn)算符右側(cè)的“AssignmentExpression”
- 評(píng)估左值:調(diào)用 GetValue 獲取右側(cè)的值
- 分辨目標(biāo):調(diào)用 PutValue 將值寫(xiě)入左值目標(biāo)
- 返回值:整個(gè)表達(dá)式的值為所寫(xiě)入的值
步驟拆解
Function EvaluateAssignmentExpression(expr):
If expr is “LeftHandSide = RightHandSide” then:
// 1. 先評(píng)估右側(cè),得到一個(gè)值 R
R = Evaluate(RightHandSide)
// 2. 再評(píng)估左側(cè),得到一個(gè)引用 L
L = ResolveReference(LeftHandSide)
// 3. 將 R 寫(xiě)入 L
PutValue(L, R)
// 4. 返回 R 作為整個(gè)表達(dá)式的值
return R
常見(jiàn)示例
1、簡(jiǎn)單鏈?zhǔn)劫x值
let x, y, z; x = y = z = 100; console.log(x, y, z); // 100 100 100
執(zhí)行順序:
- z = 100 → 返回 100
- y = (z = 100) → 返回 100
- x = (y = (z = 100)) → 返回 100
2、對(duì)象屬性與賦值順序
let obj = { a: 1 };
obj.b = obj.a = 2;
console.log(obj); // { a: 2, b: 2 }執(zhí)行順序:
- 先計(jì)算 obj.a = 2,寫(xiě)入 a,返回 2。
- 再執(zhí)行 obj.b = <返回值 2>。
重點(diǎn)示例分析
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b.x);詳解解析:
var a = { n: 1 }; // [Obj1]
var b = a; // b ? [Obj1]
a.x = a = { n: 2 }; - 解析左側(cè)目標(biāo)(Capture LHS)
- 引擎先看到整個(gè)賦值表達(dá)式 a.x = …,此時(shí) a 仍指向最初的對(duì)象 [Obj1]。
- “目標(biāo)位置”鎖定為:對(duì)象 [Obj1] 的屬性 x。
- 執(zhí)行右側(cè)賦值(Evaluate RHS)
- 新建一個(gè)對(duì)象字面量 { n:2 },記為 [Obj2]。
- 把 [Obj2] 賦給變量 a,此刻 a 從指向 [Obj1] 變?yōu)橹赶?[Obj2]。
- 賦值表達(dá)式 a = [Obj2] 的返回值是這個(gè)新對(duì)象 [Obj2]。
- 寫(xiě)入屬性(Perform original “.x = …”)
- 雖然此時(shí) a 已經(jīng)指向 [Obj2],但左側(cè)“目標(biāo)”早已捕獲,仍是 [Obj1].x 這個(gè)位置。
- 因此,將右側(cè)返回的 [Obj2] 寫(xiě)入到原始對(duì)象 [Obj1] 的 x 屬性上:Obj1.x = Obj2。
- 查看最終結(jié)果
- a 指向 [Obj2],所以 a.x 查的是 [Obj2].x ——該對(duì)象上沒(méi)有 x 屬性,結(jié)果 undefined。
- b 依舊指向最初的 [Obj1],它的 x 屬性被寫(xiě)入了 [Obj2],所以 console.log(b.x) 輸出 { n: 2 }。
關(guān)鍵點(diǎn):
- 左側(cè)目標(biāo)先捕獲:賦值表達(dá)式開(kāi)始時(shí)就確定屬性寫(xiě)入位置,不受后續(xù)變量指向變化影響。
- 左側(cè) a.x 在賦值之前就要解析“引用”,此時(shí) a 仍指向舊對(duì)象;
- 右側(cè) a = tmp 改變了 a 引用,但不會(huì)影響已經(jīng)捕獲的“屬性寫(xiě)入位置”。
- 賦值表達(dá)式返回值:a = {n:2} 返回新的對(duì)象引用,用于后續(xù)“屬性賦值”。
- 順序原則:先捕獲 LHS,再執(zhí)行 RHS 并返回值,最后將返回值寫(xiě)入先前捕獲的 LHS。
這樣,一眼就能看清變量指向與屬性寫(xiě)入的“先后關(guān)系”,避免踩坑。
總結(jié)
到此這篇關(guān)于JS賦值順序的文章就介紹到這了,更多相關(guān)JS賦值順序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Add a Table to a Word Document
Add a Table to a Word Document...2007-06-06
萬(wàn)字詳解JavaScript手寫(xiě)一個(gè)Promise
這篇文章主要介紹了萬(wàn)字詳解JavaScript手寫(xiě)一個(gè)Promise,Promise就是一個(gè)類(lèi),在執(zhí)行這個(gè)類(lèi)的時(shí)候,需要傳遞一個(gè)執(zhí)行器(回調(diào)函數(shù))進(jìn)去,執(zhí)行器會(huì)立即執(zhí)行2022-07-07
微信小程序web-view環(huán)境下H5跳轉(zhuǎn)小程序頁(yè)面方法實(shí)例代碼
微信小程序是一種全新的連接用戶(hù)與服務(wù)的方式,它可以在微信內(nèi)被便捷地獲取和傳播,同時(shí)具有出色的使用體驗(yàn),下面這篇文章主要給大家介紹了關(guān)于微信小程序web-view環(huán)境下H5跳轉(zhuǎn)小程序頁(yè)面方法的相關(guān)資料,需要的朋友可以參考下2022-08-08
JS+HTML實(shí)現(xiàn)經(jīng)典游戲吃豆人
吃豆游戲可以說(shuō)是我們80,90后共同的回憶錄,小時(shí)候常常在學(xué)習(xí)機(jī)上玩,所以也就有了強(qiáng)烈的欲望去寫(xiě)。所以本文將利用JS+HTML實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-04-04
一文詳解前端進(jìn)階之IntersectionObserver
這篇文章主要為大家介紹了前端進(jìn)階之IntersectionObserver示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Bootstrap導(dǎo)航簡(jiǎn)單實(shí)現(xiàn)代碼
這篇文章主要介紹了Bootstrap導(dǎo)航的簡(jiǎn)單實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03

