在javascript中,我们都知道使用var来声明变量。javascript是函数级作用域,函数内可以访问函数外的变量,函数外不能访问函数内的变量。
函数级作用域会导致一些问题就是某些代码块内的变量会在全局范围内有效,这我们是非常熟悉的:
1 for (var i = 0; i < 10; i++) { 2 console.log(i); // 0,1,2...,9 3 } 4 console.log(i); //10 5 6 if(true){ 7 var s = 20; 8 } 9 console.log(s); //20
在es6中增加了let(变量)和const(常量)来声明变量,使用的是块级作用域,变量声明只在代码块内有效:
1 for (let i = 0; i < 10; i++) { 2 console.log(i); // 0,1,2...,9 3 } 4 console.log(i); //ReferenceError: i is not defined 5 6 if(true){ 7 let s = 20; 8 } 9 console.log(s); //ReferenceError: s is not defined
我们最常遇到的循环事件绑定i产生的闭包问题,是因为i绑定在全局,每次访问都会取到全局的i的值,所以访问哪个元素,得到的i都是循环体最后一轮的i。我们通常的解决办法是借助IIFE。在es6中let定义的i只在循环体内有效,所以每次循环i都是一个新的变量,于是会产生正常的输出:
1 //使用var 2 var arr1 = []; 3 for (var i = 0; i < 10; i++) { 4 arr1[i] = function() { 5 console.log(i); 6 } 7 } 8 9 arr1[5](); //10 10 arr1[8](); //10 11 12 //使用let 13 var arr2 = []; 14 for (let i = 0; i < 10; i++) { 15 arr2[i] = function() { 16 console.log(i); 17 } 18 } 19 20 arr2[5](); //5 21 arr2[8](); //8
变量提升:
在es5中var定义的变量存在变量提升,es6中使用的let不存在变量提升:
1 console.log(i); 2 var i = 10; //10 3 4 console.log(s); 5 let s = 20; //ReferenceError: can't access lexical declaration `s' before initialization
函数提升:
es5中无论if语句是否执行,在if语句内的函数声明都会提升到当前的作用域的顶部而得到执行,es6支持块级作用域,无论是否进入if,内部的函数都不会影响到外部的函数
严格模式下函数声明只能在顶层环境和函数体内,不能if或者循环体内进行函数声明
1 //es5环境 2 function f(){ 3 console.log('outside'); 4 } 5 if(true){ 6 function f(){ 7 console.log('inside'); 8 } 9 } 10 11 f(); //inside 12 13 14 //es6环境 15 function d(){ 16 console.log('outside'); 17 } 18 if(true){ 19 function d(){ 20 console.log('inside'); 21 } 22 } 23 24 d(); //outside
每个代码块中即同一个作用域中,每个变量只能使用let定义一次,使用var重复定义会进行覆盖,但是使用let重复定义会报错,不同代码块可以重复定义:
1 let i = 10; 2 3 if(true){ 4 let i = 20; 5 console.log(i); //20 6 let i = 50; //redeclaration of let i 7 console.log(i); 8 }
暂时性死区:
在代码块内,使用let声明变量之前,该变量都不可用,即是变量在使用赋值之前需要先进行声明,每个代码块会进行变量绑定,以确定变量属于哪个环境,在绑定之前进行变量的操作都是非法的:
1 let i = 10; 2 3 if(true){ 4 i = 50; //ReferenceError 5 let i = 20; 6 console.log(i); 7 } 8 //因为在if中声明了i,i绑定了if的环境,所以在if的i声明之前,i都不可用,导致i=50报错
IIFE有一个重要的作用是封装变量,避免变量泄漏到全局,块级作用域的出现,其实就使得IIFE不再必要了:
1 ~function(){ 2 if(true){ 3 var i = 10; 4 } 5 }() 6 console.log(i); //ReferenceError: i is not defined 7 8 if(true){ 9 let s = 20; 10 } 11 12 console.log(s); //ReferenceError: s is not defined
let和const的区别:
let声明变量,值可以改变。const声明常量,值不可改变(使用const时,常量最好使用大写)
其他的属性let与const一致
1 let a = 10; 2 a = 20; 3 console.log(a); //20 4 5 const B = 30; 6 B = 40; //TypeError: invalid assignment to const `b' 7 console.log(B);
const只保证常量名指向的地址不变,并不能保证地址的数据不变,如果想吧对象冻结数据不可改变,可使用Object.freeze(obj)
1 const a = {}; 2 a.name = 'gary'; 3 console.log(a.name); //gary 4 5 const b = Object.freeze({}); 6 b.name = 'bob'; 7 console.log(b.name); //undefined
在es5中我们都知道,在全局定义变量其实就是等于在全局对象设置属性,全局对象属性与全局变量是等价的。全局变量的赋值和全局属性的赋值是同一件事
在es6中修改可这一规定,使用let和const和class声明的全局变量与全局对象不等价
1 var a = 10; 2 console.log(a === window.a); //true 3 4 let b = 20; 5 console.log(b === window.b); //false