共计 12658 个字符,预计需要花费 32 分钟才能阅读完成。
let、const
块作用域
ES6 引入块作用域
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
let
let 声明的变量只在它所在的代码块有效。
let 不允许在相同作用域内重复声明同一个变量
let 声明不存在变量提升,会产生暂时性死区
const
声明并赋值一个只读的变量
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,因此值类型变量不可变,而引用类型变量可以更改实体数据内容。
其他特性和 let 一致
顶层对象
let、const、class 命令声明的全局变量,不属于顶层对象的属性
解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构实质是模式匹配
等号右边的对象需要具备 Iterator 接口
// 基本模式匹配
let [a, b, , c] = [1, 2, 3]; // a = 1; b = 2; c = undefined;
// 复杂模式匹配
[a, b, c] = [1, [2], 3]; // a = 1; b = [2]; c = 3;
[a, [b], c] = [1, [2], 3]; // a = 1; b = 2; c = 3;
[a, [b], c] = [1, [2, 3], 4]; // a = 1; b = 2; c = 4;
[a, [b], c] = [1, 2, 3]; // Uncaught TypeError: undefined is not a function
// 与 rest 参数结合使用
let [head, …tail] = [1, 2, 3, 4]; // head = 1; tail = [2, 3, 4]
// 设置默认值,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于 undefined,默认值才会生效。
let [foo = 1] = []; // foo = 1;
let [foo = 1] = [null]; // foo = null;
// 默认值是表达式的情况,表达式惰性求值
function f() {
console.log(‘aaa’);
}
let [x = f()] = [1];
// 默认值为变量的情况,变量必须已声明
let [x = 1, y = x] = [];
let [x = y, y = 1] = []; // ReferenceError: y is not defined
// 解构字符串
const [a, b, c, d, e] = ‘hello’; // a = ‘h’; b = ‘e’; c = ‘l’; d = ‘l’; e = ‘o’
let {foo, bar} = {foo: “aaa”, bar: “bbb”}; // foo = ‘aaa’; bar = ‘bbb’;
// 匹配的模式: 变量
let {foo: f, bar: b} = {foo: “aaa”, bar: “bbb”}; // foo = undefined; f = “aaa”; b = “bbb”
// 默认值
let {baz = {} } = {foo: “aaa”, bar: “bbb”}; // baz = {};
let {x: y = 3} = {x: 5}; // y = 5;
// 混合解构
let {items: [{name}] } = {id: ‘1’, items: [{name: ‘joke’}, {name: ‘tony’}] }; // name = ‘joke’
let [{name, habbits: [habbit]}] = [{id: ‘1’, name: ‘kelly’, habbits: [‘piano’]}, {id: ‘2’, name: ‘tony’}]; // name = ‘kelly’; habbit = ‘piano’
// 嵌套赋值
let obj = {};
let arr = [];
({foo: obj.prop, bar: arr[0] } = {foo: 123, bar: true});
obj // {prop:123}
arr // [true]
// 在直接使用赋值表达式时,需要使用圆括号包住赋值表达式,大括号写在行首会被解释为代码块
let x;
{x} = {x: 1}; // SyntaxError: syntax error
({x} = {x: 1}); // x = 1;
const arr = [1,2,3,4]
let {0 : first, [arr.length – 1] : last, length} = arr; // first = 1; last = 4; length = 4
// 函数参数的解构赋值
function move({x, y} = {x: 0, y: 0}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
字符串
模板字符串 ${name}
扩展方法
startsWith
let s = ‘Hello world!’;
s.startsWith(‘world’) // true
s.startsWith(‘world’, 6) // true, 第二个参数针对第 n 个位置直到结束
endsWith
let s = ‘Hello world!’;
s.endsWith(‘world’) // true
s.endsWith(‘Hello’, 5) // true, 第二参数针对前 n 个字符
includes
let s = ‘Hello world!’;
s.includes(‘world’) // true
s.includes(‘Hello’, 6) // false, 第二参数针对第 n 个位置直到结束
repeat
‘hello’.repeat(2) // hellohello,repeat 重复字符串操作
padStart, padEnd
// 补全字符串方法,接受两个参数,第一个参数指定字符串长度,第二个参数指定补全内容
‘x’.padStart(5, ‘ab’) // ababx
‘x’.padEnd(3, ‘a’) // xaa
数值
在 Number 原型上新增了 isFinite(), isNaN(), parseInt(), parseFloat(), isInteger() 方法,用来代替全局方法
扩展方法
Math.trunc – 去除小数部分
Math.sign – 判断一个数到底是正数、负数、还是零
Math.cbrt – 计算立方根
函数
默认值
参数全等于 undefined 时使用默认值
默认值为函数时惰性执行
默认值为表达式时惰性执行,并不会缓存下计算值
rest 参数 – 获取函数的多余参数,代替 arguments 使用
name 属性
箭头函数
箭头函数本身不存在上下文
this 在定义时确定
不能用作构造函数
没有 arguments,用 rest 代替
不能使用 yield 命令
数组
解构
扩展运算符
扩展方法
Array.from – 将类数组对象或可遍历对象转换为数组
Array.of – 将一组值转换为数组
copyWithin
find
findIndex
fill
entries
keys
values
includes – 代替 indexOf,更加直观,避免 NaN 判断的问题
flat
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]],单层拉平
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5],两层拉平
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]
flatMap – flat 和 map 的结合
对象
属性简写
属性名表达式
let userName = ‘joe’;
let firstName = ‘curly’;
let lastName = ‘water’;
[userName]: 23,
[firstName + lastName]: 25
};
– 方法的 name 属性
– 属性遍历
– for…in – 遍历原型链,遍历自身和继承的可枚举属性(不含 Symbol 属性)
– Object.keys – 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键名
– Object.getOwnPropertyNames – 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性)的键名
– Object.getOwnPropertySymbols – 返回一个数组,包含对象自身的所有 Symbol 属性的键名
– Reflect.ownKeys – 返回一个数组,包含对象自身的所有键名
– super 关键字 – 指向当前对象的原型对象,只能用在简写的对象方法中
– 解构
– 扩展运算符
– 扩展方法
– Object.is() – 严格相等,解决全等运算符 `NaN` 不等于自身,以及 `+0` 等于 `-0` 等问题
– Object.assign() – 用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象,浅拷贝
– Object.getOwnPropertyDescriptors() – 返回对象属性的描述对象
– Object.setPrototypeOf() – 设置原型对象
– Object.getPrototypeOf() – 读取原型对象
– Object.keys() – 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键名
– Object.values() – 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键值
– Object.entries() – 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
– Object.fromEntries() – Object.entries 的逆操作
# Symbol
ES6 新加入的类型值,表示独一无二的值
let s = Symbol();
– `Symbol` 函数的参数只是表示对当前 Symbol 值的描述
– 不能与其他类型的值进行运算
– 作为属性名,不能使用点运算符
let mySymbol = Symbol();
// 第一种写法 let a = {}; a[mySymbol] = ‘Hello!’;
// 第二种写法 let a = {
[mySymbol]: ‘Hello!’
};
// 第三种写法 let a = {}; Object.defineProperty(a, mySymbol, { value: ‘Hello!’});
// 以上写法都得到同样结果 a[mySymbol] // “Hello!”
– 可以用于定义一组常量,保证这组常量的值都是不相等的
const log = {};
log.levels = {
DEBUG: Symbol(‘debug’),
INFO: Symbol(‘info’),
WARN: Symbol(‘warn’)
}; console.log(log.levels.DEBUG, ‘debug message’); console.log(log.levels.INFO, ‘info message’);
# Set 和 Map
**Set**
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
`Set` 本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set([1, 2, 3]);s.add(4);s.delete(4);s.has(4);s.clear();s.size;s.keys();s.values();s.entries();s.forEach((value, key) => console.log(key + “:” + value));
**WeakSet**
WeakSet 结构与 Set 类似,也是不重复的值的集合。
WeakSet 的成员只能是对象。
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
**Map**
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const map = new Map();map.set(‘foo’, true);map.set(‘bar’, false);map.set([1], false);
map.size // 2map.get(‘foo’);map.has(‘foo’);map.delete(‘foo’);map.clear();
for (let key of map.keys()) {console.log(key);}
for (let value of map.values()) {console.log(value);}
for (let item of map.entries()) {console.log(item[0], item[1]);}
// 或者 for (let [key, value] of map.entries()) {console.log(key, value);}
// 等同于使用 map.entries()for (let [key, value] of map) {console.log(key, value);}
**WeakMap**
`WeakMap` 结构与 `Map` 结构类似,也是用于生成键值对的集合。
`WeakMap` 只接受对象作为键名(`null` 除外)
`WeakMap` 的键名所指向的对象,不计入垃圾回收机制。
# Proxy
Proxy 是一个构造函数,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
# Reflect
保存 Object 对象的一些属于语言内部的方法,比如说 `defineProperty`/`get`/`apply`
好处在于:让 `Object` 操作都变成函数行为,在 Proxy 中可以获取对象的默认行为
> – Reflect.apply(target, thisArg, args)
> – Reflect.construct(target, args)
> – Reflect.get(target, name, receiver)
> – Reflect.set(target, name, value, receiver)
> – Reflect.defineProperty(target, name, desc)
> – Reflect.deleteProperty(target, name)
> – Reflect.has(target, name)
> – Reflect.ownKeys(target)
> – Reflect.isExtensible(target)
> – Reflect.preventExtensions(target)
> – Reflect.getOwnPropertyDescriptor(target, name)
> – Reflect.getPrototypeOf(target)
> – Reflect.setPrototypeOf(target, prototype)
# Promise
所谓 `Promise`,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
`Promise` 对象是一个构造函数,用来生成 `Promise` 实例。
状态:`pending`(进行中)、`fulfilled`(已成功)和 `rejected`(已失败)
– `Promise` 新建后就会立即执行。
– 调用 `resolve` 或 `reject` 并不会终结 `Promise` 的参数函数的执行。
– `then` 方法返回的是一个新的 `Promise` 实例
– `catch` 方法会捕获状态确定前的所有错误,包括在 then 回调函数中的错误
– `Promise` 会吃掉错误,不会对后续代码执行产生影响
– `finally` 方法,不管 `Promise` 对象最后状态如何,都会执行的操作
– `Promise.all` 方法用于将多个 `Promise` 实例,包装成一个新的 `Promise` 实例。多个 `Promise` 实例都改变状态,才会调用新 `Promise` 实例的回调
– 如果作为参数的 `Promise` 实例,自己定义了 `catch` 方法,那么它一旦被 `rejected`,并不会触发 `Promise.all()` 的 `catch` 方法。
– `Promise.race` 方法用于将多个 `Promise` 实例,包装成一个新的 `Promise` 实例。第一个 `Promise` 实例都改变状态,进入新 `Promise` 实例的回调
– `Promise.resolve(reason)` 方法也会返回一个新的 `Promise` 实例,该实例的状态为 `fulfilled`。
– `Promise.reject(reason)` 方法也会返回一个新的 `Promise` 实例,该实例的状态为 `rejected`。
const promise = new Promise(function(resolve, reject) {// … some code
if (/ 异步操作成功 /){
resolve(value);
} else {
reject(error);
}});
promise .then(function(value) {console.log(value) }, function (err) {console.log(err);}) .catch(function(error) {console.log(error) });
# Iterator
Iterator(遍历器) 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
var it = makeIterator([‘a’, ‘b’]);
it.next() // { value: “a”, done: false}it.next() // { value: “b”, done: false}it.next() // { value: undefined, done: true}
function makeIterator(array) {var nextIndex = 0; return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};}
ES6 规定,默认的 Iterator 接口部署在数据结构的 `Symbol.iterator` 属性,或者说,一个数据结构只要具有 `Symbol.iterator` 属性,就可以认为是“可遍历的”(iterable)
原生具备 Iterator 接口的数据结构
– Array
– Map
– Set
– String
– TypedArray
– 函数的 arguments 对象
– NodeList 对象
`for…of` 循环调用遍历器接口,作为遍历所有数据结构的统一的方法。
`for…in` 循环主要是为遍历对象而设计的。
`forEach` 无法跳出循环
`for…of` 可跳出循环,严格按照顺序遍历
# Generator
Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ‘ending’;}
var hw = helloWorldGenerator();
hw.next()// { value: ‘hello’, done: false}
hw.next()// { value: ‘world’, done: false}
hw.next()// { value: ‘ending’, done: true}
hw.next()// { value: undefined, done: true}
– `yield` 表达式后面的表达式,只有当调用 `next` 方法、内部指针指向该语句时才会执行
– 只有调用 `next` 方法时,Genarator 函数才会执行
– `yield` 只能被 Genarator 函数包裹,普通函数不行
– `yield*` 表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数
# async
Generator 函数的语法糖
`async` 表示函数里有异步操作,`await` 表示紧跟在后面的表达式需要等待结果。
`await` 命令后面可以是 `Promise` 对象
`async` 函数的返回值是 Promise 对象
# Class
class MyClass {constructor() {
this.name = name;
} get prop() {
return ‘getter’;
} set prop(value) {
console.log(‘setter: ‘+value);
}}
– 类和模块的内部,默认就是严格模式,所以不需要使用 `use strict` 指定运行模式
– 类不存在变量提升
new Foo(); // Error class Foo {
print () { console.log(‘Hello’); }
}
– name 属性
– 可以使用 Generator 实现 Symbol.iterator 遍历器
– 类的方法内部如果含有 `this`,它默认指向类的实例。但是如果单独提取方法出来用,容易报错
class Logger {
printName(name = ‘there’) {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger(); const { printName} = logger; printName(); // TypeError: Cannot read property ‘print’ of undefined
解决方法
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// …
}
class Logger {
prinitName () {
this.print(`Hello ${name}`)
}
// …
}
– 如果在一个方法前,加上 `static` 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return ‘hello’;
}
}
Foo.classMethod() // ‘hello’
var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
如果静态方法包含 `this` 关键字,这个 `this` 指的是类,而不是实例。
父类的静态方法,可以被子类继承。
– 实例属性除了在 `constructor()` 方法里面定义,也可以直接写在类的最顶层。
class IncreasingCounter {
_count = 0;
get value() {
console.log(‘Getting the current value!’);
return this._count;
}
increment() {
this._count++;
}
}
– 静态属性
class Foo {
static prop = 1;
}
– 私有属性
– 使用_约定
– 结合 Symbol 使用避免被覆盖
– 使用 #代表私有属性
class IncreasingCounter {
#count = 0;
get value() {
console.log(‘Getting the current value!’);
return this.#count;
}
increment() {
this.#count++;
}
}
“`
new.target – 该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。
类继承 – 子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
Decorator
修饰器是一个对类进行处理的函数
@testable
class MyTestableClass {
// …
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
修饰对象属性
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
function readonly(target, name, descriptor){
// target 要修改的对象
// name 要修饰的属性名
// descriptor 要修饰的属性的描述对象
descriptor.writable = false;
return descriptor;
}
修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
Module
CommonJS 规范
始于 nodejs
特点:同步加载模块,运行时加载
接口:
// moduleA.js
module.exports = function(value){
return value * 2;
}
// moduleB.js
var multiplyBy2 = require(‘./moduleA’);
var result = multiplyBy2(4);
原理:通过 require 读取并执行一个 JS 文件返回该模块的 exports 对象的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值。CommonJS 的一个模块,就是一个脚本文件。require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象缓存。
AMD 规范
为浏览器设计,常用 requirejs 实现
特点:异步加载模块,运行时加载
接口:
define(‘myModule’, [‘jquery’], function($) {
// $ 是 jquery 模块的输出
$(‘body’).text(‘hello world’);
});
require([‘myModule’], function(myModule) {});
// 未使用模块名,类 CommonJS 使用
define(function(require, exports, module) {})
ES6 模块
浏览器与服务器通用
特点:ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
原理:通过 export 命令显式指定输出的代码(并非输出对象),再通过 import 动态引用。
接口:
export 命令用于规定模块的对外接口
import 命令用于输入接口
export default 规定模块默认接口,本质是输出一个叫 default 的变量,所以在模块中只能唯一存在,并且不可更改,不能跟声明语句
export 其他模块,export 和 import 的复合写法,实际上并没有导入当前模块,只是转发
import 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,import 和 export 命令只能在模块的顶层,不能在代码块之中
import() 函数完成动态加载,返回一个 Promise 对象
// Module1
var m = 1;
export {
m
n as N
};
// 输出一个 default 变量,将变量 m 的值赋给变量 default
export default m;
// Module2
import {m, N} from “Module1”; // 导入 m 和 N 接口
import {m as M} from “Module1”; // 导入 m 接口,重命名为 M
import module1 from “Module1”; // 导入默认接口
import * as module1 from “Module1”; // 导入所有接口
export * from “Module1”; // 再输出,export * 命令会忽略 Module1 模块的 default 方法。
加载规则
浏览器对于带有 type=”module” 的 <script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了 <script> 标签的 defer 属性。
外部 ES6 模块脚本特性:
代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
模块脚本自动采用严格模式,不管有没有声明 use strict。
模块之中,可以使用 import 命令加载其他模块(.js 后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用 export 命令输出对外接口。
模块之中,顶层的 this 关键字返回 undefined,而不是指向 window。也就是说,在模块顶层使用 this 关键字,是无意义的。
同一个模块如果加载多次,将只执行一次。
解决循环加载问题:使用函数声明做提升
编程风格
在 let 和 const 之间,建议优先使用 const
常量表示,便于理解
有利于未来多线程编写
JavaScript 编译器会对 const 进行优化,所以多使用 const,有利于提高程序的运行效率
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
const a = ‘apple’;
let b = “banana”;
b = “batman”;
优先使用解构赋值
如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
对象
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用 Object.assign 方法。
使用属性表达式定义动态属性名
对象的属性和方法尽量使用简写
数组
使用扩展运算符拷贝数组
使用 Array.from 将类数组对象转为数组
函数
所有配置项都应该集中在一个对象,放在最后一个参数
rest 运算符代替 arguments 变量
使用默认值语法设置函数参数的默认值。
Map
注意区分 Object 和 Map,如果只是需要 key: value 的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。
模块
如果模块只有一个输出值,就使用 export default,如果模块有多个输出值,就不使用 export default,export default 与普通的 export 不要同时使用。
如果模块默认输出一个函数,函数名的首字母应该小写。
function makeStyleGuide() {
}
export default makeStyleGuide;
如果模块默认输出一个对象,对象名的首字母应该大写。
const StyleGuide = {
es6: {
}
};
export default StyleGuide;