ES2020-ES2015学习笔记

说明

自 2015 年发布 ES6 以来,JavaScript 一直在快速发展,每次迭代中都会出现大量新功能。 JavaScript 语言规范的新版本每年更新一次,新语言功能建议的定稿比以往更快。

此文章主要记录了ES语法的新特性。内容包括ES2020-ES2016的语法,每个语法都会有相应的demo(demo主要参考其他博客),demo在最新版本的谷歌浏览器81.0.4044.113的控制台可以直接运行。

ES2020

1. 类的私有字段
通过 # 可以给 class 添加私有变量。

// 类的私有字段
class Counter {
  #number = 10
  increment() {
    this.#number++
  }
  getNum() {
    return this.#number
  }
}
const counter = new Counter()
counter.increment();

console.log(counter.getNum())    //11
console.log(counter.#number)    //调用class的私有变量会报错:SyntaxError

在 class 的外部我们无法获取该值。当我们尝试输出 counter.#number,语法错误被抛出:Uncaught SyntaxError: Private field ‘#number’ must be declared in an enclosing class

2. 可选链操作符
可选链操作符?.能够去读取一个被连接对象的深层次的属性的值而无需明确校验链条上每一个引用的有效性。以下面对象为例:

const obj = {
  prop1: {
    prop2: {
      prop3: {
        prop4: {
          prop5: 5
        }
      }
    }
  }
}

假如要打印出prop5的结果,必须先要判断prop1,prop2,prop3,prop4是否为null或者undefined。

obj.prop1 &&
obj.prop1.prop2 &&
obj.prop1.prop2 &&
obj.prop1.prop2.prop3 &&
obj.prop1.prop2.prop3.prop4 &&
console.log(obj.prop1.prop2.prop3.prop4.prop5);

更简单的方式是使用可选链操作符。如果obj的prop1,prop2,prop3,prop4为null或者undefined,console.log结果直接返回null或者undefined,而不会出现像console.log(obj.prop1.prop2.prop3.prop4.prop5)那样的Uncaught TypeError: Cannot read property ‘prop3’ of undefined。

console.log(obj?.prop1?.prop2?.prop3?.prop4?.prop5);

3.空位合并运算符
现在有下面这个表达式。我们都知道,当x为null,undefined,0,false,空字符的时候,y的值都会输出500。但是我们只想要x为 undefined 或 null 的时候,才让y输出500。怎么办?

const y = x || 500;

这时候就需要引入空位合并运算符的概念,写作??

const y = x ?? 500;

4. BigInt(内容参考自MDN)
Javascript中的 Number 能表示的最大数字为2^53 - 1。但是,有时候可能会不够用。因此ES2020引入了BigInt 这个标准内置对象,可以表示任意大的整数。

const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);// ↪ 9007199254740991n
const hugeString = BigInt("9007199254740991");// ↪ 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");// ↪ 9007199254740991n
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111");
// ↪ 9007199254740991n

BigInt在某些方面类似于 Number ,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。

BigInt的类型
使用 typeof 测试时, BigInt 对象返回 “bigint” :

typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true

使用 Object 包装后, BigInt 被认为是一个普通 “object” :

typeof Object(1n) === 'object'; // true

BigInt的运算
BigInt 可以和 +、*-**% 。除 >>> (无符号右移)之外的位操作进行运算。因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigInt。为了兼容 asm.js ,BigInt 不支持单目 (+) 运算符。另外,当使用 BigInt 时,带小数的运算会被取整。

 const rounded = 5n / 2n;  // 2n, not 2.5n

BigInt的比较
BigInt 和 Number 不是严格相等的,但是宽松相等的。

0n === 0 // false
0n == 0 // true

Number 和 BigInt 可以进行比较。

1n < 2 // true
2n > 1 // true
2 > 2  // false
2n > 2 // false
2n >= 2 // true

两者也可以混在一个数组内并排序。

const mixed = [4n, 6, -12n, 10, 4, 0, 0n];  // [4n, 6, -12n, 10, 4, 0, 0n]
mixed.sort(); //  [-12n, 0, 0n, 10, 4n, 4, 6]

注意被 Object 包装的 BigInts 使用 object 的比较规则进行比较,只用同一个对象在比较时才会相等。

0n === Object(0n); // false
Object(0n) === Object(0n); // false

const o = Object(0n);
o === o // true

