ES6笔记

2019-01-09  本文已影响18人  5df463a52098

一、let和const

二、变量的解构赋值

三、字符串的扩展

1、字符串遍历

for (let k of 'hello') {
  console.log(k)
}
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "
for (let i of text) {
  console.log(i);
}

除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

2、includes()、startsWith()、endsWith()

let a = 'hello world!'
    console.log(a.includes('o'))//true
    console.log(a.startsWith('he'))//true
    console.log(a.endsWith('!'))//true
    console.log(a.includes('d', 1))//false
    console.log(a.startsWith('d', 2))//false
    console.log(a.endsWith('h', 1))//true

第二个参数表示开始搜索的位置到字符串结束,endsWith不同,它针对的是前n个字符。

3、repeat()

'hello'.repeat(2) // "hellohello"
'hello'.repeat(0) // ""
'hello'.repeat(2.9) // "hellohello"
'hello'.repeat(-1) // 报错
'hello'.repeat(Infinity) // 报错
'hello'.repeat(-0.8) // “”
'hello'.repeat(NaN) // “”
'hello'.repeat('aa') // “”
'hello'.repeat('2') // “hellohello”

参数是小数,会被取整;
参数是负数或者Infinity,会报错;
参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0;
参数NaN是等同于 0;
参数是字符串,则会先转换成数字。

4、padStart(),padEnd()

padStart()用于头部补全,padEnd()用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
'xxx'.padStart(5, 'abcd') // 'abxxx'
'xxx'.padEnd(5, 'abcd') // 'xxxab'
'xxx'.padStart(5) // '  xxx'
'xxx'.padEnd(5) // 'xxx  '

一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
用途:
padStart()的常见用途是为数值补全指定位数。另一个用途是提示字符串格式。

'12'.padStart(10, '0') // "0000000012"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

5、matchAll()

matchAll方法返回一个正则表达式在当前字符串的所有匹配

6、模版字符串

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

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

四、正则的扩展

1、RegExp构造函数

var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;
new RegExp(/abc/ig, 'i').flags

参数是字符串,这时第二个参数表示正则表达式的修饰符(flag);
参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。
如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符,原有正则对象的修饰符是ig,它会被第二个参数i覆盖。

2、字符串的正则方法(match()、repalce()、search()、split())

3、u修饰符

4、RegExp.prototype.unicode 属性

正则实例对象新增unicode属性,表示是否设置了u修饰符

const r1 = /hello/;
const r2 = /hello/u;
r1.unicode // false
r2.unicode // true

5、y修饰符

‘粘连’(sticky)修饰符
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null

lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。

const REGEX = /a/g;
// 指定从2号位置(y)开始匹配
REGEX.lastIndex = 2;
// 匹配成功
const match = REGEX.exec('xaya');
// 在3号位置匹配成功
match.index // 3
// 下一次匹配从4号位开始
REGEX.lastIndex // 4
// 4号位开始匹配失败
REGEX.exec('xaya') // null

<-----!----->
const REGEX = /a/y;

// 指定从2号位置开始匹配
REGEX.lastIndex = 2;

// 不是粘连,匹配失败
REGEX.exec('xaya') // null

// 指定从3号位置开始匹配
REGEX.lastIndex = 3;

// 3号位置是粘连,匹配成功
const match = REGEX.exec('xaya');
match.index // 3
REGEX.lastIndex // 4

6、RegExp.prototype.sticky 属性

ES6 的正则实例对象多了sticky属性,表示是否设置了y修饰符。

var r = /hello\d/y;
r.sticky // true

7、RegExp.prototype.flags 属性

ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符。

// ES5 的 source 属性
// 返回正则表达式的正文
/abc/ig.source
// "abc"

// ES6 的 flags 属性
// 返回正则表达式的修饰符
/abc/ig.flags
// 'gi'

8、s 修饰符:dotAll 模式

9、后行断言

10、Unicode 属性类

11、具名组匹配

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用.

12、String.prototype.matchAll

五、数值的扩展

1、二进制和八进制表示法

ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。

Number('0b111')  // 7
Number('0o10')  // 8

