js中的变量提升

ES6中为何要提出用let和const来定义变量?使用var会有什么弊端呢?其中最关键的就是:使用var关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内部,则视为在全局作用域的顶部)。这就是所谓的变量提升。

栗子如下:

function getValue(condition){
    if(condition){
        var value = 'blue';
        return value;
    }else{
        return null;
    }
}

实际上当condition为false或者true时候value都被创建。在else的语句中value的值也是可以被访问的,是undefined。

ES6中引入块级声明:目的就是防止这种变量提升的现象。让声明的变量在指定的块级的作用域外无法被访问。创建形式如下:

1,在一个函数内部

2,由一对花括号包裹的代码块内部 

function getValue(condition){
    if(condition){
        let value = 'blue';
        return value;
    }else{
        return null;
    }
}

 使用let声明变量,该声明没有被提升到函数定义的顶部。因此value值if代码块之外无法访问,在condition为false时候,该变量永远不会被声明并初始化。

使用let定义变量要注意禁止变量重复定义。

常量声明:

const进行变量的声明设置完成后就不允许再被更改,所以,所有的const变量都需要在声明时候进行初始化,不初始化会报错。

let和const声明的对比:

相同点:1,都是块级声明,不存在变量提升,2,如果变量在同一作用域中(无论是全局还是函数作用域)之前声明过,再用let或者const进行重复定义会报错。

使用const声明对象:

const person = {
    name:'miya'
}

person.name = 'jone'
person
Object {name: "jone"}

Object {name: "jone"}
person = {
    name:'chris'
}
VM183:1 Uncaught TypeError: Assignment to constant variable.(…)

如果常量是一个对象,对象所包含的值是可以被修改的。但是尝试修改person对象本身就会报错。const阻止的是对于变量绑定的修改,而不阻止对成员值得修改。

还有个相同点就是:暂时性死区

使用let或者const声明变量时候,在未达到声明位置之前都是无法访问的,试图访问会导致引用错误。

js引擎在处理var时将声明提升到函数或者全局作用域的顶部,而在处理let或者cosnt时,则会将声明放入暂时性死区。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除,才可以安全使用。

暂时性死区是块级绑定的一个独特表现,而另一个独特表现就是在循环内使用。

循环中的块级绑定:

for(var i = 0;i<10;i++){
    setTimeout(function(){
        console.log(i)
    },0)
}
console.log(`i==>${i}`)

分析下这个代码的输出:肯定是 i==>10 然后输出10个10,因为使用var声明导致了变量提升,循环结束后i仍然可以被访问,但是如果使用let进行定义i,则在for循环的外部访问i是会报错的。

循环内的函数:

var的特点使得循环变量在循环作用于之外仍然可以被访问,于是在循环内部创建函数会不会出什么问题?思考入下:

var funcs = [];
for(var i = 0;i<10;i++){
    funcs.push(function(){console.log(i)});
}
funcs.forEach(function(func){
    func();
})
//10 times 10
func()方法里面的i在循环的每次迭代中都被共享,所以在循环内部创建的那些函数都拥有对同一个变量的引用。循环结束后,变量i的值会是10。
为了修正这个问题,开发者在循环内部使用立即调用函数表达式,在每次迭代中强制创建变量的一个新副本。例子如下:
var func = [];

for(var i = 0;i<10;i++){
    func.push((function(value){
      return function(){
          console.log(value);
      }
    }(i)))
}

func.forEach(function(func){
    func();
})
这种写法在循环内部使用了立即执行函数,变量i传递给立即执行函数,从而创建作为副本的value变量,每个value都存储每次迭代时变量i的值,迭代中的函数使用这些副本值,所以得到0-9的期望结果。
循环内部let的使用:
let可以简化像上面立即执行函数那样的作用,在每次迭代中,都会创建一个新的同名变量并对其进行初始化。
而用const进行循环遍历会报错,因为第二次执行循环语句试图更改常量的值。因此在循环中只能使用const来声明一个确定不会被更改的变量。
使用for/in for/of 进行遍历时候。看下面的栗子==>
var funcs = [],
object = {
a: true,
b: true,
c: true
};
// 不会导致错误
for (const key in object) {
funcs.push(function() {
console.log(key);
});
}
funcs.forEach(function(func) {
func(); // 依次输出 "a"、 "b"、 "c"
});
这段代码没有报错。而且正常的输出结果,原因是:循环为每次迭代创建一个新的变量绑定,而不是像for循环那样去修改已绑定的变量的值。
全局块级绑定:
使用var进行全局变量声明时,该变量成为全局对象的一个属性(浏览器中是window),所以有可能无意中会覆盖一个已有的全局属性。
像酱紫:
var RegExp = 'hello';
console.log(window.RegExp)
可以看出window上面的RegExp对象被覆盖,而如果在全局使用let或者const,虽然在全局作用域会创建新的绑定,但是不会有任何的属性被添加到全局对象上面。
最佳实践===>
默认情况下应当使用let来代替var。let的行为方式是var本应有的方式,因此直接用let替代var更符合逻辑。而需要受到保护的变量再使用const。
而更加流行的方式可以酱紫:默认情况下使用const,仅当明确变量值需要被更改的情况下才使用let,因为大部分变量在初始化后都应当保持不变。

总结:

let和const将块级作用域引入js中,都不会进行变量提升,不能在变量声明位置之前访问,因为块级绑定存在暂时性死区,试图在声明之前访问就会导致错误。

let和const在循环中的差异,let和const都能在每次迭代时创建一个新的变量绑定。

块级绑定当前的最佳实践是:默认情况使用const,而仅在变量值需要给更改的时候才使用let,有助于防止某些类型的错误。 

原文地址:https://www.cnblogs.com/tangjiao/p/9163826.html