ES8初探

ES8

在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(在函數參數列表和調用中減小逗號的使用)瀏覽器

首先咱們來介紹一下主要的兩個特性:多線程

Shared memory and atomics (共享內存和原子)

在咱們先須要主要須要瞭解SharedArrayBuffer 和 Atomics異步

SharedArrayBuffer

在瞭解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 發揮他應有的價值呢?
這個時候伴隨它誕生的還有:

Atomics

原子操做作的一件事就是在多線程中讓計算機按照人所想的單操做方式工做。
這就是爲何被叫作原子操做,由於它可讓一個包含多條指令(指令能夠暫停和恢復)的操做執行起來像是一會兒就完了,就好像一條指令,相似一個不可分割的原子。

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 函數

首先咱們來看下在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.
});

Object.values/Object.entries

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(一種新的原始數據類型, 表示獨一無二的值) 值的屬性。)

String padding

字符填充函數,在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函數

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)
);

在裝飾器上應用

Trailing commas in function parameter lists and calls(結尾逗號)

此處結尾逗號指的是在函數參數列表中最後一個參數以後的逗號以及函數調用時最後一個參數以後的逗號。ES8 容許在函數定義或者函數調用時,最後一個參數以後存在一個結尾逗號而不報 SyntaxError 的錯誤。示例代碼以下:

函數聲明時

function es8(var1, var2, var3,) {
  // ...
}

函數調用時es8(10, 20, 30,);ES8的這項新特性受啓發於對象或者數組中最後一項內容以後的逗號,如 [10, 20, 30,] 和 { x: 1, } 。

相關文章
相關標籤/搜索