讓咱們一塊兒聊聊JS中多等號的賦值操做

對知識的學習應當保持一種敬畏之心和不爲艱難之心 --前言bash

題目

var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a); // {n:2}
console.log(a.x); //{undefined}
console.log(b);  //{n: 1, x: {n:2}}
console.log(b.x); // {n:2}
複製代碼

個人思考思路

先來看看ECMAScript規範如何定義賦值的:11.13.1 簡單賦值(翻譯版)

產生式 AssignmentExpression : LeftHandSideExpression = AssignmentExpression 按照下面的過程執行 :

1.令 lref 爲解釋執行 LeftH 和 SideExpression 的結果 .
2.令 rref 爲解釋執行 AssignmentExpression 的結果 .
3.令 rval 爲 GetValue(rref).
4.拋出一個 SyntaxError 異常,當如下條件都成立 :
    4.1Type(lref) 爲 Reference
    4.2IsStrictReference(lref) 爲 true
    4.3Type(GetBase(lref)) 爲環境記錄項
    4.4GetReferencedName(lref) 爲 "eval""arguments"
5.調用 PutValue(lref, rval).
6.返回 rval.
複製代碼

也就是說,賦值操做是一個遞歸的操做。也就是說先執行LeftHandSideExpression(簡記左值表達式),執行完以後在執行AssignmentExpression(最後的賦值操做)。講下lref,lval,rref,rval的理解app

lref是等號左邊表達式的計算結果,
lval是lref Getvalue以後的結果,也就是左值,
rref是等號右邊表達式的計算結果,
rval是rref Getvalue以後的結果,也就是右值。
複製代碼

再來看看左值表達式的定義:ide

Syntax
MemberExpression[Yield] :
PrimaryExpression[?Yield]
MemberExpression[?Yield] [ Expression[In, ?Yield] ]
MemberExpression[?Yield] . IdentifierName
MemberExpression[?Yield] TemplateLiteral[?Yield]
SuperProperty[?Yield]
MetaProperty
new MemberExpression[?Yield] Arguments[?Yield]
SuperProperty[Yield] :
super [ Expression[In, ?Yield] ]
super . IdentifierName
MetaProperty :
NewTarget
NewTarget :
new . target
NewExpression[Yield] :
MemberExpression[?Yield]
new NewExpression[?Yield]
CallExpression[Yield] :
MemberExpression[?Yield] Arguments[?Yield]
SuperCall[?Yield]
CallExpression[?Yield] Arguments[?Yield]
CallExpression[?Yield] [ Expression[In, ?Yield] ]
CallExpression[?Yield] . IdentifierName
CallExpression[?Yield] TemplateLiteral[?Yield]
SuperCall[Yield] :
super Arguments[?Yield]
Arguments[Yield] :
( )
( ArgumentList[?Yield] )
ArgumentList[Yield] :
AssignmentExpression[In, ?Yield]
... AssignmentExpression[In, ?Yield]
ArgumentList[?Yield] , AssignmentExpression[In, ?Yield]
ArgumentList[?Yield] , ... AssignmentExpression[In, ?Yield]
LeftHandSideExpression[Yield] :
NewExpression[?Yield]
CallExpression[?Yield]
複製代碼

簡記左值表達式有類型約束,只能爲:函數

構造函數 :new F(),supper()
函數調用:a.f(), f(),f(1,2)
屬性訪問:a.i a.j
普通:i,j,1,"abc",this等變量,字面量,this等。
複製代碼

再來看下左值表達式的屬性訪問的定義:學習

屬性是經過 name 來訪問的,可使用點表示法訪問,
 ···省略一些
 CallExpression . IdentifierName
 是等同於下面的行爲
CallExpression [ <identifier-name-string> ]
 是一個字符串字面量,它與 Unicode 編碼後的 IdentifierName 包含相同的字符序列。

 ·產生式 MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

