wuyanqian0503 / blogs

1 stars 0 forks source link

ES新特性 #48

Open wuyanqian0503 opened 3 years ago

wuyanqian0503 commented 3 years ago

参考:

上沅同学ES6、ES7、ES8、ES9、ES10新特性一览 浪里行舟ES2020新特性

ECMA、ECMAScript与JavaScript的关系

ECMA国际的前身是欧洲计算机制造商协会,是一个信息和电信的标准组织,主要工作是与相关组织合作开发通信技术和消费电子标准。

例如,最开始JavaScript诞生于1994年的网景实验室,1996年,网景将JavaScript提交给ECMA国际进行标准化,ECMA-262标准化的脚本语言在1997年被ECMA组织所采纳,ECMAScript就是这一标准化脚本语言的名称。可以说 JavaScript 是 ECMAScript 的 一个实现和扩展。

ECMA规范最终由TC39敲定。TC39由包括浏览器厂商在内的各方组成,是ECMA的一个委员会组,他们开会推动JavaScript提案沿着一条严格的发展道路前进。 从提案到入选ECMA规范主要有以下几个阶段:

ES6新特性(2015)

ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度很大,所以ES6中的特性比较多。 在这里列举几个常用的:

1、类

对熟悉Java,object-c,c#等纯面向对象语言的开发者来说,都会对class有一种特殊的情怀。ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解

在ES6以前,继承都是通过一些其他的方式实现的,可以参考 继承的几种方式,其中寄生组合式继承是比较推荐的方式。

注意: 构造函数在类实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数 并且子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错. 如果没有置顶consructor,默认带super函数的constructor将会被添加

2、模块化(Module)

ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

导出(export) ES6允许在一个模块中使用export来导出多个变量或函数。 ES6中一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

// utils.js
// 方式一,可以多次导出
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

// 方式二,与方式一等价,必须在文件底部声明,且同名的变量和函数不能重复导出
export {firstName, lastName, year};

// 方式三,为模块导出一个默认的输出,只能使用一次
export default {firstName, lastName, year};

import

// 针对直接 export 的 内容,花括号中的内容必须与export的内容名称一致,可以用 as 来重命名
import { firstName, lastName, year as Year }  from './utils.js'

// 针对 export 的默认导出内容,可以自定义名称,但不需要花括号
import Detail from './utils.js'

ps: NodeJS 从 13.2.0 起开始正式支持 ES Modules 特性,在Node 中使用 ES Modules可以通过在package.json中增加"type": "module"属性来支持。

3.箭头(Arrow)函数

从语法上看,箭头函数是function关键字的简写,但事实上,远不止如此。

详解为什么不能通过new关键字调用

通常JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]

当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。 当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。 箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。

4、函数参数默认值

支持在定义函数时指定参数的默认值:

function foo (size = 50) {
    // ...
}

5、模板字符串

var name =Your name is ${first} ${last}.``

6、解构赋值

获取数组中的值

var a, b;

[a, b] = [1, 2]; // 1,2
[a=5, b=7] = [1];  // 1,7
[a, b] = [b, a]; // 7,1

获取对象中的值

const student = {
  name:'Ming',
  age:'18',
  city:'Shanghai'  
};

const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"

7、延展操作符/扩展运算符(Spread operator)

延展操作符...可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造对象时, 将对象表达式按key-value的方式展开。

延展操作符对对象的支持是在ECMAScript 2018 中提出的新特性。

8、对象属性简写

const student = {
    name,
    age,
    city
};

9、 Promise

Promise 是异步编程的一种解决方案,目的是解决回调地狱的问题,比传统的解决方案callback更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise 通过then来进行链式调用,解决了回调地狱的问题。

不过即便是可以进行链式调用,在异步操作较多的场景中,还是不太优雅,所以在ECMAScript 2018中,就又提出了 async/await的新特性来解决这一问题。

10、支持let与const

在ES6以前,JS是没有块级作用域的,const与let填补了这方便的空白,const与let都是块级作用域。

使用var定义的变量都会被提升到函数作用域或全局作用域,并且不存在块级作用域,这带来的问题有:

这里我们可以引申到一个常见思考题,就是循环输出的问题,在for循环中调用setTimeout去输出计数:

for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000)  // 5 5 5 5
}

for (let i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000)  // 1 2 3 4
}

这个例子就涉及到了块级作用域的作用

const、let 与 var 之间存在的区别有:

不能提升变量和暂时性死区的意义在于: 为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

const 与 let 的区别

Generator

async await 和 generator

wuyanqian0503 commented 3 years ago

ES7新特性

1.Array.prototype.includes()

arr.includes(x)
// 等同于
arr.indexOf(x) >= 0

2.指数操作符

使用指数运算符**,就像+、-等操作符一样:

console.log(2**10); // 输出1024
wuyanqian0503 commented 3 years ago

ES8新特性(2017)

async/await

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。相较于 Generator,async 函数的改进在于下面四点:

wuyanqian0503 commented 3 years ago

ES9新特性(2018)

wuyanqian0503 commented 3 years ago

ES10新特性(2019)

1.行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配

以前,这些符号在字符串文字中被视为行终止符,因此使用它们会导致SyntaxError异常。

2.更加友好的 JSON.stringify

如果输入 Unicode 格式但是超出范围的字符,在原先JSON.stringify返回格式错误的Unicode字符串。现在实现了一个改变JSON.stringify的第3阶段提案,因此它为其输出转义序列,使其成为有效Unicode(并以UTF-8表示)

3.新增了Array的flat()方法和flatMap()方法

flat()和flatMap()本质上就是是归纳(reduce) 与 合并(concat)的操作。

