自从前端开发流行起来,前端的各类技术如雨后春笋般出现,让人应接不暇,几年前还是jq一把梭,现在除了三大主流框架var,何种混合开发也接踵而至,还有可能取代JS的TS出现,我们要跟上时代的步伐,了解最新的技术,走在技术的前沿。
别停留在ES6了,ES10都要出来了。
ES7在ES6的基础上主要添加了两项内容:
1.Array.prototype.includes()方法
2.求幂运算符(**)
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
var array = [1, 2, 3];
console.log(array.includes(2));
// expected output: true
var pets = ['cat', 'dog', 'bat'];
console.log(pets.includes('cat'));
// expected output: true
console.log(pets.includes('at'));
// expected output: false
Array.prototype.includes()方法接收两个参数:
1.要搜索的值
2.搜索的开始索引。
当第二个参数被传入时,该方法会从索引处开始往后搜索(默认索引值为0)。若搜索值在数组中存在则返回true,否则返回false。 且看下面示例:
['a', 'b', 'c', 'd'].includes('b') // true
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
乍一看,includes的作用跟数组的indexOf重叠,为什么要特意增加这么一个api呢?主要区别有以下几点:
返回值。看一个函数,先看他们的返回值。indexOf的返回数是值型的,includes的返回值是布尔型,所以在if条件判断的时候includes要简单得多,而indexOf 需要多写一个条件进行判断。
var ary = [1];
if (ary.indexOf(1) !== -1) {
console.log("数组存在1")
}
if (ary.includes(1)) {
console.log("数组存在1")
}
NaN的判断。如果数组中有NaN,你又正好需要判断数组是否有存在NaN,这时你使用indexOf是无法判断的,你必须使用includes这个方法。
var ary1 = [NaN];
console.log(ary1.indexOf(NaN))//-1
console.log(ary1.includes(NaN))//true
当数组的有空的值的时候,includes会认为空的值是undefined,而indexOf不会。
var ary1 = new Array(3);
console.log(ary1.indexOf(undefined));//-1
console.log(ary1.includes(undefined))//true
加/减法我们通常都是用其中缀形式,直观易懂。在ECMAScript2016中,我们可以使用**来替代Math.pow。
4 ** 3 // 64
效果等同于
Math.pow(4,3)
值得一提的是,作为中缀运算符,**还支持以下操作
let n = 4;
n **= 3;
// 64
主要新功能:
1.异步函数 Async Functions(Brian Terlson)
2.共享内存和Atomics(Lars T. Hansen)
次要新功能:
1.Object.values / Object.entries(Jordan Harband)
2.String padding(Jordan Harband,Rick Waldron)
3.Object.getOwnPropertyDescriptors() (Jordan Harband,Andrea Giammarchi)
4.函数参数列表和调用中的尾逗号(Jeff Morrison)
Async Functions也就是我们常说的Async/Await,相信大家对于这个概念都已经不陌生了。Async/Await是一种用于处理JS异步操作的语法糖,可以帮助我们摆脱回调地狱,编写更加优雅的代码。
通俗的理解,async关键字的作用是告诉编译器对于标定的函数要区别对待。当编译器遇到标定的函数中的await关键字时,要暂时停止运行,带到await标定的函数处理完毕后,再进行相应操作。如果该函数fulfiled了,则返回值是fulfillment value,否则得到的就是reject value。
下面通过拿普通的promise写法来对比,就很好理解了:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc()
.then(result => {
console.log(result);
});
}
按顺序处理多个异步函数的时候优势更为明显:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc1()
.then(result1 => {
console.log(result1);
return otherAsyncFunc2();
})
.then(result2 => {
console.log(result2);
});
}
并行处理多个异步函数:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
]);
console.log(result1, result2);
}
// Equivalent to:
function asyncFunc() {
return Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
])
.then([result1, result2] => {
console.log(result1, result2);
});
}
处理错误:
async function asyncFunc() {
try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc()
.catch(err => {
console.error(err);
});
}
ECMAScript 2017 特性 SharedArrayBuffer 和 atomics”,由Lars T. Hansen设计。它引入了一个新的构造函数 SharedArrayBuffer 和 具有辅助函数的命名空间对象 Atomics。
在我们开始之前,让我们澄清两个相似但截然不同的术语:并行(Parallelism) 和 并发(Concurrency) 。他们存在许多定义,我使用的定义如下
并行(Parallelism) (parallel 并行 vs. serial 串行):同时执行多个任务;
并发(Concurrency) (concurrent 并发 vs. sequential 连续):在重叠的时间段内(而不是一个接一个)执行几个任务。
JS并行的历史
JavaScript 在单线程中执行。某些任务可以异步执行:浏览器通常会在单线程中运行这些任务,然后通过回调将结果重新加入到单线程中。
Web workers 将任务并行引入了 JavaScript :这些是相对重量级的进程。每个 workers 都有自己的全局环境。默认情况下,不共享任何内容。 workers 之间的通信(或在 workers 和主线程之间的通信)发展:
起初,你只能发送和接收字符串。
然后,引入结构化克隆:可以发送和接收数据副本。结构化克隆适用于大多数数据(JSON 数据,TypedArray,正则表达式,Blob对象,ImageData对象等)。它甚至可以正确处理对象之间的循环引用。但是,不能克隆 error 对象,function 对象和 DOM 节点。
可在 workers 之间的转移数据:当接收方获得数据时,发送方失去访问权限。
通过 WebGL 使用 GPU 计算(它倾向于数据并行处理)
共享数组缓冲区(Shared Array Buffers)
共享阵列缓冲区是更高并发抽象的基本构建块。它们允许您在多个 workers 和主线程之间共享 SharedArrayBuffer 对象的字节(该缓冲区是共享的,用于访问字节,将其封装在一个 TypedArray 中)这种共享有两个好处:
你可以更快地在 workers 之间共享数据。
workers 之间的协调变得更简单和更快(与 postMessage() 相比)。
// main.js
const worker = new Worker('worker.js');
// 要分享的buffer
const sharedBuffer = new SharedArrayBuffer( // (A)
10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements
// 使用Worker共用sharedBuffer
worker.postMessage({sharedBuffer}); // clone
// 仅限本地使用
const sharedArray = new Int32Array(sharedBuffer); // (B)
创建一个共享数组缓冲区(Shared Array Buffers)的方法与创建普通的数组缓冲区(Array Buffer)类似:通过调用构造函数,并以字节的形式指定缓冲区的大小(行A)。你与 workers 共享的是 缓冲区(buffer) 。对于你自己的本地使用,你通常将共享数组缓冲区封装在 TypedArray 中(行B)。
workers的实现如下所列。
// worker.js
self.addEventListener('message', function (event) {
const {sharedBuffer} = event.data;
const sharedArray = new Int32Array(sharedBuffer); // (A)
// ···
});
sharedArrayBuffer 的 API
构造函数:
new SharedArrayBuffer(length)
创建一个 length 字节的 buffer(缓冲区)。
静态属性:
get SharedArrayBuffer[Symbol.species]
默认情况下返回 this。 覆盖以控制 slice() 的返回。
实例属性:
get SharedArrayBuffer.prototype.byteLength()
返回 buffer(缓冲区) 的字节长度。
SharedArrayBuffer.prototype.slice(start, end)
创建一个新的 this.constructor[Symbol.species] 实例,并用字节填充从(包括)开始到(不包括)结束的索引。
Atomics: 安全访问共享数据
举一个例子
// main.js
sharedArray[1] = 11;
sharedArray[2] = 22;
在单线程中,您可以重新排列这些写入操作,因为在中间没有读到任何内容。 对于多线程,当你期望以特定顺序执行写入操作时,就会遇到麻烦:
// worker.js
while (sharedArray[2] !== 22) ;
console.log(sharedArray[1]); // 0 or 11
Atomics 方法可以用来与其他 workers 进行同步。例如,以下两个操作可以让你读取和写入数据,并且不会被编译器重新排列:
Atomics.load(ta : TypedArray, index)
Atomics.store(ta : TypedArray, index, value : T)
这个想法是使用常规操作读取和写入大多数数据,而 Atomics 操作(load ,store 和其他操作)可确保读取和写入安全。通常,您将使用自定义同步机制,例如锁,其实现基于Atomics。
这是一个非常简单的例子,它总是有效的:
// main.js
console.log('notifying...');
Atomics.store(sharedArray, 0, 123);
// worker.js
while (Atomics.load(sharedArray, 0) !== 123) ;
console.log('notified');
Atomics 的 API
Atomic 函数的主要操作数必须是 Int8Array ,Uint8Array ,Int16Array ,Uint16Array ,Int32Array 或 Uint32Array 的一个实例。它必须包裹一个 SharedArrayBuffer 。
所有函数都以 atomically 方式进行操作。存储操作的顺序是固定的并且不能由编译器或 CPU 重新排序。
加载和存储
Atomics.load(ta : TypedArray, index) : T
读取和返回 ta[index] 上的元素,返回数组指定位置上的值。
Atomics.store(ta : TypedArray, index, value : T) : T
在 ta[index] 上写入 value,并且返回 value。
Atomics.exchange(ta : TypedArray, index, value : T) : T
将 ta[index] 上的元素设置为 value ,并且返回索引 index 原先的值。
Atomics.compareExchange(ta : TypedArray, index, expectedValue, replacementValue) : T
如果 ta[index] 上的当前元素为 expectedValue , 那么使用 replacementValue 替换。并且返回索引 index 原先(或者未改变)的值。
简单修改 TypeArray 元素
以下每个函数都会在给定索引处更改 TypeArray 元素:它将一个操作符应用于元素和参数,并将结果写回元素。它返回元素的原始值。
Atomics.add(ta : TypedArray, index, value) : T
执行 ta[index] += value 并返回 ta[index] 的原始值。
Atomics.sub(ta : TypedArray, index, value) : T
执行 ta[index] -= value 并返回 ta[index] 的原始值。
Atomics.and(ta : TypedArray, index, value) : T
执行 ta[index] &= value 并返回 ta[index] 的原始值。
Atomics.or(ta : TypedArray, index, value) : T
执行 ta[index] |= value 并返回 ta[index] 的原始值。
Atomics.xor(ta : TypedArray, index, value) : T
执行 ta[index] ^= value 并返回 ta[index] 的原始值。
等待和唤醒
Atomics.wait(ta: Int32Array, index, value, timeout=Number.POSITIVE_INFINITY) : (‘not-equal’ | ‘ok’ | ‘timed-out’)
如果 ta[index] 的当前值不是 value ,则返回 ‘not-equal’。否则继续等待,直到我们通过 Atomics.wake() 唤醒或直到等待超时。 在前一种情况下,返回 ‘ok’。在后一种情况下,返回’timed-out’。timeout 以毫秒为单位。记住此函数执行的操作:“如果 ta[index] 为 value,那么继续等待” 。
Atomics.wake(ta : Int32Array, index, count)
唤醒等待在 ta[index] 上的 count workers。
Object.values() 方法返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于for-in循环枚举原型链中的属性 )。
obj参数是需要待操作的对象。可以是一个对象,或者一个数组(是一个带有数字下标的对象,[10,20,30] -> {0: 10,1: 20,2: 30})。
const obj = { x: 'xxx', y: 1 };
Object.values(obj); // ['xxx', 1]
const obj = ['e', 's', '8']; // 相当于 { 0: 'e', 1: 's', 2: '8' };
Object.values(obj); // ['e', 's', '8']
// 当我们使用数字键值时,返回的是数字排序
// 根据键值排序
const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' };
Object.values(obj); // ['yyy', 'zzz', 'xxx']
Object.values('es8'); // ['e', 's', '8']
Object.entries 方法返回一个给定对象自身可遍历属性 [key, value] 的数组, 排序规则和 Object.values 一样。这个方法的声明比较琐碎:
const obj = { x: 'xxx', y: 1 };
Object.entries(obj); // [['x', 'xxx'], ['y', 1]]
const obj = ['e', 's', '8'];
Object.entries(obj); // [['0', 'e'], ['1', 's'], ['2', '8']]
const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' };
Object.entries(obj); // [['1', 'yyy'], ['3', 'zzz'], ['10': 'xxx']]
Object.entries('es8'); // [['0', 'e'], ['1', 's'], ['2', '8']]
为 String 对象增加了 2 个函数:padStart 和 padEnd。
像它们名字那样,这几个函数的主要目的就是填补字符串的首部和尾部,为了使得到的结果字符串的长度能达到给定的长度。你可以通过特定的字符,或者字符串,或者默认的空格填充它。下面是函数的声明:
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
这些函数的第一个参数是 targetLength(目标长度),这个是结果字符串的长度。第二个参数是可选的 padString(填充字符),一个用于填充到源字符串的字符串。默认值是空格。
'es8'.padStart(2); // 'es8'
'es8'.padStart(5); // ' es8'
'es8'.padStart(6, 'woof'); // 'wooes8'
'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
'es8'.padStart(7, '0'); // '0000es8'
'es8'.padEnd(2); // 'es8'
'es8'.padEnd(5); // 'es8 '
'es8'.padEnd(6, 'woof'); // 'es8woo'
'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
'es8'.padEnd(7, '6'); // 'es86666'
getOwnPropertyDescriptors 方法返回指定对象所有自身属性的描述对象。属性描述对象是直接在对象上定义的,而不是继承于对象的原型。ES2017加入这个函数的主要动机在于方便将一个对象深度拷贝给另一个对象,同时可以将getter/setter拷贝。声明如下:
Object.getOwnPropertyDescriptors(obj)
obj 是待操作对象。返回的描述对象键值有:configurable, enumerable, writable, get, set and value。
const obj = {
get es7() { return 777; },
get es8() { return 888; }
};
Object.getOwnPropertyDescriptor(obj);
// {
// es7: {
// configurable: true,
// enumerable: true,
// get: function es7(){}, //the getter function
// set: undefined
// },
// es8: {
// configurable: true,
// enumerable: true,
// get: function es8(){}, //the getter function
// set: undefined
// }
// }
结尾逗号用代码展示非常明了:
// 参数定义时
function foo(
param1,
param2,
) {}
// 函数调用时
foo(
'abc',
'def',
);
// 对象中
let obj = {
first: 'Jane',
last: 'Doe',
};
// 数组中
let arr = [
'red',
'green',
'blue',
];
这个改动有什么好处呢?
首先,重新排列项目更简单,因为如果最后一项更改其位置,则不必添加和删除逗号。
其次,它可以帮助版本控制系统跟踪实际发生的变化。例如,从:
[
'foo'
]
修改为
[
'foo',
'bar'
]
导致线条’foo’和线条’bar’被标记为已更改,即使唯一真正的变化是后一条线被添加。
结尾逗号用代码展示非常明了:
// 参数定义时
function foo(
param1,
param2,
) {}
// 函数调用时
foo(
‘abc’,
‘def’,
);
// 对象中
let obj = {
first: ‘Jane’,
last: ‘Doe’,
};
// 数组中
let arr = [
‘red’,
‘green’,
‘blue’,
];
这个改动有什么好处呢?
首先,重新排列项目更简单,因为如果最后一项更改其位置,则不必添加和删除逗号。
其次,它可以帮助版本控制系统跟踪实际发生的变化。例如,从:
[
‘foo’
]
修改为
[
‘foo’,
‘bar’
]
导致线条’foo’和线条’bar’被标记为已更改,即使唯一真正的变化是后一条线被添加。
ES9的新特性索引如下:
主要新功能:
1.异步迭代(Domenic Denicola,Kevin Smith)
2.Rest/Spread 属性(SebastianMarkbåge)
新的正则表达式功能:
1.RegExp named capture groups(Gorkem Yakin,Daniel Ehrenberg)
2.RegExp Unicode Property Escapes(Mathias Bynens)
3.RegExp Lookbehind Assertions(Gorkem Yakin,NozomuKatō,Daniel Ehrenberg)
4.s (dotAll) flag for regular expressions(Mathias Bynens)
其他新功能:
1.Promise.prototype.finally() (Jordan Harband)
2.模板字符串修改(Tim Disney)
首先来回顾一下同步迭代器:
ES6引入了同步迭代器,其工作原理如下:
Iterable:一个对象,表示可以通过Symbol.iterator方法进行迭代。
Iterator:通过调用iterable [Symbol.iterator] ()返回的对象。它将每个迭代元素包装在一个对象中,并通过其next()方法一次返回一个。
IteratorResult:返回的对象next()。属性value包含一个迭代的元素,属性done是true 后最后一个元素。
示例:
const iterable = ['a', 'b'];
const iterator = iterable[Symbol.iterator]();
iterator.next()
// { value: 'a', done: false }
iterator.next()
// { value: 'b', done: false }
iterator.next()
// { value: undefined, done: true }
异步迭代器
先前的迭代方式是同步的,并不适用于异步数据源。例如,在以下代码中,readLinesFromFile()无法通过同步迭代传递其异步数据:
for (const line of readLinesFromFile(fileName)) {
console.log(line);
}
异步迭代器和常规迭代器的工作方式非常相似,但是异步迭代器涉及promise:
async function example() {
// 普通迭代器:
const iterator = createNumberIterator();
iterator.next(); // Object {value: 1, done: false}
iterator.next(); // Object {value: 2, done: false}
iterator.next(); // Object {value: 3, done: false}
iterator.next(); // Object {value: undefined, done: true}
// 异步迭代器:
const asyncIterator = createAsyncNumberIterator();
const p = asyncIterator.next(); // Promise
await p;// Object {value: 1, done: false}
await asyncIterator.next(); // Object {value: 2, done: false}
await asyncIterator.next(); // Object {value: 3, done: false}
await asyncIterator.next(); // Object {value: undefined, done: true}
}
异步迭代器对象的next()方法返回了一个Promise,解析后的值跟普通的迭代器类似。
用法:iterator.next().then(({ value, done })=> {//{value: ‘some val’, done: false}}
const promises = [
new Promise(resolve => resolve(1)),
new Promise(resolve => resolve(2)),
new Promise(resolve => resolve(3)),
];
async function test() {
for await (const p of promises) {
console.log(p);
}
}
test(); //1 ,2 3
这个就是我们通常所说的rest参数和扩展运算符,这项特性在ES6中已经引入,但是ES6中的作用对象仅限于数组:
restParam(1, 2, 3, 4, 5);
function restParam(p1, p2, ...p3) {
// p1 = 1
// p2 = 2
// p3 = [3, 4, 5]
}
const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100
在ES9中,为对象提供了像数组一样的rest参数和扩展运算符:
const obj = {
a: 1,
b: 2,
c: 3
}
const { a, ...param } = obj;
console.log(a) //1
console.log(param) //{b: 2, c: 3}
function foo({a, ...param}) {
console.log(a); //1
console.log(param) //{b: 2, c: 3}
}
//正则表达式命名捕获组
const RE_DATE = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
通过数字引用捕获组有几个缺点:
1.找到捕获组的数量是一件麻烦事:必须使用括号。
2.如果要了解组的用途,则需要查看正则表达式。
3.如果更改捕获组的顺序,则还必须更改匹配代码。
命名的捕获组
ES9中可以通过名称来识别捕获组:(?[0-9]{4})
在这里,我们用名称标记了前一个捕获组year。该名称必须是合法的JavaScript标识符(认为变量名称或属性名称)。匹配后,您可以通过访问捕获的字符串matchObj.groups.year来访问。
让我们重写前面的代码:
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{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
// 使用解构语法更为简便
const {groups: {day, year}} = RE_DATE.exec('1999-12-31');
console.log(year); // 1999
console.log(day); // 31
可以发现,命名捕获组有以下优点:
找到捕获组的“ID”更容易。
匹配代码变为自描述性的,因为捕获组的ID描述了正在捕获的内容。
如果更改捕获组的顺序,则无需更改匹配代码。
捕获组的名称也使正则表达式更容易理解,因为您可以直接看到每个组的用途。
正则表达式 Unicode 转义
该特性允许您使用\p{}通过提及大括号内的Unicode字符属性来匹配字符,在正则表达式中使用标记 u (unicode) 设置。
/^\p{White_Space}+$/u.test('\t \n\r')
// true
/^\p{Script=Greek}+$/u.test('μετά')
// true
ES10还在草案,所以这里只有介绍。
ES2019中的JavaScript新功能包括:
1.Array#{flat,flatMap}
2.Object.fromEntries
3.String#{trimStart,trimEnd}
4.Symbol#description
5.try { } catch {} // 可选的错误参数绑定
6.JSON ⊂ ECMAScript
7.格式良好的 JSON.stringify
8.稳定的排序 Array#sort
9.新版 Function#toString
10.新增 BigInt 原始类型 (stage 3).
11.动态引入模块(stage 3).
12.标准的 globalThis 对象 (stage 3).
13.ES10 Class: private, static & public (stage 3).
两个新的数组方法:
Array.flat() 方法创建一个新数组,所有子数组元素都以递归方式合并到该数组中,直至达到指定深度。
Array.flatMap() 方法首先使用map函数转换每个元素,然后将结果展平为新数组。它与map()后再调用深度为1的flat() 效果相同,但是flatMap()将两者合并为一种方法,效率更高。
把键值对数组为元素的二维数组转换为一个对象。
有两种新的String方法可从字符串中删除空格:
trimStart() 方法从字符串的开头删除空格。
trimEnd() 方法从字符串末尾删除空格。
当创建符号时,可以提供一个字符串作为描述。在ES10中,有一个获取描述的访问器。
过去,try / catch语句中的catch子句需要一个变量。现在,它允许开发人员使用try / catch而不创建未使用的error变量绑定。
在ES10之前的版本中,不接受非转义的行分隔符U+2028和段落分隔符U+2029。
U+2028是段落分隔符。
U+2029是行分隔符。
JSON.stringify() 可能返回U+D800和U+DFFF之间的字符,来作为没有等效UTF-8字符的值。但是,JSON格式需要UTF-8编码。解决方案是,将未配对的替代代码点表示为JSON转义序列,而不是将其作为单个UTF-16代码单元返回。
V8的先前实现,对包含10个以上项的数组使用了不稳定的快速排序算法。
一种稳定的排序算法是,当两个具有相同键的对象在排序输出中出现的顺序,与未排序输入中出现的顺序相同。
toString() 方法返回一个表示函数源代码的字符串。在ES6中,当在函数上调用toString时,它将根据ECMAScript引擎返回该函数的字符串表示形式。如果可能,它将返回源代码,否则-一个标准化的占位符。
BigInt是第7个原始类型,它是一个任意精度的整数。而不仅仅是在9007199254740992处的最大值。
动态import()返回所请求模块的Promise。因此,可以使用async/await 将导入的模块分配给变量。
全局 this 在ES10之前尚未标准化。在生产代码中,您可以通过编写下边代码来“标准化”它:
现在,新的语法字符#(哈希标签)用于直接在类中定义变量,函数,getter和setter,以及构造函数和类方法。