阅读 968

es6常用语法简记

前言:

只为重新总结和学习一下es6的相关语法,面试的时候忘了许多,在此记录一下自己复习的es6相关知识。我会从文档中精简其中的要点部分,不太容易懂的地方会加上自己的见解和说明,我是一个在学习路上的小菜鸡,如果有任何问题还望各位大佬批评指正,谢谢!


声明方式

  1. let声明:

声明一个变量,有暂时性死区、无变量提升有块级作用域

  // 声明的是一个变量,变量在声明且赋值后,能更改其赋的值
  let a = 1;
  a = 2;
  console.log(a); // 2
  // 暂时性死区:只要代码块内有let它所声明的变量就“绑定”这个区域,不再受外部的影响。
  var a = 3;
  {
    a = 123; // Identifier 'a' has already been declared
    let a;
  }
  // 无变量提升
  console.log(a); // a is not defined
  let a = '张三';
  // 块级作用域
  {
    let n = '张三'
  }
  console.log(n); // n is not defined
复制代码
  1. const声明:

声明一个常量,有暂时性死区、无变量提升有块级作用域

  // 声明一个常量,一旦声明必须立即初始化,一旦初始化之后,它的值不能再进行任何修改!
  const a = 2;
  a = 4;// Assignment to constant variable.(赋值给常数变量。)
  // 初始值为对象时为特殊情况
  const obj = {};
  obj.name = "张三";
  obj.age = 24;
  console.log(obj); // {name: "张三", age: 24}
  // 初始值为数组时也是特殊情况
  const arr = [];
  arr[0] = 1;
  arr[1] = 2;
  console.log(arr); // [1, 2]
  // 其他的同let是一样的
复制代码
  1. var声明:

var声明一个变量,无暂时性死区,有变量提升无块级作用域,只有函数作用域和全局作用域

  // 声明一个变量
  var a = 1;
  a = 2;
  console.log(a); // 2
  // 无暂时性死区
  var a = 3;
  {
    a = 123;
    var a;
  }
  console.log(a); // 123
  // 变量提升
  console.log(a); // undefined
  var a = 3;
  // 全局作用域:
  var a = 3;
  function fun () {
    console.log(a);
  }
  fun(); // 3
  // 函数作用域
  function fun () {
    var a = 3;
    console.log(a);
  }
  fun(); // 3
  console.log(a); // a is not defined
复制代码

补充:对象window中声明的变量,具有全局作用域,在函数中声明的变量具有函数作用域,在代码块中用let、const声明的变量具有块级作用域。

变量的解构赋值:

这个东西只说用法,没有太多概念性的东西

  1. 交换变量的值
  let x = 1;
  let y = 2;
  [x, y] = [y, x];
  console.log(x,y); // 2 1
复制代码
  1. 从函数返回多个值
  // 返回一个数组
  function fun () {
    return [1, 2, 3];
  }
  let [x, y, z] = fun();
  console.log(x, y, z); // 1, 2, 3
  // 返回一个对象
  function fun () {
    return {
      name: '张三',
      age: 24,
      sex: '女'
    }
  }
  let {name, age, sex} = fun();
  console.log(name, age, sex); // 张三 24 女
复制代码
  1. 函数参数的定义
   // 参数是一组有次序的值
   function f([x, y, z]) { ... }
   f([1, 2, 3]);

   // 参数是一组无次序的值
   function f({x, y, z}) { ... }
   f({z: 3, y: 2, x: 1});
复制代码
  1. 提取 JSON 数据
 let data = {
   name: '张三',
   age: 24
 };
 let {name, age} = data;
 console.log(name, age); // 张三 24
复制代码
  1. 函数参数的解构赋值
   function add([x, y]){
     return x + y;
   }

   add([1, 2]); // 3
复制代码
  1. 遍历 Map 结构
   for (let [key, value] of map) {
     console.log(key + " is " + value);
   }
复制代码
  1. 输入模块的指定方法
   const { SourceMapConsumer, SourceNode } = require("source-map");
复制代码

字符串扩展

  1. 字符串的遍历器接口
   for (let codePoint of 'foo') {
     console.log(codePoint)
   }
复制代码
  1. 模板字符串
  let name = '张三';
  let str = `${name}的女朋友`;
  console.log(str); // 张三的女朋友
复制代码
  1. includes()

返回布尔值,表示是否找到了参数字符串。

  let str = "张三的女朋友";
  console.log(str.includes("张三")); // true
复制代码

补充: indexOf如果查找到了就返回索引,未找到则返回-1

  1. startsWith()

