let|const

块级作用域:let,应用场景:for 循环,获取索引值

声明常量:const,应用场景:不可更改的值、引用第三方库时声明的变量,避免重名导致bug

  • 变量不提升
  • 暂时性死区(TDZ)

顶层对象的属性

window对象即顶层对象

var命令和function命令声明的全局变量,依旧是顶层对象的属性;
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

获取顶层对象的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof 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');
};

统一顶层对象:globalThis

class|extends|super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
constructor(){
this.type = 'animal'
/* 构造方法,内部定义的方法、属性属于实力自己 */
}
says(say){
/* 所有实例共享 */
console.log(this.type + ' says ' + say)
}
}

class Cat extends Animal { /* extends 实现继承 */
constructor(){
super() /* 指代父类实例(父类的this对象,子类`必须`在 `constructor` 方法中`调用 super 方法`) */
this.type = 'cat'
}
}

字符串模板

mustache:模板工具库

1
2
3
4
5
`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`

标签模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;

function passthru(literals) {
let result = '';
let i = 0;

while (i < literals.length) {
result += literals[i++];
if (i < arguments.length) {
result += arguments[i];
}
}

return result;
}

literals: ['The total is', '(', 'with tax)']
arguments: [literals, total, total*1.05]

主要作用:过滤 HTML 字符串,防止用户输入恶意内容

字符串扩展

padStart()、padEnd()

1
2
3
'x'.padStart(5, '0') // '0000x',省略第二个参数,默认使用空格补全长度

'na'.repeat('3') // 'nanana'

trimLeft() => trimStart()
trimRight() => trimEnd()

解构

变量解构

1
2
3
4
5
6
7
8
9
10
let [a,b,c] = [1,2,3]

a = [1, [[2], 3]]
let [foo, [[bar], baz]] = a

foo -> 1
bar -> 2
baz -> 3

let [x, y, z] = new Set(['a', 'b', 'c']);

对象解构

1
2
3
4
5
6
7
8
9
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
let { log, sin, cos } = Math;

// 设置别名
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;

first: 模式
f: 变量,可赋值

字符串解构赋值

1
const [a, b, c, d, e] = 'hello';

等号右边值不是对象或数组,就先将其转为对象,必须是数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值,

解构不成功,变量的值就等于undefined

解构赋值允许指定默认值,只有位置上的值 === undefined,默认值才会生效

*对象的属性没有次序,变量必须与属性同名,对象的解构赋值可以取到继承的属性

使用场景:

(1) 交换变量值
(2) 函数返回多个值
(3) 函数参数的定义
(4) 提取 JSON 数据
(5) 遍历 Map 结构
(6) 输入模块的指定方法

ES6 扩展

数值扩展

指数运算符

1
2
3
4
5
6
2**3**2
=> 2**9
=> 512

2 **= 3
=> 2 * 2 * 2

bigInt

函数扩展

length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数

默认值参数形成一个单独的作用域

rest 参数

只能是最后一个参数

函数的length属性,不包括 rest 参数

严格模式,参数使用

第一种是设定全局性的严格模式

第二种是把函数包在一个无参数的立即执行函数里面

箭头函数直接返回一个对象,必须在对象外面加上括号

箭头函数没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

1
let getTempItem = id => ({ id: id, name: "Temp" })

尾调用,尾递归

函数的最后一步操作是函数调用

目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

function Fibonacci (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};

return Fibonacci (n - 1, ac2, ac1 + ac2);
}
Fibonacci(100) // 573147844013817200000
Fibonacci(1000)

** 尾调用优化只在严格模式下开启 **

正常模式下,函数内部有两个变量,可以跟踪函数的调用栈

func.arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。

蹦床函数

1
2
3
4
5
6
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}

尾递归优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function tco(f) {
var value;
var active = false;
var accumulated = [];

return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}

var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});

sum(1, 100000)

catch 参数省略

1
2
3
4
5
try {
// ...
} catch {
// ...
}

数组扩展

扩展运算符 …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(...[1, 2, 3])
// 1,2,3

...(x > 0 ? ['a'] : [])

Math.max(...[14, 3, 77])

const a1 = [1, 2]
const a2 = [...a1]

const arr1 = ['a', 'b']
const arr2 = ['c']
const arr3 = ['d', 'e']
const newArr = [...arr1, ...arr2, ...arr3]

