让咱们一块儿聊聊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)
复制代码

总结

趁发际线还没那么高的时候!!!争取早日脱单把!!!!!其它都是浮云

相关文章
相关标签/搜索