返回布尔值,表示参数字符串是否在原字符串的头部。

   let str = "张三的女朋友";
   console.log(str.startsWith("张")); || console.log(str.indexOf("张") == 0); // true
复制代码
  1. endsWith():

返回布尔值,表示参数字符串是否在原字符串的尾部。

   let str = "张三的女朋友";
   console.log(str.endsWith("友")); || console.log(str.indexOf("友") == str.length - 1); // true
复制代码
  1. repeat():

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

  let str = '6';
  console.log(str.repeat(3)); // 666
复制代码
  1. padStart()

用于头部补全

    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'
复制代码
  1. padEnd():

用于尾部补全

    'x'.padEnd(5, 'ab') // 'xabab'
    'x'.padEnd(4, 'ab') // 'xaba'
复制代码
  1. 关于去除空格

(1). trim():

方法会从一个字符串的两端删除空白字符。

  let str = ' 10 10 ';
  console.log(str); // ' 10 10 '
  console.log(str.trim()); // '10 10'
  // 如果要去除所有空格
  let str = ' 10 10 ';
  console.log(str.replace(/ /g, '')); // 1010
复制代码

(2). trimStart():

消除字符串头部的空格

  let str = ' 10 10 ';
  console.log(str); // ' 10 10 '
  console.log(str.trimStart()); // '10 10 '
复制代码

(3). trimEnd()

消除尾部的空格

  let str = ' 10 10 ';
  console.log(str); // ' 10 10 '
  console.log(str.trimEnd()); // ' 10 10'
复制代码
  1. replaceAll()

可以一次性替换所有匹配。

  'aabbcc'.replaceAll('b', '_') == 'aabbcc'.replace(/b/g, '_') // true
复制代码
  1. matchAll():

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

  let str = 'aabb';
  console.log([...str.matchAll(/a/g)]);//[["a", index: 0, input: "aabb", groups: undefined] ["a",
  index: 1, input: "aabb", groups: undefined]]
复制代码

正则表达式

  1. y 修饰符

“粘连”(sticky)修饰符。后一次匹配都从上一次匹配成功的下一个位置开始,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
复制代码
  1. 先行断言

x只有在y前面才匹配

let s = 'xyzx';
let RE = /x(?=y)/;
RE.exec(s); // ["x", index: 0, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第一个的位置
复制代码
  1. 先行否定断言

x只有不在y前面才匹配

let s = 'xyzx';
let RE = /x(?!y)/;
RE.exec(s); // ["x", index: 3, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第四个的位置
复制代码
  1. 后行断言

z只有在y后面才匹配

let s = 'xyzx';
let RE = /(?<=y)z/;
RE.exec(s); // ["z", index: 2, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第3个的位置
复制代码
  1. 后行否定断言

z只有不在y后面才匹配

let s = 'xyyiizx';
let RE = /(?<!y)z/;
RE.exec(s); // ["z", index: 5, input: "xyyiizx", groups: undefined]
注:以上可以看出匹配到的结果在第6个位置
复制代码

JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言

  1. flags

返回正则表达式的修饰符。

/xyz/i.flags // "i"
复制代码
  1. sticky

表示是否设置了y修饰符。

/xyz/y.sticky // true
复制代码

数值的扩展

  1. 数值分隔符

ES2021,允许 JavaScript 的数值使用下划线(_)作为分隔符。

1_000_000_000_000 // 1000000000000
复制代码

数值分隔符有几个使用注意点

-   不能放在数值的最前面(leading)或最后面(trailing)。
-   不能两个或两个以上的分隔符连在一起。
-   小数点的前后不能有分隔符。
-   科学计数法里面,表示指数的`e`或`E`前后不能有分隔符。
复制代码
  1. Number.isFinite()

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

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
复制代码
  1. Number.isNaN()

用来检查一个值是否为NaN

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
复制代码
  1. Number.parseInt()

ES6 将全局方法parseInt(),移植到Number对象上面,行为完全保持不变。为了逐步减少全局性方法,使得语言逐步模块化。

Number.parseInt('12.34') // 12
注:用来获取一个浮点数的整数部分
复制代码
  1. Number.parseFloat()

ES6 将全局方法parseFloat(),移植到Number对象上面,行为完全保持不变。为了逐步减少全局性方法,使得语言逐步模块化。

Number.parseFloat('123a') // 123
注:用来截取数值部分
复制代码
  1. Number.isInteger()

用来判断一个数值是否为整数。

Number.isInteger(25) // true
Number.isInteger(25.1) // false
复制代码

整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

Number.isInteger(25) // true
Number.isInteger(25.0) // true
复制代码

由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。

