面试题中经常会遇到js连续赋值与求值顺序问题:
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x); // undefined
console.log(b.x); // {n:2}
上面的例子看似简单,但结果并不好了解a.x不是指向对象a了么?为啥log(a.x)是undefined?
这个题目其实就是问的你是否了解js引用类型“赋值”的工作方式。下面对这个进行分析:
首先是
var a = {n:1};
var b = a;
在这里a指向了一个对象A:{n:1},b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的:
继续看下一行非常重要的代码:
a.x = a = {n:2};
js的赋值运算顺序永远都是从右往左的,不过由于“.”是优先级最高的运算符,所以这行代码先“计算”了a.x。
这时候发生了这个事情——a指向的对象{n:1}新增了属性x(虽然这个x是undefined的):a={n:1,x:undefined}
由于b跟a一样是指向对象A的,要表示A的x属性除了用a.x,所以:b={n:1,x:undefined}
接着继续执行 a.x=a,很多人会认为这里是“对象B也新增了一个属性x,并指向对象B自己”
但实际上并非如此,由于. 运算符最先计算,一开始js已经先计算了a.x,便已经解析了这个a.x是对象A的x,所以在同一条公式的情况下再回来给a.x赋值,也不会说重新解析这个a.x为对象B的x。
所以 a.x=a 应理解为对象A的属性x指向了对象B:{n:2},所以:
b={n:1,x:{n:2}}
a={n:2}
当console.log(a.x)的时候,a是指向对象B的,但对象B没有属性x。没关系,当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。但当查找到达原型链的顶部 - 也就是 Object.prototype - 仍然没有找到指定的属性B.prototype.x,自然也就输出undefined;
那么这个理论我们会不会遇到呢?其实这里对象改变引用地址的理论在vue源码中就有体现。这里不涉及优先级问题,比如:vue中的坑:监控对象重新改变引用地址一定触发,但是监控变量赋值相同值不会触发监控器:
<button @click="changeCity">切换城市</button>
<div>{{city.name}}</div>
new Vue({
data() {
return {
citys:"北京",
city: {id: 1, name: '北京'}
}
},
watch: {
city() {
console.log('city changed 上海')
},
citys() {
console.log('city changed 大连')
}
},
methods:{
changeCity() {
this.city = {id: 1, name: '上海'}
this.citys="大连"
}
}
})
说明:当第一次点击切换城市,citys变量发生改变,监控器触发了,后面再点击变量并没有发生改变,监控器无法触发。但是对象改变了地址,会一直被触发,即使属性值一样。
这种场景一般用于监控状态值,状态值的轮回难免会有重复 ,但是动作依然要执行。