ES6学习笔记1——let和const的认识

一. let

    let和var一样用于定义变量,和var的区别在于:let是块级的,而var是函数级的。在以前的JavaScript中,只有两种作用域,一种是全局作用域,另一种是函数作用域,ES6通过let关键字增加了块级作用域

(1)let的块级作用域

{
var a = 1 ;
let b = 2 ;
}
console . log ( a , b );

    此时将会报错:Uncaught ReferenceError: b is not defined,因为变量b无法在块级作用域以外被访问。

{
var a = 1 ;
let b = 2 ;
{
console . log ( a , b );
}
}

    此时输出:1 2 。子级是可以访问到父块级作用域中的变量。

for ( var i = 0 ; i < 5 ; i ++) {}
console . log ( i );

    此时输出:5 。若将var改为let,也会报错:Uncaught ReferenceError: i is not defined。因为let声明的i只在for循环中起作用。

(2)let不存在变量的提升

function f () {
console . log ( a );
var a = 1 ;
}
f ();

    定义一个函数f()并执行,结果:undefined,此时并没有报错,因为var声明有变量提升的作用,在var声明的变量a之前使用到a,因为此时a还未赋值,所以值为undefined的。以上的代码相当于如下:

function f () {
var a ;
console . log ( a );
a = 1 ;
}
f ();

    若将var改为let:

function f () {
console . log ( a );
let a = 1 ;
}
f ();

    则报错:Uncaught ReferenceError: a is not defined,说明let不能使变量的声明提升

    变量临时失效现象。如下:

var a = 1 ;
function f () {
console . log ( a );
}
f ();

    此时f()函数执行时肯定是可以访问到a变量,结果:1。若代码为如下:

var a = 1 ;
function f () {
console . log ( a );
let a = 1 ;
}
f ();

    此时将报错:Uncaught ReferenceError: a is not defined 。因为在f()函数的块级作用域内,此时已经有let定义的变量a,所以外面var定义的全局变量a将失效,而console.log是在声明变量a之前调用,并且let声明没有变量提升的功能,所以此时a还未定义,所以会报错。ES6明确规定,如果在区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域,外面的同名变量不起作用,并且只要在声明之前使用这些变量,就会报错,这在语法上称为“暂时性死区”(Temporal Dead Zone,简称TDZ),如下所示:

var a = 1 ;
function f () {
//TDZ开始
a = 2 ; //Uncaught ReferenceError: a is not defined
console . log ( a ); //Uncaught ReferenceError: a is not defined
let a = 1 ; //TDZ结束
console . log ( a ); //1
}
f ();
    TDZ的存在以及变量不可提升的原因是为了减少运行时错误,防止变量在声明前就使用。

(3)let不允许重复声明变量

{
var a = 1 ;
var a = 2 ;
console . log ( a );
}

    用var声明两次变量a,并且赋予不同的值,结果输出为:2 。说明了var声明变量允许重复声明,并且值为最后一次赋予的值。若将var改为let:

{
let a = 1 ;
let a = 2 ;
console . log ( a );
}

{
let a = 1 ;
var a = 2 ;
console . log ( a );
}

    以上两个都报错:Uncaught SyntaxError: Identifier 'a' has already been declared 。说明let不允许在相同作用域中重复声明变量。不过以下这种使用没有影响:

{ //作用域1开始
let a = 1 ;
console . log ( a ); //1
//作用域1结束
{ //作用域2开始
let a = 2 ;
console . log ( a ); //2
} //作用域2结束
//作用域1开始
console . log ( a ); //1
} ////作用域1结束
    从以上可以看出,let的这种块级作用域以及不允许重复声明的特性,可以用来替代立即执行匿名函数的作用,如下:
var config = ( function (){
var config = [];
config . push ( 1 );
config . push ( 2 );
config . push ( 3 );
})();

    可以使用let的写法来替代:

let config ;
{
config = [];
config . push ( 1 );
config . push ( 2 );
config . push ( 3 );
}

(4)实际的例子

    var arr = [];
    function f () {
     for ( var i = 0 ; i < 5 ; i ++){
     arr . push ( function () {
     console . log ( i );
     })
     }
    }
    f ();
    arr [ 2 ]();

    以上代码想实现的功能是:arr数组中的每个元素保存一个函数,调用指定下标的函数时能输出对应的下标。然而并不像预想的那样会输出2,结果输出为5,arr[1]()....,arr[4]()的输出结果都是5。因为for循环中var定义的i的作用域为整个f()函数,每一次循环,新的i值都会覆盖旧值。执行完函数f()后,i最终为5,而arr中函数使用到的始终是这个i,所以最终执行都输出为5 。

    要想实现预想的功能,以前的做法是通过立即执行匿名函数(IIFE)来实现:

    var arr = [];
    function f () {
     for ( var i = 0 ; i < 5 ; i ++){
     arr . push (( function ( i ) {
     return function () {
     console . log ( i );
     }
     })( i ));
     }
    }
    f ();
    arr [ 2 ]();

    通过立即执行匿名函数来保存对应的i值。

    现在单地将for循环中的var改为let即可:

    var arr = [];
    function f () {
     for ( let i = 0 ; i < 5 ; i ++){
     arr . push ( function () {
     console . log ( i );
     })
     }
    }
    f ();
    arr [ 2 ]();

    变量i是let声明的,当前的i只在本轮循环中有效。所以每一次循环的i其实都是一个新的变量。

    另外补充一点,在for循环中,其定义循环条件的部分和循环体部分并不是在同一个作用域的,循环条件部分是循环体的父级域,如下例子:

for ( let i = 0 ; i < 2 ; i ++) {
let i = "CC" ;
console . log ( i );
}
    这段代码能正常执行,输出两次“CC”。

二. const

   const除了和let的特性都一样以外,唯一的区别是const定义的常量的值不可更改。这里的常量不可更改是指物理内存地址不可更改,而地址中的内容是可以更改的,这就要求const声明时必须初始化。如下:

const a = 1 ;
a = 2 ;
console . log ( a );

    以上代码将报错:Uncaught TypeError: Assignment to constant variable. 常量定义以后是不可以再重新赋值的,而常量对象内的变量是重新赋值的,如下:

const a = {
name: 'Bob'
};
a . name = 'Chen cong' ;
console . log ( a . name );

    此时输出为:Chen cong 。这里改变的是a对象中的内容,而指向对象a的地址并未改变,故可以成功运行。若将a指向另一个对象的话将报错。对于复类型(如数组,对象等)的变量,变量名不指向数据,而是指向数据所在的地址,const命令只保证变量名指向的地址不变,并不保证该地址的数据不变。

    备注:若想对象中的内容也不可更改的话,那么可以使Object.freeze()方法来冻结对象。除了将对象本身冻结,对象的属性也应该冻结,如下函数为将一个对象彻底冻结

var constantize = function ( obj ) {
Object . freeze ( obj );
Object . keys ( obj ). forEach ( function ( key , value ) {
if ( typeof obj [ key ] === 'object' ) {
constantize ( obj [ key ]);
}
})
}

三. 其他:

    let、const、class命令声明的全局变量不属于全局对象的属性。全局变量有两种,在浏览器中指的是window对象,在Node.js中指的是global对象。ES5中,定义全局变量与定义全局对象的属性是等价的。如下:

//ES5
var a = 1 ;
console . log ( a ); //1
console . log ( window . a ); //1
//ES6
let a = 1 ;
console . log ( a ); //1
console . log ( window . a ); //undefined

猜你喜欢

转载自blog.csdn.net/cc18868876837/article/details/79957030