var foo={rzx:1}
var bar =foo;
foo.x=foo={rzx:100}
console.log(foo.x)
console.log(bar.x)

 

有这样一个热门问题:

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); // --> undefined
alert(b.x); // --> {n: 2}

其实这个问题很好理解,关键要弄清下面两个知识点:

  • JS引擎对赋值表达式的处理过程

  • 赋值运算的右结合性

一. 赋值表达式

形如

A = B

的表达式称为赋值表达式。其中A和B又分别可以是表达式。B可以是任意表达式,但是A必须是一个左值

所谓左值,就是可以被赋值的表达式,在ES规范中是用内部类型引用(Reference)描述的。例如:

  • 表达式foo.bar可以作为一个左值,表示对foo这个对象中bar这个名称的引用;

  • 变量email可以作为一个左值,表示对当前执行环境中的环境记录项envRec中email这个名称的引用;

  • 同样地,函数名func可以做左值,然而函数调用表达式func(a, b)不可以。

那么JS引擎是怎样计算一般的赋值表达式 A = B的呢?简单地说,按如下步骤:

  1. 计算表达式A,得到一个引用refA

  2. 计算表达式B,得到一个值valueB

  3. valueB赋给refA指向的名称绑定;

  4. 返回valueB

二. 结合性

所谓结合性,是指表达式中同一个运算符出现多次时,是左边的优先计算还是右边的优先计算。

赋值表达式是右结合的。这意味着:

A1 = A2 = A3 = A4

等价于

A1 = (A2 = (A3 = A4))

三. 连等的解析

好了,有了上面两部分的知识。下面来看一下JS引擎是怎样运算连等赋值表达式的。

以下面的式子为例:

Exp1 = Exp2 = Exp3 = Exp4

首先根据右结合性,可以转换成

Exp1 = (Exp2 = (Exp3 = Exp4))

然后,我们已经知道对于单个赋值运算,JS引擎总是先计算左边的操作数,再计算右边的操作数。所以接下来的步骤就是:

  1. 计算Exp1,得到Ref1;

  2. 计算Exp2,得到Ref2;

  3. 计算Exp3,得到Ref3;

  4. 计算Exp4,得到Value4。

现在变成了这样的:

Ref1 = (Ref2 = (Ref3 = Value4))

接下来的步骤是:

  1. 将Value4赋给Exp3;

  2. 将Value4赋给Exp2;

  3. 将Value4赋给Exp1;

  4. 返回表达式最终的结果Value4。

注意:这几个步骤体现了右结合性。

总结一下就是:

从左到右解析各个引用,然后计算最右侧的表达式的值,最后把值从右到左赋给各个引用。

四. 问题的解决

现在回到文章开头的问题。

首先前两个var语句执行完后,ab都指向同一个对象{n: 1}(为方便描述,下面称为对象N1)。然后来看

a.x = a = {n: 2};

根据前面的知识,首先依次计算表达式a.xa,得到两个引用。其中a.x表示对象N1中的x,而a相当于envRec.a,即当前环境记录项中的a。所以此时可以写出如下的形式:

[[N1]].x = [[encRec]].a = {n: 2};

其中,[[]]表示引用指向的对象。

接下来,将{n: 2}赋值给[[encRec]].a,即将{n: 2}绑定到当前上下文中的名称a

接下来,将同一个{n: 2}赋值给[[N1]].x,即将{n: 2}绑定到N1中的名称x

由于b仍然指向N1,所以此时有

b <=> N1 <=> {n: 1, x: {n: 2}}

a被重新赋值了,所以

a <=> {n: 2}

并且

a === b.x

五. 最后的最后

如果你明白了上面所有的内容,应该会明白a.x = a = {n:2};b.x = a = {n:2};是完全等价的。因为在解析a.xb.x的那个时间点ab这两个名称指向同一个对象,就像C++中同一个对象可以有多个引用一样。而在这个时间点之后,不论是a.x还是b.x,其实早就不存在了,它已经变成了那个内存中的对象.x了。

最后用一张图表示整个表达式的运算过程:

六. 参考文档

11.13.1 Simple Assignment ( = )

