使用var和let声明变量踩坑笔记

677 阅读2分钟

参考链接

let 和 const 命令

情景描述

在使用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声明的变量具有以下特征:

  1. 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
  1. 也是特别重要的一点,在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.