BigInt的条件
BigInt 在需要转换成 Boolean 的时表现跟 Number 类似:如通过 Boolean 函数转换;用于 Logical Operators ||, &&, 和 ! 的操作数;或者用于在像 if statement 这样的条件语句中。

if (0n) {
  console.log('Hello from the if!');
} else {
  console.log('Hello from the else!');
}
// "Hello from the else!"

0n || 12n// 12n

0n && 12n// 0n

Boolean(0n)// false

Boolean(12n)// true

!12n// false

!0n// true

BigInt静态方法

BigInt.asIntN(width, bigint);

width表示可存储整数的位数;bigint表示要存储在指定位数上的整数。
返回值:bigint 模(modulo) 2^width 作为有符号整数的值。

同样的,还有BigInt.asUintN(width, bigint)将 BigInt 转换为一个 0 和 2width-1 之间的无符号整数。

BigInt的实例方法

BigInt.prototype.toLocaleString()

返回此数字的 language-sensitive 形式的字符串。覆盖 Object.prototype.toLocaleString() 方法。

BigInt.prototype.toString()

返回以指定基数(base)表示指定数字的字符串。覆盖 Object.prototype.toString() 方法。

BigInt.prototype.valueOf()

返回指定对象的基元值。 覆盖 Object.prototype.valueOf() 方法。
具体使用方法,可查阅MDN

5. Promise.allSettled()
ES6引入的Promise极大地提升了我们在写js应用的编码体验, 我们可以很方便得使用Promise进行异步流程控制, 但是有一种情况处理起来其实很是不方便, 这就是Promise.allSettled提案的存在理由.

假设用户在页面上面同时填了3个独立的表单, 这三个表单分三个接口提交到后端, 三个接口独立, 没有顺序依赖, 这个时候我们需要等到请求全部完成后给与用户提示表单提交的情况

分析: 在多个promise同时进行时我们很快会想到使用Promise.all来进行包装, 但是由于Promise.all的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与我们需求不符合。

Promise.allSettled跟Promise.all类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 其不会进行短路, 也就是说当Promise全部处理完成后我们可以拿到每个Promise的状态, 而不管其是否处理成功.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result)));

// expected output:
// > Object { status: "fulfilled", value: 3 }
// > Object { status: "rejected", reason: "foo" }

6. 动态import
关键字import可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise。

import('/modules/my-module.js')
  .then((module) => {
    // Do something with the module.
  });

这种使用方式也支持 await 关键字。

let module = await import('/modules/my-module.js');

7. globalThis
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是 window,但 Node 和 Web Worker 没有window。
  • 浏览器和 Web Worker 里面,self 也指向顶层对象,但是 Node 没有 self。
  • Node 里面,顶层对象是 global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用 this 变量,但是有局限性。

  • 全局环境中,this 会返回顶层对象。但是,Node 模块和 ES6 模块中,this 返回的是当前模块。
  • 函数里面的 this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this 会指向顶层对象。但是,严格模式下,这时 this 会返回 undefined。
  • 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全策略),那么 eval、new Function 这些方法都可能无法使用。

globalThis 之前,获取这些个全局对象的唯一方式就是 Function(return this)(),但是这在某些情况下会违反 CSP 内容安全规则,所以,es6-shim 使用了类似如下的方式。

// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// 方法二
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};

而现在可以直接使用globalThis来获取全局对象。

8. String.prototype.matchAll
matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。可以使用一个扩展运算符…,把迭代器转换为数组。

let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';
let array = [...str.matchAll(regexp)];
console.log(array[0]);
console.log(array[1]);

// answer
//["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]
//["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]

ES2019

1. Array.prototype.flat()
flat() 是一种用于展平嵌套数组的方法。该方法接受一个参数,用于表示嵌套的深度,默认为1。

const arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

2. Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

//callback 可以生成一个新数组中的元素的函数,可以传入三个参数:
//currentValue:当前正在数组中处理的元素
//index:可选,数组中正在处理的当前元素的索引。
//array:可选,被调用的 map 数组
//thisArg:可选,执行 callback 函数时 使用的this 值。

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
    // return element for new_array
}[, thisArg])

返回一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。

var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
// only one level is flattened
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

3. Object.fromEntries()
Object.fromEntries() 方法把键值对列表转换为一个对象。同样的,还有Object.entries() ,将对象转换成键值对列表。

