ECMAScript 6(简称 ES6)是 JavaScript 的一个版本,也被称为 ECMAScript 2015。它是 JavaScript 的一个标准规范,提供了一系列新的语法和 API,用于帮助开发者更加轻松、高效地编写 JavaScript 代码。
一. let 和 const 命令
let 命令声明一个变量,可以修改。
const 命令声明一个常量,声明之后不允许修改,因此 const 一旦声明变量,就必须立即初始化,不能留到以后赋值。
const 实际上保证的,并不是变量的值不变,而是变量指向的内存地址所存储的数据不变。对基础数据类型,值就存储在变量指向的内存地址,因此相当于常量。对于引用数据类型,变量指向的内存地址,存储的只是一个指向实际数据的指针。const 只能保证这个指针是固定的,至于它指向的数据结构就不能控制了。所以 const 声明的数组和对象,可以增减元素和属性。
要点(和 var 的不同):
-
不存在变量提升,变量一定要在声明之后才能使用。
-
块级作用域:let 和 const 声明的变量只在它所在的代码块有效。因此,for 循环的计数器很适合使用 let 命令。
const f = () => { let x = 1; // 只在 f 函数内有效 if (true) { let x = 2; // 只在 if 语句块内有效 const y = 'hello world !'; console.log(x); // 输出 2 } console.log(x); // 输出 1 console.log(y); // ReferenceError: y is not defined } f();
在同一作用域下,不能重复声明同一个变量。
暂时性死区:是指在代码块中,在变量声明语句之前使用该变量会导致 ReferenceError 错误的现象。
在代码块中,如果使用 let 或 const 声明一个变量,那么这个变量会从开始执行该代码块的时间开始进入暂时性死区,直到声明语句完成为止。在这个时期内,如果使用该变量,将会触发 ReferenceError 错误。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
二. 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于 undefined。
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let [x = 1, y = x] = [2]; // x = 2, y = 2
let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; // bar = 'bbb', foo = 'aaa'
实际上,对象的解构赋值是let {bar: bar, foo: foo} = { foo: 'aaa', bar: 'bbb'};
的简写形式。对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
// bar是匹配的模式,foo才是变量,真正被赋值的是变量foo
let { bar: foo } = { foo: 'aaa', bar: 'bbb' }; // foo = 'bbb'
三.扩展和新增
1. 字符串
- 模板字符串:使用反引号(`)标记,变量名写在
${}
之中。 - 字符串包含
let s = 'hello, world!';
s.includes('ll'); //true
s.startsWith('hello'); //true
s.endsWith('!'); //true
// 支持第二个参数,表示开始搜索的位置
-
replaceAll()
- 之前,replace() 只能替换第一个匹配,要想全部替换,就需要用到正则表达式的g修饰符,这种情况就可以使用 replaceAll()
let s = 'abbc'; s.replace('b', '_'); // a_bc s.replace(/b/g, '_'); // a__c s.replaceAll('b', '_'); // a__c
- replaceAll() 的第一个参数可以是字符串,也可以是正则表达式(必须带 g 修饰符,否则会报错)。
- replaceAll() 的第二个参数表示替换的文本,也可以是一些特殊字符串
// $&:匹配的字符串,即`b`本身 'abbc'.replaceAll('b', '$&'); // abbc //$`:匹配结果前面的文本,对第一个b,指的是a,对第二个b,指的是ab 'abbc'.replaceAll('b', '$`'); // aaabc // $':品牌结果后面的文本 'abbc'.replaceAll('b', `b'`); // abccc // $$:表示美元符号$ ‘abbc’,replaceAll('b', '$$'); // a$$c // $1表示正则表达式的第一组匹配'ab',$2指正则匹配第二组匹配'bc' 'abbc'.replaceAll(/(ab)(bc)/g, '$2$1'); // bcab
2. 数值
- 数值分隔符:允许使用下划线 _ 作为分割符
123_40 === 12_340 === 1234 * 10;
,但是,字符串转数值的函数不能使用数值分隔符parseInt('123_456') = 123;
。
3. 函数
-
箭头函数:
=>
-
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
- 参数默认值可以与解构赋值结合使用
const fetch = (url, {body = '', method = 'GET', headers = {}}) => { console.log(method); } fetch('http://example.com', {}); // GET fetch('http://example.com'); // 报错
以上代码中,fetch() 的第二个参数必须是对象,且不能省略,这样才能获取默认参数值,否则会报错。如果结合函数参数的默认值,就可以省略第二个参数。
const fetch = (url, {body = '', method = 'GET', headers = {}} = {}) => { console.log(method); } fetch('http://example.com'); // GET
-
rest 参数:使用 (…变量名)的形式来获取函数的剩余参数
const sortNumbers = ( ... numbers) => numbers.sort();
4. 数组
- 扩展运算符:
...
,可以用于合并数组和函数调用。
const add = (x, y) => x + y;
add( ...[2, 3]);
- includes():
[1, 2, 3].includes(2);
- arr.entries()、arr.keys()、arr.values():遍历数组
- ES6 明确将数组中的空位转为 undefined
5. 对象
- 属性简洁表示:当变量名和和属性名一样时可以省略。
const foo = 'abc';
const baz = { foo }; // { foo: 'abc' }
// 相当于const baz = { foo: foo };
- 属性名表达式:当对象的属性名为参数时,使用
[ ]
表达式
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word']; // hello
a[lastWord]; // world
- 解构赋值:浅拷贝
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
- 扩展运算符:…
let n = { ...{a: 2, b: 3} };
- Object.assign():合并元对象到目标对象,浅拷贝
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
- Object.keys(obj),Object.values(obj),Object.entries(obj):遍历对象
6. 运算符
- 指数运算符 **
2 ** 2 = 4;
- 链判断运算符 ?.
const firstName = message?.body?.user?.firstName || 'default';
- Null 判断运算符 ??
const headerText = response.settings.headerText ?? 'Hello, world!';
,只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值。和 || 类似。但是 || 使用时,左侧为 0、 false、 空字符串、NaN、null、undefined时都返回右侧的值,判断更为宽松。
7. Symbol
- 基础数据类型,Symbol 值通过 Symbol() 函数生成,是一种类似字符串的数据类型。
const s = Symbol('foo');
s; // Symbol(foo)
typeof s; // 'symbol'
s.toString(); // 'Symbol(foo)'
s.description; // 'foo'
- 表示独一无二的值,即使使用相同的参数调用 Symbol 函数,也认为是不同的值
const s1 = Symbol('foo');
const s2 = Symbol('foo');
console.log(s1 === s2); // false
- 由于每一个 Symbol 值都是不相等的,所以 Symbol 常被用做属性名,来防止某一个键被改写或覆盖。注意,Symbol 值作为对象属性名时,不能用点运算符,而是需要放在方括号内。
const name = Symbol('John');
let users = {
Alice: 12,
[name]: 13,
}
users; // {Alice: 12, Symbol(John): 13}
users[name] = 15; // {Alice: 12, Symbol(John): 15}
8. Set
- 类似数组,但是成员的值唯一,没有重复
let s = new Set();
- 可以使用扩展运算符和 Set 去除数组中的重复成员
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]
- 增删查
let s = new Set();
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(2) // true
s.has(3) // false
s.delete(2) // true
s.has(2) // false
9. Map
- 类似对象,本质上是键值对的集合。但是对象只能使用字符串当作键,而 Map 中,各种数据类型(包括对象 )都可以当作键。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
- 只有对同一个对象的引用,Map 结构才将其视为同一个键,主要针对的是引用数据类型作为键的情况。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
// 表面上是相同的键,但实际上是两个不同的数组实例,内存地址不同
四. 异步编程
1. Promise 对象
-
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
-
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。并且,Promise 新建后就会立即执行。
const fetchData = new Promise((resolve, reject) => { console.log('Promise'); // Promise新建后立即执行,输出'Promise' setTimeout(() => { // 异步操作 const data = 'Some data'; if (data) { resolve(data); // 异步操作成功时调用,使Promise的状态由pending转为resolved,并将异步操作的结果作为参数传递出去 } else { reject('Error occurred'); // 异步操作失败时调用,使Promise的状态由pending转为rejected,并将错误信息作为参数传递出去 } }, 2000); });
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别为 resolve 和 reject,这两个参数也都是函数,由 JavaScript 引擎提供,不需要自己写,分别指异步操作成功和失败后的回调函数。
-
Promise 实例生成之后,可以用 then 方法指定 resolve 状态的回调函数,用 catch 方法指定 reject 状态的回调函数。
fetchData.then(data => { // Promise状态变为resolved之后触发then方法 console.log('Data:', data); // 输出 Data: Some data }).catch(err => { // Promise状态变为rejected时触发catch方法 console.log('Error:', err); })
一般来说,调用 resolve 或 reject 以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。
-
Promise 实例具有 then 方法,then 方法会返回一个新的 Promise 实例。因此,可以采用链式写法,then 方法后面再调用下一个 then 方法。
fetchData.then(data => { fetchData(data.result) }).then(comments => { console.log("resolved: ", comments), }).catch(err => { console.log("rejected: ", err) });
catch 方法放在最后,会捕捉前面所有异步操作的错误。
2. Genetator 函数(用不到)
Generator 函数有两个特征。一是,function关键字与函数名之间有一个星号(*);二是,函数体内部使用 yield 表达式,定义不同的内部状态。
const fs = require('fs');
const readFile = fileName => {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (error, data) => {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
var co = require('co'); // co模块用于 Generator 函数的自动执行,返回一个Promise
co(gen).then(() => {
console.log('Generator 函数执行完成');
});
3. async 函数
async 函数其实是 Generator 函数的语法糖,将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await 。而且 async 函数自带执行器,不需要 co 模块。
const asyncReadFile = async () => {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
};
asyncReadFile();
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
五. Module 语法
1. export
const name = 'John';
const age = 18;
export {name, age};
// 还可以写成
export const name = 'John';
export const age = 18;
在一个文件或模块中,export 可以有多个。
2. export default
export default const user = {
name: 'John',
age: 18
}
export default 指默认输出,一个文件或模块中只能有一个。
3. import
import {name, age as year} from './index.js'; // export和import都可以使用as重命名
import user from './index.js';
通过 export 导出,导入时需要 {}
,通过 export default 导出则不需要。