ES6学习笔记

今天看的是这个ES6入门文档,很详细地讲解了ES6.未来再看阮一峰的ECMAScript教程作为补充吧
ECMAScript是JavaScript的规格,JavaScript是ECMAscript的实现。

部署

使用node.js

Babel转码器: 广泛使用的ES6转码器,可将ES6转化为ES5,从而在现有环境中进行。

Babel配置文件是.babelrc,用于设置转码规则和插件

命令行转码工具:babel-cli

babel-node直接运行ES6代码

Traceur转码器,也可以将ES6转为ES5

let和const

let所声明的变量只在所在的代码块内有效。适合在for循环内使用,避免值覆盖的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a=[];
for (var i=0;i<10;i++){
a[i] = function(){
console.log(i);
};
}
a[6];//10


var a=[];
for (let i=0;i<10;i++){
a[i] = function(){
console.log(i);
};
}
a[6];//6

let 不存在变量提升,因此一定要先声明变量才能使用。var存在变量提升。

暂时性死区

一旦区块中存在let和const变量,就一定要先声明再使用。凡是在声明之前使用这些变量,就会报错。哪怕已经有一个var声明的全局变量。

用let声明的变量,在声明之前都是该变量的死区,用到就会报错。

暂时性死区的本质:已进入当前作用域,要使用的 变量就已经存在,但是不可获取。只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

let不允许在同一作用域内重复声明变量。

块级作用域

外层代码块不受内层代码块的影响。

外层作用域无法读取内层作用域的变量。

内层作用域可以定义外层作用域的同名变量。

ES5中函数只能在顶层作用域和函数作用域中声明,不能在块级作用域声明。ES6允许在块级作用域中声明函数。

const

声明的变量不能改变值,const一旦声明变量,就必须立刻初始化,不能留到最后赋值。

const声明的变量只在块级作用域中有效。同样存在暂时性死区

对于复合类型,const指向的是数据所在的地址,只是保证该地址不变,数据可以改变。Object.freeze可以将对象冻结。

解构赋值

var [a,b,c] = [1,2,3]——属于模式匹配的写法。

可以从数组中提取值,按照对应位置,对变量赋值。

只要两边的模式相同,左边的变量就会赋予对应的值。

不想要的值可以用,隔开,如var [, , third] = [1,2,3];third//3

把多余的变量赋值成为数组:let [a,..b] = [1,2,3,4];a//1;b//[2,3,4]注意:...只能用在最末尾,不能用在中间。

解构不成功:变量等于undefined

默认值

在左端定义默认值

1
2
3
4
5
6
7
var [foo=true] = []
foo//true

[x,y='b'] = ['a']
x//a
y//b

对象的解构赋值

变量必须与属性同名才能取到正确的值。

先找到同名属性,再赋值给变量

模式不会被赋值

对象的解构也可以使用默认值

字符串的解构赋值

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

还可以对length解构赋值let {length :len} = 'hello';len//5

对象

右边的值不是对象的话,则先转为对象。

函数参数的解构赋值

建议只要有可能,就不要在模式中放置圆括号。

不能使用圆括号:

  1. 变量声明中不能使用圆括号
  2. 函数参数中不能使用圆括号
  3. 赋值语句中不能将整个模式或嵌套模式中的一层放入圆括号

可以使用圆括号——赋值语句中的非模式部分

解构变量的用途

  1. 交换变量的值 [x,y] = [y,x]

  2. 从函数返回多个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function example() {
    return [1, 2, 3];
    }
    var [a, b, c] = example();

    // 返回一个对象

    function example() {
    return {
    foo: 1,
    bar: 2
    };
    }
    var { foo, bar } = example();
  3. 函数参数的定义

  4. 提取JSON的数据

  5. 函数参数的默认值

  6. 遍历map结构

  7. 输入模块的指定方法

字符串的扩展

加强对Unicode的支持:只要将码点放入{}。就能正确解读Unicode

codePointAt() 返回一个字符的码点(十进制)

string.fromCodePoint() 从码点返回字符

遍历器接口

使字符串可以被for...of循环遍历

方法

at()返回字符串的字节 ‘hello‘.at(0) //h

repeat()返回一个将源字符串重复n次的新字符串'hello'.repeat(n),参数不能是负数和Infinity。参数是0到1之间的小数:等同于0。NaN等同于0。参数是字符串:则先转为数字。

补全长度:

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'

模板字符串

用反引号标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

所有的空格和缩进都会被保留在输出之中。