// 语法:Object.fromEntries(iterable);
// iterable为可迭代对象,类似 Array 、 Map 或者其它实现了可迭代协议的对象。

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

const obj = Object.fromEntries(entries);

console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }

4. trimStart()
trimStart() 方法删除字符串开头的空格。 trimLeft() 是此方法的别名。

const greeting = '   Hello world!   ';

console.log(greeting);
// expected output: "   Hello world!   ";

console.log(greeting.trimStart());
console.log(greeting.trimLeft());
// expected output: "Hello world!   ";

5. trimEnd()
trimEnd() 方法从一个字符串的末端移除空白字符。trimRight() 是这个方法的别名。

const greeting = '   Hello world!   ';

console.log(greeting);
// expected output: "   Hello world!   ";

console.log(greeting.trimEnd());
console.log(greeting.trimRight());
// expected output: "   Hello world!";

6. Optional catch binding
在 ES10 之前,我们必须通过语法为 catch 子句绑定异常变量,无论是否有必要。很多时候 catch 块是多余的。 ES10 提案使我们能够简单的把变量省略掉。之前 try…catch 中的err是必须的,指定局部变量应用的错误。

try {
  // tryCode
} catch(err) {
  // catchCode
}

现在将 err 变成 optional 的,可以省略 catch 后的括号和错误对象。

try {
  // tryCode
} catch {
  // catchCode
}

7. Symbol.prototype.description
description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

console.log(Symbol('desc').description);
// expected output: "desc"

console.log(Symbol.iterator.description);
// expected output: "Symbol.iterator"

console.log(Symbol.for('foo').description);
// expected output: "foo"

console.log(Symbol('foo').description + 'bar');
// expected output: "foobar"

8. JSON Superset 超集
之前如果JSON字符串中行分割符(\u2028)和段分割符(\u2029),那么在解析的时候会报错。

JSON.parse('"\u2028"');
// SyntaxError

现在ES2019对其多了修改。

JSON.parse('"\u2028"');
// ''

9. JSON.stringify() 加强格式转化
我们看一下熟知的emoji表现:

''.length;
// 2

JavaScript将emoji解释为两个字符的原因是UTF-16将emojis(以及其他不寻常的字符)表示为两个代理项的组合。我们的emoji用字符’\uD83D’和’\uDE0E’编码。但是如果试图单独编写这样一个字符,例如’\uD83D’,则会认为这是一个无效的文本字符串。在早期版本中,这些字符将替换为特殊字符:

JSON.stringify('\uD83D');
// '"�"'

现在在字符代码之前插入转义字符,结果仍是可读且有效的UTF-8/UTF-16代码:

JSON.stringify('\uD83D');
// '"\\ud83d"'

10. Array.prototype.sort() 更加稳定
之前,规范允许不稳定的排序算法,如快速排序。

let array = [
  {a: 1, b: 2},
  {a: 2, b: 2},
  {a: 1, b: 3},
  {a: 2, b: 4},
  {a: 5, b: 3}
];
array.sort((a, b) => a.a - b.a);
// [{a: 1, b: 2}, {a: 1, b: 3}...] / [{a: 1, b: 3}, {a: 1, b: 2}...]

在之前的排序中,可能出现[{a: 1, b: 2}, {a: 1, b: 3}…]、[{a: 1, b: 3}, {a: 1, b: 2}…]等多种情况。
现在所有主流浏览器都使用稳定的排序算法。实际上,这意味着如果我们有一个对象数组,并在给定的键上对它们进行排序,那么列表中的元素将保持相对于具有相同键的其他对象的位置。

0: {a: 1, b: 2}
1: {a: 1, b: 3}
2: {a: 2, b: 2}
3: {a: 2, b: 4}
4: {a: 5, b: 3}

11. Function.prototype.toString() 重新修订
从ES2019开始,Function.prototype.toString()将从头到尾返回源代码中的实际文本片段。这意味着还将返回注释、空格和语法详细信息。

function foo() { /* a comment */ }

之前,Function.prototype.toString()只会返回了函数的主体,但没有注释和空格。

foo.toString();
// 'function foo() {}'

但现在,函数返回的结果与编写的一致。

foo.toString();
// 'function /* a comment  */ foo () {}'​

ES2018

1. Asynchronous iteration (异步迭代)
for await…of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如arguments 或者NodeList),TypedArray,Map, Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。下面是迭代异步生成器的例子。

