所有内容基于阮一峰的ECMAScript 6 入门
1.let命令
只在命令所在的代码块内有效。
for循环有一个特别之处,循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
不存在变量提升
暂时性死区(temporal dead zone,简称TDZ)
ar tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
// 报错
let x = x;
// ReferenceError: x is not defined
在变量x的声明语句还没执行完成前,就去取x的值导致报错“x未定义”。
不允许重复声明
不能在函数内部重新声明参数
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
2.块级作用域
为什么需要块级作用域
1.内层变量可能会覆盖外层变量
2.用来计数的循环变量泄露为全局变量
ES6的块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
外层作用域无法读取内层作用域的变量。
内层作用域可以定义外层作用域的同名变量。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不必要了。
// IIFE 写法
(function (){
var tmp = ...;
}());
// 块级作用域写法
{
let tmp = ...;
...
}
块级作用域与函数声明
ES6中允许在块级作用域中声明函数。
ES6中块级作用域内声明的函数类似与let,对作用域外没有影响。
但是为了减轻因此产生的不兼容问题,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
- 允许在块级作用域内声明函数
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
- 同时,函数声明还会提升到所在的块级作用域的头部。
上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当做let处理。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域中声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外,还有一个需要注意的地方,ES6允许块级作用域内声明函数的规则,只在使用大括号的情况下成立。
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}
do 表达式
本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
现在有一个提案,使得块级作用域可以变为表达式,也即是说可以返回值:
let x = do {
let = f()
t*t+1;
};
上面代码中,变量x会得到整个块级作用域的返回值。(只是提案,现在可能还用不了)
3.const 命令
基本用法
const声明一个只读的常量,const一旦声明就必须立即初始化不能留到以后赋值。
const的作用域与let命令相同:只在声明所在块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
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']; // 报错
想将对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
ES6声明变量的六种方法
var命令function命令。
ES6除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。
4.顶层对象的属性
顶层对象,浏览器环境指的是window对象,在Node指的是global对象。
ES5之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。
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
上面代码中,全局变量a是var声明的所以它是顶层对象的属性;全局变量b是由let声明,所以它不是顶层对象的属性,返回undefined。
global对象
ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
- Node 里面,顶层对象是global,但其他环境都不支持。
同一段代码为了在各种环境都能取到顶层对象,现在一般是使用this,但是有局限性。
- 全局环境中,this会返回顶层对象,但是,node模块和ES6模块中返回的是当前模块。
- 函数里面的this,如果函数不是作为对象方法运行,而是单纯作为函数运行,this会指向顶层对象。但是在严格模式下,这是this会返回undefined。
- 不管是严格模式,还是普通模式,new Function(’return this‘)(),总是会返回全局对象。但是,如果浏览器用来CSP(Content Security Policy,内容安全政策),那么eval,new Function这些方法可能都无法使用。
下面是两种勉强可以在所有情况下取到顶层对象的方法。
//方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeor global === 'object')
? global
: this);
//方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
现在有一个提案,在语言标准的层面,引入global
作为顶层对象。也就是说,在所有环境下,global
都是存在的,都可以从它拿到顶层对象。
垫片库system.global
模拟了这个提案,可以在所有环境拿到global
。
// CommonJS的写法
require('system.global/shim')();
// ES6模块的写法
import shim from 'system.global/shim'; shim();
上面代码可以保证各种环境里面,global对象都是存在的。
// CommonJS的写法
var global = require('system.global')();
// ES6模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
上面代码将顶层对象放入变量global。