引子

var a = {n:1};

var b = a; // 持有a,以回查

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

alert(a.x);// --> undefined

alert(b.x);// --> {n:2}

请问结果为何是这样?

连等赋值的赋值顺序

假设有一句代码: A=B=C; ,赋值语句的执行顺序是从右至左,所以问题在于:

是猜想1: B = C; A = C; ?

还是猜想2: B = C; A = B;  ?

我们都知道若两个对象同时指向一个对象,那么对这个对象的修改是同步的,如:

var a={n:1};

var b=a;

a.n=2;

console.log(b);//Object {n: 2}

所以可以根据这个特性来测试连续赋值的顺序。

按照猜想1,把C换成具体的对象,可以看到对a的修改不会同步到b上,因为在执行第一行和第二行时分别创建了两个 {n:1} 对象。如:

var b={n:1};

var a={n:1};

a.n=0;

console.log(b);//Object {n: 1}

再按照猜想2,把C换成具体的对象,可以看到对a的修改同步到了b,因为a和b同时引用了一个对象,如:

var b={n:1};

var a=b;

a.n=0;

console.log(b);//Object {n: 0}

测试真正的连等赋值:

var a,b;

a=b={n:1};

a.n=0;

console.log(b);//Object {n: 0}

可以看到是符合猜想2的,如果有人觉得这个测试不准确可以再来测试,使用ECMA5的setter和getter特性来测试。

首先setter和getter是应用于变量名的,而不是变量真正储存的对象,如下:

复制代码

Object.defineProperty(window,"obj",{

get:function(){

console.log("getter!!!");

}

});

var x=obj;

obj;//getter!!! undefined

x;//undefined

复制代码

可以看到只有obj输出了“getter!!!”,而x没有输出,用此特性来测试。

连等赋值测试2:

Object.defineProperty(window,"obj",{

get:function(){

console.log("getter!!!");

}

});

a=b=obj;//getter!!!  undefined

image

通过getter再次证实,在A=B=C中,C只被读取了一次。

所以,连等赋值真正的运算规则是  B = C; A = B;  即连续赋值是从右至左永远只取等号右边的表达式结果赋值到等号左侧。

赋值表达式的右结合性

但是,你真正懂得右结合性是怎样起作用的吗?

看下面的连续赋值表达式:

exp1 = exp2 = exp3 = ... = expN;

其中的exp是一个表达式,并且除最后一个expN外,其他表达式都必须可以作为左值。

你能告诉我它是怎样运算吗?

是这样的,首先根据赋值运算的右结合性,可以改写成:

