ES6

个人回顾整理学习。

关于变量

ES5

全部作用域变量和函数作用域变量

“变量提升” 程序进入某一个函数时,会先将函数内的变量声明放在函数开头,不会提升变量的赋值。

1
2
console.log(foo); // 输出:undefined
var foo = 2;

ES6

变量声明let和const

块级作用域

没有“变量提升”,必须先声明后使用

let 声明的变量不能重复

const 定义的是只读变量,需在声明时即赋值,变量名不能重复

1
2
console.log(bar); // 报错
let bar = 2;

块级作用域的应用场景

内层变量可能会覆盖外层变量

1
2
3
4
5
6
7
8
9
10
> var tmp = new Date();
> function f() {
> console.log(tmp);
> if (false) {
> var tmp = 'hello world';
> }
> }
>
> f(); // undefined
>

用来计数的循环变量泄露为全局变量

1
2
3
4
5
6
7
8
> var s = 'hello';
>
> for (var i = 0; i < s.length; i++) {
> console.log(s[i]);
> }
>
> console.log(i); // 5
>

不必要立即执行函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
> // IIFE 写法
> (function () {
> var tmp = ...;
> ...
> }());
>
> // 块级作用域写法
> {
> let tmp = ...;
> ...
> }
>

应尽量避免在块级作用域中声明函数

声明变量的方式

var function let const import class

变量的结构赋值

数组

ES5

1
2
3
$ let a = 1;
$ let b = 2;
$ let c = 3;

ES6

1
$ let [a, b, c] = [1, 2, 3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ 嵌套数组结构
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

不完全结构

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
对象
1
2
3
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

等价于

1
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
字符串
1
2
3
4
5
6
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

length 属性

1
2
let {length : len} = 'hello';
len // 5

关于数组

扩展运算符

… 数组的展开运算

1
2
3
var arr = [1,2,3,4,5];
console.log(arr);//[1, 2, 3, 4, 5]
console.log(...arr)// 1 2 3 4 5

函数批量传参

1
fun5(...[11,22,33,44,55,66])

替代函数apply方法

应用Math.max方法,简化求出一个数组最大元素的写法。

1
2
3
4
5
6
7
8
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

push函数

1
2
3
4
5
6
7
8
9
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

应用

复制数组

数组是复合数据类型,直接复制的话只是复制了指向底层数据结构的指针,而不是克隆一个新的数组。

1
2
3
4
5
const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]
1
ES5 变通方法克隆数组
1
2
3
4
5
const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]
1
ES6 扩展运算符简单写法
1
2
3
4
5
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
合并数组
1
2
3
4
5
6
7
8
9
10
11
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' ]

注意

这两种方法合成的新数组都是对原数组成员的引用,属于浅拷贝,如果原数组成员改变,则会反映到新合成的数组上。

与结构赋值结合
1
2
3
4
5
6
7
8
9
10
11
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 // []

注意

采用此方法,数组扩展运算符只能放在参数的最后一位,否则,报错。

字符串

扩展运算符可以将字符串转为数组

1
2
[...'hello']
// [ "h", "e", "l", "l", "o" ]
Array.form()

Array.from用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。

1
2
3
4
5
6
7
8
9
10
11
12
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

1
2
3
4
5
6
7
8
9
10
11
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});

// arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}

字符串扩展

字符串 for…of 遍历

1
2
3
4
5
6
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"

ES5

indexOf 用来确定一个字符串是否包含在另一个字符串中

ES6

Includes() 返回布尔值 是否找到参数字符串

startsWith() 返回布尔值 表示参数是否在原字符串头部

endsWith() 返回布尔值 表示参数是否在原字符串尾部

备注

这三个方法都支持第二个参数,表示开始搜索的位置。

使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束

repeat()

返回一个新字符串,表示将原字符串重复n

1
2
3
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

参数如果是小数,会被取整

1
'na'.repeat(2.9) // "nana"

备注

  • 如果repeat的参数是负数或者Infinity,会报错
  • 如果参数是 0 到-1 之间的小数,则等同于 0
  • 参数NaN等同于 0
padStart(), padEnd()

字符串补全长度的功能,如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

1
2
3
4
5
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
模板字符串

ES5

1
2
3
4
5
6
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);

ES6

模板字符串是增强版的字符串,反引号(`)标识。可以当做普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

1
2
3
4
5
$('#result').append(`
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
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}