async function* asyncGenerator() {
  var i = 0;
  while (i < 3) {
    yield i++;
  }
}

(async function() {
  for await (num of asyncGenerator()) {
    console.log(num);
  }
})();
// 0
// 1
// 2

2. 展开语法(Spread syntax)
展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。(字面量一般指 [1, 2, 3] 或者 {name: “mdn”} 这种简洁的构造方式)。

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [100, 200, 400];

console.log(sum(...numbers)); // expected output: 700
console.log({...numbers});  // expected ouput: Object { 0: 100, 1: 200, 2: 400 }
// expected output: 6

与之相反的还有reset属性。

const { first, second, ...others } = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
console.log(first) // 1
console.log(second) // 2
console.log(others) // { third: 3, fourth: 4, fifth: 5 }

3. Promise.prototype.finally()
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。

fetch('file.json')
  .then(data => data.json())
  .catch(error => console.error(error))
  .finally(() => console.log('finished'))

4. 正则表达式—先行断言与后行断言
先行断言(lookahead):您可以使用 ?= 匹配一个字符串,该字符串后面跟着一个特定的子字符串。

/Roger(?= Waters)/.test('Roger is my dog') //false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true

先行断言的逆操作,使用?! 匹配一个字符串,该字符串后面没有一个特定的子字符串。

/Roger(?! Waters)/.test('Roger is my dog') //true
/Roger(?! Waters)/.test('Roger Waters is a famous musician') //false

同样的,还有后行断言?<=,以及后行断言的逆操作?

/(?<=Roger) Waters/.test('Pink Waters is my dog') //false
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true

/(?<!Roger) Waters/.test('Pink Waters is my dog') //true
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false

5. unicode属性转义\p{…}和\P{…}
\p{…}和\P{…},允许正则表达式匹配符合 Unicode 某种属性的所有字符。其中,\p{} 匹配所有 Unicode 字符,否定为 \P{} 。任何 unicode 字符都有一组属性。 例如,Script 确定语言系列,ASCII 是一个布尔值, 对于 ASCII 字符,值为 true,依此类推。 您可以将此属性放在花括号中,正则表达式将检查是否为真:

/^\p{ASCII}+$/u.test('ABC@')  //✅
/^\p{ASCII}+$/u.test('ABC') //❌

ASCII_Hex_Digit 是另一个布尔属性,用于检查字符串是否仅包含有效的十六进制数字:

/^\p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅
/^\p{ASCII_Hex_Digit}+$/u.test('h')                //❌

还有许多其他布尔属性,您只需通过在花括号中添加它们的名称来检查它们,包括 Uppercase, Lowercase, White_Space, Alphabetic, Emoji 等:

/^\p{Lowercase}$/u.test('h') //✅
/^\p{Uppercase}$/u.test('H') //✅
 
/^\p{Emoji}+$/u.test('H')   //❌
/^\p{Emoji}+$/u.test('') //✅

另外,为了/p和/P能正常执行,需要在正则表达式的后面加上修饰符u。

6.命名捕获组(Named capturing groups)
在 ES2018 中,可以为捕获组分配一个名称,而不是仅在结果数组中分配一个 slot(插槽):

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const res = reg.exec('2015-01-02')
console.log(res)

// expected outputs:
//0: "2015-01-02"
//1: "2015"
//2: "01"
//3: "02"
//groups:
//	day: "02"
//	month: "01"
//	year: "2015"
//index: 0
//input: "2015-01-02"
//length: 4

从上面例子可以看出,给捕获组添加名称后,groups添加了day,month和year属性。

7. RegExp.prototype.dotAll
我们都知道JavaScript正则表达式中点(.)是一个特殊字符,它可以匹配除了以下条件外的任意字符。

  • 四个字节的UTF-16字符
  • 换行符(\n)
  • 回车符(\r)
  • 行分隔符
  • 段分隔符

为了使点(.)可以匹配任意字符,ES2018引入新的修饰符s。这种模式被称为dotAll模式,根据字面意思便是dot(.)匹配一切字符。dotAll 是一个只读的属性,属于单个正则表达式实例。

console.log(/mazey.happy/.test('mazey\nhappy')); // false
console.log(/mazey.happy/s.test('mazey\nhappy')); // true

ES2017

1. padStart和padEnd
padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。语法为:str.padStart(targetLength [, padString])。其中,padString为填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的缺省值为 " "(U+0020)。

