在es8中主要有6个特性:
主要的有:javascript
Shared memory and atomics (共享内存和原子)java
Async Functions(异步函数)web
其余的特性:编程
Object.values/Object.entries (配合Object.keys使用)api
String padding (字符串填充)数组
Object.getOwnPropertyDescriptors()promise
Trailing commas in function parameter lists and calls(在函数参数列表和调用中减小逗号的使用)浏览器
首先咱们来介绍一下主要的两个特性:多线程
在咱们先须要主要须要了解SharedArrayBuffer 和 Atomics异步
在了解SharedArrayBuffer 以前咱们须要了解下须要share的这个ArrayBuffer:
ArrayBuffer对象表明储存二进制数据的一段内存,它不能直接读写,只能经过视图(TypedArray视图和DataView视图)来读写,视图的做用是以指定格式解读二进制数据。这个接口的原始设计目的,与 WebGL 项目有关。所谓WebGL,就是指浏览器与显卡之间的通讯接口,为了知足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通讯必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将很是耗时。这时要是存在一种机制,能够像 C 语言那样,直接操做字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提高。
那么咱们为何须要SharedArrayBuffer这个function呢?
任何可以从主线程负载减小工做的方法都对代码运行效率有帮助,某些状况下,ArrayBuffers 能够减小大量应该由主线程作的工做(免去了格式转换的耗时和压力直接操做字符),可是也有些时候减小主线程负载是远远不够的,有时你须要增援,你须要分割你的任务,那么咱们这个时候就须要传说中的多线程。
在 JavaScript 里,你能够借助 web worker 作这种事,可是web worker是不能共享内存的,那么这意味着若是你想分配你的任务给别的线程,你须要完整把任务复制过去,这能够经过 postMessage 实现,postMessage 把你传给它的任何对象都序列化,发送到其它 web worker,而后那边接收后反序列化并放进内存。可见这个过程也是至关慢的。咱们下面来看下这个过程的code [from MDN]:
main.js
var first = document.querySelector('#number1'); var second = document.querySelector('#number2'); var result = document.querySelector('.result'); if (window.Worker) { // Check if Browser supports the Worker api. // Requires script name as input var myWorker = new Worker("worker.js"); // onkeyup could be used instead of onchange if you wanted to update the answer every time // an entered value is changed, and you don't want to have to unfocus the field to update its .value first.onchange = function() { myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker console.log('Message posted to worker'); }; second.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); }; myWorker.onmessage = function(e) { result.textContent = e.data; console.log('Message received from worker'); }; }
worker.js
onmessage = function(e) { console.log('Message received from main script'); var workerResult = 'Result: ' + (e.data[0] * e.data[1]); console.log('Posting message back to main script'); postMessage(workerResult); }
从上面的code能够看出这样传来传去是很是慢的。
那么咱们想多个 web worker 就能够同时读写同一块内存,这样咱们就不用传来传去的了,这就是SharedArrayBuffers 为咱们提供的。
咱们来看下使用使用shareArrayBuffers是怎样实现这个乘法的
main.js
"use strict"; var first = document.querySelector('#number1'); var second = document.querySelector('#number2'); var result = document.querySelector('.result'); if (window.Worker) { // Check if Browser supports the Worker api. // Requires script name as input var myWorker = new Worker("worker.js"); // onkeyup could be used instead of onchange if you wanted to update the answer every time // an entered value is changed, and you don't want to have to unfocus the field to update its .value var sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT); first.onchange = function() { addNumber(); } second.onchange = function() { addNumber(); }; const addNumber = () => { myWorker.postMessage({ aTopic: [first.value, second.value], aBuf: sharedBuffer // The array buffer that we passed to the transferrable section 3 lines below }); console.log('Message posted to worker'); result.textContent = new Int32Array(sharedBuffer)[0]; console.log('Message received from worker'); } }
worker.js
onmessage = function(e) { console.log('Message received from main script'); var workerResult = e.data.aTopic[0] * e.data.aTopic[1]; new Int32Array(e.data.aBuf)[0] = workerResult; }
咱们这个时候也不用担忧postMessage 伴有时延的通讯。可是多个worker同时访问一块内存这个时候会出现竞争的问题的。
从 CPU 层面看,增长一个变量值须要三条指令,这是由于计算机同时有长期存储器(内存)和短时间存储器(寄存器全部的线程共享同一个长期存储器(内存),可是短时间存储器(寄存器)并非共享的。每一个线程须要把值先从内存搬到寄存器,以后就能够在寄存器上进行计算了,再而后会把计算后的值写回内存)。
这个由于竞争的关系会产生错误的结果,那么咱们应该怎样让SharedArrayBuffers 发挥他应有的价值呢?
这个时候伴随它诞生的还有:
原子操做作的一件事就是在多线程中让计算机按照人所想的单操做方式工做。
这就是为何被叫作原子操做,由于它可让一个包含多条指令(指令能够暂停和恢复)的操做执行起来像是一会儿就完了,就好像一条指令,相似一个不可分割的原子。
var sab = new SharedArrayBuffer(1024); var ta = new Uint8Array(sab); //The static Atomics.add() method adds a given value at a given //position in the array and returns the old value at that position. Atomics.add(ta, 0, 1); // returns 0, the old value Atomics.load(ta, 0); // 1
首先咱们来看下在async 函数诞生以前咱们是怎么处理异步的,我先简单列举几个编程模型:
回调函数:
// more code function loading(callback) { // wait 3s setTimeout(function () { callback(); }, 3000); } function show() { // show the data. } loading(show); // more code
在这种状况下容易出现你们所熟知的回调黑洞:
A(function () { B(function () { C(function() { D(function() { // ... }) }) }) })
Promise模式
所谓 Promise,就是一个对象,用来传递异步操做的消息。它表明了某个将来才会知道结果的事件(一般是一个异步操做),而且这个事件提供统一的 API,可供进一步处理。
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('Resolved.'); }); console.log('Hi!'); // Promise // Hi! // Resolved
这种实现方式和AngularJs中的Promise用法相近。
下面简单提下ES6中的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 }
gen函数返回不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
其实在ES8以前咱们还有许多其余的异步实现方式,好比:
在angualrJs中咱们实现的subscribe, unsubscribe, publish这种消息订阅/发布模式其实也是实现异步的一种方式。
下面咱们来讲说今天的主角:async 函数
关键词:await async
那么咱们先看下await, await后面能够是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操做),当函数执行的时候,一旦遇到await就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。
async函数返回一个 Promise 对象,可使用then方法添加回调函数。
咱们看下下面的代码来理解上面的话:
function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add(x) { var a = await resolveAfter2Seconds(20); var b = await resolveAfter2Seconds(30); return x + a + b; } add(10).then(v => { console.log(v); // prints 60 after 4 seconds. });
ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的全部可遍历(enumerable)属性的键名。
var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"]
es8中新添加的Object.values/Object.entries做为遍历对象的一种补充手段。
Object.values方法返回一个数组,成员是参数对象自身的全部可遍历(enumerable)属性的键值。
var obj = { 100: 'a', 'a': 'b', 7: 'c' }; Object.values(obj) // ["c", "a", "b"]
Object.entries方法返回一个数组,成员是参数对象自身的全部可遍历(enumerable)属性的键值对数组。
var obj = { 'foo': 'bar', 'baz': 42 , '1': 43}; Object.entries(obj) // [['1', 43], ["foo", "bar"], ["baz", 42] ]
以上的遍历对象的属性,都遵照一样的属性遍历的次序规则。
首先遍历全部属性名为数值的属性,按照数字排序。
其次遍历全部属性名为字符串的属性,按照生成时间排序。
最后遍历全部属性名为 Symbol 值的属性,按照生成时间排序。
(Object.keys,Object.values,Object.entries都会过滤属性名为 Symbol(一种新的原始数据类型, 表示独一无二的值) 值的属性。)
字符填充函数,在es8中引入的两个方法: String.padStart 和String.padEnd, 这两个放发的主要是为了在必定程度上填充字符串的长度, 语法以下:
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
这个方法主要是为了实现字符串补全长度的功能。若是某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba' //若是原字符串的长度,等于或大于指定的最小长度,则返回原字符串。 'xxx'.padStart(2, 'ab') // 'xxx' 'xxx'.padEnd(2, 'ab') // 'xxx' //若是省略第二个参数,默认使用空格补全长度。 'x'.padStart(4) // ' x' 'x'.padEnd(4) // 'x ' //用途: 为数值补全指定位数。 '1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456" //另外一个用途是提示字符串格式。 '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
getOwnPropertyDescriptors函数: 返回指定对象全部自身属性(非继承属性)的描述对象。
ES5 有一个Object.getOwnPropertyDescriptor方法,返回某个对象属性的描述对象(descriptor)。
var obj = { p: 'a' }; Object.getOwnPropertyDescriptor(obj, 'p') // Object { value: "a", // writable: true, // enumerable: true, // configurable: true // }
ES2017 引入了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: bar], // set: undefined, // enumerable: true, // configurable: true } }
value
包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。该特性的默认值为undefined。直接在对象上定义的属性,该特性被设置为指定的值
writable
表示可否修改属性的值。直接在对象上定义的属性,该特性默认为true
get
获取该属性的访问器函数(getter)。若是没有访问器, 该值为undefined。(仅针对包含访问器或设置器的属性描述有效)
set
获取该属性的设置器函数(setter)。 若是没有设置器, 该值为undefined。(仅针对包含访问器或设置器的属性描述有效)
configurable
表示可否经过delete删除属性从而从新定义属性。,可否修改属性的特性,或者可否把属性修改成访问器属性。直接在对象上定义的属性,该特性默认为true;
enumerable
表示可否经过for-in循环返回属性。直接在对象上定义的属性,该特性默认为true
应用:浅拷贝
const shallowClone = (obj) => Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) );
在装饰器上应用
此处结尾逗号指的是在函数参数列表中最后一个参数以后的逗号以及函数调用时最后一个参数以后的逗号。ES8 容许在函数定义或者函数调用时,最后一个参数以后存在一个结尾逗号而不报 SyntaxError 的错误。示例代码以下:
函数声明时
function es8(var1, var2, var3,) { // ... }
函数调用时es8(10, 20, 30,);ES8的这项新特性受启发于对象或者数组中最后一项内容以后的逗号,如 [10, 20, 30,] 和 { x: 1, } 。