Number.isInteger(3.0000000000000002) // true
复制代码
  1. 安全整数## 和 Number.isSafeInteger()

JavaScript 能够准确表示的整数范围在-2^532^53之间(不含两个端点),超过这个范围,无法精确表示这个值ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

Number.MAX_SAFE_INTEGER === 9007199254740991// true
Number.MIN_SAFE_INTEGER === -9007199254740991// true
复制代码

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

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
复制代码

Math 对象的扩展

  1. Math.trunc()

用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc('123.456') // 123 // 内部使用`Number`方法将其先转为数值。
复制代码
  1. Math.sign()

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

-   参数为正数,返回`+1`;
-   参数为负数,返回`-1`;
-   参数为 0,返回`0`;
-   参数为-0,返回`-0`;
-   其他值,返回`NaN`。
复制代码
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
复制代码
  1. Math.cbrt()

用于计算一个数的立方根。

Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(1)  // 1
复制代码
  1. Math.imul()

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

Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4
复制代码
  1. Math.hypot

返回所有参数的平方和的平方根。

Math.hypot(3, 4);        // 5
注:勾三股四玄五
复制代码
  1. 对数方法
1.`Math.expm1(x)`返回 ex - 1,即`Math.exp(x) - 1`。
2. `Math.log1p(x)`方法返回`1 + x`的自然对数,即`Math.log(1 + x)`。如果`x`小于-1,返回`NaN`。
3. `Math.log10(x)`返回以 10 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
4. `Math.log2(x)`返回以 2 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
复制代码
  1. 双曲函数方法
