参考链接
情景描述
在使用var进行变量声明,实现mvvm的数据劫持时,遇到下面这样一个问题:
function observe(data){
if(typeof data !== 'object' ){
return;
}
for(var key in data){
var val = data[key]
Object.defineProperty(data,key,{
configurable:false,
enumerable:true,
get(){
return val
},
set(newVal){
if(val === newVal){
return;
}//如果新值和旧值相等,那就什么都不做
val = newVal
observe(val)
}
})
}
}
var obj = {a:1,b:2,c:3,d:4}
observe(obj)
问题描述
因为我们实在for循环中使用Object.defineProperty(),而val是会随着循环不断发生变化的,再者,我们再get()中返回的是val,所以会造成值覆盖问题
var obj = {a:1,b:2,c:3,d:4}
observe(obj)
在这段代码中,obj[a],obj[b],obj[c],obj[d]的输出结果都是4。验证了我们的结论。
原因分析
在上面的实现中,变量val是使用var声明的,在全局范围内都有效。全局只有一个val。每一次循环,变量val的值都会发生改变。也就是说所有的data[key]指向的都是同一个val,也就是最后一轮循环val的值,也就是4.
解决思路
在ES6中,新添了使用let声明变量的方式。
使用let声明的变量具有以下特征:
let声明的变量不存在变量提升。
x=10;
let x; //报错Uncaught ReferenceError: Cannot access 'x' before initialization
也就是说,使用let声明的变量必须先声明,再使用。
2. let的变量具有块级作用域
for(let i=0;i<10;i++){
console.log(i)
}
console.log(i) // 报错ReferenceError
- 也是特别重要的一点,在for循环中,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。每一轮循环变量都是重新声明的。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// 上面这段代码生成了3以下三个作用域
{
let i=0
{
let i='abc'
console.log(i) // 输出abc
}
}
{
let i=1
{
let i='abc'
console.log(i) // 输出abc
}
}
{
let i=2
{
let i='abc'
console.log(i) // 输出abc
}
}
这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
解决方案
根据前面的分析,我们不难得出,我们需要做的就是改变val的声明方式,将其声明方式由var改成let.