2、Number.isFinite(), Number.isNaN()

ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity;如果参数类型不是数值,Number.isFinite一律返回false。
Number.isNaN()用来检查一个值是否为NaN;如果参数类型不是NaN,Number.isNaN一律返回false。

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true

它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。

isFinite(25) // true
isFinite("25") // true
Number.isFinite(25) // true
Number.isFinite("25") // false

isNaN(NaN) // true
isNaN("NaN") // true
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false
Number.isNaN(1) // false

3、Number.parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。

// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

4、Number.isInteger()

Number.isInteger()用来判断一个数值是否为整数。

Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.isInteger(25.0) // true
Number.isInteger(true) // false
Number.isInteger(null) // false
Number.isInteger('12') // false
Number.isInteger() // false
Number.isInteger(3.0000000000000002) // true

注意,由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。总之,如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数。

5、Number.EPSILON

ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。

Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16

6、安全整数和 Number.isSafeInteger()

JavaScript 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值。

Math.pow(2, 53) === Math.pow(2, 53) + 1
// true

超出 2 的 53 次方之后,一个数就不精确了.
ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
// true
Number.MAX_SAFE_INTEGER === 9007199254740991
// true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
// true
Number.MIN_SAFE_INTEGER === -9007199254740991
// true

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger(9007199254740992) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

7、Math 对象的扩展

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

Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};

(2)Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
----参数为正数,返回+1;
----参数为负数,返回-1;
----参数为 0,返回0;
----参数为-0,返回-0;
----其他值,返回NaN。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.sign = Math.sign || function(x) {
  x = +x; // convert to a number
  if (x === 0 || isNaN(x)) {
    return x;
  }
  return x > 0 ? 1 : -1;
};

(3)Math.cbrt方法用于计算一个数的立方根。
对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。

Math.cbrt(2)  // 1.2599210498948734
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.cbrt = Math.cbrt || function(x) {
  var y = Math.pow(Math.abs(x), 1/3);
  return x < 0 ? -y : y;
};

(4)Math.clz32()方法将参数转为 32 位无符号整数的形式,然后这个 32 位值里面有多少个前导 0。

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2
Math.clz32() // 32
Math.clz32(NaN) // 32
Math.clz32(Infinity) // 32
Math.clz32(null) // 32
Math.clz32('foo') // 32
Math.clz32([]) // 32
Math.clz32({}) // 32
Math.clz32(true) // 31

对于小数,Math.clz32方法只考虑整数部分。
对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算。
(5)Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)与a * b的结果是相同的,即该方法等同于(a * b)|0的效果(超过 32 位的部分溢出)。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4

(6)Math.fround方法返回一个数的32位单精度浮点数形式。
(7)Math.hypot方法返回所有参数的平方和的平方根。
如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。

Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3);          // 3

(8)对数方法:
ES6 新增了4 个对数方法:
----Math.expm1()
Math.expm1(x)返回 e^{x}- 1,即Math.exp(x) - 1。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.expm1 = Math.expm1 || function(x) {
  return Math.exp(x) - 1;
};

----Math.log1p()
Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log1p = Math.log1p || function(x) {
  return Math.log(1 + x);
};

----Math.log10()
Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};

----Math.log2()
Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log2 = Math.log2 || function(x) {
  return Math.log(x) / Math.LN2;
};

(9)双曲函数方法
ES6 新增了 6 个双曲函数方法。
----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)
(10)指数运算符
ES2016 新增了一个指数运算符()。
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的.
指数运算符可以与等号结合,形成一个新的赋值运算符(
=)。

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
let b = 4;
b **= 3;
// 等同于 b = b * b * b;

六、函数的扩展

1、函数参数的默认值

ES6允许函数参数指定默认值

function log(x, y = 'World') {
  console.log(x, y);
}

注意:参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}
foo() // 100
x = 100;
foo() // 101

2、函数length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

3、作用域

4、rest参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
函数的length属性,不包括 rest 参数。

function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

5、严格模式

从 ES5 开始,函数内部可以设定为严格模式。
S2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 报错
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 报错
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 报错
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 报错
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

6、name属性

函数的name属性,返回该函数的函数名。

function foo() {}
foo.name // "foo"