'abc'.padStart(10);         // "       abc"
'abc'.padStart(10, "foo");  // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0");     // "00000abc"
'abc'.padStart(1);          // "abc"

同样的,还有padEnd。与padStart相反的是,该方法从当前字符串的末尾(右侧)开始填充。

'abc'.padEnd(10);          // "abc       "
'abc'.padEnd(10, "foo");   // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1);           // "abc"

2. Object.value()
Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]

当然,Object.values() 也适用于数组。

3.Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)语法为Object.getOwnPropertyDescriptor(obj, prop)。一个属性描述符是一个记录,由下面属性当中的某些组成的:

  • value 该属性的值(仅针对数据属性描述符有效) writable 当且仅当属性的值可以被改变时为true。(仅针对数据属性描述有效)
  • get 获取该属性的访问器函数(getter)。如果没有访问器, 该值为undefined。(仅针对包含访问器或设置器的属性描述有效)
  • set 获取该属性的设置器函数(setter)。 如果没有设置器, 该值为undefined。(仅针对包含访问器或设置器的属性描述有效)
  • configurable 当且仅当指定对象的属性描述可以被改变或者属性可被删除时,为true。
  • enumerable 当且仅当指定对象的属性可以被枚举出时,为 true。
o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
// d {
//   configurable: true,
//   enumerable: true,
//   get: /*the getter function*/,
//   set: undefined
// }

o = { bar: 42 };
d = Object.getOwnPropertyDescriptor(o, "bar");
// d {
//   configurable: true,
//   enumerable: true,
//   value: 42,
//   writable: true
// }

为什么要有该属性?ES2015 给我们带来了 Object.assign() 方法,它从一个或多个对象复制所有可枚举的属性,并返回一个新对象。但是存在问题,它无法正确复制具有非默认特性(attribute) 的属性 (property)(getter,setter,不可写属性,等)。如果一个对象只有一个 setter ,则无法使用 Object.assign() 正确地复制到一个新对象。

4. 尾随逗号
此功能允许在函数声明和函数调用中使用尾随逗号:

const doSomething = (var1, var2,) => {
  //...
}
doSomething('test2', 'test2',)

这个功能同样支持对象字面量和数组字面量后面跟的逗号。

5.Async Functions (异步函数)
async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。如果你在代码中使用了异步函数,就会发现它的语法和结构会更像是标准的同步函数。

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
}

asyncCall();

//  expected output:
//  "calling"
//	"resolved"

6. SharedArrayBuffer和Atomics
SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。语法:new SharedArrayBuffer(length),其中length为所创建的数组缓冲区的大小,以字节(byte)为单位。

// create a SharedArrayBuffer with a size in bytes
const buffer = new SharedArrayBuffer(8);
console.log(buffer.byteLength);
// expected output: 8

为了将一个SharedArrayBuffer 对象从一个用户代理共享到另一个用户代理(另一个页面的主进程或者当前页面的一个 worker )从而实现共享内存,我们需要运用 postMessage 和结构化克隆算法( structured cloning )。

然后,通过原子操作Atomics更新及同步来共享内存。共享内存能被同时创建和更新于工作者线程或主线程。依赖于系统(CPU,操作系统,浏览器),变化传递给所有上下文环境需要一段时间。需要通过 atomic 操作来进行同步。

多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。
ES2020-ES2015学习笔记_第1张图片

ES2016

ES7,正式名称为 ECMAScript 2016 ,于2016年6月完成。与ES6相比,ES7 是 JavaScript 的一个小版本,仅包含两个功能。
1. Array.prototype.includes()
使用 ES6 和更低版本,要检查数组是否包含某个元素项,您必须使用 indexOf ,它检查数组中的索引,如果元素不存在则返回 -1 。而该功能引入了更易读的语法,用于检查数组是否包含元素。语法为arr.includes(valueToFind[, fromIndex])

if (![1,2].includes(3)) {
  console.log('Not found')
}

2. 求幂运算符()**
求幂运算符 ** 等价于 Math.pow(),但是它被引入语言本身,而不是库函数。** 运算符在许多语言中都是标准化的,包括Python,Ruby,MATLAB,Lua,Perl等等。