大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性

1
2
3
4
5
6
7
8
9
10
11
12
let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

对象扩展

属性简洁表示

ES6 允许直接写入变量和函数。

1
2
3
4
5
6
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

方法简写

1
2
3
4
5
6
7
8
9
10
11
12
13
const o = {
method() {
return "Hello!";
}
};

// 等同于

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

属性名表达式

除了用标识符作为属性名,ES6 增加了使用表达式作为对象的属性名,需要放在[]中。

1
2
3
4
5
// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;
1
2
3
4
5
6
7
8
9
10
let lastWord = 'last word';

const a = {
'first word': 'hello',
[lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

Object.is()

ES5 比较两个值是否相等:==(相等运算符)和 ===(严格相等运算符)。

ES6 Object.is()=== 行为基本一致。

1
2
3
4
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

两个不同之处

1
2
3
4
5
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

1
2
3
4
5
6
7
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.assign() 应用

为对象添加属性

x属性和y属性添加到Point类的对象实例。

1
2
3
4
5
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
为对象添加方法

使用assign方法添加到SomeClass.prototype之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});

// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
克隆对象
1
2
3
function clone(origin) {
return Object.assign({}, origin);
}
合并多个对象
1
2
const merge =
(target, ...sources) => Object.assign(target, ...sources);

属性的遍历

(1) for…in

循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)

(2)Object.keys(obj)

返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

扩展运算符

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

1
2
3
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
1
2
3
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

合并对象

1
2
3
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

Symbol

ES6 引入一种新的原始数据类型Symbol,表示独一无二的值。

对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

1
2
3
4
let s = Symbol();

typeof s
// "symbol"

Set和Map数据结构

Set

含义和用法

Set 数据结构类似数组,但是它的值都是唯一的,没有重复的值。

1
2
3
4
5
6
7
8
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
console.log(i);
}
// 2 3 5 4

Set 可以接受一个数组参数,用来初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56

// 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56

去除数组重复成员

1
2
// 去除数组的重复成员
[...new Set(array)]
Set 实例属性和方法

属性

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

操作方法

  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

判断是否包括一个键,Object结构和Set结构的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 对象的写法
const properties = {
'width': 1,
'height': 1
};

if (properties[someName]) {
// do something
}

// Set的写法
const properties = new Set();

properties.add('width');
properties.add('height');

if (properties.has(someName)) {
// do something
}

Array.from方法可以将 Set 结构转为数组。

1
2
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

数组去重

1
2
3
4
5
function dedupe(array) {
return Array.from(new Set(array));
}

dedupe([1, 1, 2, 3]) // [1, 2, 3]

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

Map

含义及用法

ES6 提供Map结构,类似于对象,对象本质上是键值对的集合,Object 结构只能字符串当做对象的键,而Map结构“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应。

1
2
3
4
5
6
7
8
9
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
实例属性及操作方法

(1)size 属性

(2)set(key, value)

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。set方法返回的是当前的Map对象,因此可以采用链式写法。

1
2
3
4
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');

(3)get(key)

get方法读取key对应的键值,如果找不到key,返回undefined

(4)has(key)

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

(5)delete(key)

delete方法删除某个键,返回true。如果删除失败,返回false

(6)clear()

clear方法清除所有成员,没有返回值。

数值扩展

二进制和八进制表示法

分别用前缀0b(或0B)和0o(或0O)表示二进制和八进制

Number.isFinite(), Number.isNaN()

Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity

Number.isNaN()用来检查一个值是否为NaN

这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false

Number.parseInt(), Number.parseFloat()

和全局方法相比行为完全不变,是为了减少全局方法的使用,使语言逐步模块化。

Number.isInteger()

用来判断一个数值是否为整数,如果不是数值返回false

1
2
3
4
Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.isInteger(25) // true
Number.isInteger(25.0) // true

Math 对象扩展

Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

注意

  • 对于非数值,Math.trunc内部使用Number方法将其先转为数值
  • 对于空值和无法截取整数的值,返回NaN
Math.sign()

Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

  • 参数为正数,返回+1
  • 参数为负数,返回-1
  • 参数为 0,返回0
  • 参数为-0,返回-0
  • 其他值,返回NaN

关于函数

函数默认值

ES6

给形参函数设置默认值,构造函数同理