-   `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)
复制代码

BigInt 数据类型

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回InfinityES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

  1. 为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n
1234 // 普通整数
1234n // BigInt

// BigInt 的运算
1n + 2n // 3n
复制代码
  1. BigInt 同样可以使用各种进制表示,都要加上后缀n
0b1101n // 二进制
0o777n // 八进制
0xFFn // 十六进制
复制代码
  1. BigInt 与普通整数是两种值,它们之间并不相等。
42n === 42 // false
复制代码
  1. typeof运算符对于 BigInt 类型的数据返回bigint
typeof 123n // 'bigint'
复制代码
  1. BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。
-42n // 正确
+42n // 报错
复制代码
  1. JavaScript 以前不能计算70的阶乘(即70!),因为超出了可以表示的精度。
let p = 1;
for (let i = 1; i <= 70; i++) {
  p *= i;
}
console.log(p); // 1.197857166996989e+100
复制代码
  1. 现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。
let p = 1n;
for (let i = 1n; i <= 70n; i++) {
  p *= i;
}
console.log(p); // 11978571...00000000n
复制代码
  1. 数学运算方面,BigInt 类型的+-***这四个二元运算符,与 Number 类型的行为一致。除法运算/会舍去小数部分,返回一个整数。
9n / 5n
// 1n
// 例外:
-   不带符号的右移位运算符`>>>`
-   一元的求正运算符`+`
复制代码
  1. BigInt 不能与普通数值进行混合运算。
1n + 1.3 // 报错
复制代码
  1. BigInt 与字符串混合运算时,会先转为字符串,再进行运算。
'' + 123n // "123"
复制代码

函数扩展

  1. 函数参数的默认值
function fn(x, y = 1) {
  console.log(x + y);
}
fn(10); // 11
// 注:通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
复制代码
  1. rest 参数
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
复制代码

3.严格模式

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

// 报错
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
  }
};
复制代码
  1. name 属性
function foo() {}
foo.name // "foo"
复制代码
  1. 箭头函数
const fn = x => x+1; // 最简写法
// 等价于:
const fn = (x) => {
    return x+1;
}
注:如果只有一个函数形参,()可省略;如果箭头函数只有一行语句{}和return可省略;
复制代码

特殊情况

//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
复制代码

特别注意!!!(此处面试考了几百遍,敲黑板!!!)

(1)箭头函数没有自己的`this`对象,内部的`this`就是定义时上层作用域中的`this`。。

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

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

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

数组的扩展

  1. 扩展运算符

复制数组

const a1 = [1, 2];
const a2 = [...a1]; // [1, 2];
注:返回的是一个新数组
复制代码

合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const newArr = [...arr1, ...arr2]; //['a', 'b', 'c'];

// `a3`和`a4`是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];

a3[0] === a1[0] // true
a4[0] === a1[0] // true
复制代码

将字符串转为真正的数组。

[...'hello'] 
// [ "h", "e", "l", "l", "o" ]
复制代码

数组去重

[...new Set(arr)];
注:如果是数组对象,不能使用该方法去重
复制代码
  1. 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']
复制代码
  1. Array.of()

用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
复制代码
  1. find()

用于找出第一个符合条件的数组成员。

[1, 4, -5, 10, -10].find(n => n < 0) // -5
复制代码
  1. findIndex()

返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

[1, 4, -5, 10, -10].findIndex(n => n == 1) // 0
复制代码
  1. fill()

使用给定值,填充一个数组。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
注:第一个参数是用什么填充,第二个是填充起始位置的索引,第三个参数是结束位置的索引
复制代码
  1. includes()

方法返回一个布尔值,表示某个数组是否包含给定的值,返回布尔值。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
复制代码
  1. keys()

是对键名的遍历,数组的键名为索引。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1
复制代码
  1. values()

是对键值的遍历

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'
复制代码
  1. entries()

是对键值对的遍历。

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
复制代码
  1. flat()

用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
复制代码
  1. flatMap()

flatMap()方法对原数组的每个成员执行一个函数

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

[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
复制代码

超级重点(最常用的数组方法)

  1. forEach

方法对数组的每个元素执行一次给定的函数。(类似于for循环)

[1,2,3].forEach((item, index, arr) => console.log(item,index,arr))
// 1 0 (3) [1, 2, 3]
// 2 1 (3) [1, 2, 3]
// 3 2 (3) [1, 2, 3]
注:个人喜欢结合箭头函数使用
复制代码

该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。

  1. filter

创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。(也就是过滤出满足当前条件的所有元素)

[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].filter(item => item.name == "张三");
// [{name: '张三',id: 1},{name: '张三',id: 3}] 筛选出name == "张三"的数据.
也可以设置同forEach一样的参数。
复制代码

该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。

  1. map

创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

// 第一种用法
[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].map(item => item.name);
// ["张三", "李四", "张三"]
// 第二种用法
[1, 2, 3].map(item => item*10);
// [10, 20, 30]
// 第三种用法
[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].map(item => {
    return {
        value: item.id,
        label: item.name
    }
});
// [{label: '张三',value: 1},{label: '李四',value: 2},{label: '张三',value: 3}]
复制代码

该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。

  1. some

方法测试数组中是不是至少有1个元素通过了被提供的函数测试。(只要有一个元素满足条件就返回true)

const roleList = [{roleId: 1, roleName: '超级管理员'},{roleId: 2, roleName: '审核员'},{roleId: 3, roleName: '学员'}]; // 当前用户的角色
// 判断他是不是超级管理员
const isAdmin = roleList.some(item => item.roleId == 1); // true
if (isAdmin) {
}
复制代码

该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。

  1. every

测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

const roleList = [{roleId: 1, roleName: '超级管理员'},{roleId: 2, roleName: '审核员'},{roleId: 3, roleName: '学员'}]; // 当前用户的角色
// 判断他不是超级管理员
const isAdmin = roleList.every(item => item.roleId != 1); // false
if (isAdmin) {
}
复制代码

该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。

  1. reduce()

累加

let reduce=(...arr)=>{ 
    return arr.reduce((num,sum)=>{ 
        return sum + num; 
    },0); 
} 
console.log(reduce(...arr));//15
复制代码

将二维数组转化为一维

const flattened = [[0, 1], [2, 3], [4, 5]].reduce((a, b) => a.concat(b),[]);
// [0, 1, 2, 3, 4, 5]
复制代码

计算数组中每个元素出现的次数

const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

const countedNames = names.reduce((allNames, name) => {
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
复制代码

数组去重

let arr = [1,2,1,2,3,5,4,5,3,4,4,4,4];
let result = arr.sort().reduce((init, current) => {
    if(init.length === 0 || init[init.length-1] !== current) {
        init.push(current);
    }
    return init;
}, []);
console.log(result); //[1,2,3,4,5]
复制代码

按顺序运行Promise

function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input)
  );
}

function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

function f3(a) {
 return a * 3;
}

function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10)
  .then(console.log);   // 1200
复制代码

使用 reduce实现map

if (!Array.prototype.mapUsingReduce) {
  Array.prototype.mapUsingReduce = function(callback, thisArg) {
    return this.reduce(function(mappedArray, currentValue, index, array) {
      mappedArray[index] = callback.call(thisArg, currentValue, index, array)
      return mappedArray
    }, [])
  }
}

[1, 2, , 3].mapUsingReduce(
  (currentValue, index, array) => currentValue + index + array.length
) // [5, 7, , 10]
复制代码

该方法可以传入四个参数,acc累加器,cur当前值,idx当前索引,arr为源数组。以上操作还有其他方式,就不一一列举了。

对象的扩展

  1. 属性的简洁表示法
const name = "张三";
const id = 1;
const obj = {
  name: name,
  id: id
};
// 如果对象的属性名和变量名一致,可简写成:
const obj = {name,id};
obj // {name: "张三", id: 1}
复制代码
  1. 属性名表达式
// 第一种
obj['a' + 'bc'] = 123;

// 第二种
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};

obj.hello() // hi
复制代码
  1. 方法的 name 属性
const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"
复制代码
  1. 属性的可枚举性和遍历

Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。

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

描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略`enumerable`为`false`的属性。

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

属性的遍历

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. 对象的扩展运算符

解构赋值

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

扩展运算符

(1). 取出参数对象的所有可遍历属性,拷贝到当前对象之中

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
复制代码

(2). 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
复制代码

(3). 合并两个对象(此处用的较多)

let params = {
    ...this.pageObj, // 分页对象
    ...this.searchForm  // 搜索框表单对象
}
复制代码

(4). 如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉

let obj = {name: '张三', id: 1}; // 可能里面还有几十条数据,但是我们只想改变用户名
let userObj = {...obj,name:'李四'}; // {name: "李四", id: 1}
复制代码

扩展: 如果想完整克隆一个对象,还拷贝对象原型的属性,可采用以下方法。

// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)
复制代码
  1. Object.is()

它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
复制代码
  1. Object.assign()

用于对象的合并

const target = { a: 1 };

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

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

如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

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

浅拷贝

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2
// `Object.assign()`拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
复制代码

数组的处理

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
复制代码

取值函数的处理

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

Object.assign(target, source)
// `Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
复制代码

