`
zhouyrt
  • 浏览: 1163393 次
  • 性别: 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中的数学运算介绍.docx

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

    Javascript模拟训练

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

    javascript10源码大全

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

    javascript中解析四则运算表达式的算法和示例

    在编写代码时我们有时候会碰到需要自己解析四则运算表达式的情况,本文简单的介绍使用JavaScript实现对简单四则运算表达式的解析。 一、熟悉概念 中缀表示法(或中缀记法)是一个通用的算术或逻辑公式表示方法, ...

Global site tag (gtag.js) - Google Analytics