ECMAScript 6 教程参考: https://wangdoc.com/es6/intro.html
1. let和const命令
let 命令 : 用来声明变量, 只在let命令所在的代码块内有效
const命令 : 用来声明只读的常量, 必须立即初始化, 值不能改变, 只在const命令所在的代码块内有效
不存在变量提升 : 声明的变量一定要在声明后使用, 否则报错
不允许重复声明 : 相同作用域, 不允许重复声明同一个变量
声明变量的方法 : ES5: var, function; ES6: let, const, import, class
声明的全局变量, 不属于顶层对象的属性
2. 变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
用途 :
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取JSON数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
(6)遍历Map结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
(7)输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
3. 字符串的扩展
- 遍历器接口
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
- 模板字符串
增强版字符串, 用反引号(`)标识, 可以当做普通字符串, 多行字符串, 在字符串中嵌入变量
- 实例方法
includes() 是否找到参数字符串
startsWith() 参数字符串是否在原字符串的头部
endsWith() 参数字符串是否在原字符串尾部
repeat(n) 将原字符串重复n次
padStart() 不够指定长度, 则使用指定字符串在头部补全
padEnd() 不够指定长度, 则使用指定字符串在尾部补全
trimStart() 消除字符串头部空格
trimEnd() 消除字符串尾部空格
replaceAll() 一次替换所有匹配
4. 数值的扩展
- 常量
EPSILON 最小误差范围
MAX_SAFE_INTEGER 最大安全整数
MIN_SAFE_INTEGER 最小安全整数
- Number方法
isFinite() 是否为有限的
isNaN() 是否为NaN
isInteger() 是否为整数
isSafeInteger() 是否在最大和最小安全整数内
- Math方法
trunc() 去除一个数的小数部分
sign() 判断一个数是正数, 负数, 还是0
cbrt() 计算一个数的立方根
- BigInt
只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示
为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n
不能与普通数值进行混合运算
5. 函数的扩展
- 允许为函数的参数设置默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
- length属性 返回没有指定默认值的参数个数
- 作用域 一旦设置了参数默认值, 函数进行声明初始化时, 参数会形成单独的作用域
- rest参数 形式为(...变量名), 获取函数的多余参数
- 箭头函数
基本用法 :
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
注意 :
函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象
不可以使用new
命令
不可以使用arguments
对象
不可以使用yield
命令
不适用场合 :
定义对象的方法,且该方法内部包括this
需要动态this
的时候,也不应使用箭头函数
- 尾调用优化(只在严格模式下开启)
指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
以下三种情况, 不属于尾调用
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
// 情况三
function f(x){
g(x);
}
作用 : 大大节省内存
注意 : 目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持
- 尾递归
尾调用自身,就称为尾递归, 不会发生栈溢出
- catch命令的参数省略
6. 数组的扩展
- 扩展运算符
三个点... , 将一个数组转为用逗号分隔的参数序列
只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错
应用 :
(1)复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
//a2都是a1的克隆
(2)合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
(3)与解构赋值结合
用于数组赋值时, 只能放在参数的最后一位
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
(4)字符串
[...'hello']
// [ "h", "e", "l", "l", "o" ]
(5)实现了Iterator接口的对象
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组
- 方法
Array.from() 将类似数组的对象和可遍历的对象转为真正的数组
Array.of() 将一组值, 转换为数组
- 实例方法
copyWithin() 在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组
find() 返回第一个符合条件的数组成员, 参数为回调函数, 没有则返回undefined
findIndex() 返回第一个符合条件的数组位置, 没有则返回-1
fill() 使用给定值, 填充数组, 参数2填充起始位置, 参数3结束位置
keys() 遍历键名
values() 遍历键值
entries() 遍历键值对
includes() 是否包含指定值
flat() 将嵌套数组变为一维数组
flatMap() 对数组每个成员执行一个函数
7. 对象的扩展
- 简介表示法
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
- 属性名表达式
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
- 属性的可枚举性
属性中的描述对象getOwnPropertyDescriptor中enumerable属性, 如果为false, 表示某些操作会忽略当前属性
以下操作会忽略enumerable为false属性
for...in循环 只遍历对象自身的和继承的可枚举的属性
Object.keys() 返回对象自身的所有可枚举的属性的键名
JSON.stringify() 只串行化对象自身的可枚举的属性
Object.assign() 只拷贝对象自身的可枚举的属性
- 属性的遍历
for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
Object.keys 返回对象自身的所有可枚举的属性的键名(不含 Symbol 属性)
Object.getOwnPropertyNames 返回对象自身的所有属性的键名(不含 Symbol 属性)
Object.getOwnPropertySymbols 返回对象自身的所有 Symbol 属性的键名
Reflect.ownKeys 包含对象自身的所有键名
- super关键字
指向当前对象的原型对象
只能用在对象的方法之中
- 扩展运算符...
解构赋值
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
取出参数对象的所有可遍历属性
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
- 链判断运算符?.
判断对象是否存在, 存在则取出某个属性值
const firstName = message?.body?.user?.firstName || 'default';
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
注意 :
短路机制 不满足条件, 就不再往下执行
delete运算符 如果为undefined或null, 会直接返回undefined, 不会进行delete运算
括号的影响 只对括号内部有影响
报错场合 构造函数 / 右侧有模板字符串 / 左侧是super / 用于赋值运算符左侧
右侧不得为十进制数值
- Null判断运算符??
只有运算符左侧的值为null
或undefined
时,才会返回右侧的值
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
- 方法
Object.is() 比较两个值是否严格相等, 与===的不同, +0不等于-0; NaN等于自身
Object.assign() 对象的合并, 将源对象的所有可枚举属性, 复制到目标的对象
常见用途 : 添加属性 / 添加方法 / 克隆对象 / 合并多个对象 / 为属性指定默认值
注意 : 浅拷贝 / 同名属性的替换 / 数组的处理 / 取值函数求值后再赋值
Object.setPrototypeOf() 设置一个对象的原型对象
Object.keys() 返回对象自身的所有可遍历属性键名
Object.values() 返回对象自身的所有可遍历属性的键值
Object.entries() 返回对象自身的所有可遍历属性的键值对数组
Object.fromEntries() 将一个键值对数组转为对象
8 . Symbol
表示独一无二的值
- Symbol.prototype.description Symbol的描述, 创建时可添加
- 作为属性名的Symbol 不能使用点运算符
- 属性名的遍历
Object.getOwnPropertySymbols() 返回对象自身的所有 Symbol 属性的键名
Reflect.ownKeys() 返回所有类型的键名,包括常规键名和 Symbol 键名
- Symbol.for 搜索有没有以该参数作为名称的 Symbol 值。有就返回这个 Symbol 值,否则就新建, 并将其注册到全局
- Symbol.keyFor() 返回一个已登记的 Symbol 类型值的
key
9. Set和Map
- Set
类似数组, 成员的值都是唯一的
实例属性/方法 :
size 成员总数
add() 添加值
delete() 删除值
has() 是否为Set成员
clear() 清除所有成员
keys() / values() / entries() 返回键值/ 键值对遍历器
forEach() 使用回调函数遍历每个成员
- Map
键值对集合
实例属性/方法 :
size 成员总数
set() 设置键名对应键值
get() 读取键名对应的值, 找不到key, 返回undefined
has() 某键是否在当前Map对象中
delete() 删除某个键
clear() 清除所有成员
keys() / values() / entries() 返回键名/键值/ 键值对遍历器
forEach() 遍历Map的所有成员
互相转换 :
(1)Map转为数组
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
(2)数组转为Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
(3)Map转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
(4)对象转Map
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
10. Proxy
在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截
11. Reflect
目的 :
(1) 将Object
对象的一些明显属于语言内部的方法, 放到Reflect对象上
(2) 修改某些Object
方法的返回结果,让其变得更合理
(3) 让Object
操作都变成函数行为
(4) Reflect
对象的方法与Proxy
对象的方法一一对应
12. Generator 函数
ES6提供的一种异步编程解决方案
语法上可以理解为状态机, 封装了多个内部状态
特征 :
function 关键字与函数名之间有一个星号
函数体内部使用yield表达式, 定义不同的内部状态
分段执行, yield表达式是暂停执行标记, next方法恢复执行
使用 :
调用后, 并不执行, 指向内部状态的指针对象
下一步, 调用遍历器对象的next方法, 使得指针移向下一个状态
每次调用next方法, 内部指针就从函数头部或上一次停下来的地方开始执行
直到遇到下一个yield表达式或return语句为止
13. async 函数
Generator 函数的语法糖
将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
对 Generator 函数的改进 :
(1)内置执行器
与普通函数一样执行, 无需调用next方法, 或用co模块
(2)更好的语义
async
和await
,比起星号和yield
,语义更清楚了
async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果
(3)更广的适用性
(4)返回值是 Promise
await
命令就是内部then
命令的语法糖
await命令后是一个Promise 对象, 返回该对象的结果, 否则, 直接返回对应的值
注意 :
await命令放在try...catch中
不存在继发关系, 最好同时触发
await命令只能用在async函数中
async函数可以保留运行堆栈
14. Class
引入Class类概念, 作为对象的模板
类的数据类型就是函数, 类本身指向构造函数
类的所有方法都定义在类的prototype属性上面
类必须使用new调用
类必须有constructor()方法, 没有显式定义, 会默认添加空的constructor()方法
实例的属性除非显式的定义在其本身(this对象上), 否则都是定义在原型上
static修饰静态方法,属性
前缀#修饰私有方法, 属性
new.target属性, 返回new命令作用于的构造函数
通过extends关键字实现继承
Object.getPrototypeOf() 从子类上获取父类
super作为函数调用时, 代表父类的构造函数
super作为对象时, 在普通方法中, 指向父类的原型对象; 静态方法中, 指向父类
15. Module 语法
ES6 的模块自动采用严格模式
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取
可以出现在模块的任何位置, 处于模块顶层就可以, 处于块级作用域内, 会报错
可以用as关键字重命名
export 命令 :
用于规定模块的对外接口
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
import 命令 :
用于输入其他模块提供的功能
import { lastName as surname } from './profile.js';
模块的整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default 命令 :
为模块指定默认输出
一个模块只能有一个默认输出
其他模块加载该模块时, import命令可以指定任意名字, 不使用大括号
复合写法 :
相当于对外转发, 当前模块无法直接使用
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
import() :
动态加载指定的模块, 可以用在任何地方
适用场合 :
(1)按需加载 在需要的时候, 再加载
(2)条件加载 根据不同的情况, 加载不同的模块
(3)动态的模块路径 允许模块路径动态生成
浏览器加载 :
通过<script>
标签加载 JavaScript 脚本
打开defer
或async
属性,脚本就会异步加载
defer 等到页面在内存中正常渲染结束后执行
async 下载完执行, 无法保证加载顺序
16. 编程风格
- 块级作用域
let 取代 var 语义相近, let没有副作用
let和const之间, 优先使用const
- 字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号
- 解构赋值
使用数组成员对变量赋值时, 优先使用解构赋值
const arr = [1, 2, 3, 4];
const [first, second] = arr;
函数的参数如果是对象的成员,优先使用解构赋值
function getFullName({ firstName, lastName }) {}
函数返回多个值,优先使用对象的解构赋值
function processInput(input) {
return { left, right, top, bottom };
}
const { left, right } = processInput(input);
- 对象
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign
方法
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
对象的属性和方法,尽量采用简洁表达法
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
- 数组
使用扩展运算符(...)拷贝数组
const itemsCopy = [...items];
使用 Array.from 方法,将类似数组的对象转为数组
- 函数
立即执行函数可以写成箭头函数的形式
(() => {
console.log('Welcome to the Internet.');
})();
匿名函数当作参数的场合,尽量用箭头函数代替
// good
[1, 2, 3].map((x) => {
return x * x;
});
// best
[1, 2, 3].map(x => x * x);
不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替
function concatenateAll(...args) {
return args.join('');
}
使用默认值语法设置函数参数的默认值
function handleThings(opts = {}) {}
- Class
用 Class,取代需要 prototype 的操作
使用extends
实现继承
- 模块
使用import
取代require
如果模块只有一个输出值,就使用export default
如果模块有多个输出值,就不使用export default
export default
与普通的export
不要同时使用
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)
如果模块默认输出一个函数,函数名的首字母应该小写
如果模块默认输出一个对象,对象名的首字母应该大写
- ESLint
语法规则和代码风格的检查工具