let 命令
ES6 新增了let命令,用来声明局部变量。它的用法类似于var,但是所声明的变量,只在let命令所在的 1代码块内有效,而且有2暂时性死区的约束。
关于使用var 如题
var a = 99; // 全局变量a
f(); // f是函数,虽然定义在调用的后面,但是函数声明会提升到作用域的顶部。
console.log(a); // a=>99, 此时是全局变量的a
function f() {
console.log(a); //当前的a变量是下面变量a声明提升后,默认值undefined
var a = 10;
console.log(a); // a => 10
}
// 输出结果:
undefined
10
99
第一题
{
var i = 9;
}
console.log(i); // 输出结果: 9
第二题
{
let i = 9; // i变量只在 花括号内有效!!!
}
console.log(i); // 输出结果: ReferenceError
第三题
{
let a = 10;
var b = 1;
}
console.log(a)
a // ReferenceError: a is not defined.
console.log(b)
b // 1
由上我们可以很清楚的得出,let是块级作用域,只在作用域内有效。
var let 遇到 for循环使用
for (var i = 0; i <10; i++) {
setTimeout(function() { // 同步注册回调函数到 异步的 宏任务队列。
console.log(i); //执行此代码时,同步代码for循环已经执行完成
}, 0);
}
// 输出结果十个 10
//当使用let时
//i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // i 是循环体内局部作用域,不受外界影响。
}, 0);
}
打印结果 0 到 9
let没有变量提升 与 暂时性死区
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
console.log(a); // 错误:ReferenceError ...
let a = 123;
// 这里就可以安全使用a
当使用var
console.log(b); //打印结果undefined,所以var会出现变量提升
var b = 123;
console.log(b); //打印结果123
暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError,在块级作用域内已经多let前生成了死区范围当let或const前使用变量则会报错;
let tmp;
}
// 不报错
var x = x;
console.log(x); undefined 变量提示后 相当于 把undefined 给了undedined
// 报错
let x = x;
// ReferenceError: x is not defined
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
let不允许在相同作用域内重复声明同一变量
function func() {
let a = 10;
var a = 1; 报错:'a' has already been declared
}
// 报错
function func() {
let a = 10;
let a = 1;
}
因此,不能在函数内部重新声明参数。
function func(arg) {
let arg;
}
func() // 报错因为函数初始化时候已经相当于let了一个 arg,
//如果 在次let arg;同一作用域重复声明就会报错;
function func(arg) {
{
let arg;
}
}
func() // 不报错
为什么需要块级作用域?
1 第一种场景,内层变量可能会覆盖外层变量。
1 用来计数的循环变量泄露为全局变量。
块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
function f1() {
let var = 5;
if (true) {
let var = 10;
}
console.log(n); // 10
}
块级作用域与函数声明 ``` // 情况一 if (true) { function f() {} }
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
```
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
ES6 支持在块级作用域中声明 函数 但尽量用 函数表达
// 块级作用域内部,优先使用函数表达式
{
// 块级作用域内部的函数声明语句,建议不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
}
const 命令
声明一个只读的常量。一旦声明,常量的值就不能改变。 const 和 let特性一样,
- 1 只在块级作用域内有效,
- 2 也不会发生变量提升
- 3 同样存在暂时性死区。
- 4 不可以重复声明
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
ES6 声明变量的六种方法
ES6 声明变量的六种方法 ES5 只有两种声明变量的方法:var命令和function命令。
ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。
let const class 等不是 顶层对象的属性
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
```