Array.prototype.flat():

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

flat()方法最基本的作用就是数组降维

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

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

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

//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity); 
// [1, 2, 3, 4, 5, 6]

其次,还可以利用flat()方法的特性来去除数组的空项

var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

Array.prototype.flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。 这里我们拿map方法与flatMap方法做一个比较。

var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]

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

// 只会将 flatMap 中的函数返回的数组 “压平” 一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

4.新增了String的trimStart()方法和trimEnd()方法

新增的这两个方法很好理解,分别去除字符串首尾空白字符,这里就不用例子说声明了。

5.Object.fromEntries()

Object.entries()方法的作用是返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。 而Object.fromEntries()则是Object.entries()的反转。 Object.fromEntries() 函数传入一个键值对的列表,并返回一个带有这些键值对的新对象。这个迭代参数应该是一个能够实现@iterator方法的的对象,返回一个迭代器对象。它生成一个具有两个元素的类似数组的对象,第一个元素是将用作属性键的值,第二个元素是与该属性键关联的值。

通过 Object.fromEntries, 可以将 Map 转化为 Object:

const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }

通过 Object.fromEntries, 可以将 Array 转化为 Object:

const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

6.Symbol.prototype.description

通过工厂函数Symbol()创建符号时,您可以选择通过参数提供字符串作为描述:

7.Function.prototype.toString()现在返回精确字符,包括空格和注释

8.修改 catch 绑定

在 ES10 之前,我们必须通过语法为 catch 子句绑定异常变量,无论是否有必要。很多时候 catch 块是多余的。 ES10 提案使我们能够简单的把变量省略掉。

// 之前是
try {} catch(e) {}

// 现在是
try {} catch {}
wuyanqian0503 commented 3 years ago

ES11新特性(2020)

1.新的基本数据类型BigInt

javascript 在 Math 上一直很糟糕的原因之一是只能安全的表示-(2^53-1)至 2^53-1 范的值,即Number.MIN_SAFE_INTEGER 至Number.MAX_SAFE_INTEGER,超出这个范围的整数计算或者表示会丢失精度。

var num = Number.MAX_SAFE_INTEGER;  // -> 9007199254740991

num = num + 1; // -> 9007199254740992

// 再次加 +1 后无法正常运算
num = num + 1; // -> 9007199254740992

// 两个不同的值,却返回了true
9007199254740992 === 9007199254740993  // -> true

于是 BigInt 应运而生,它是第7个原始类型,可安全地进行大数整型计算。 你可以在BigInt上使用与普通数字相同的运算符,例如 +, -, /, *, %等等。

创建 BigInt 类型的值也非常简单,只需要在数字后面加上 n 即可。例如,123 变为 123n。也可以使用全局方法 BigInt(value) 转化,入参 value 为数字或数字字符串。

const aNumber = 111;
const aBigInt = BigInt(aNumber);
aBigInt === 111n // true
typeof aBigInt === 'bigint' // true
typeof 111 // "number"
typeof 111n // "bigint"

也就是说,只要在数字末尾加上 n,就可以正确计算大数了:


1234567890123456789n * 123n;
// -> 151851850485185185047n

不过有一个问题,在大多数操作中,不能将 BigInt与Number混合使用。比较Number和 BigInt是可以的,但是不能把它们相加。

那么现在的原始数据类型加上BigInt一共有七种,分别是: String、Number、Boolean、Null、Undefined、Symbol、BigInt****

BigInt的支持情况 image

可选链操作符?.(Optional Chaining)

在查询具有多个层级的对象时、不再需要进行冗余的前置校验.

当引用为null或者undefined的情况下不会引起报错,并且返回值是undefined.

空位合并操作符??(Nullish coalescing Operator)

如果表达式在??的左侧运算符求值为 undefined 或 null,就返回其右侧默认值。 排除了 undefined 或 null 以外的如 false、0、空字符串被视为假值造成的繁琐的非空判断。

Promise.allSettled

我们知道 Promise.all 具有并发执行异步任务的能力。但它有一个特点是如果数组中的任何一个promise被reject的话,则整个Promise.all 调用会立即终止,并返回一个reject的新的 Promise 对象。

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

String.prototype.matchAll

matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器。

Dynamic import

现在前端打包资源越来越大,前端应用初始化时根本不需要全部加载这些逻辑资源,为了首屏渲染速度更快,很多时候都是动态导入(按需加载)模块,比如懒加载图片等,这样可以帮助您提高应用程序的性能。

其中按需加载这些逻辑资源都一般会在某一个事件回调中去执行:

el.onclick = () => {
  import('/modules/my-module.js')
    .then(module => {
      // Do something with the module.
    })
    .catch(err => {
      // load error;
    })
}

import()可以用于script脚本中,import(module) 函数可以在任何地方调用。它返回一个解析为模块对象的 promise。

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

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

通过动态导入代码,您可以减少应用程序加载所需的时间,并尽可能快地将某些内容返回给用户。

globalThis

globalThis 是一个全新的标准方法用来获取全局 this 。之前开发者会通过如下的一些方法获取:

过去获取全局对象,可通过一个全局函数:


// ES10之前的解决方案
const 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')
}

// ES10内置
globalThis.Array(0,1,2) // [0,1,2]

// 定义一个全局对象v = { value:true } ,ES10用如下方式定义
globalThis.v = { value:true }

globalThis 目的就是提供一种标准化方式访问全局对象

新提案也规定了,Object.prototype 必须在全局对象的原型链中。下面的代码在最新浏览器中已经会返回 true 了:

Object.prototype.isPrototypeOf(globalThis); // true

globalThis的支持情况:

image