引子

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. JVM-String.intern()

    故事起源于书籍<深入理解Java虚拟机>,案例如下: public class RunTimeConstantPoolOOM { public static void main(Strin ...

  2. Codeforces617E(莫队)

    E. XOR and Favorite Number time limit per test: 4 seconds memory limit per test: 256 megabytes input ...

  3. ajax知识点

    什么是AJAX? AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现有标准的新方法. ...

  4. wx-charts 微信小程序图表 -- radarChart C# .net .ashx 测试

    radarChart:原始代码 new wxCharts({ canvasId: 'radarCanvas', type: 'radar', categories: ['1', '2', '3', ' ...

  5. JDCP连接池连接数据库报错:java.lang.AbstractMethodError: com.mysql.jdbc.Connection.isValid(I)Z

    完整报错是这样的: 小编的情况: 使用mysql的jar包版本: 使用的jdcp的相关jar包版本: 报错的原因: mysql的jar包版本过低. 更新到最新版mysql的jar包即可. 小编更新后的 ...

  6. ThreadPoolExecutor 线程池的源码解析

    1.背景介绍 上一篇从整体上介绍了Executor接口,从上一篇我们知道了Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newSchedul ...

  7. 快速开发跨平台应用之Xamarin技术

    Xamarin 介绍 Xamarin 是一个允许开发人员有效创建可跨 iOS.Android.Windows 应用程序的开发工具集.Xamarin是免费且开源的,遵循 MIT (麻省理工学院许可证)协 ...

  8. 性能优化4--Bitmap内存优化

    1.Bitmap在Android虚拟机中的内存分配 在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中.而像素数据的内存是分配在Native堆中,而到了 ...

  9. Git:二、本地文件操作

    文件必须放在本地Git仓库的文件夹下,子文件夹也可以. 1.添加/修改 git add <文件名> 2.提交 git commit -m "本次提交说明" 可以add很 ...

  10. ERP项目应该由谁来主导?

    前段时间在朋友圈看到了别人分享的公众号,主要是谈ERP项目应该由谁来主导的问题.文章的观点认为应该由哪个部门主导ERP的判断标准如下: 1.应该由一个期望上进的部门主导ERP项目: 2.应该由一个有话 ...