为对象添加属性

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}
复制代码

为对象添加方法

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

// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};
复制代码

为属性指定默认值

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

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}
复制代码
  1. 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 } }
复制代码
  1. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()

以上方法都是用来操作原型链的,比较简单省略不写

  1. Object.keys(),Object.values(),Object.entries()

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

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
复制代码
  1. Object.fromEntries()

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

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

运算符扩展

  1. 指数运算符(**
2 ** 2 // 4
2 ** 3 // 8

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;
复制代码
  1. 链判断运算符

如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下面这样。

// 错误的写法
const  firstName = message.body.user.firstName || 'default';

// 正确的写法
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
  
// 链判断运算符简写
const firstName = message?.body?.user?.firstName || 'default';
复制代码

(1)短路机制

本质上,?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

a?.[++x]
// 等同于
a == null ? undefined : a[++x]
复制代码

(2)括号的影响

如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
复制代码

(3)报错场合

以下写法是禁止的,会报错。

// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c
复制代码

4)右侧不得为十进制数值

为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。

  1. Null 判断运算符

读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。

const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
复制代码

开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效。 为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
复制代码
  1. 逻辑赋值运算符

将逻辑运算符与赋值运算符进行结合。

// 或赋值运算符
x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

// 老的写法
user.id = user.id || 1;

// 新的写法
user.id ||= 1;
复制代码

Symbol

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

const sym = Symbol('foo');

sym.description // "foo"
复制代码

你只需要暂时知道有这么个数据类型,表示独一无二的值即可。因为项目上真的很少很少用到。如果想看详细介绍,请移步阮一峰的es6文档

Set 和 Map 数据结构

  1. Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

  set
  1. add方法为其添加成员;
  2. has查看是否有该值;
  3. delete删除某个值,返回布尔值
  4. clear清除所有成员
  5. size返回Set实例的成员总数
  // 比较简单,省略代码
复制代码

2.Map

它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

 map
  1. has查看是否有该值
  2. get获取属性
  3. set设置属性
  4. delete删除属性
  5. size属性返回 Map 结构的成员总数。
  6. clear方法清除所有成员,没有返回值。
  // 比较简单,省略代码
复制代码
  1. Set和Map的遍历
//set
let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}

//-------------------------------------------------------------------------
// map
const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"
复制代码

4.WeakSet

1.WeakSet 的成员只能是对象,而不能是其他类型的值。2.WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);

const ws = new WeakSet();
const obj = {};
const foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false

ws.delete(window);
ws.has(window);    // false
复制代码
  1. WeakMap

1.只接受对象作为键名(null除外),不接受其他类型的值作为键名。2.WeakMap的键名所指向的对象,不计入垃圾回收机制。

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"
复制代码

WeakMap 的用途

let myWeakmap = new WeakMap();

myWeakmap.set(
  document.getElementById('logo'),
  {timesClicked: 0})
;

document.getElementById('logo').addEventListener('click', function() {
  let logoData = myWeakmap.get(document.getElementById('logo'));
  logoData.timesClicked++;
}, false);
复制代码