模板字符串中嵌入变量,需要将变量名写在${}之中。

正则的扩展

如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

1
2
new RegExp(/abc/ig, 'i').flags
// "i"

正则方法

match(),replace(),search(),split()

u修饰符

  1. 点字符

    对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符。

    1
    2
    3
    4
    var s = '𠮷';

    /^.$/.test(s) // false
    /^.$/u.test(s) // true

    上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。

  2. ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。

    1
    2
    3
    /\u{61}/.test('a') // false
    /\u{61}/u.test('a') // true
    /\u{20BB7}/u.test('𠮷') // true

    上面代码表示,如果不加u修饰符,正则表达式无法识别\u{61}这种表示法,只会认为这匹配61个连续的u

  3. 使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的Unicode字符。

    1
    2
    3
    4
    /a{2}/.test('aa') // true
    /a{2}/u.test('aa') // true
    /𠮷{2}/.test('𠮷𠮷') // false
    /𠮷{2}/u.test('𠮷𠮷') // true

    另外,只有在使用u修饰符的情况下,Unicode表达式当中的大括号才会被正确解读,否则会被解读为量词。

  4. u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的Unicode字符。

    1
    2
    /^\S$/.test('𠮷') // false
    /^\S$/u.test('𠮷') // true

    上面代码的\S是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。

y修饰符

y修饰符,叫做“粘连”(sticky)修饰符。

y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

属性

sticky属性:是否设置了y修饰符

flags属性:返回正则表达式的修饰符

数值的扩展

二进制:0b(0B)

八进制:0o(0O)

转为十进制:使用Number()

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

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

Number.parseInt('12.34') // 12

Number.parseFloat('123.45#') // 123.45

Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。

Number.EPSILON设置误差范围

Number.isSafeInteger()则是用来判断一个整数是否落在整数范围之内(整数范围在-2^532^53之间)。

Math对象的扩展

Math.trunc() 去除小数部分,返回整数部分。对于非数值,则先转为数值。对于空值和无法截去小数的值:返回NaN。

Math.sign() 判断一个数是正数、负数还是零。

正数返回+1

负数返回-1

参数为0,返回0

参数为-0,返回-0

其他值:返回NaN

Math.cbrt()计算一个数的立方根。对于非数值,则先转为数值。

Math.clz32() 返回一个数的32位无符号整数形式有多少个前导0。对于小数,只考虑整数部分。

Math.imul() 返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。

Math.fround方法返回一个数的单精度浮点数形式。

Math.hypot方法返回所有参数的平方和的平方根。

Math.expm1(x)返回ex - 1,即Math.exp(x) - 1

Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN

Math.log10(x)返回以10为底的x的对数。如果x小于0,则返回NaN。

Math.log2(x)返回以2为底的x的对数。如果x小于0,则返回NaN。

三角函数方法。

  • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)

数组的扩展

  1. Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。

  2. Array.of方法用于将一组值,转换为数组。

  3. 数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

1
Array.prototype.copyWithin(target, start = 0, end = this.length)
  1. 数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
1
2
[1, 4, -5, 10].find((n) => n < 0)
// -5

上面代码找出数组中第一个小于0的成员。

1
2
3
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10

上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

  1. 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

  2. fill方法使用给定值,填充一个数组。数组中已有的元素,会被全部抹去。

  3. entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    for (let index of ['a', 'b'].keys()) {
    console.log(index);
    }
    // 0
    // 1

    for (let elem of ['a', 'b'].values()) {
    console.log(elem);
    }
    // 'a'
    // 'b'

    for (let [index, elem] of ['a', 'b'].entries()) {
    console.log(index, elem);
    }
    // 0 "a"
    // 1 "b"
  4. Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值

空位

  1. 空位 Array(3) // [, , ,]ES6则是明确将空位转为undefined
  2. Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
  3. 扩展运算符(...)也会将空位转为undefined
  4. copyWithin()会连空位一起拷贝。
  5. fill()会将空位视为正常的数组位置。
  6. for...of循环也会遍历空位。
  7. entries()keys()values()find()findIndex()会将空位处理成undefined
  8. 由于空位的处理规则非常不统一,所以建议避免出现空位。

函数的扩展

允许为函数的参数设定默认值,即直接写在函数参数的后面。

