let
let具有块级作用域,用法类似于var但是又不同于它。
{
var a = 10;
let b = 20;
}
a // 10
b // 无法访问到b,因为b只在代码块内有效
// for循环很适合
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
// var
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
同样的代码如果是使用let那么结果完全不一样
//
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
另外、for
循环有一个特殊之处,在设置变量的那部分是一个父作用域,在循环体内又是一个单独的作用域。
for(let i = 0 ;i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
不存在变量提升
var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
暂时性死区
var tmp = 123;
if(true) {
tmp = '111',
let tmp;
}
// 会报错的
// 因为在块级作用域中,使用了let,那么就不能再let之前使用这个变量了,而这里tmp = '111'。已经违反了规则。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
typeof x; // ReferenceError
let x;
// 这里为什么报错呢,其实还是暂时性死区的原因,变量x使用let命令声明,所以在声明之前,都属于x的“死区”
// 作为比较
typeof undeclared_variable // "undefined"
//
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错 ,是因为将y赋值给x,但是x并未申明呢
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2] 因为x已经是默认值了,y 赋值 x
let x = x; // 报错, 在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
var x = x; // 不报错
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。帮助开发者养成好多的习惯
不能次重复申明
let 声明的变量不允许再重新声明一次
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
// 在函数中
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
// var存在的一些弊端
// 【1】 内层变量可能会覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp); // 所以这里会输出undefined
if (false) {
var tmp = 'hello world'; // 这里发生了变量提升
}
}
f(); // undefined
// if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
// 【2】 用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5 这里就是全局的了
相信大家看了以上两个例子,应该知道为什么块级作用域好了吧。
var变量提升
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升
.
// 在学习块级作用域之前我们先来回顾一下var吧
console.log(a);
var a =1;
// 以上语句并不会报错,只是提示undefined。实际运行过程:
var a;
console.log(a);
a =1;
块级作用域
// 先来一个直观的例子
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
// 外层作用域无法读取内层作用域的变量。
块级作用域中允许声明函数
//
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// 上诉代码在ES5中会输出I am inside!, 因为if语句中所定义的函数发生了变量提升
// 在ES6中执行道理上是输出I am outside!,因为无法访问块级作用域中的函数,实际上则会报错 f is not a function
// 在ES6下实际运行的是
function f() { console.log('I am outside!'); }
(function () {
var f = undefined; // 在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。发生了变量提升
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
const命令
基本语法
//
const PI = 3.1415;
PI // 3.1415
PI = 3;
// 报错
// 也就是const 声明里变量就必须立即初始化,不然之后就无法赋值了
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
// const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
console.log(MAX); // 报错
const MAX = 5;
}
// const声明的常量,也与let一样不可重复声明。
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
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
顶层对象属性
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。 ES5
之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
ES6
一方面规定,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
未完待续
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。