Math.pow(4,2== 4 ** 2 // expected output:true

ES2015

ES2015的新语法很多,而且很多语法已经使用的很广了,所以某些语法就一笔带过。
1. let、const和块级作用域
2. 箭头函数(Arrow Function)
3. 模板字符串(Template String)
4. 对象字面量扩展语法(Enhanced Object Literals)

  • 函数类属性的省略语法。用法:{ method() {…} }
const obj = {
	//Before
	foo: function(){
		return 'foo';
	},
	
	//After
	bar(){
		return 'bar';
	}
}
  • 支持 proto 注入
    开发者允许直接向一个对象字面量注入__proto__,使其直接成为指定类的一个实例,而无须另外创建一个类来实现继承。
import {EventEmitter} from 'events'

const machine = {
	__proto__: new EventEmitter(),
	
	method(){ /* …*/ },
	// …
}

console.log(machine);  //EventEmitter {}
console.log(machine instanceof EventEmitter)  //true

machine.on('event', msg => console.log(`Received message: ${msg}`));
machine.emit('event', 'hello world');
// Received message: hello world

machine.method(/* …. */);
  • 可动态计算的属性名
    ES6 引入的新语法允许我们直接使用一个表达式来表达一个属性名用法:{ [statement]: value}
const prefix = 'ES6';
const obj = {
	[prefix + 'enhancedObject']: 'foo'
}
  • 将属性名定义省略
    有时候我们需要将一些已经被定义的变量(或常量)作为其他对象字面量的属性值进行返回或传入操作,而大多数情况下这些变量名和属性名都是相同的,我们可以对属性名定义进行省略。
const foo = 123;
const bar = () => foo;

const obj = {
	foo,
	bar
}

console.log(obj);  //{ foo: 123, bar: [Function] }

5. 解构赋值
定义:按照一定的模式从数组或对象中取值,对变量进行赋值的过程。让我们一起先来看数组解构的基本用法:

let [a, b, c] = [1, 2, 3] // a=1, b=2, c=3
let [d, [e], f] = [1, [2], 3] // 嵌套数组解构 d=1, e=2, f=3
let [g, ...h] = [1, 2, 3] // 数组拆分 g=1, h=[2, 3]
let [i,,j] = [1, 2, 3] // 不连续解构 i=1, j=3
let [k,l] = [1, 2, 3] // 不完全解构 k=1, l=2

接下来再让我们一起看看对象解构的基本用法:

let {a, b} = {a: 'aaaa', b: 'bbbb'} // a='aaaa' b='bbbb'
let obj = {d: 'aaaa', e: {f: 'bbbb'}}
let {d, e:{f}} = obj // 嵌套解构 d='aaaa' f='bbbb'
let g;
(g = {g: 'aaaa'}) // 以声明变量解构 g='aaaa'
let [h, i, j, k] = 'nice' // 字符串解构 h='n' i='i' j='c' k='e'

6. Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。所以,现在的ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined外 ,还新增了 Symbol 。

Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。

let sy = Symbol("KK");
console.log(sy);   // Symbol(KK)
typeof(sy);        // "symbol"
 
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk"); 
sy === sy1;       // false
  • Symbol.for()
    Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1;      // false
 
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2;     // true
  • Symbol.keyFor()
    Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1);    // "Yellow"

7. Map
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。与Object的主要区别在于:

  • Map 中的键和值可以使任意类型的,而 Object 中键名只能是字符串或者Symbol。
  • Map 的键值是有序的且有内置的迭代方法,而 Object 是无序的需要通过for…in或者Object.keys()来实现迭代。
let myMap = new Map();
 
let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';
 
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");
 
myMap.size; // 3
 
// 读取值
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get(keyObj);       // "和键keyObj关联的值"
myMap.get(keyFunc);      // "和键keyFunc关联的值"
 
myMap.get('a string');   // "和键'a string'关联的值"
                         // 因为keyString === 'a string'
myMap.get({});           // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}

8. Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。语法为const p = new Proxy(target, handler)。target是使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);handler通常是一个以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

在上面简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。
9. Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同与大多数全局对象不同,Reflect不是一个构造函数。你不能将其与一个new运算符一起使用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。
Reflect与Proxy是相辅相成的,在Proxy上有的方法,在Reflect就一定有。下面是例子

    let obj={
      //属性yu部署了getter读取函数
      get yu(){
        //this返回的是Reflect.get的receiver参数对象
        return this.name+this.age
      }
    }

    let receiver={
      name:"shen",
      age:"18",
    }
    
    let result=Reflect.get(obj,"yu",receiver)
    console.log(result) //shen18