var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"

const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"

注意:ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
Function构造函数返回的函数实例,name属性的值为anonymous。

(new Function).name // "anonymous"

bind返回的函数,name属性值会加上bound前缀。

function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "

7、箭头函数

ES6 允许使用“箭头”(=>)定义函数。
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
使用注意点
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
注意:
this对象的指向是可变的,但是在箭头函数中,它是固定的。内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
不适用场合:
定义函数的方法,且该方法内部包括this;

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

8、双冒号运算符

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。用来取代call、apply、bind调用。
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法.

foo::bar;
// 等同于
bar.bind(foo);

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

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

9、尾调用优化

尾调用是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

function f(x){
  return g(x);
}

函数f的最后一步是调用函数g,这就叫尾调用。
ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
func.arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

function restricted() {
  'use strict';
  restricted.caller;    // 报错
  restricted.arguments; // 报错
}
restricted();

10、尾递归

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

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
factorial(5) // 120

七、数组的扩展

1、扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

扩展运算符后面还可以放置表达式。
如果扩展运算符后面是一个空数组,则不产生任何效果。

function push(array, ...items) {
  array.push(...items);
}

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];

[...[], 1]
// [1]

注意,扩展运算符如果放在括号中,JavaScript 引擎就会认为这是函数调用。如果这时不是函数调用,就会报错。

console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number

2、### 替代函数的 apply 方法

// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

2、扩展运算符的应用

(1)复制数组

// ES5
const a1 = [1, 2];
const a2 = a1.concat();
// ES6
const b1 = [...a1]
const [...b2] = 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)与解构赋值结合
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

(4)字符串
扩展运算符还可以将字符串转为真正的数组。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

(5)实现了 Iterator 接口的对象
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];


let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];

(6) Map 和 Set 结构,Generator 函数

3、Array.from()

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

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']

对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。

const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

4、Array.of()

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

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

Array.of方法可以用下面的代码模拟实现。

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

5、数组实例的 copyWithin()

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

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三个参数。
----target(必需):从该位置开始替换数据。如果为负值,表示倒数。
----start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
----end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

6、数组实例find()和findIndex()

find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

[NaN].indexOf(NaN)
// -1

[NaN].findIndex(y => Object.is(NaN, y))
// 0

7、数组实例fill()

fill方法使用给定值,填充一个数组。fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。

['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]

8、数组实例的 entries(),keys() 和 values()

ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

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"

如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

9、数组实例includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

[NaN].indexOf(NaN)
// -1
[NaN].includes(NaN)
// true

10、数组实例flat(),flatMap()

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。flat()默认只会“拉平”一层:
如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
如果原数组有空位,flat()方法会跳过空位。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
[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, , 4, 5].flat()
// [1, 2, 4, 5]

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。flatMap()只能展开一层数组。

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

11、数组的空位

数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

Array(3) // [, , ,]
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
----forEach(), filter(), reduce(), every() 和some()都会跳过空位。
----map()会跳过空位,但会保留这个值
----join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。

// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1
// filter方法
['a',,'b'].filter(x => true) // ['a','b']
// every方法
[,'a'].every(x => x==='a') // true
// reduce方法
[1,,2].reduce((x,y) => x+y) // 3
// some方法
[,'a'].some(x => x !== 'a') // false
// map方法
[,'a'].map(x => 1) // [,1]
// join方法
[,'a',undefined,null].join('#') // "#a##"
// toString方法
[,'a',undefined,null].toString() // ",a,,"

ES6 则是明确将空位转为undefined。

Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
[...['a',,'b']]
// [ "a", undefined, "b" ]
new Array(3).fill('a') // ["a","a","a"]
let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1

八、对象的扩展

1、属性的简洁表示法

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

function f(x, y) {
  return {x, y};
}
// 等同于
function f(x, y) {
  return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}

const o = {
  method() {
    return "Hello!";
  }
};
// 等同于
const o = {
  method: function() {
    return "Hello!";
  }
};

2、属性名表达式

注意,属性名表达式与简洁表示法,不能同时使用,会报错。

// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
/**************************/
let propKey = 'foo';
let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
/**************************/
let lastWord = 'last word';
const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
/**************************/
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};
obj.hello() // hi