只有函数调用时,扩展运算符才可以放在()

Array.from()

将类数组、可遍历对象转为数组

等价于:

const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();
1
2
3
4
Array.from(arrayLike).map(x => x * x)

Array.from({ length: 2 }, () => 'jack')
// 第一个参数指定了第二个参数运行的次数

Array.of()

将一组值,转换为数组

等价于:

function ArrayOf(){
  return [].slice.call(arguments);
}

find、findIndex

fill

填充数组(填充值,起始位置,结束位置)

includes

flat、flatMap

1
2
3
4
5
[1, 2, [3, 4]].flat(整数:维数)
// [1,2,3,4]

[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

Infinity: 不管多少维,全部拉平

对象扩展

直接写入变量和函数,作为对象的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const foo = 'bar'
const baz = {foo}

function f(x, y) {
return {x, y}
}

const o = {
method() {
return "Hello!"
}
}

let obj = {
['h' + 'ello']() {
return 'hi'
}
}

简写的对象方法不能作为构造函数使用

对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
get foo() {},
set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name

对象属性描述

Object.getOwnPropertyDescriptor(obj, [key1]:属性参数必传)

value: 123,
writable: true,
enumerable: true,
configurable: true

enumerable:false 的属性以下操作会被过滤:

  1. for…in循环:只遍历对象自身的和继承的可枚举的属性;
  2. Object.keys():返回对象自身的所有可枚举的属性的键名;
  3. JSON.stringify():只串行化对象自身的可枚举的属性;
  4. Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举

ES6规定:所有 Class 原型的方法都不可枚举

对象属性遍历规则:

首先遍历所有数值键,按照数值升序排列
遍历所有字符串键,按照加入时间升序排列
遍历所有Symbol键,按照加入时间升序排列

super

链判断运算符 ?.

1
const firstName = message?.body?.user?.firstName || 'default';

Null 判断运算符 ??

与逻辑运算符一起使用,必须用()表明优先级

1
2
3
4
5
6
7
8
9
10
11
(lhs && middle) ?? rhs
lhs && (middle ?? rhs)

(lhs ?? middle) && rhs
lhs ?? (middle && rhs)

(lhs || middle) ?? rhs
lhs || (middle ?? rhs)

(lhs ?? middle) || rhs
lhs ?? (middle || rhs)

对象新增方法

Object.is()

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 针对+0 不等于 -0的情况
return x !== 0 || 1 / x === 1 / y;
}
// 针对NaN的情况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
})

Object.assign()

源对象(source)的所有可枚举属性,复制到目标对象(target)

1
2
3
4
5
6
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }

Object.assign(target, source1, source2)
// {a:1, b:2, c:3}

同名属性值覆盖,应用场景:对象按顺序赋值排序
不可以转换成拥有枚举实义属性的类型,首参数报错,其余位置跳过。

Object.setPrototypeOf()(写操作)

Object.getPrototypeOf()(读操作)

Object.create()(生成操作)

Object.fromEntries()

1
2
3
4
5
6
7
8
9
10
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])

const map = new Map().set('foo', true).set('bar', false)
=> new Map([
['foo', 'bar'],
['baz', 42]
])

Symbol

Symbol函数前不能使用new命令,是一个原始类型的值,不是对象。

1
let s1 = Symbol('foo')

Symbol.prototype.description

1
2
const sym = Symbol('foo')
sym.description

Symbol 值不能与其他类型的值进行运算,可以显式转为字符串,也可以转为布尔值,但是不能转为数值
Symbol 值作为对象属性名时,不能用点运算符
Symbol 值作为属性名时,该属性还是公开属性,不是私有属性

Symbol.for(),Symbol.keyFor()

Symbol.for() 全局等级
Symbol.keyFor() 获取全局等级的key

内置值

Symbol.hasInstance:判断是否为该对象的实例
Symbol.isConcatSpreadable:对象用于Array.prototype.concat()时,是否可以展开
Symbol.species:创建衍生对象时,会使用该属性

1
2
3
4
5
6
7
8
9
10
class MyArray extends Array {
}

const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

b instanceof MyArray // true
c instanceof MyArray // true
衍生对象

