引子

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. 34.QT-制作串口助手(并动态检测在线串口,附带源码)

    qextserialport-1.2rc库下载链接: http://www.pudn.com/Download/item/id/2298532.html 1.添加源码到工程 将qextserialpo ...

  2. python中的property属性

    目录 1. 什么是property属性 2. 简单的实例 3. property属性的有两种方式 3.1 装饰器方式 3.2 类属性方式,创建值为property对象的类属性 4. property属 ...

  3. vim打开不同的文件

    sp   vim -o file1 file2    纵向打开文件 Ctrl+w Ctrl+v         vim -O file1 file2   横向打开文件

  4. CSS3使用transition属性实现过渡效果

    transition属性目的是让css的一些属性(如background)的以平滑过渡的效果出现.它是一个合并属性,是由以下四个属性组合而成: transition-property:设置应用过渡的C ...

  5. 9.Odoo产品分析 (二) – 商业板块(4) –讨论(1)

    查看Odoo产品分析系列--目录 讨论模块就是一个信息记录和消息提醒的功能,在登录到odoo平台的时候第一个界面就是它:  1. 收件箱 收件箱,登录者的邮箱管理,并显示未读邮件数量:    点击上面 ...

  6. Jenkins Jenkins结合GIT Maven持续集成环境配置

    Jenkins结合GIT Maven持续集成环境配置   by:授客 QQ:1033553122 安装Git插件 1 安装Git客户端 1 安装JAVA JDK及MAVEN 2 Jenkins JDK ...

  7. Kotlin入门(23)适配器的进阶表达

    前面在介绍列表视图和网格视图时,它们的适配器代码都存在视图持有者ViewHolder,因为Android对列表类视图提供了回收机制,如果某些列表项在屏幕上看不到了,则系统会自动回收相应的视图对象.随着 ...

  8. WiFi的名词缩写

    http://blog.csdn.net/jayxujia123/article/details/12842295 无线网络最初采用的安全机制是WEP(有线等效私密),但是后来发现WEP是很不安全的, ...

  9. Appium+java 模拟键盘输入

    功能键   KEYCODE_CALL 拨号键 5 KEYCODE_ENDCALL 挂机键 6 KEYCODE_HOME 按键Home 3 KEYCODE_MENU 菜单键 82 KEYCODE_BAC ...

  10. Electron开发笔记—electron-builder打包流程

    该文章说明基于win平台下,mac及linux没有实验 关于electron-builder打包可以有两种方案: 1. 打包成文件夹及绿色免安装: electron-builder --dir(依赖w ...