上面代码中,document.getElementById('logo')是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

  1. WeakRef

用于直接创建对象的弱引用

let target = {};
let wr = new WeakRef(target);
复制代码

target是原始对象,构造函数WeakRef()创建了一个基于target的新对象wr。这里,wr就是一个 WeakRef 的实例,属于对target的弱引用,垃圾回收机制不会计入这个引用,也就是说,wr的引用不会妨碍原始对象target被垃圾回收机制清除。

Proxy

用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35
复制代码
-   **get(target, propKey, receiver)** :拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`。
-   **set(target, propKey, value, receiver)** :拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。
-   **has(target, propKey)** :拦截`propKey in proxy`的操作,返回一个布尔值。
-   **deleteProperty(target, propKey)** :拦截`delete proxy[propKey]`的操作,返回一个布尔值。
-   **ownKeys(target)** :拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`、`for...in`循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而`Object.keys()`的返回结果仅包括目标对象自身的可遍历属性。
-   **getOwnPropertyDescriptor(target, propKey)** :拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。
-   **defineProperty(target, propKey, propDesc)** :拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。
-   **preventExtensions(target)** :拦截`Object.preventExtensions(proxy)`,返回一个布尔值。
-   **getPrototypeOf(target)** :拦截`Object.getPrototypeOf(proxy)`,返回一个对象。
-   **isExtensible(target)** :拦截`Object.isExtensible(proxy)`,返回一个布尔值。
-   **setPrototypeOf(target, proto)** :拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
-   **apply(target, object, args)** :拦截 Proxy 实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。
-   **construct(target, args)** :拦截 Proxy 实例作为构造函数调用的操作,比如`new proxy(...args)`。
复制代码

Reflect

也是 ES6 为了操作对象而提供的新 API,该方法用的比较少,所以暂不讨论,只需要暂时知道有这些东西即可。

(1) 将`Object`对象的一些明显属于语言内部的方法(比如`Object.defineProperty`),放到`Reflect`对象上。现阶段,某些方法同时在`Object`和`Reflect`对象上部署,未来的新方法将只部署在`Reflect`对象上。也就是说,从`Reflect`对象上可以拿到语言内部的方法。
(2) 修改某些`Object`方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回`false`。
(3) 让`Object`操作都变成函数行为。某些`Object`操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。
(4)`Reflect`对象的方法与`Proxy`对象的方法一一对应,只要是`Proxy`对象的方法,就能在`Reflect`对象上找到对应的方法。这就让`Proxy`对象可以方便地调用对应的`Reflect`方法,完成默认行为,作为修改行为的基础。也就是说,不管`Proxy`怎么修改默认行为,你总可以在`Reflect`上获取默认行为。
复制代码

Promise

  1. 含义

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

特点。

(1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`pending`(进行中)、`fulfilled`(已成功)和`rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`pending`变为`fulfilled`和从`pending`变为`rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
复制代码

2.基本用法

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1
复制代码
  1. .then()

操作成功时的回调

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
复制代码
  1. .catch()

操作失败时的回调

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});
复制代码
  1. .finally()

指定不管 Promise 对象最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码
  1. .all()

用于将多个 Promise 实例,包装成一个新的 Promise 实例。(可用于同时进行多个请求)

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
复制代码
  1. .race()

将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);
复制代码
  1. .allSettled()

等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
复制代码
  1. .any()

该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {  // 只要有一个 fetch() 请求成功
  console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
  console.log(error);
});
复制代码

10..resolve()操作成功时传入

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么`Promise.resolve`将不做任何修改、原封不动地返回这个实例。
复制代码

(2) 参数是一个thenable对象

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
复制代码

(3).参数不是具有then()方法的对象,或根本就不是对象

const p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
// Hello
复制代码

(4)不带有任何参数

const p = Promise.resolve();

p.then(function () {
  // ...
});
复制代码

事件循环(考点)

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three
复制代码

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

  1. reject()操作失败时传入
Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true
复制代码

Iterator 和 for...of 循环

  1. Iterator

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

Iterator 的遍历过程:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的`next`方法,直到它指向数据结构的结束位置。
复制代码
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 接口的数据结构如下。

-   Array
-   Map
-   Set
-   String(字符串是一个类似数组的对象,也原生具有 Iterator 接口。)
-   TypedArray
-   函数的 arguments 对象
-   NodeList 对象(类似数组的对象)
复制代码

只要数据结构具备Iterator(遍历器)接口便可以使用for...of遍历,对象(Object)没有默认部署 Iterator 接口,但是能通过特殊的方式进行for...of遍历

  1. for...of 循环