1
2
3
4
5
function fun2(a=1,b=2){
console.log(a,b)//1,2
}
fun2(11,22);//11 22
fun2(100);//100 2

箭头函数

1
2
3
4
5
6
7
//正常函数
var fun3 = function(a){
console.log(a);
}
//箭头函数
var fun3 = (a)=>{console.log(a);}
fun3(999);
this知识

函数体外this指window对象

函数体内,谁调用函数this就指向谁

构造函数,this指的是新创建的对象

html标签中,this指的是这个标签元素

ES6 对于箭头函数,this指向需要根据在哪里创建和当前作用域确定。

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
不适用场合
  • 定义函数的方法,且该方法内部包括this
1
2
3
4
5
6
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。

  • 需要动态this的时候,也不应使用箭头函数
1
2
3
4
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

双冒号运算符

函数绑定运算符(::),用来取代callapplybind调用。冒号左边是对象,右边是一个函数。双冒号运算符会自动将左边对象作为上下文环境(this对象)绑定到右边函数。

1
2
3
4
5
6
7
8
9
10
11
12
foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

1
2
3
4
5
6
7
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

let log = ::console.log;
// 等同于
var log = console.log.bind(console);

如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。

尾部调用

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

1
2
3
function f(x){
return g(x);
}

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

1
递归
1
2
3
4
5
6
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}

factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n)

1
尾递归
1
2
3
4
5
6
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

Proxy

🏠简介

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

1
var proxy = new Proxy(target, handler);

其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

拦截读取属性

1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果handler没有设置任何拦截,那就等同于直接通向原对象。

1
2
3
4
5
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

Reflect

🏠简介

ES6 为了操作对象提供的新的API。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。

(2) 修改某些Object方法的返回结果,让其变得更合理。

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。

静态方法

Reflect.get(target, name, receiver)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

如果name属性部署了读取函数(getter),则读取函数的this绑定receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};

var myReceiverObject = {
foo: 4,
bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8
Reflect.set(target, name, value, receiver)

Reflect.set方法设置target对象的name属性等于value

如果name属性设置了赋值函数,则赋值函数的this绑定receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myObject = {
foo: 4,
set bar(value) {
return this.foo = value;
},
};

var myReceiverObject = {
foo: 0,
};

Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1
Reflect.has(obj, name)

Reflect.has方法对应name in obj里面的in运算符。

1
2
3
4
5
6
7
8
9
var myObject = {
foo: 1,
};

// 旧写法
'foo' in myObject // true

// 新写法
Reflect.has(myObject, 'foo') // true
Reflect.deleteProperty(obj, name)

Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。

1
2
3
4
5
6
7
const myObj = { foo: 'bar' };

// 旧写法
delete myObj.foo;

// 新写法
Reflect.deleteProperty(myObj, 'foo');
Reflect.construct(targer, args)

Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

1
2
3
4
5
6
7
8
9
function Greeting(name) {
this.name = name;
}

// new 的写法
const instance = new Greeting('张三');

// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

应用

使用Proxy实现观察者模式。

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
const person = observable({
name: '张三',
age: 20
});

function print() {
console.log(`${person.name}, ${person.age}`)
}

observe(print);
person.name = '李四';
// 输出
// 李四, 20

Promise 对象

🏠简介

Promise 是异步编程的一种解决方案。

  • Promise 对象的状态不受外界影响。
  • 代表异步操作,有三种状态:pending(进行中),fulfilled(已成功),rejected(已失败)。
  • 一旦状态改变,就不会再变,任何时候都是这个结果。有两种可能的状态改变:由pending变为fulfilled,由pending变为rejected。

优势:

  • 表达方面,将异步操作用同步操作的形式表达出来,避免了层层嵌套。
  • 提供统一的接口,使异步操作更加方便。

缺点:

  • Promise无法取消,一旦创建,立即执行。
  • 如果不设置回调函数,Promise内部报错,不会反映到外部。
  • 处于pending状态时无法知道进展到哪个阶段,是刚刚开始还是马上结束。

基本用法

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

1
2
3
4
5
promise.then(function(value) {
// success
}, function(error) {
// failure
});

链式调用

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

不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.all([p1, p2, p3]);

其中p的状态由p1 p2决定

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

Promise.resolve()

Promise.resolve可以将现有对象转为 Promise 对象。

1
2
3
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • 参数是Promise实例,返回这个实例
  • 参数是一个thenable对象(具有then方法的对象),Promise.resolve将其转化为Promise对象,并立即执行thenable对象的then方法
  • 参数不是具有then方法的对象,或根本就不是对象,返回一个新的Promise对象,状态为resolved
  • 不带参数,直接返回一个resolved状态的 Promise 对象

Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

1
2
3
4
5
6
7
8
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
console.log(s)
});
// 出错了