1.令 baseReference 爲解釋執行 MemberExpression 的結果 .
2.令 baseValue 爲 GetValue(baseReference).
3.令 propertyNameReference 爲解釋執行 Expression 的結果 .
4.令 propertyNameValue 爲 GetValue(propertyNameReference).
5.調用 CheckObjectCoercible(baseValue).
6.令 propertyNameString 爲 ToString(propertyNameValue).
7.若是正在執行中的語法產生式包含在嚴格模式代碼當中,令 strict 爲 true, 不然令 strict 爲 false.
8.返回一個值類型的引用,其基值爲 baseValue 且其引用名爲 propertyNameString, 嚴格模式標記爲 strict.
9.產生式CallExpression : CallExpression [ Expression ] 以徹底相同的方式執行,除了第1步執行的是其中的CallExpression。
複製代碼

也就是說若是左值表達式左邊說經過屬性訪問,好比a.x它會執行上述操做。ui

看到這裏咱們來個小結

在多=號操做時,好比 a = b = c.x = ····,因爲左值表達式的特性,它會對等號的左側的全部表達式進行lref和lval迭代操做,執行到最後右邊時開始真正的賦值操做,也就是把右邊的值挨個賦給左邊,若是左側的表達式不符合規範,則拋出錯誤,不然繼續下一個迭代。 這裏有一點就是若是計算出的lref是undefined,則賦值操做會被忽略,再賦值其值仍爲undefined。如何理解呢,以下:this

let a = 'singleDog';
console.log(a.n); // undefined
a.n = 'i wanna have a girlfriend';
console.log(a.n) // undefined
let a1 = 13;
console.log(a1.n); // undefined
a1.n = 14;
console.log(a1.n) // undefined
let undefined = 111;
console.log(undefined) // undefined
複製代碼

但有一個特例,這個特例對咱們解答原題有重要的輔助做用:編碼

let a = {n:'I am a happy single dog'};
console.log(a.m) // undefined;
a.m = {q:'I have a lovely girlfriend'};
console.log(a.m) //{q:'I have a lovely girlfriend'};
複製代碼

再看一下這樣的內容:lua

let a = 1;
let b = a;
a =2;
console.log(a); //2
console.log(b); //1
let c = undefined;
c = 2;
console.log(c)
複製代碼

至於第一段代碼結果相信你們都能瞭解,這裏就再也不贅述,寫此題是爲了方便接下來的題目理解。再來看下第二段代碼,若是把一個undefined賦給c,在給c從新賦值,c能夠從新獲取值。這點很重要spa

再來看看 a.x = a = {n:2}的賦值過程:
a.x的lref → a的lref
                               ↓ 計算rval
a.x調用PutValue(lref, rval)    ← a調用PutValue(lref, rval)    ←
複製代碼
這裏a.x的lref有兩個:一個是對象a的屬性引用獲取即 a.x的lref => undefined
複製代碼
另外一個a.x的lref計算過程能夠抽象理解爲b.x的lref求值。由於b是對a的引用 第一步a.x 的lref =>undefined,第二步b對a的引用,即b.x = undefined,此時b.x的lref爲x,即第二個a.x的lref => x;
複製代碼
最終執行到a.x的賦值:對對象a來講:PutValue(undefined, {n:2}) ;
對對象b來講:PutValue(x, {n:2}) 
複製代碼

這也就解釋了 爲何的打印a.x爲undefined,打印b.x爲{n:2};

複製代碼

當左側表達式爲結構表達式時,結構函數的變量名不會再函數初始化的時候,提早聲明。只要運行表達式計算後才什麼,並且是全局變量

console.log(j)//Uncaught ReferenceError: j is not defined
var i = {j} ={i:1,j:2}
console.log(j)//2

console.log(j)//Uncaught ReferenceError: j is not defined
var i = {j} ={i:1,k:2}
console.log(j)//undefine

function A(){
    var i = { k} ={i:1,k:2}
console.log(k)//2
}
A()
console.log(k)//2
複製代碼

看到這裏咱們再來一道題目複習下前面所講:

var a=1
function A(){
    a={i:0,c:2}
    return a
}

var b={c}=(A()).i=a.d=2

console.log(a)  //結果請自行思考
console.log(b)
console.log(c)
複製代碼

總結

趁髮際線還沒那麼高的時候!!!爭取早日脫單把!!!!!其它都是浮雲

相關文章
相關標籤/搜索