数组

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
}
复制代码

字符串

let str = "hello";

for (let s of str) {
  console.log(s); // h e l l o
}
复制代码

NodeList(类数组对象)

// DOM NodeList对象
let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("test");
}
复制代码

arguments对象

function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs('a', 'b');
复制代码

map和set

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
复制代码

对象

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}
复制代码

Generator 函数

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5
复制代码
  1. 遍历方法的比较(重点和面试考点)

for和forEach的区别?

`forEach`循环,`break`命令或`return`命令都不能奏效,而原始的for循环可以。另外补充一句forEach只有抛出异常才能退出!
复制代码

for...in和for...of的区别?

-   数组的键名是数字,但是`for...in`循环是以字符串作为键名“0”、“1”、“2”等等。
-   `for...in`循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
-   某些情况下,`for...in`循环会以任意顺序遍历键名。
复制代码
  1. for...of的优势
-   有着同`for...in`一样的简洁语法,但是没有`for...in`那些缺点。
-   不同于`forEach`方法,它可以与`break`、`continue`和`return`配合使用。
-   提供了遍历所有数据结构的统一操作接口。
复制代码

Generator 函数

  1. 基本定义
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数
复制代码
  1. 特征

两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* getNumber() {
  yield 1;
  yield 2;
}

//调用方式
getNumber().next() // 1
getNumber().next() //2
注:可将调用后的函数先赋值给变量
复制代码
  1. yield 表达式
(1)遇到`yield`表达式,就暂停执行后面的操作,并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值。

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

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

(4)如果该函数没有`return`语句,则返回的对象的`value`属性值为`undefined`。
复制代码
  1. yield 表达式*

用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
复制代码
  1. .throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
复制代码
  1. .return()
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
复制代码
  1. 作为对象属性的 Generator 函数
let obj = {
  * myGeneratorMethod() {
    ···
  }
};
// es6对象中函数可以省略function
复制代码
  1. Generator 与协程

(1). 协程

可以并行执行、交换执行权的线程(或函数),就称为协程。

(2). 协程与普通线程的差异

协程适合用于多任务运行的环境。在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。

  1. 应用

异步操作的同步化表达

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()

// 卸载UI
loader.next()
复制代码

控制流管理

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}
复制代码

部署 Iterator 接口(利用 Generator 函数,可以在任意对象上部署 Iterator 接口。)

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
复制代码

作为数据结构

function* doStuff() {
  yield fs.readFile.bind(null, 'hello.txt');
  yield fs.readFile.bind(null, 'world.txt');
  yield fs.readFile.bind(null, 'and-such.txt');
}
复制代码

Generator 函数的异步应用

  1. 传统方法
ES6 诞生以前,异步编程的方法,大概有下面四种。
-   回调函数
-   事件监听
-   发布/订阅
-   Promise 对象
复制代码
  1. 同步与异步的区别(面试常问)
异步: 简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
同步: 连续的执行。
复制代码
  1. 协程的 Generator 函数实现
function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
复制代码
  1. 异步任务的封装
var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});
复制代码

async 函数

  1. 介绍

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

`async`函数对 Generator 函数的改进,体现在以下四点:
(1)内置执行器。
(2)更好的语义。
(3)更广的适用性。
(4)返回值是 Promise。(面试常问)
复制代码
  1. 基本用法
// 函数声明
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 () => {};
复制代码
  1. 语法

返回 Promise 对象

(1). async函数返回一个 Promise 对象。 async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"
复制代码

(2).async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了
复制代码

Promise 对象的状态变化

只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

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 命令

(1).正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123
复制代码

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

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
// 1000
复制代码

(3).await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
复制代码

(4).任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
复制代码

(5).有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

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方法,处理前面可能出现的错误。

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后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
复制代码

防止出错的方法,也是将其放在try...catch代码块之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}
复制代码

使用注意点

第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
复制代码

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

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]); 

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
复制代码

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}
复制代码

如果将forEach方法的参数改成async函数,也有问题。

function dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
复制代码

正确的写法是采用for循环

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
复制代码

如果确实希望多个请求并发执行,可以使用Promise.all方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}
复制代码

第四点,async 函数可以保留运行堆栈。

const a = async () => {
  await b();
  c();
};
复制代码

async 函数的实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

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); });
  });
}
复制代码

与其他异步处理方法的比较

实现某个需求时代码完全都是 Promise 的 API(`then`、`catch`等等),操作本身的语义反而不容易看出来。
Generator问题在于,必须有一个任务运行器,自动执行 Generator 函数
async它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少
复制代码