10. ES6 字符串
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。

  • startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。第二个参数为在 str 中搜索 searchString 的开始位置。
const str1 = 'Saturday night plans';
console.log(str1.startsWith('Sat'));// expected output: true
console.log(str1.startsWith('Sat', 3));// expected output: false
  • endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。第二个参数为字符串的长度。
const str1 = 'Cats are the best!';
console.log(str1.endsWith('best', 17));// expected output: true
const str2 = 'Is this a question';
console.log(str2.endsWith('?'));// expected output: false
  • repeat():返回新的字符串,表示将字符串重复指定次数返回。
console.log("Hello,".repeat(2));  // "Hello,Hello,"

11. 标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串。

alert`Hello world!`;
// 等价于
alert('Hello world!');

当模板字符串中带有变量,会将模板字符串参数处理成多个参数。

function f(stringArr,...values){
 let result = "";
 for(let i=0;i<stringArr.length;i++){
  result += stringArr[i];
  if(values[i]){
   result += values[i];
        }
    }
 return result;
}
let name = 'Mike';
let age = 27;

f`My Name is ${name},I am ${age+1} years old next year.`;
// 等价于
f(['My Name is',',I am ',' years old next year.'],'Mike',28);
  • 国际化处理(转化多国语言)
i18n`Hello ${name}, you are visitor number ${visitorNumber}.`;  
// 你好**,你是第**位访问者

12. ES6 数值
这一块内容涉及的知识点较多,就不一一说明了,可以看菜鸟教程——ES6数值

13. Object.assign(target, source_1, ···)
用于将源对象的所有可枚举属性复制到目标对象中。assign 的属性拷贝是浅拷贝。

let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);  
// 第一个参数是目标对象,后面的参数是源对象
target;  // {a: 1, b: 2, c: 3}

14. Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。

Object.is("q","q");      // true
Object.is(1,1);          // true
Object.is([1],[1]);      // false
Object.is({q:1},{q:1});  // false

与(===)的区别

//一是+0不等于-0
Object.is(+0,-0);  //false
+0 === -0  //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN  //false

15. ES6 数组

  • Array.of():将参数中所有值作为元素形成数组
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
// 参数为空时返回空数组
console.log(Array.of()); // []
  • Array.from():将类数组对象或可迭代对象转化为数组,语法:Array.from(arrayLike[, mapFn[, thisArg]])
// 参数为数组,返回与原数组一样的数组
console.log(Array.from([1, 2])); // [1, 2]// 参数含空位
console.log(Array.from([1, , 3])); // [1, undefined, 3]
Array.from([1, 2, 3], x => x + x); //[2, 4, 6]
  • find():查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
let arr = Array.of(1, 2, 3, 4);
console.log(arr.find(item => item > 2)); // 3
  • findIndex():查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
let arr = Array.of(1, 2, 1, 3);
// 参数1:回调函数
// 参数2(可选):指定回调函数中的 this 值
console.log(arr.findIndex(item => item = 1)); // 0

还有很多新的语法可以查阅菜鸟教程——ES6 数组

16. ES6 迭代器
Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现;迭代器是用于遍历数据结构元素的指针(如数据库中的游标)。

迭代过程:通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置。随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束,当 done 为 true 时则遍历结束

const items = ["zero", "one", "two"];
const it = items[Symbol.iterator]();
 
it.next();
>{value: "zero", done: false}
it.next();
>{value: "one", done: false}
it.next();
>{value: "two", done: false}
it.next();
>{value: undefined, done: true}

可迭代的数据类型:Array;String;Map;Set;Dom元素(正在进行中)。一般使用for…of循环进行迭代。

17. ES6Class类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。class 的本质是 function。它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。菜鸟教程——ES6Class类

18. ES6 模块
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 的模块化分为导出(export) @与导入(import)两个模块。

19. Promise对象
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

const p1 = new Promise(function(resolve,reject){
    resolve('success1');
    resolve('success2');
}); 
const p2 = new Promise(function(resolve,reject){  
    resolve('success3'); 
    reject('reject');
});
p1.then(function(value){  
    console.log(value); // success1
});
p2.then(function(value){ 
    console.log(value); // success3
});

20. Generator 函数
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。详见ES6 Generator 函数

你可能感兴趣的:(js,javascript,web,chrome,ES,ES新特性)