let基本用法
ES6新增了let属性,于声明变量。用法类似var,但是和var有很多不同。
- let声明的变量只在其所在代码块内有效
{
let a = 10;
var b = 1;
}
a // a is not defined
b // 1
- 不存在变量提升
// var 的情况
console.log(foo) // undefined
var foo = 2
// let 的情况
congsole.log(bar) // 报错
let bar = 2;
- 暂时性死区
只要块级作用域内存在let命令,它所声明的变量就‘绑定’(binding)这个区域,不再受外部影响
var tmp = 123
{
if (true) {
tmp = 'abc'; // 报错
let tmp;
}
}
ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。只要在声明之前就使用这些变量,就会报错
在代码块内,使用let命令声明变量之前,改变量都是不可用的。这在语法上称为“暂时性死区”。
- 不允许重复声明
let不允许在相同作用域内重复声明同一个变量, 不管是在前面声明还是在后面声明,都会报错
// 报错
function() {
let a = 10;
var a = 1;
}
// 报错
function() {
let a = 10;
let a = 1;
}
// 报错
function(arg) {
let arg;
}
- var 声明的全局变量会自动添加到window对象的属性
var a = 1;
console.log(a) // 1
console.log(window.a) // 1
let b = 1;
console.log(b) // 1
console.log(window.b) // undefined
块级作用域
为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多不合理的场景
- 内部变量覆盖外部变量
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
console.log(tmp)
var tmp = 'hellow world'
}
}
- 用来计数的循环变量泄漏为全局变量
for (var i = 0; i < 5; i++) {
}
console.log(i) // 5
块级作用域特点
- ES6允许块级作用域任意嵌套
{{{{let insane = 'hello World' }}}};
- 外层作用域不能访问内层作用域的变量
{{{{
{let insane = 'abc';}
console.log(insane); // 报错
}}}}
- 内层作用域可以定义外层作用域的同名变量
{{{{
let insane = 'abc';
{let insane = 'qaz'}
}}}}
块级作用域出现后,匿名函数(IIFE)也就不需要了
// 匿名函数写法
(function() {
var tmp = ...;
...
}())
// 块级作用域写法
{
let tmp = ...;
}
块级作用域实例
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function() {
consle.log(i);
}
}
a[6] // 值是什么
for(let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// 输出什么 为什么 let改成var 输出什么为什么
块级作用域中声明函数
ES6块级作用域声明函数规则
- 允许在块级作用域内声明函数
- 函数声明类似var ,即会提升到全局作用域或函数作用域的头部
- 函数声明还会提升到所在的块级作用域的头部
function f() {
console.log('I am outside')
}
(function() {
if (false) {
function f() { // 函数声明式
console.log('I am inside')
}
};
f();
}())
// 在ES6语法环境下会报错 代码执行为
function f() {
console.log('I am outside')
}
(function() {
var f = undefined;
if (false) {
function f() { // 函数声明式
console.log('I am inside')
}
};
f();
}())
// 改为函数表达式的语句,就不会报错
注意
ES6块级作用域允许声明函数的规则只在使用大括号的情况下成立,如果没有大括号会报错
do表达式
const命令
- const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
- const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
- const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
- const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
- 不可重复声明
- 声明不会自动添加到window对象的属性
const本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
声明变量的六种方法
var、function、let、const、import、class
顶层对象属性
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
顶层对象的属性与全局变量挂钩,带来的影响:
- 没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)
- 程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。
- window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。