按顺序完成异步操作

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
复制代码

顶层 await

根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
目前,有一个[语法提案],允许在模块的顶层独立使用`await`命令,使得上面那行代码不会报错了。这个提案的目的,是借用`await`解决模块异步加载的问题。
注意,顶层`await`只能用在 ES6 模块,不能用在 CommonJS 模块。这是因为 CommonJS 模块的`require()`是同步加载,如果有顶层`await`,就没法处理加载了。
注:这个提案了解即可。
复制代码

Class

  1. 简介

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

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

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
const p = new Point(1,2);
Point.toString();
注意:方法与方法之间不需要逗号分隔,加了会报错。使用的时候,也是直接对类使用`new`命令,跟构造函数的用法完全一致。
复制代码

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};
复制代码

由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign()方法可以很方便地一次向类添加多个方法。

class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});
复制代码

类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
复制代码
  1. constructor 方法

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

class Point {
}

// 等同于
class Point {
  constructor() {}
}
复制代码

类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

class Foo {
  constructor() {
    return Object.create(null);
  }
}

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
复制代码

__proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

  1. 取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'
复制代码
  1. 类的属性名,可以采用表达式。

类的属性名,可以采用表达式。

let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}
复制代码
  1. Class 表达式

采用 Class 表达式,可以写出立即执行的 Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"
复制代码
  1. 注意点

(1)严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。

(2)不存在提升

new Foo(); // ReferenceError
class Foo {}
复制代码

(3)name 属性

class Point {}
Point.name // "Point"
复制代码

(4)Generator 方法

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world
复制代码

(5)this 的指向

类的方法内部如果含有this,它默认指向类的实例。

  1. 静态方法

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
复制代码
  1. 实例属性的新写法
class IncreasingCounter {
  _count = 0;
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}
复制代码
  1. 静态属性
class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}
复制代码
  1. 私有方法和私有属性

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。

class Widget {

  // 公有方法
  foo (baz) {
    this._bar(baz);
  }

  // 私有方法
  _bar(baz) {
    return this.snaf = baz;
  }

  // ...
}
复制代码

目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示。

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}
复制代码
  1. in 运算符

in运算符判断当前类A的实例,是否有私有属性#foo,如果有返回true,否则返回false

class A {
  use(obj) {
    if (#foo in obj) {
      // 私有属性 #foo 存在
    } else {
      // 私有属性 #foo 不存在
    }
  }
}
复制代码

12.静态块

ES2022 引入了静态块(static block),允许在类的内部设置一个代码块,在类生成时运行一次,主要作用是对静态属性进行初始化。

class C {
  static x = ...;
  static y;
  static z;

  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}
复制代码
  1. 继承

通过extends关键字实现继承。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}
复制代码

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
复制代码

在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。

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

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}
复制代码

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

Object.getPrototypeOf(ColorPoint) === Point
// true
复制代码

super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

class A {}

class B extends A {
  constructor() {
    super();
  }
}
复制代码
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();
复制代码

类的 prototype 属性和__proto__属性

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

实例的 proto 属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
复制代码

原生构造函数的继承

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined
复制代码

Module

  1. 介绍

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。 2. 严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

  1. export

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

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };
复制代码

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
复制代码

正确输出的方法:

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
复制代码
  1. import

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

import { lastName as surname } from './profile.js';
复制代码
  1. 模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}
复制代码
import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
复制代码
  1. export default 命令

export default命令,为模块指定默认输出。

// `export-default.js`,它的默认输出是一个函数。
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'
复制代码
  1. export 与 import 的复合写法

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

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
复制代码
  1. 模块的继承

假设有一个circleplus模块,继承了circle模块。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}
复制代码
  1. 跨模块常量

将两个文件中的常量都保存在某个对象中,然后在常量文件(index.js)中引入这两个模块儿的常量,然后在其他文件中引入这个常量文件

// 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'];
复制代码
// constants/index.js
export {db} from './db';
export {users} from './users';
复制代码
// script.js
import {db, users} from './constants/index';
复制代码

参考文献:

1.es6阮一峰文档

2.MDN

后记

只是简简单单的过了一遍文档吧,用的少的一些东西是被我省略了的,面试问的多的差不多还是给你们标注出来了。我还是大概说一下嘛,箭头函数和普通函数的区别问过,异步面试必问,深浅拷贝是必问的,以及各种循环之间的区别是必问的,还有就是扩展运算符的应用场景也会问。就这么多吧,过了一遍之后,发现以前不太清楚模棱两可的地方,瞬间清晰了好几倍。

文章分类
前端