很喜欢蔡蔡
的这个标题,实际蔡蔡已经分析过了,这里借用了。或许有点标题党的意思。看完就知了。
一、引子
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
分享到:
评论
a只是一个指针,只能指向一个地址
相当于是先 {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指针的值。
和结果:
我觉得是这样的,执行这句话的时候,发现有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也不一定,总之是虚拟机内部实现的。
这帖子里的问题和这个是一样的问题吧。
其中
只是声明了局部变量a,而b并未声明,对于未声明的变量直接在js中赋值就会当做全局变量。
所有b本来就可以在外部读到。
也就是说最后赋值的那个a,没有地方引用,所以alert(a.x);才会是未定义
LZ写了这么多也没明白。看这一句就明白了。。
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属性赋值。
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
个人见解,难免有误,欢迎指正.
这段代码分如下步骤执行
(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);
其实,我认为上面的代码也是一样的
a.x = a = {n:2};左边a.x中的a和右边的a代表两个不同的地址,解释器如下处理;
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
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
测试了好几便终于搞明白了;
用下面的代码验证一下,就明白由了...
http://lifesinger.org/blog/2010/10/a-x-a/
哇 ,射雕我在javaeye第一次见你发言呢……
这种写法在新发布的jQuery1.4.3中1833行
至于巧妙或价值可联系jQuery作者:http://ejohn.org/blog/ 。
b在函数内就不是一个局部变量。。默认就是全局变量。 执行后变量也就有值了。。
结合性和求值顺序是没有必然关系的。
JavaScript的表达式的求值顺序都是从左向右的。赋值运算的结合性虽然是右结合,但同样是从左向右求值的。看ECMAScript 5的附录D,
这里的描述说明ECMAScript是使用从左向右的求值顺序
这条语法规则定义了赋值运算符是右结合的。怎么看出来呢?
首先要能读懂ECMAScript规范里语法的记法。在冒号左边的是语法规则的名字,右边的是规则的推导内容。推导内容中,在同一行上的属于同一条子规则,在不同行上的属于不同子规则;不同子规则之间是“或”的关系。
上面的语法规则的意思是:
一个“赋值表达式”,
可以由一个“条件表达式”构成;
或者可以由一个“左手边表达式”加上一个“赋值运算符”加上一个“赋值表达式”构成。
如果在一条语法规则里,推导内容中出现了该规则自身,则这条规则是“直接递归”的。如果自身出现的位置在推导内容某条子规则的最左边,则为“左递归”,出现在最右边则为“右递归”。运算符结合性也正好在此体现:左递归的规则意味着左结合,右递归的规则意味着右结合。
可以看到,ECMAScript的赋值表达式的语法是右递归的,因而是右结合的。
这两者对程序执行有什么影响呢?前面clue的解答已经对路了。有兴趣看结合性、优先级和求值顺序的关系的例子的请参考我之前的一帖,虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩,中间讲到抽象语法树的时候有举例。Java与C#同JavaScript一样,是用从左向右的求值顺序,而赋值运算是右结合的,所以那帖的例子也可以用来帮助理解JavaScript的状况。
这个与结合性是没有关系的。别乱想了。
只与解释器的进栈地址有关。