引子

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. 【转】为什么要使用ModelDriven

    ---恢复内容开始--- package cn.com.leadfar.struts2.actions; public class User { private int id ; private St ...

  2. js中字符串和数组的使用

    函数: 函数在调用的时候,会形成一个私有作用域,内部的变量不会被外面访问,这种保护机制叫闭包.这就意味着函数调用完毕,这个函数形成的栈内存会被销毁. 但有时候我们不希望他被销毁. 函数归属谁跟它在哪调 ...

  3. ApplicationListener详解

    ApplicationListener详解 ApplicationListener可以监听某个事件event 通过实现这个接口,传入一个泛型事件,在run方法中就可以监听这个事件,从而做出一定的逻辑 ...

  4. JavaScript黑客是这样窃取比特币的,Vue开发者不用担心!

    如果你是JavaScript或者区块链开发者,如果你有关注区块链以及比特币,那么你应该听说了比特币钱包Copay被黑客攻击的事情.但是,你知道这是怎么回事吗? 总结 比特币钱包copay依赖event ...

  5. 聊聊找AI算法岗工作

    https://blog.csdn.net/weixin_42137700/article/details/81628028 首先,本文不是为了增加大家的焦虑感,而是站在一名学生的角度聊聊找AI算法岗 ...

  6. Vue之resource请求数据

    导入resource文件 <script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js& ...

  7. <python3-cookbook>第一章:数据结构和算法

    第一章:数据结构和算法 介绍:python3-cookbook这本书是高级用法,不是小白使用书目的:写作目的是记录下自己学习这本书的过程以及收获书籍地址:https://python3-cookboo ...

  8. Android Studio调试时遇见Install Repository and sync project的问题

    我们可以看到,报的错是“Failed to resolve: com.android.support:appcompat-v7:16.+”,也就是我们在build.gradle中最后一段中的compi ...

  9. day10(闭包、import模块、函数命名空间)

    #闭包:嵌套函数,内部函数调用外部函数的变量 # def outer(): # a = 1 # def inner(): # print(a) # inner() # outer() def oute ...

  10. POST 400 的一次遭遇

    使用Springboot开发接口的过程中,使用POST接收提交的数据. @PostMapping("/input") public void inputData(@RequestB ...