let命令基本用法ES6增加了一个let命令来声明变量。它的用法与var类似,但声明的变量只在let命令所在的代码块内有效。{让a=10;varb=1;}a//ReferenceError:a未定义。b//1上面代码在代码块中,分别用let和var声明了两个变量。然后在代码块外调用这两个变量。结果是let声明的变量报错,var声明的变量返回正确值。这说明let声明的变量只在它所在的代码块中有效。for循环的计数器非常适合使用let命令。for(leti=0;i<10;i++){//...}console.log(i);//ReferenceError:iisnotdefined在上面的代码中,计数器i只在函数体中有效for循环。体外参考会报错。如果下面的代码使用了var,那么最后的输出就是10。vara=[];对于(vari=0;i<10;i++){a[i]=function(){console.log(i);};}a[6]();//10上面的代码中,变量i是通过var命令声明的,全局有效,所以全局只有一个变量i。每次循环,变量i的值都会变化,循环中函数里面的console.log(i)赋值给数组a,里面的i指向全局的i。也就是说,数组a的所有成员中的i指向同一个i,导致运行时输出最后一轮i的值,即10。如果使用let,则声明的变量为只在块级作用域有效,最终输出为6vara=[];for(leti=0;i<10;i++){a[i]=function(){console.log(i);};}a[6]();//6上面的代码中,变量i是通过let声明的,而当前的i只在当前循环中有效,所以每次循环中的i其实都是一个新的变量,所以最终输出的是6。你可能会问,如果每次循环都重新声明变量i,它怎么知道上一次循环的值来计算当前循环的值呢?这是因为JavaScript引擎内部会记住上一个循环的值,在初始化当前循环的变量i时,是在上一个循环的基础上计算的。此外,for循环还有一个特殊之处,就是设置循环变量的部分是一个父作用域,而循环体内是一个单独的子作用域。for(leti=0;i<3;i++){让i='abc';console.log(i);}//abc//abc//abc上面代码运行正确,输出了3次abc。这说明函数内部的变量i和循环变量i不在同一个作用域内,有各自独立的作用域。没有变量提升var命令中出现了“变量提升”现象,即变量在声明之前就可以使用,而其值是undefined。这种现象或多或少有些奇怪。按照一般的逻辑,变量应该用在声明语句之后。为了纠正这种现象,let命令改变了语法行为,它声明的变量必须在声明之后使用,否则会报错。//varcaseconsole.log(foo);//输出undefinedvarfoo=2;//让案例console.log(bar);//错误ReferenceErrorletbar=2;上面代码中,变量foo是用var命令声明的,会发生变量提升,即脚本开始运行时,变量foo已经存在,但是没有值,所以会输出undefined。变量bar是用let命令声明的,不会发生变量提升。这意味着变量bar在声明之前是不存在的,如果使用它会抛出错误。暂时死区只要块级作用域内有let命令,它声明的变量就会“绑定”(binding)这个区域,不再受外界影响。vartmp=123;if(true){tmp='abc';//ReferenceErrorlettmp;}上面代码中有一个全局变量tmp,但是let在块级作用域内声明了一个局部变量tmp,导致后者绑定了这个块级作用域,所以在变量之前是let声明的,给tmp赋值的时候会报错。ES6明确规定,如果块中有let和const命令,则块为这些命令声明的变量从一开始就形成一个封闭的作用域。在声明之前使用这些变量将导致错误。简而言之,在代码块中,变量在使用let命令声明之前是不可用的。这在语法上称为“时间死区”(简称TDZ)。if(true){//TDZ开始tmp='abc';//ReferenceErrorconsole.log(tmp);//ReferenceErrorlettmp;//TDZ结束console.log(tmp);//未定义tmp=123;控制台日志(tmp);//123}上面代码中,变量tmp在被let命令声明之前,属于变量tmp的“死区”。“临时死区”也意味着typeof不再是100%安全的操作。x类型;//ReferenceErrorletx;上面代码中,变量x是使用let命令声明的,所以在声明之前,属于x的“死区”,只要使用该变量就会报错。因此,typeof在运行时会抛出ReferenceError。作为对比,如果一个变量根本没有声明,使用typeof是不会报错的。typeofundeclared_variable//"undefined"上面代码中undeclared_variable是一个不存在的变量名,结果返回"undefined"。所以在没有let之前,typeof操作符是100%安全的,永远不会报错。这不再是真的。这样的设计是为了让大家养成良好的编程习惯。变量必须在声明后使用,否则会报错。有些“死区”比较隐蔽,不易发现。函数bar(x=y,y=2){return[x,y];}bar();//报错上面代码中,之所以调用bar函数报错(有的实现可能不报错)是因为参数x的默认值等于另一个参数y,而y还没有声明此时,属于“死区”。如果y的默认值为x,则不会报错,因为此时x已经声明了。函数bar(x=2,y=x){return[x,y];}bar();//[2,2]另外,下面的代码也会报错,这与var的行为不同。//没有报错varx=x;//报错letx=x;//ReferenceError:xisnotdefined上面的代码报错是因为临时死区。在使用let声明变量时,只要在声明完成之前使用该变量,就会报错。上面一行就属于这种情况。在执行变量x的声明语句之前,取了x的值,导致报错“xisundefined”。ES6规定临时死区和let和const语句没有变量提升,主要是为了减少运行时错误,防止变量在声明之前就被使用,导致意外行为。像这样的错误在ES5中很常见,现在有了这个规定,避免它们很容易。简而言之,临时死区的本质就是一进入当前作用域,要使用的变量已经存在,但是获取不到。只有当声明变量的代码行出现时,才能获取并使用该变量。不允许重复声明。Let不允许在同一范围内重复声明同一变量。//错误函数func(){leta=10;vara=1;}//错误函数func(){leta=10;leta=1;}因此,参数不能在函数内部重新声明。functionfunc(arg){letarg;}func()//报错functionfunc(arg){{letarg;}}func()//不报错Block-levelscope为什么需要block-levelscope?ES5只有全局作用域和函数作用域,没有块级作用域,带来很多不合理的场景。在第一种情况下,内部变量可能会覆盖外部变量。vartmp=newDate();functionf(){console.log(tmp);如果(假){vartmp='你好世界';}}F();//undefined上面代码的原意是,ifcode块外使用外层tmp变量,内层使用内层tmp变量。但是函数f执行后,输出结果是undefined。原因是变量提升导致内层的tmp变量覆盖了外层的tmp变量。在第二种情况下,用于计数的循环变量作为全局变量泄漏。vars='hello';for(vari=0;i
