JS的连等赋值

文章标题这句话原本是在国外某JavaScript规范里看到的,当时并没有引起足够的重视,直到最近一次出现了bug发现JS里的连等赋值操作的特色(坑)。

网上搜索一番发现一个非常好的连等赋值的( 来源1来源2 )例子:

var a = {n:1};
a.x = a = {n:2};
console.log(a.x); // 输出?

答案是:

console.log(a.x); // undefined

答案

不知道各位有没有答对,至少我是答错了。

遂借此机会好好看看JS连等赋值是怎么回事

赋值顺序?

假设有一句代码: 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

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

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

连续赋值能拆开写么?

通过上面可以看到连续赋值的真正规则,那么再回归到文章开头的那个案例,如果按照上述规则将连续赋值拆开会发现结果不一样了,如:

var a={n:1};
a={n:2};
a.x=a;
console.log(a.x);//Object {n: 2, x: Object}

所以连续赋值语句虽然是遵从从右至左依次赋值的规则但依然不能将语句拆开来写,至于为什么

猜测 :js内部为了保证赋值语句的正确,会在一条赋值语句执行前,先把所有要赋值的引用地址取出一个副本,再依次赋值。

所以我认为这段代码 a.x=a={n:2}; 的逻辑是:

1、在执行前,会先将a和a.x中的a的引用地址都取出来,此值他们都指向{n:1}

2、在内存中创建一个新对象{n:2}

3、执行a={n:2},将a的引用从指向{n:1} 改为 指向新的{n:2}

4、执行a.x=a,此时a已经指向了新对象,而a.x因为在执行前保留了原引用,所以a.x的a依然指向原先的{n:1}对象,所以给原对象新增一个属性x,内容为{n:2}也就是现在a

5、语句执行结束,原对象由{n:1}变成{n:1,x:{n:2}},而原对象因为无人再引用他,所以被GC回收,当前a指向新对象{n:2}

6、所以就有了文章开头的运行结果,再执行a.x,自然就是undefined了

上述过程按序号图示:

按照上述过程可以看出旧的a.x和新的a都指向新创建的对象{n:2},所以他们应该是 全等 的。

测试:

var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a===b.x); //true

因为我们增加了var b=a,即将原对象增加了一条引用,所以在上述第5步时不会被释放,证实了上面的结论。

后记

通过这次了解了连续赋值的特点,再回过头看文章标题,似乎应该叫:

尽量不要使用JS的连续赋值操作,除非真的了解它的内部机制及可能会产生的后果。

(完)

时间: 2024-08-24 18:02:20

JS的连等赋值的相关文章

js 给指定ID赋值

js 给指定ID赋值 1 <script language="javascript" type="text/javascript"> 2 document.getElementById("box_1").innerHTML = "内容"; 3 </script> 4 <span id="box_1"></span>

由命名空间函数而引发思考--js中的对象赋值问题

最近没有编码任务,作为一个才毕业的小辣鸡,给的任务就是看一下公司的新系统,熟悉怎么用哪些地方是干什么的. 下午喝了两杯水,感觉有点浪.然后就开始看了下代码.发现有一个函数是这样子的. var TX = {}; //命名空间函数 TX.nameSpace = function (str) { var arr = str.split("."); var o = TX; for (i = (arr[0] == "TX") ? 1 : 0; i < arr.lengt

js数组的拷贝赋值复制二三事总结

今天在看React-native性能优化的时候,看到如何避免shouldComponentUpdate的异常数据时,脑内一阵风暴,从而牵连出一连串的问题,于是有了这一篇关于js数组的复制(深浅拷贝)与赋值等为何能产生异常数据的文章. 有什么问题欢迎指正 现在进入正题: 首先异常数据的产生在于我们在复制赋值时,会有或没有改变到本身的值. 一.push与concat push的定义是:像数组末尾添加一个或更多元素,并返回新的长度.该方法会改变数组的长度. concat的定义是:连接两个或更多的数组,

js 获取select的值 / js动态给select赋值

jQuery获取Select选择的Text和Value:语法解释:1. $("#select_id").change(function(){//code...});   //为Select添加事件,当选择其中一项时触发2. var checkText=$("#select_id").find("option:selected").text();  //获取Select选择的Text3. var checkValue=$("#select

js生成随机编码并赋值给input文本框

效果图如下: 页面代码: <div class="form-item form-width-in fr"> <label>产 品 编 码</label> <input type="text" id="product_code" name="product_code" value="" class="field fwidth" placeholde

js给文本框赋值 value与innerHTML

<input type="test" name="testName" id="testId"> 赋值操作: <script language="javascript">     document.getElementById("testId").value="hello"; </script> value与innerHTML的区别 上面的value也可

关于js 浅拷贝 深拷贝 以及赋值操作。

最近同事又碰到关于深浅拷贝以及赋值的问题,今天我也研究一下记录一下,加深一下记忆. 举一个简单的例子: var people = { age:10, name:"小华", arr:[1,2,3] }; 做一个 赋值操作: var people2 = people; 然后做一个浅拷贝操作: var people3 = {}; for (var i in people){ people3[i] = people[i] }; 然后最后再做一个深拷贝操作: function deepClone

js es6 解构赋值

解构赋值:es6允许按照一定的模式,从数组或对象中提取值 let [a,b,c] = [1,2,3]; let {a,b}= {a:1,b:2}; 对象 let {a,b}= {b:1,a:2} //输出2,1,即找属性名,不管什么位置 数组例子1:(完全解构) let [a,b,c] = [1,2,3]; console.log(a)//1 console.log(b)//2 console.log(c)//3 例子2:(不完全解构) let [a,[b],c] = [1,[2,3],func

js递归无法正常赋值

错误: var b = 10; var c = 0; var a = abc(); function abc() { if (c == b){ return 1 } else{ b--; abc() } } console.log(a) //无法正确赋值 undefined 正确: var b = 10; var c = 0; var a = abc(); function abc() { if (c == b){ return 1 } else{ b--; return abc() } } c