Symbol.match
Symbol.replace
Symbol.search
Symbol.split
Symbol.iterator
Symbol.toPrimitive
Symbol.toStringTag
Symbol.unscopables

Set、Map

Set:类似数组,成员值唯一,本身是一构造函数

1
2
const set = new Set([1, 2, 3, 4, 4]);
[...set]

向Set插值时,不会发生类型转换,相当于 “===”,NaN 等于自身

常用方法

add、delete、has、clear

keys、values、entries、forEach

WeakSet

只能放置对象的成员,不适合放置应用,不计入垃圾回收机制,引用随时消失,因此不可遍历

1
2
3
4
a = new WeakSet([[2342,234234,453,45656,35]])

a.add({}) // true
a = new WeakSet({}) // object is not iterable

Map

Object 结构: 字符串—值
Map 结构:值-值

1
2
3
4
5
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o)

常用方法

has、delete、size

**只有对同一个对象的引用,Map结构才视为同一个键(内存地址的绑定)

1
2
3
4
const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

主要用途:
DOM 节点作为键名
部署私有属性

Proxy

修改某些操作的默认行为,即元编程,对编程语言进行编程

let proxy = new Proxy(target[所要拦截的目标对象], handler[定制拦截行为])

支持的拦截操作

get、set、has、deleteProperty、ownKeys、getOwnPropertyDescriptor、defineProperty、preventExtensions、getPrototypeOf、isExtensible、setPrototypeOf、apply、construct

Reflect

  1. 获取语言内部的方法
  2. 修改某些Object方法的返回结果
  3. Object操作都变成函数行为
  4. Reflect对象的方法与Proxy对象的方法一一对应

import|export

AMD

1
2
3
4
5
6
7
8
//content.js
define('content.js', function(){
return 'A cat';
})
//index.js
require(['./content.js'], function(animal){
console.log(animal);
})

CommomJs

1
2
3
4
//index.js
var animal = require('./content.js')
//content.js
module.exports = 'A cat'

ES6

1
2
3
4
//index.js
import animal from './content'
//content.js
export default 'A cat'

Module

1
2
3
4
5
6
7
8
9
export default 'A cat'
export function say(){
return 'Hello!'
}
export const type = 'dog'

import animal /* 引用默认 */, { say, type as animalType /* 修改变量名 */ } from './content'

import animal, * as content /* 引用全部 */ from './content'

Tree-shaking(rollup.js)将未被依赖或使用的过滤掉

Promise

  • promise对象的状态不受处界影响;

  • promise对象共有3种可能的状态:Pending、Fulfiled、Rejected;

  • 但promise对象的状态变化只能是以下2种情况之一:

    由Pending变为Fulfiled
    由Pending变为Rejected
  • promise对象的状态一旦改变,就不会再变,任何时候都可以得到这个结果;

  • promise对象一旦创建就会立即执行创建该promise对象时传入的异步函数;

1
2
3
4
5
6
7
const promise = new Promise(function(resolve, reject) {
if (true){
resolve(value);
} else {
reject(error);
}
}).then(function(value){}, function(error){})

异步加载图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();

image.onload = function() {
resolve(image);
};

image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};

image.src = url;
});
}
1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···})

Promise.all([p1, p2, p3]) 且关系
Promise.race([p1, p2, p3]) 或关系
Promise.any() 所有参数实例都变成rejected状态,返回rejected状态
Promise.allSettled() 所有参数实例都返回结果,才返回fulfilled状态
Promise.try()

Iterator、Generator

执行 Generator 函数会返回一个遍历器对象,Generator 函数除了状态机,还是一个遍历器对象生成函数

1
2
3
4
5
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
yield表达式只能用在 Generator 函数里面

yield*:Generator 函数里面执行另一个 Generator 函数

async、await

Generator函数的语法糖

async => * :表示函数里有异步操作
await => yield:表示需要等待结果

async函数内部 return 语句返回的值,会成为 then 方法回调函数的参数,抛出的错误,会导致返回的 Promise 对象变为 reject 状态

1
2
3
4
5
async function f() {
return 'hello world';
}

f().then(v => console.log(v))

await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象

await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

1
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

async 原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function fn(args) {
return spawn(function* () {

});
}

function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}

顶层await只能用在 ES6 模块,不能用在 CommonJS 模块。这是因为 CommonJS 模块的require()是同步加载

参考文档