由ES规范学JavaScript(二):深入理解“连等赋值”问题的更多相关文章

  1. 从头开始学JavaScript (二)——变量及其作用域

    原文:从头开始学JavaScript (二)--变量及其作用域 一.变量 ECMAscript变量是松散型变量,所谓松散型变量,就是变量名称可以保存任何类型的数据,每个变量仅仅是一个用于保存值的占位符 ...

  2. Android OpenGL ES和OpenGL一起学(二)------理解Viewport(视口)和坐标系Android OpenGL ES篇(转帖)

      来自:http://www.cnblogs.com/xiaobo68688/archive/2011/12/01/2269985.html   首先我们在屏幕中心显示一个矩形,效果如图: // 代 ...

  3. 从零开始学JavaScript二(基本概念)

    基本概念 一.区分大小写 在ECMAScript中的一切(变量.函数名.操作符)都是区分大小写的. 如变量名test和Test分别表示两个不同的变量, 二.标识符 所谓标识符,就是指变量.函数.属性的 ...

  4. 从头开始学JavaScript (十二)——Array类型

    原文:从头开始学JavaScript (十二)--Array类型 一.数组的创建 注:ECMAscript数组的每一项都可以保存任何类型的数据 1.1Array构造函数 var colors = ne ...

  5. 从头开始学JavaScript (五)——操作符(二)

    原文:从头开始学JavaScript (五)--操作符(二) 一.乘性操作符 1.乘法:*      乘法操作符的一些特殊规则: 如果操作数都是数值,按照常规的乘法计算,如果乘积超过了ECMAscri ...

  6. 怎么学JavaScript?

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  7. 统一回复《怎么学JavaScript?》

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  8. 从头开始学JavaScript (十)——垃圾收集

    原文:从头开始学JavaScript (十)--垃圾收集 一.垃圾收集 1.1javascript垃圾收集机制: 自动垃圾收集,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中 ...

  9. 从头开始学JavaScript (六)——语句

    原文:从头开始学JavaScript (六)--语句 一.条件分支语句:if 基本格式: if (<表达式1>){    <语句组1>}else if (<表达式2> ...

随机推荐

  1. 状态码是canceled

    timeout : 1000 给ajax配置如上属性 $.ajax({ type:"post", url:"pro/savePro", timeout : 10 ...

  2. zabbix修改默认字体

    在zabbixweb界面里有时候显示界面为框框 所以修改默认字体 现在windows里  随便找个字体: win+R  搜fonts 然后随便复制了一个字体,传到zabbix服务器里 cp calib ...

  3. Linux-定时任务排查

    前段时间,哥们的服务器被人反弹了shell,由于反弹的地址不可达,系统总是会发送一条mail邮件到root账户,导致入侵行为被发现,由于反弹的动作是通过crontab来定时执行的,所以来梳理下cron ...

  4. oracle高水位降低法

    1.什么是高水位?(high water mark 简称:HWM)     所有的oracle段(segments,在此,为了理解方便,建议把segment作为表的一个同义词)都有一个在段内存放数据的 ...

  5. django middleware介绍

    Middleware Middleware是一个镶嵌到django的request/response处理机制中的一个hooks框架.它是一个修改django全局输入输出的一个底层插件系统. 每个中间件 ...

  6. NETCONF

    NETCONF协议(Network Configration Protocol) NETCONF是一个基于XML的交换机配置接口,用于替代CLI.SNMP等配置交换机. 本质上来说,NETCONF就是 ...

  7. vue3之组件

    目录 组件 根组件 局部组件 全局组件 组件的注册 组件名 组件名大小写 全局注册 局部注册 模块系统 组件注册实例: 组件化 组件间数据的传递 父组件传递数据给子组件 父组件传递数据给子组件例子 子 ...

  8. javascript(腾讯)

    var a={key:"1",value:2}; war b=a; b.value+=a.key, 打印b.value是多少,a.value呢? 答案:都是21.因为javascr ...

  9. 【leetcode】1229.Meeting Scheduler

    题目如下: 你是一名行政助理,手里有两位客户的空闲时间表:slots1 和 slots2,以及会议的预计持续时间 duration,请你为他们安排合适的会议时间. 「会议时间」是两位客户都有空参加,并 ...

  10. 新建的COM组件中没有 MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

    创建ATL组件之后,添加简单ATL对象,添加对话框资源,随后发现没有m_hWnd句柄,不响应初始化函数. 于是重新创建ATL组件,之后添加ATL控件,添加对话框资源,有m_hWnd句柄,但仍然不响应初 ...