1
2
3
4
5
6
7
function log(x, y = 'World') {
console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

参数变量是默认声明的,所以不能用letconst再次声明。

参数默认值可以与解构赋值的默认值,结合起来使用。

1
2
3
4
5
6
7
8
function foo({x, y = 5}) {
console.log(x, y);
}

foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined

rest参数(…val):获取函数的多余参数.使用rest参数,可以向函数传入任意数目的参数。

扩展运算符(…)

将一个数组转为用逗号分隔的参数序列

取代apply方法:

Math.max(...[14,3,7]) 等同于Math.max(14,3,7)

通过push函数,将数组添加到另一个数组的尾部。

扩展运算符的应用:

  1. 合并数组 [...arr1, ...arr2, ...arr3]
  2. 与解构赋值结合[a, ...rest] = list
  3. 函数的多个返回值
  4. 将字符串转为真正的数组 [...'hello']
  5. 实现Iterator接口对象.任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
  6. Map和Set结构,Generator函数

name

箭头函数

1
var f = v => v;

上面的箭头函数等同于:

1
2
3
var f = function(v) {
return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

1
2
3
4
5
6
7
8
9
var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

1
var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

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

箭头函数与变量解构结合使用。

简化回调函数

1
2
3
4
5
6
7
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

数组排序:

1
2
3
4
5
6
7
// 正常函数写法
var result = values.sort(function (a, b) {
return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

箭头函数绑定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
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}

优化:

函数调用会形成调用记录——调用帧。所有的调用帧形成一个调用栈。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

尾递归

尾调用自身,就成为尾递归

尾递归优化过的fibonacci 递归算法

1
2
3
4
5
6
7
8
9
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};

return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScript的实现,都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

函数式编程有一个概念,叫做柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}

function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

上面代码通过柯里化,将尾递归函数 tailFactorial 变为只接受1个参数的 factorial 。

蹦床函数(trampoline)可以将递归执行转为循环执行。

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
function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1);
} else {
return x;
}
}

上面代码中,sum函数的每次执行,都会返回自身的另一个版本。

现在,使用蹦床函数执行sum,就不会发生调用栈溢出。

1
2
trampoline(sum(1, 100000))
// 100001

对象的扩展

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}

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

var birth = '2000/01/01';

var Person = {

name: '张三',

//等同于birth: birth
birth,

// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }

};

属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var cart = {
_wheels: 4,

get wheels () {
return this._wheels;
},

set wheels (value) {
if (value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}

如果某个方法的值是一个Generator函数,前面需要加上星号。

1
2
3
4
5
var obj = {
* m(){
yield 'hello world';
}
};
  • Object.is() 比较两个值是否严格相等
1
2
3
4
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
  • Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    1
    2
    3
    4
    5
    6
    7
    var target = { a: 1 };

    var source1 = { b: 2 };
    var source2 = { c: 3 };

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

    Object.assign方法的第一个参数是目标对象,后面的参数都是源对象

    实行的是浅拷贝,而不是深拷贝。

    常见用途:

    1. 为对象添加属性
    2. 为对象添加方法
    3. 克隆对象
    4. 合并多个对象
    5. 为属性指定默认值

ES6一共有5种方法可以遍历对象的属性

(1)for…in

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

(2)Object.keys(obj)

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

(3)Object.getOwnPropertyNames(obj)

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

(4)Object.getOwnPropertySymbols(obj)

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

(5)Reflect.ownKeys(obj)

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

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。

方法

  1. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()

  2. __proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。

  3. Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。

  4. Object.getPrototypeOf与setPrototypeOf方法配套,用于读取一个对象的prototype对象。

  1. Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
  2. Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
  3. Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的。Object.values不会返回这个属性。
  4. Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
  5. Object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。

Symbol

symbol 表示独一无二的值。

let s = Symbol()

Symbol()函数前不能使用new。因为生成的Symbol是一个原始类型的值,不是对象。因此Symbol值也不能添加属性。

Symbol不能与其他类型的值进行运算,会报错。但是可以显式转为字符串。也可以转换为布尔值,但是不能转化为数值。

由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

Symbol值作为对象属性名时,不能用点运算符。

Symbol还可以用于定义一组常量,保证这组常量的值都是不相等的。

常用的消除魔术字符串的方法,就是把它写成一个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var shapeType = {
triangle: 'Triangle'
};

function getArea(shape, options) {
var area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

上面代码中,我们把“Triangle”写成shapeType对象的triangle属性,这样就消除了强耦合。

改用Symbol值。

1
2
3
const shapeType = {
triangle: Symbol()
};

上面代码中,除了将shapeType.triangle的值设为一个Symbol,其他地方都不用修改。

Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Symbol.for方法接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key

内置的Symbol值

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。

对象的Symbol.species属性,指向一个方法。该对象作为构造函数创造实例时,会调用这个方法。即如果this.constructor[Symbol.species]存在,就会使用这个属性作为构造函数,来创造新的实例对象。

对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。

对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object][object Array]object后面的那个字符串。

对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。

Set和Map数据结构

Set:类似于数组,但是每个值都是唯一的。

1
2
3
4
5
6
var s = new Set();
//初始化:
var s1 = new Set([1,2,3,3,5]);

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

Set的属性

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

操作方法:(操作数据)

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

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

去除数组重复成员:

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():使用回调函数遍历每个成员,没有返回值

需要特别指出的是,Set的遍历顺序就是插入顺序。

应用:

扩展运算符和Set结构相结合,就可以去除数组的重复成员。

1
2
3
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]

实现并集(Union)、交集(Intersect)和差集(Difference)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

weakSet

WeakSet的成员只能是对象,而不能是其他类型的值。

WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

WeakSet结构有以下三个方法。

  • **WeakSet.prototype.add(value)**:向WeakSet实例添加一个新成员。
  • **WeakSet.prototype.delete(value)**:清除WeakSet实例的指定成员。
  • **WeakSet.prototype.has(value)**:返回一个布尔值,表示某个值是否在WeakSet实例之中。

Map

Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

Map的属性

  1. size 返回Map结构的成员总数
  2. set(key,value) set方法设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。 可以采用链式写法
  3. get(key) get方法读取key对应的键值,如果找不到key,返回undefined
  4. has(key) has方法返回一个布尔值,表示某个键是否在Map数据结构中。
  5. delete(key) delete方法删除某个键,返回true。如果删除失败,返回false。
  6. clear()clear方法清除所有成员,没有返回值。

Map 的方法

遍历方法:

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历Map的所有成员。

(1)Map转为数组

前面已经提过,Map转为数组最方便的方法,就是使用扩展运算符(…)。

1
2
3
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

(2)数组转为Map

将数组转入Map构造函数,就可以转为Map。

1
2
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}