3、方法的name属性

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

4、属性的可枚举性和遍历

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

目前,有四个操作会忽略enumerable为false的属性。
----for...in循环:只遍历对象自身的和继承的可枚举的属性。
----Object.keys():返回对象自身的所有可枚举的属性的键名。
----JSON.stringify():只串行化对象自身的可枚举的属性。
----Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

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 或字符串,也不管是否可枚举。

6、super关键字

this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

const proto = {
  foo: 'hello'
};
const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

// 报错,super用在属性里面,
const obj = {
  foo: super.foo
}

// 报错 super用在一个函数里面,然后赋值给foo属性
const obj = {
  foo: () => super.foo
}

// 报错 super用在一个函数里面,然后赋值给foo属性
const obj = {
  foo: function () {
    return super.foo
  }
}

7、对象的扩展运算符

数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
如果扩展运算符后面是一个空对象,则没有任何效果。
如果扩展运算符后面不是对象,则会自动将其转为对象。
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
对象的扩展运算符等同于使用Object.assign()方法。

let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
/*******************/
{...{}, a: 1}
// { a: 1 }
/********************/
// 等同于 {...Object(1)}
{...1} // {}
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
/************************/
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
/*************************/
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

8、解构赋值

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

注意:
解构赋值要求等号右边是一个对象,如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
解构赋值必须是最后一个参数,否则会报错。
扩展运算符的解构赋值,不能复制继承自原型对象的属性。

let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误

九、对象的新增方法

1、Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

2、Object.assign()

Object.assign方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。
如果只有一个参数,Object.assign会直接返回该参数。
如果该参数不是对象,则会先转成对象,然后返回。
如果该参数是undefined和null,就会报错。
如果非对象参数出现在源对象的位置(即非首参数),首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

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

const obj = {a: 1};
Object.assign(obj) === obj // true
typeof Object.assign(2) // "object"

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

注意:
(1)浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
(3)数组的处理
Object.assign可以用来处理数组,但是会把数组视为对象。

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

(4)取值函数的处理
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source)
// { foo: 1 }

用途:
(1)为对象添加属性

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

(2)为对象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

(3)克隆对象

function clone(origin) {
  return Object.assign({}, origin);
}

(4)合并多个对象

const merge =
  (...sources) => Object.assign({}, ...sources);

(5)为属性指定默认值

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

3、Object.getOwnPropertyDescriptors()

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

4、proto属性

5、Object.setPrototypeOf()

6、Object.setPrototypeOf()

7、Object.keys()

ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

8、Object.values()

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

9、Object.entries()

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

10、Object.fromEntries()

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

十、Symbol

1、symbol

它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。
注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
Symbol 值不能与其他类型的值进行运算,会报错。但是,Symbol 值可以显式转为字符串,Symbol 值也可以转为布尔值,但是不能转为数值。

let s = Symbol();
typeof s
// "symbol"
let s1 = Symbol('foo');
s1 // Symbol(foo)
/******************/
const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
sym // Symbol(abc)
/******************/
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false
/******************/
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
Boolean(sym) // true
!sym  // false
Number(sym) // TypeError
sym + 2 // TypeError

2、作为属性名的Symbol

Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

3、实例:消除魔术字符串

4、属性名的遍历

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

const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

5、Symbol.for()

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

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true

Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

6、Symbol.keyFor()

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

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

7、Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

8、内置的 Symbol 值

(1)Symbol.hasInstance
(2)Symbol.isConcatSpreadable
(3)Symbol.species
(4)Symbol.match
(5)Symbol.replace
(6)Symbol.search
(7)Symbol.split
(8)Symbol.iterator
(9)Symbol.toPrimitive
(10)Symbol.toStringTag
(11)Symbol.toStringTag

十一、Set和Map数据结构

1、Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

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
/****************************/
// 例一
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
/****************************/

2、Set 实例的属性和方法