应用

加载图片
1
2
3
4
5
6
7
8
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
Generator函数与Promise结合

Iterator、for…of 循环

表示”集合“的数据结构:Array,Object,Map,Set。需要一个统一的接口机制,开处理不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。

Iterator 作用

  • 为各种数据结构提供统一的、简便的访问接口
  • 使得数据结构的成员能够按照某种次序排列
  • 创造一种新的遍历命令,for…of循环

模拟next方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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};
}
};
}

默认 Iterator 接口

一种数据结构只要部署了Iterator接口,就称这种数据结构是”可遍历的“。ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。

原生具备Iterator接口的数据:

  • Array
  • Map
  • Set
  • String
  • TypeArray
  • 函数的 arguments 对象
  • NodeList 对象

调用Iterator接口的情况

结构赋值

对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。

1
2
3
4
5
6
7
let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
扩展运算符
1
2
3
4
5
6
7
8
/ 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
yield*
其他情况

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

Iterator接口

Symbol.Iterator方法的简单实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]

// 或者采用下面的简洁写法

let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};

for (let x of obj) {
console.log(x);
}
// "hello"
// "world"

for…of

JavaScript 原有的for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值。

1
2
3
4
5
6
7
8
9
var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
console.log(a); // 0 1 2 3
}

for (let a of arr) {
console.log(a); // a b c d
}

优点:

  • 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
  • 不同于forEach方法,它可以与breakcontinuereturn配合使用。
  • 提供了遍历所有数据结构的统一操作接口。

async 函数

Generator函数的语法糖

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

async函数的返回值是一个Promise对象。

async可以看做是多个一步操作包装成的一个Promise对象,await命令就是内部then命令的语法糖。

基本用法

async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,遇到await函数就会先返回,等到异步操作完成,才会执行函数体后面的语句。

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
// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}

async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

返回Promise对象

async函数内部return返回的值,会成为then回调函数的参数。

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

f().then(v => console.log(v))
// "hello world"

async函数发生错误,会使返回的Promise对象变为reject状态,错误信息可以由catch方法得到。

1
2
3
4
5
6
7
8
9
async function f() {
throw new Error('出错了');
}

f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了

Promise对象的状态变化

async函数返回的Promise对象,只有函数内部await函数后边跟着的Promise对象执行完毕,才会产生状态改变,除非遇到return函数,或者发生错误。

async函数内部的异步操作执行完成,才会执行then回调函数操作。

1
2
3
4
5
6
7
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

await 命令

通常await函数后面是一个Promise对象,如果不是,则返回相应的值。

1
2
3
4
5
6
7
8
async function f() {
// 等同于
// return 123;
return await 123;
}

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

只要一个await函数后面的Promise对象变为reject,则async函数就会中断执行,如果一个await函数出错,使其不影响后续await函数的执行,就需要将其加上try…catch结构。

1
2
3
4
5
6
7
8
9
10
11
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一种方法是在await函数后面的Promise对象加上一个catch方法。

1
2
3
4
5
6
7
8
9
10
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

注意点

  • 最好把await函数放在try..catch结构中
  • 多个await函数如果不存在继发关系,最好让他们同时触发
1
2
3
继发触发
let foo = await getFoo();
let bar = await getBar();
1
2
3
4
5
6
7
8
9
同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  • await函数只能写在async函数中

apply 和 call

对象本身没有某个属性或者方法,进而使用apply或者call去引用其他对象的属性方法,改变this的指向。

使用方法

apply(this指向,数组/arguments)

call(this指向,参数1,参数2,参数3)

1
2
3
4
5
6
7
8
9
10
11
var name ="window的name";
var obj = {
name:"obj的name",
showName:function(v1,v2,v3){
console.log(v1,v2,v3)
}
}
obj.showName();
obj.showName.apply(window,[10,20,30]);//10 20 30
//apply括号里的是谁,调用的前面的函数里面的this就是谁
obj.showName.call(window,10,20,30)//10 20 30

参考链接