exp1 = (exp2 = (exp3 = (... = expN)...);

然后按照下面步骤进行运算:

解析exp1;

解析exp2;

解析exp3;

...

N. 解析expN;

以上步骤完成后,上面表达式变成了:

ref1 = (ref2 = (ref3 = (... = value)...);

其中value是表达式expN的值。接下来的步骤是:

将value赋给引用refN-1;

将value赋给引用refN-2;

...

N-1.将value赋给引用ref1;

结束。

特殊问题的坑

var a = {n:1};

var b = a; // 持有a,以回查

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

alert(a.x);// --> undefined

alert(b.x);// --> {n:2}

请问结果为何是这样?

赋值是从右到左的,但不要被绕晕了, 其实很简单,从运算符优先级来考虑

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

.运算优先于=赋值运算,因此此处赋值可理解为

声明a对象中的x属性,用于赋值,此时b指向a,同时拥有未赋值的x属性

对a对象赋值,此时变量名a改变指向到对象{n:2}

对步骤1中x属性,也即a原指向对象的x属性,也即b指向对象的x属性赋值

赋值结果:

a => {n: 2}

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

详细解答

var a = {n:1};

/*定义a,a赋值为`{n:1}`;

为a在内存堆中分配一块内存用于存储`{n:1}`,假设其地址为add_1;

此时add_1引用计数为1,即a,内容为`{n:1}`。*/

var b = a;

/*定义b,b赋值a,add_1被b引用。

此时add_1引用计数为2,即a和b,内容为`{n:1}`。*/

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

/*(`=`赋值运算符:关联性为从右向左,优先级为3。`.`成员访问运算符:关联性为从左向右,优先级为19。19>3,所以先计算成员访问运算符)

(1):a.x是成员访问运算表达式,a.x中的x赋值为`a = {n:2}`的返回值`{n:2}`,add_1被改写`{n:1,x:{n:2}}`。

此时add_1引用计数为2,即a、b,内容为`{n:1,x:{n:2}}`。

(2):a赋值为`{n:2}`;

为a在内存堆中分配一块内存用于存储`{n:2}`,假设其地址为add_2;

此时add_1引用计数为1,即b,内容为`{n:1,x:{n:2}}`。

此时add_2引用计数为1,即a,内容为`{n:2}`。*/

alert(a.x);

/*现在a的存储地址add_2,内容为{n:2},上面并不存在a.x属性,所以为undefined*/

alert(b.x);

/*现在b的存储地址add_1,内容为{n:1,x:{n:2}},所以b.x为{n:2}*/

写了12年JS也未必全了解的连续赋值运算的更多相关文章

  1. CSS 黑魔法小技巧,让你少写不必要的JS,代码更优雅

    首页   登录注册         CSS 黑魔法小技巧,让你少写不必要的JS,代码更优雅 阅读 8113 收藏 927 2017-09-26 原文链接:github.com 腾讯云容器服务CSS,立 ...

  2. 仿新浪游戏频道js多栏目全屏下拉菜单导航条

    仿新浪游戏频道js多栏目全屏下拉菜单导航条,新浪,游戏频道,js组件,多栏目,全屏下拉,下拉菜单,导航条.代码下载地址:http://www.huiyi8.com/sc/26765.html更多请访问 ...

  3. Python3+Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)

    #!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)'''from ...

  4. js指定区域全屏

    <html>     <head>         <title>js指定区域全屏</title>         <style>      ...

  5. js设置页面全屏

    html代码 <!-- 全屏按钮 --> <img id="alarm-fullscreen-toggler" src="/public/index/i ...

  6. 模糊查询(附上源码和jquery-1.12.1.js,jquery-ui.js,jquery-ui.css)

    直接上源码: <!doctype html> <html lang="en"> <head> <meta charset="ut ...

  7. 菜单栏伸缩(附jquery-1.12.1.js)

    Css: <style type="text/css"> .leftMenu { min-width:220px; width:268px; margin:40px a ...

  8. 12 (H5*) JS第二天 流程控制:顺序结构、分支结构、循环结构

    目录 1:一元运算符 2:流程控制 3:分支之if语句 4:分支之if-else语句 5:分支语句之三元运算符 6:if和else if语句 7:switch-case语句 8:while循环 9:d ...

  9. 模仿淘宝首页写的高仿页面,脚本全用的原生JS,菜鸟一枚高手看了勿喷哈

    自己仿照淘宝首页写的页面,仿真度自己感觉可以.JS脚本全是用原生JavaScript写得,没用框架.高手看了勿喷,请多多指正哈!先上网页截图看看效果,然后上源码: 上源码,先JavaScript : ...

随机推荐

  1. Linux-cut命令(22)

    cut剪切命令cut命令通常用来对某个文本文件进行解析,擅长处理以一个字符间隔的文本内容 -b :以字节(bytes)为单位进行分割.这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志. -c ...

  2. Java高并发--消息队列

    Java高并发--消息队列 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 举个例子:在购物商城下单后,希望购买者能收到短信或者邮件通知.有一种做法时在下单逻辑执行后调 ...

  3. 4. 泛型_EJ

    第23条: 不要在新代码中使用原生态类型 声明中具有一个或多个类型参数的类或接口,就是泛型类或接口.每种泛型都定义一组参数化的类型,每个泛型都定义一个原生态类型.例如List<E>相对应的 ...

  4. js 判断数组中是否有某值

    function arrHasValue(v, arr) { var output = false; for (var i in arr) { if (v == arr[i]) { output = ...

  5. [笔记]JavaScript 秘密花园

    1.hasOwnProperty相关 为了判断一个对象是否包含自定义属性而不是原型链上的属性,我们需要使用继承自 Object.prototype 的 hasOwnProperty方法.hasOwnP ...

  6. 三星450R5J windows8.1系统重装小结

    本人一台三星450R5J,到今年也差不多五六年了.虽然颜值很高,但是用久了真的不行,毕竟是属于商务型笔记本,这里我就不晒配置了. ​ 比较一下四五年前的三星与现在使用的华硕,三星看起来更鲜. ​ ​准 ...

  7. java I/O工作机制

    java I/O 的基本架构: 1:基于字节操作的I/O接口 InputStream OutputStream 2:基于字符操作的I/O接口 Writer 和Reader 3:基于磁盘操作的I/O接口 ...

  8. Jetbrains Idea连接TFS时配置的坑

    #Team Explorer Everywherehttps://www.microsoft.com/en-us/search/result.aspx?q=team+explorer+everywhe ...

  9. [katalon] 页面切换

    UI自动化测试过程中会涉及到需要切换多个页面, 如点击一个按钮之后跳转到新的页面, 后者A站点提交信息后,B站点审核. Katalon虽然不支持控制多个浏览器,但是支持处理tab切换. 核心方法是使用 ...

  10. Windows服务器防火墙配置规范

    本文属于一篇内部规范文档,整理的初衷是为了规范.统一集团的Windows服务器(仅仅SQL Server数据库服务器)防火墙设置,仅仅供内部其它同事设置Windows防火墙时作为参考的文档资料.如有不 ...