属性:
----Set.prototype.constructor:构造函数,默认就是Set函数。
----Set.prototype.size:返回Set实例的成员总数。
方法:分两种,操作方法(用于操作数据)和遍历方法(用于遍历成员)
----add(value):添加某个值,返回 Set 结构本身。
----delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
----has(value):返回一个布尔值,表示该值是否为Set的成员。
----clear():清除所有成员,没有返回值。
Array.from方法可以将 Set 结构转为数组。(这就提供了去除数组重复成员的另一种方法。)

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

3、Set的遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。
----keys():返回键名的遍历器
----values():返回键值的遍历器
----entries():返回键值对的遍历器
----forEach():使用回调函数遍历每个成员

4、WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
WeakSet 结构有以下三个方法。
----WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
----WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
----WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
WeakSet 没有size属性,没有办法遍历它的成员。

4、Map

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

5、Map实例的属性和操作方法

(1)size 属性
(2)set(key, value)
set方法返回的是当前的Map对象,因此可以采用链式写法。
(3)get(key)
(4)has(key)
(5)delete(key)
(6)clear()

5、Map遍历方法

Map 结构原生提供三个遍历器生成函数和一个遍历方法。
----keys():返回键名的遍历器。
----values():返回键值的遍历器。
----entries():返回所有成员的遍历器。
----forEach():遍历 Map 的所有成员。

6、与其他数据结构相互转换

(1)Map转为数组
Map 转为数组最方便的方法,就是使用扩展运算符(...)。

const myMap = new Map()
        .set(true, 7)
        .set({foo: 3}, ['abc']);
    console.log([...myMap]); // // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

(2)数组转为Map
将数组传入 Map 构造函数,就可以转为 Map。

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

(3)Map 转为对象
如果所有 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

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})
// Map {"yes" => true, "no" => false}

(5)Map 转为 JSON
Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。

//1
function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

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

// 2
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,正常情况下,所有键名都是字符串。但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

//1
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
//2
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

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

7、WeakMap

十二、Proxy

十三、Reflect

十四、Promise对象

十五、Iterator和for...of循环

十六、Generator函数语法

十七、Generator函数的异步应用

十八、async函数

十九、Class基本语法

1、基本语法

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
// ES6的class改写
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
typeof Point // "function"
Point === Point.prototype.constructor // true
/**---------------------------*/
class Point {
  constructor() {
    // ...
  }
  toString() {
    // ...
  }
  toValue() {
    // ...
  }
}
// 等同于
Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

类的数据类型就是函数,类本身就指向构造函数。
在类的实例上面调用方法,其实就是调用原型上的方法
注意:类不存在变量提升,这一点与 ES5 完全不同。

2、constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

class Point {
}
// 等同于
class Point {
  constructor() {}
}

二十、Class的继承

二十一、Decorator

二十二、Module语法

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

1、export

注意:export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1;
// 报错
var m = 1;
export m;
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};

// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};

2、import

import命令具有提升效果,会提升到整个模块的头部,首先执行

foo();
import { foo } from 'my_module';

import语句会执行所加载的模块,如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';

3、export default

export default 命令为模块指定默认输出。因此,一个模块只能有一个默认输出,因此export default命令只能使用一次。
使用export default时,对应的import语句不需要使用大括号;不使用export default时,对应的import语句需要使用大括号。

// 第一组
export default function crc32() { // 输出
  // ...
}

import crc32 from 'crc32'; // 输入

// 第二组
export function crc32() { // 输出
  // ...
};

import {crc32} from 'crc32'; // 输入

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';

4、export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。

5、跨模块常量

设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

// constants/db.js
export const db = {
  url: 'http://my.couchdbserver.local:5984',
  admin_username: 'admin',
  admin_password: 'admin password'
};

// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

然后,将这些文件输出的常量,合并在index.js里面。

// constants/index.js
export {db} from './db';
export {users} from './users';

使用的时候,直接加载index.js就可以了。

// script.js
import {db, users} from './constants/index';

6、import()

import命令会被 JavaScript 引擎静态分析,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。import()函数,完成动态加载。

const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

二十三、Module的加载实现

二十四、编程风格

二十五、读懂风格

二十六、ArrayBuffer

。。。。。。

注:摘抄于ECMAScript 6 入门

上一篇下一篇

猜你喜欢

热点阅读