`
zhouyrt
  • 浏览: 1163374 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

写了10年Javascript未必全了解的连续赋值运算

阅读更多

很喜欢蔡蔡 的这个标题,实际蔡蔡已经分析过了,这里借用了。或许有点标题党的意思。看完就知了。

 

一、引子

var a = {n:1};
a.x = a = {n:2};
alert(a.x); // --> undefined

 

这是蔡蔡在看jQuery源码 时发现这种写法的。以上第二句 a.x = a = {n:2} 是一个连续赋值表达式。这个连续赋值表达式在引擎内部究竟发生了什么?是如何解释的?

 

二、猜想

猜想1:从左到右赋值,a.x 先赋值为{n:2},但随后 a 赋值为 {n:2},即  a 被重写了,值为 {n:2},新的 a 没有 x属性,因此为undefined。步骤如下

 

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

 

这种解释得出的结果与实际运行结果一致,貌似是对的。注意猜想1中 a.x 被赋值过。

 

猜想2:从右到左赋值,a 先赋值为{n:2},a.x 发现 a 被重写后(之前a是{a:1}),a.x = {n:2} 引擎限制a.x赋值,忽略了。步骤如下:

 

1, a = {n:2};
2, a.x 未被赋值{n:2}

 

等价于 a.x = (a = {n:2}),即执行了第一步,这样也能解释a.x为undefined了。注意猜想2中a.x压根没被赋值过。

 

三、证明

上面两种猜想相信多数人都有,群里讨论呆呆认为是猜想1, 我认为是猜想2。其实都错了。我忽略了引用的关系。如下,加一个变量b,指向a。

var a = {n:1};
var b = a; // 持有a,以回查
a.x = a = {n:2};
alert(a.x);// --> undefined
alert(b.x);// --> [object Object]

 

发现a.x仍然是undefined,神奇的是 b.x 并未被赋值过(比如:b.x={n:2}),却变成了[object Object]。b 是指向 a({n:1})的,只有a.x = {n:2}执行了才说明b是有x属性的。实际执行过程:从右到左,a 先被赋值为{n:2},随后a.x被赋值{n:2}。

 

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

等价于


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

 

与猜想2的区别在于a.x 被赋值了,猜想2中并未赋值。最重要的区别,第一步 a = {n:2} 的 a 指向的是新的对象{n:2} , 第二步 a.x = {n:2} 中的 a 是 {a:1}。即在这个连等语句

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

 

a.x 中的a指向的是 {n:1},a 指向的是 {n:2}。如下图

 

 

四:解惑

这篇写完,或许部分人看完还是晕晕的。因为里面的文字描述实在是绕口。最初我在理解这个连等赋值语句时

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

 

认为引擎会限制a.x的重写(a被重写后),实际却不是这样的。指向的对象已经不同了。引擎也没有限制a.x={n:2}的重写。
谢谢所有参与讨论的人:蔡蔡、猪大肠 、呆呆、雅儒。这个问题最早是蔡蔡提出的。雅儒在 菜鸟灰呀灰 群里每次的讨论都那么投入,认真,哪怕是别人提出的话题。

 

五:结束

呵,以另一个连续赋值题结束。fun执行后,这里的 变量 b 溢出到fun外成为了全局变量。想到了吗?

function fun(){
	var a = b = 5;
}
fun();
alert(typeof a); // --> undefined
alert(typeof b); // --> number
 

 

 

  • 大小: 17.2 KB
分享到:
评论
39 楼 liuyikk163 2012-04-25  
我觉得从一开始就认定是从右往左赋值的顺序就是错的,执行的顺序应该是从左往右。
a只是一个指针,只能指向一个地址
var a = {n:1}; a.x = a = {n:2}
相当于是先 {n:1}.x = {n:2},再a = {n:2},不然如果先把a指向了{n:2},那样a.x就已经不是{n:1}.x,而是{n:2}.x,于是根本不会把x赋值到{n:1}了。
a只是一个指针,不是对象,对a.x赋值改的是{n:1}.x的值,而对a赋值是改的a指针的值。
38 楼 superobin 2011-02-16  
看这个代码
var a,b,c,d;
a=b=c=d={a:1};
a.x=a=b.y=b=c.z=c={}
console.log(a,b,c,d)

和结果:

我觉得是这样的,执行这句话的时候,发现有a.x b.y c.z,虚拟机会先把地址准备好,也就是虚拟机要做这件事:
先分别找a.x、a、b.y、b、c.z、c的地址,这时,a.x,b.y,c.z 分别指向旧对象内部的地址,而a,b,c分别为存放对象指针的地址,当赋值时,会将所有这些地址的值都赋值成赋值语句最右面的值。但是由于a.x,b.y,c.z分别指向旧对象的x、y、z域,故这几个值是赋给了就对象,即d,而a、b、c分别赋予了新值,即空对象,所以可以看到如图的结果

纯属推测,也许不是地址,是一个临时的setter也不一定,总之是虚拟机内部实现的。
这帖子里的问题和这个是一样的问题吧。
37 楼 lsqwzz 2011-02-14  
对于lz第5点
 function fun(){  
     var a = b = 5;  
 }  
fun();  
alert(typeof a); // --> undefined  
alert(typeof b); // --> number 



其中
var a=b=5;

只是声明了局部变量a,而b并未声明,对于未声明的变量直接在js中赋值就会当做全局变量。
所有b本来就可以在外部读到。
36 楼 lsqwzz 2011-02-14  
rainsilence 写道
看了两遍才明白。实际上最后alert(a.x);的a是{n:2},a.x=a的左边的a是{n:1}。
也就是说最后赋值的那个a,没有地方引用,所以alert(a.x);才会是未定义




LZ写了这么多也没明白。看这一句就明白了。。
35 楼 myter7 2011-01-17  
a.x = a = {n:2};
1. a={n:2};
2. a.x={n:2};   //此时的a为{n:1},即{n:1}.x={n:2};

所以alert(a.x)为undefined, 因为此时的a已经指向{n:2}, 没有为它的x属性赋值。
34 楼 haiyang5210 2010-12-29  
昨天遇到一位大神告知
var a=b=1;
运行时会报错,怎么都有点想不明白,试着尝试运行了一下,发现并没有出错提示,有点不甘心,看到
var a = {n:1}; 
a.x = a = {n:2}; 
alert(a.x); // --> undefined 
感觉原理有点类似, 遂改写了一下
var a; 
a.x = a = {n:2}; 
alert(a.x);
居然真的报错了!看了上面各位的解释,这里在做一点小小补充,以便新手能够理解:
1.赋值运行算的顺序是从右到左的;
2.JS垃圾回收不会在语句执行时进行.

a.x = a = {n:2};  这条语句执行过程猜得没错的话应该是
1.查找a并得到a的引用
2.查找a.x发现未定义就重新分配内存并得他的引用
3.再次查找a并得到a的引用
4.给{n:2}分配内存并得到他的引用
5.将新a的引用指向{n:2}的引用
//注意:这里本来应该回收旧a及旧a的x,但因为执行完这条语句后才能GC,所以尚未销毁a,即第2步中的旧a.x的引用在内存中仍然存在
6.将旧a的x指向新的a的引用
7.垃圾回收旧a及旧a的x

个人见解,难免有误,欢迎指正.
33 楼 litfei 2010-11-09  
我是一名新手下面是我的理解,先看下面一个例子
 		int i = 0;
		i = i++;
		System.out.println("i = " + i);//i = 0;

这段代码分如下步骤执行
(1)i = 0 ,读取变量i的地址,把0的值赋给i;
(2)i = i ++ ,因为=的优先级高于++,所以先执行赋值运算符=,编译器读取右边i的地址
(3)读取左边的i的地址(编译器并不认为右边i的地址等于左边的i的地址)
(4)右边i++
(5)打印左边的i值(因为编译器拿到的是左边的i的值)
可以看出编译器按下面处理
int i = 0;
int j = 0;//
j = i ++ ;
System.out.println("j = " + j);
其实,我认为上面的代码也是一样的
    	var a = {n:1};
    	a.x = a = {n:2};
    	alert(a.x);

a.x = a = {n:2};左边a.x中的a和右边的a代表两个不同的地址,解释器如下处理;
    	var a = {n:1};
    	var b = {n:1};
    	b.x = a = {n :2};
    	alert(b.n);
    	alert(a.n);
    	alert(a.x);  

    	var a = {n:1};
    	a.x = a = {n:2};//a.x中的a指向的是 {n:1},
    	alert(a.n);//2 ,证明解释器拿到的是 a = {n:2};
    	alert(a.x);//	

32 楼 wjsl 2010-11-08  
//感觉这个问题大家说的有点复杂
var a=[1,2,3]
alert(a[2]) //3 因为数组有这个值a[2]所以当然要返回了
a[2]=a=[]   // ?
alert(a[2]) //undefined
            //常规思维
              //如果要给变量赋值哪么首先是确定变量本身的属性,比如要给a[2]赋值
              //首先计算a[2]之前是什么,如果没有定义a是一个数组哪么此时脚本就会直接报错
              //因为 var a=[1,2,3]
            //既然此时确定了a[2]的值是3
            //a[2]=3
              //哪么往下走:从右到左,先执行a=[]此时的a是一个空数组[],也就是改变了
              //原始定义的var a=[1,2,3]值的变量定义值a=[]
            //接着往右赋值a[2]=[],也就是说此时的a[2]不管值是多少,是继承自var a=[1,2,3]
            //而不是a=[]
            //因为a=[]没有a[2]这个元素, 此时a[]既然没有a[2]这个元素哪么    
              //a[2]==undefined相当于:

var a=[]
    a[2]=?  //没有值,当然返回的是undefined

31 楼 wsq198753 2010-11-08  
学习学习了
30 楼 kimmking 2010-10-20  
跟 i=i++类似。。。。
29 楼 hackpro 2010-10-20  
var a = {n:1};           //把a的值赋成 {n:1};        
a.x = a = {n:2};        //把a的值赋成 {b:2};
                              //然后要把a.x的值 赋成a也就是({b:2}); 但a中没有x这个 所以undefined
                              //第二句改成a.x = a = {n:2,x{}}; 
                              //便会得出 a.x= object a.x.n=2;
                              //{n:1};  被覆盖了...
alert(a.x); // --> undefined 

测试了好几便终于搞明白了;
用下面的代码验证一下,就明白由了...

function init(){
				var a = {n:1,x:{}};   
				var b
				alert("a.x="+ a.x);
				alert(b);
				a.x = b = {n:2};   
				alert (a.x)
				alert("a.x.n=" +a.x.n); 
				alert("b.n=" +b.n)
			}

28 楼 pingjing 2010-10-20  
‘.’运算符的优先级高于‘=’运算符
27 楼 libmw 2010-10-18  
lifesinger 写道
挺有意思,尝试分析了下:

http://lifesinger.org/blog/2010/10/a-x-a/

哇 ,射雕我在javaeye第一次见你发言呢……
26 楼 zhouyrt 2010-10-18  
西门吹牛 写道
这么写有什么巧妙的地方吗?有使用价值吗?


这种写法在新发布的jQuery1.4.3中1833行
elemData[ eventKey ] = elemData = function(){};

至于巧妙或价值可联系jQuery作者:http://ejohn.org/blog/
25 楼 watermud 2010-10-18  
前面挺好,结束的地方不对,不写var 的变量就是全局变量。
24 楼 西门吹牛 2010-10-18  
这么写有什么巧妙的地方吗?有使用价值吗?
23 楼 telyy123 2010-10-18  
简单的问题复杂化,lz没事做
22 楼 zhouyrt 2010-10-17  
谢谢clue、lifesinger,RednaxelaFX的深入讨论。尤其是RednaxelaFX竟然深入到了虚拟机,学习了!
21 楼 reeze 2010-10-17  
结尾的例子和前面的连续赋值没有关系啊。。。
b在函数内就不是一个局部变量。。默认就是全局变量。 执行后变量也就有值了。。
20 楼 糊涂虫3000 2010-10-17  
嘛,这也是运算符结合性(associativity)与求值顺序(order of evaluation)的概念分不清楚时容易弄错的问题。
结合性和求值顺序是没有必然关系的。

JavaScript的表达式的求值顺序都是从左向右的。赋值运算的结合性虽然是右结合,但同样是从左向右求值的。看ECMAScript 5的附录D,
ECMAScript 5 写道
11.8.2, 11.8.3, 11.8.5: ECMAScript generally uses a left to right evaluation order, however the Edition 3 specification language for the > and <= operators resulted in a partial right to left order. The specification has been corrected for these operators such that it now specifies a full left to right evaluation order. However, this change of order is potentially observable if side-effects occur during the evaluation process.
这里的描述说明ECMAScript是使用从左向右的求值顺序

AssignmentExpression :
    ConditionalExpression
    LeftHandSideExpression AssignmentOperator AssignmentExpression

这条语法规则定义了赋值运算符是右结合的。怎么看出来呢?
首先要能读懂ECMAScript规范里语法的记法。在冒号左边的是语法规则的名字,右边的是规则的推导内容。推导内容中,在同一行上的属于同一条子规则,在不同行上的属于不同子规则;不同子规则之间是“或”的关系。
上面的语法规则的意思是:
一个“赋值表达式”,
  可以由一个“条件表达式”构成;
  或者可以由一个“左手边表达式”加上一个“赋值运算符”加上一个“赋值表达式”构成。

如果在一条语法规则里,推导内容中出现了该规则自身,则这条规则是“直接递归”的。如果自身出现的位置在推导内容某条子规则的最左边,则为“左递归”,出现在最右边则为“右递归”。运算符结合性也正好在此体现:左递归的规则意味着左结合,右递归的规则意味着右结合。
可以看到,ECMAScript的赋值表达式的语法是右递归的,因而是右结合的。

这两者对程序执行有什么影响呢?前面clue的解答已经对路了。有兴趣看结合性、优先级和求值顺序的关系的例子的请参考我之前的一帖,虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩,中间讲到抽象语法树的时候有举例。Java与C#同JavaScript一样,是用从左向右的求值顺序,而赋值运算是右结合的,所以那帖的例子也可以用来帮助理解JavaScript的状况。


这个与结合性是没有关系的。别乱想了。
只与解释器的进栈地址有关。

相关推荐

    javascript实现连续赋值

    这说明了JavaScript中的赋值运算的右结合性如何影响连续赋值。在编写连续赋值代码时,开发者需要考虑到这一点,以避免因理解错误而导致的bug。 此外,当涉及到对象和属性的赋值时,还应特别注意JavaScript中对象...

    使用HTML5、CSS3和JavaScript实现简易四则运算

    在本项目中,我们将探讨如何使用HTML5、CSS3和JavaScript技术来创建一个简单的四则运算计算器。这个计算器能够执行基本的加法、减法、乘法和除法操作,对于初学者来说是一个很好的实践项目,它能帮助理解前端开发的...

    javascript input自动赋值

    在"javascript input赋值"这个压缩包文件中,可能包含了一些示例代码或者教程,用于演示如何在实际项目中应用这些技术。通过学习和理解这些示例,开发者能够更好地掌握JavaScript中对input元素的操作,提高前端开发...

    js 连续赋值的简单实现

    在JavaScript中,连续赋值是一种常见的操作,它允许开发者将一个值赋给多个变量。通常情况下,连续赋值不会引起混淆,但是当涉及对象和引用时,就可能出现一些不易察觉的陷阱。了解这些陷阱和连续赋值的内部机制对于...

    计算器运算逻辑实现(带括号,求余,乘方,加减乘除),不使用eval函数-前端JavaScript 源码实现-括号匹配与初等运算

    加法(+)、减法(-)、乘法(*)和除法(/)是基础,但为了支持括号和更复杂的运算,我们需要了解优先级和运算顺序。在JavaScript中,括号用于改变运算顺序,表达式`a * (b + c)`会先计算括号内的`b + c`,然后将...

    JavaScript实现算术运算算子演示系统

    此外,JavaScript还支持复合赋值运算符,如`+=`, `-=`等,它们可以简化代码并进行连续运算: ```javascript a += b; // 等价于 a = a + b a -= b; // 等价于 a = a - b ``` 在HTML5中,我们可以利用`&lt;canvas&gt;`元素...

    JavaScript 浮点 运算 函数

    此函数是我自己写的,虽然在网上可以搜到很多,不过我找到的都是在算法中存在基本的浮点数的运算,导致结果仍然是错误的。由于刚刚学写JS,所以可能考虑不够周全,望大家批评指正。 代码中加了四舍五入函数,是网上...

    Javascript做的计算器源代码

    JavaScript是一种广泛应用于网页和网络应用开发的脚本语言,它主要负责处理客户端的交互逻辑,为用户提供...通过这个项目,你可以深入了解浏览器环境下的JavaScript运行机制,以及如何利用JavaScript提高网页的互动性。

    PHP+JavaScript+HTML变量之间赋值及传递1

    在构建Web应用时,PHP、JavaScript和HTML之间的变量赋值和传递是至关重要的。这篇文章主要讲解了如何在这些技术之间进行数据交互。 首先,我们来看HTML超链接如何传递值。在HTML中,我们可以使用`&lt;a&gt;`标签创建链接...

    JAVASCRIPT通过移位操作实现四则运算

    JavaScript是一种广泛应用于Web开发的脚本语言,它在处理四则运算时提供了多种方式,包括基本的算术运算符和位运算符。本篇将详细探讨如何利用位移操作实现四则运算,以及这些方法在实际编程中的应用。 在...

    JavaScript内核系列 pdf

    JavaScript语法基于ECMAScript规范,包括变量声明(var、let、const)、数据类型(如字符串、数字、布尔值、对象、数组等)、运算符(算术、比较、逻辑、位运算等)、流程控制(条件语句、循环结构)以及函数等。...

    李炎恢JavaScript全套课程讲义

    **JavaScript全方位解析** JavaScript,简称JS,是一种广泛应用于网页和网络应用的脚本语言,尤其在前端开发领域占据着核心地位。它与HTML和CSS一起,构成了网页开发的三驾马车,使得网页具备交互性、动态性和实时...

    JavaScript\JavaScript教程

    JavaScript是一种广泛应用于Web开发的脚本语言,由Netscape公司的Brendan Eich在1995年发明。它最初被命名为LiveScript,后来为了与Sun Microsystems的Java语言搭上关系,更名为JavaScript,但实际上两者并无直接...

    【JavaScript源代码】JavaScript中极易出错的操作符运算总结.docx

    本文主要探讨了JavaScript中几个易出错的操作符类别,包括算术运算符、比较运算符、逻辑运算符以及赋值和一元运算符的运算顺序和优先级。 首先,我们来看算术运算符。在JavaScript中,有些特殊的字面量值,如`NaN`...

    javascript float运算

    javascript float运算会有误差,可以用此脚本中的函数进行修补。

    JavaScript中的数学运算介绍.docx

    JavaScript中的数学运算主要涵盖基本算术运算和使用Math对象的高级计算。JavaScript支持常见的加法(+), 减法(-), 乘法(*), 除法(/)以及取模(%)运算符。此外,它还提供了处理特殊计算结果的能力,如正负Infinity和NaN...

    Javascript模拟训练

    10. 表达式运算:Javascript中的表达式运算可以是算术运算、比较运算、逻辑运算等,例如`a++==b?a:b`的结果是根据条件判断的结果而定的。 11. Radio按钮默认选中:在Html中,可以使用checked属性来设置Radio按钮的...

    javascript10源码大全

    JavaScript10源码大全是一个关于JavaScript编程语言的资源集合,主要针对那些想要深入理解JavaScript核心概念、函数以及工作机制的开发者。这个压缩包可能包含了多个示例、教程、代码片段,甚至是完整的项目源码,...

Global site tag (gtag.js) - Google Analytics