let 命令
-
基本用法
ES6 新增了let命令,用于声明变量。用法和var类似,但是声明的变量只在 let 命令所在的代码块内有效。{ let a = 1; var b = 2; } console.log(a) // ReferenceError: a is not defined console.log(b) // 2上面的代码在代码块中分别用了
let和var命令声明变量,又在代码块之外调用了这两个变量,let声明的变量输出报错,说明let声明的变量在代码块外无法访问。for循环的计数器用let命令再合适不过了!for (let i = 0; i < 10; i++) { console.log(i) // 1 2 3 4 ... 10 } console.log(i) // ReferenceError: i is not defined以上代码中的计数器 i 只在
for循环体内有效,在循环体外引用就会报错。下面的题属于比较经典的一个面试题,可以很好的反应 let 和 var 的区别
var a = []; for (var i = 0; i < 10; i++){ a[i] = function() { console.log(i); } } a[6](); // 10 ------------------------------------ var a = []; for (let i = 0; i < 10; i++){ a[i] = function() { console.log(i); } } a[6](); // 6为什么会是这样的结果呢?
- 当用
var声明 i 变量时,i 在全局范围内都有效,所以全局只有一个变量 i,每一次循环 i 的值都会发生改变,在循环内被赋给数组 a 的函数内部输出的 i 是指向全局的 i,也就是说 a 数组成员中的 i 指向的都是同一个 i,导致运行时输出的是最后一轮的 i 值,也就是10。 - 当用
let声明 i 变量时,当前的 i 只在本轮循环有效,所以每一轮循环的 i 都是一个新变量,于是最后输出的是 6 另外,for循环还有一个特别之处:设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc正确运行以上代码并且输出三次
abc,表明函数内部变量 i 与循环变量 i 不在同一作用域!- 不存在变量提升
var 命令会发生 变量提升 现象。
什么叫做 变量提升 呢?变量提升指的是变量可以在声明之前调用,其值为undefined。其实按照一般的逻辑,这样是有点奇怪的,正常的顺序应该是先声明再使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后才能使用,否则会报错!
console.log(a); // undefined var a = 1; --------------- console.log(b); let b = 2; // 报错 ReferenceError在以上代码中,变量
a用var命令声明会发生变量提升,即脚本开始运行时,变量a已经存在,但是没有值,所以输出undefined。变量b用let命令声明则不会发生变量提升。这表明在声明它之前,变量b是不存在的,这时如果用到它就会抛出一个错误。- 暂时性死区
只要块级作用域内存在
let命令,它所声明的变量就绑定这个区域,不再受外部影响!
var temp = 123; if(1) { temp = 456; // ReferenceError let temp; }上面的代码中存在全局变量
temp,但是块级作用域内let又声明了一个局部变量temp,导致后者绑定这个块级作用域,所以在let声明变量前对temp赋值会报错!
ES6明确规定,如果块级作用域中存在let和const命令,则这个区块对这些命令声明的变量一开始就形成封闭的作用域,只要在声明之前就使用变量就会报错!
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上称为“暂时性死区”(temporal dead zone,简称TDZ)
有些死区比较隐蔽,不太容易发现。function bar(x = y,y = 2) { return [x,y]; } bar(); // 报错以上代码之所以报错是因为参数
x的默认值等于另一个参数y,而此时y还没有声明,属于死区。ES6规定暂时性死区和
let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。总之,暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的哪一行代码出现才可以获取和使用该变量。
- 不允许重复声明 let 不允许在相同作用域内重复声明同一个变量
// 报错 function () { let a = 10; var a = 1; } // 报错 function () { let a = 10; let a = 1; } // 不能在函数内重复声明函数 function func(arg) { let arg; // 报错 } function func(arg) { { let arg; // 正常运行 } }块级作用域
- 为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合理。
第一种场景,内层变量可能会覆盖外层变量。
var temp = new Date(); function f() { console.log(temp); if(false){ var temp = 'abc'; } } f(); // undefined第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5上面的代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,而是泄露成为了全局变量。
- ES6 的块级作用域
let实际上为 JavaScript 新增了块级作用域。
function fn() { let n = 5; if(true) { let n = 10; } console.log(n); // 5 }代码中两个变量
n属于不同块,打印只会输出同一块下的该变量,如果用var声明变量n,则输出的结果为10。
块级作用域的出行,实际上使得广泛应用的立即执行匿名函数(IIFE)不再必要了!- 块级作用域与函数声明 你知道吗,这样声明函数在ES5中其实是非法的:
// 方式一 if (true) { function fn () {} } // 方式二 try { function f () {} } catch (e) {}其实把代码敲进编译器会发现浏览器其实也能运行,并不会报错,其实是因为浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域中去声明函数!
ES6中引入了块级作用域,明确允许在块级作用域中声明函数。ES6规定,在块级作用域中,函数声明语句的行为类似于
let,块级作用域外不能引用!const 命令
- 基本用法 const声明一个制度的常量,一旦声明,常量的值就不能改变。
const PI = 3.1415; console.log(PI); // 3.1415 PI = 3; // Assignment to constant variable.表明改变常量的值会报错!
const声明的常量不得改变值,这意味着,const一旦声明常量,就必须立即初始化,不能留到以后赋值,只声明不赋值也会报错。const的作用域和let命令相同:不可用重复声明,只在声明所在的块级作用域内有效,声明的常量也不会提升,同样存在暂时性死区,只能在声明后使用。- 本质
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单的数据类型(数值、字符串、布尔值)而言,值就保存在变量指向的内存地址中,因为等同于常量。但是对于复合型的数据(主要是对象和数组)而言,变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,这完全不能控制,因此,将一个对象声明为常量时必须非常小心。
const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; // 将 foo 指向另一个对象,报错 foo = {}; // TypeError:"foo" is read-only上面的foo储存的是一个地址,这个地址指向一个对象,不可变的是它的地址,即不可用把foo指向另一个地址,但是对象本身是可变的,所以依然可以为其添加新属性。
const a = []; a.push('Hello'); // 可以 a.length = 0; // 可以 a = ['Dave']; // 报错- ES6声明变量的6种方法
- ES5 只有两种声明变量的方法:
var命令和function命令。 - ES6 除了添加
let和const命令,还有import命令和class命令
顶层对象的属性
- 当用
顶层对象在浏览器环境中指的是
window对象,在 Node 环境中指的是global对象。在 ES5 中,顶层对象的属性与全局变量等价。
顶层对象的属性与全局变量相关,被认为是 JavaScript 语言中最大的设计败笔之一。这样的设计带来了几个很大的问题:
- 无法在编译时就提示变量未申明的错误,只有运行时才能知道
- 开发过程中很容易不知不觉就创建全局变量(比如打错字)
- 顶层对象的属性到处都可以访问到,非常不利于模块化编程
- window 对象是有实体含义的,指的是浏览器的窗口对象
综上,这个设计模式都是不太合适的。
在ES6中,var命令和function命令声明的全局变量依旧是顶层对象的属性,let、const、class命令声明的全局变量不属于顶层对象的属性。
var a = 1;
// 如果在Node的 REPL 环境,可以写成 global.a
console.log(window.a); // 1
let b = 1;
console.log(window.b); // undefined