(3)Map转为对象

如果所有Map的键都是字符串,它可以转为对象。

1
2
3
4
5
6
7
8
9
10
11
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

(4)对象转为Map

1
2
3
4
5
6
7
8
9
10
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}

objToStrMap({yes: true, no: false})
// [ [ 'yes', true ], [ 'no', false ] ]

(5)Map转为JSON

Map转为JSON要区分两种情况。一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON。

1
2
3
4
5
6
7
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

另一种情况是,Map的键名有非字符串,这时可以选择转为数组JSON。

1
2
3
4
5
6
7
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'

(6)JSON转为Map

JSON转为Map,正常情况下,所有键名都是字符串。

1
2
3
4
5
6
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}

但是,有一种特殊情况,整个JSON就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为JSON的逆操作。

1
2
3
4
5
6
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

Proxy和Reflect

Proxy用于修改某些操作的默认行为。属于一种元编程,即对编程语言进行编程。

Reflect

Iterator和for…of循环

Iterator 遍历器,为各种不同的数据结构提供统一的访问机制。

模拟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};
}
};
}

在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。

1
2
3
4
5
6
7
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

调用Iterator接口的场合:

  1. 解构赋值
  2. 扩展运算符
  3. yield*
  4. 关于数组遍历的场合

for…of

for…of 和for…in 的区别

1
2
3
4
5
6
7
8
9
10
let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
console.log(i); // "3", "5", "7"
}

Generator函数

Generator函数——异步编程解决方案。返回一个遍历器对象。

yield语句

yield语句是暂停的标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。

(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

需要注意的是,yield语句后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

Promise对象

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

promise保存着某个未来才会结束的事件(通常是异步操作)。

promise是一个对象,可以获得异步操作的信息。

特点:

  1. 对象的状态不受外界影响。 promise的三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。状态只有异步操作的结果才能决定。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。

缺点:

  1. 无法取消promise
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  3. 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基本用法

promise实例

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

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

可以用then方法分别指定Resolved状态和Reject状态的回调函数。

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

Promise对象的简单例子。

1
2
3
4
5
6
7
8
9
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}

timeout(100).then((value) => {
console.log(value);
});

promise.prototype.then() 为Promise实例添加状态改变时的回调函数

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

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

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

Promise.resolve方法将现有对象转为Promise对象

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致