ES2017
一.特性概覽
2個主要特性:
-
ofollow,noindex" target="_blank">Async functions
4個小特性:
二.Async functions
一個里程碑式的特性,標誌著JS非同步程式設計體驗上升到了一個新高度,具體見從Generator到Async function
三.Shared memory and atomics
算是在多執行緒並行能力方面的基礎建設 ,分為2部分:
-
SharedArrayBuffer
允許主執行緒、及WebWorkers之間共享資料 -
Atomic operations
(原子操作)用來解決資料同步的問題,如加鎖、事務
例如:
// 主執行緒 var w = new Worker("myworker.js"); var sab = new SharedArrayBuffer(1024);// 1KiB shared memory // 同樣通過postMessage給worker執行緒丟過去 w.postMessage(sab); // worker執行緒(myworker.js) var sab; onmessage = function (ev) { sab = ev.data;// 1KiB shared memory, the same memory as in the parent }
之前
執行緒之間傳遞的是值copy,而不是共享引用
現在可以通過SharedArrayBuffer
共享同一份資料,並且在worker執行緒裡也可以建立共享資料:
Memory can be created in any agent and then shared with any other agent, and can be shared among many agents simultaneously.
另外,SharedArrayBuffer
可以作為ArrayBuffer
使用,所以也可以共享JavaScript/Reference/Global_Objects/TypedArray" rel="nofollow,noindex" target="_blank">TypedArray
:
var sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000); // 100000 primes var ia = new Int32Array(sab);// ia.length == 100000 var primes = new PrimeGenerator(); for ( let i=0 ; i < ia.length ; i++ ) ia[i] = primes.next(); w.postMessage(ia);
由於資料是多執行緒共享的,勢必面臨資料同步的問題
,通過Atomics
全域性物件提供的一些方法來解決:
// 讀 Atomics.load(typedArray, index) // 寫 Atomics.store(typedArray, index, value) // 寫,返回舊值 Atomics.exchange(array, index, value) // 條件寫,僅當舊值等於oldval時才寫,返回舊值 compareExchange(array, index, oldval, newval) // 帶讀寫鎖的運算(加、減、與、或、異或) Atomics.add(array, index, value) Atomics.sub(array, index, value) Atomics.and(array, index, value) Atomics.or(array, index, value) Atomics.xor(array, index, value)
這些原子操作不會被打斷(not interruptible),在此基礎上可以實現:
-
保證連續讀寫操作的順序
-
避免寫操作“丟失”(比如寫到髒資料上了)
此外,還允許掛起/喚醒(更友好的執行緒等待方式,不多佔資源):
Atomics.wait(typedArray, index, value[, timeout]) Atomics.wake(typedArray, index, count)
例如:
// A執行緒寫 console.log(ia[37]);// Prints 163 Atomics.store(ia, 37, 123456); Atomics.wake(ia, 37, 1); // B執行緒等著讀 Atomics.wait(ia, 37, 163); console.log(ia[37]);// Prints 123456
而不需要靠死迴圈來實現阻塞式等待:
while (Atomics.load(ia, 37) == 163); console.log(ia[37]);// Prints 123456
P.S.有意思的一點,主執行緒不允許掛起:
The specification allows the browser to deny wait on the main thread, and it is expected that most browsers will eventually do so. A denied wait throws an exception.
P.S.關於Shared memory and atomics特性的更多資訊,請檢視:
四.小特性
Object.values/Object.entries
// 返回 (1)自身的 (2)可列舉的 (3)非Symbol型別的 屬性的值 Object.values(obj)
polyfill實現 大致如下:
function values(obj) { var vals = []; for (var key in obj) { if (obj.hasOwnProperty(key) && obj.propertyIsEnumerable(key)) { vals.push(obj[key]); } } return vals; }
與Object.keys()
一致,對屬性都有3個限定條件(own && enumerable && non-Symbol-only
)。因此,不考慮效能的話,可以實現更簡單的polyfill:
function values(obj) { return Object.keys(obj).map(key => obj[key]); }
類似的,還提供了:
// 返回 (1)自身的 (2)可列舉的 (3)非Symbol型別的 屬性的鍵值對兒 Object.entries(obj)
polyfill 也類似:
function entries(obj) { var entrys = []; for (var key in obj) { if (obj.hasOwnProperty(obj, key) && obj.propertyIsEnumerable(obj, key)) { entrys.push([key, obj[key]]); } } return entrys; };
除了返回值形式不同以外,與Object.values(obj)
一毛一樣
應用場景上,Object.entries(obj)
可以用來完成mapObject
轉Map的工作:
new Map(Object.entries({ one: 1, two: 2, })) // 輸出 Map(2) {"one" => 1, "two" => 2}
列舉性,原型屬性與Symbol
-
列舉性:通過
obj.propertyIsEnumerable(key)
來檢查,下面用enumerable
表示可列舉 -
是不是原型屬性:通過
obj.hasOwnProperty(key)
來檢查,下面用own
表示僅針對非原型屬性 -
是不是Symbol:通過
typeof key === 'symbol'
來檢查,下面用non-Symbol-only
表示僅針對非Symbol型別屬性,用Symbol-only
表示僅針對Symbol型別屬性
JS裡圍繞物件屬性的這3個特點提供了很多工具方法,除了上面提到的Object.keys()
、Object.values()
、Object.entries()
外,還有:
-
JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames" rel="nofollow,noindex" target="_blank">Object.getOwnPropertyNames(obj) :
own && non-Symbol-only
-
Object.getOwnPropertySymbols() :
own && Symbol-only
-
Reflect.ownKeys(obj) :
own
。等價於Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
以及1種遍歷方式:
-
for…in
:
enumerable && non-Symbol-only
P.S.想起了for…of
?這個東西與物件關係不大,僅針對iterable,如類陣列物件(arguments
、DOMNodeList等)
Object.getOwnPropertyDescriptors
// 以物件字典形式返回 (1)自身的 所有屬性的描述符 Object.getOwnPropertyDescriptors(obj)
包括Symbol型別屬性與不可列舉屬性,例如:
const obj = { [Symbol('foo')]: 123 }; Object.defineProperty(obj, 'bar', { value: 42, enumerable: false }); console.log(Object.getOwnPropertyDescriptors(obj)); // 輸出 // { //bar: {value: 42, writable: false, enumerable: false, configurable: false}, //Symbol(foo): {value: 123, writable: true, enumerable: true, configurable: true} // } // 而 Object.keys(obj).length === 0
可以通過Reflect.ownKeys(obj)
實現polyfill:
function getOwnPropertyDescriptors(obj) { const result = {}; for (let key of Reflect.ownKeys(obj)) { result[key] = Object.getOwnPropertyDescriptor(obj, key); } return result; }
應用場景上,主要用來完成精細的物件拷貝 工作:
// 連帶屬性描述符原樣搬過去 function clone(obj) { return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); } // 會丟失不可列舉屬性以及原描述符 function copy(obj) { return Object.assign({}, obj); }
區別如下:
const obj = {}; Object.defineProperty(obj, 'bar', { value: 42, enumerable: false }); Object.defineProperty(obj, 'foo', { value: 24, enumerable: true, writable: false }); Object.getOwnPropertyDescriptors(clone(obj)); // 屬性保持原狀 // bar: {value: 42, writable: false, enumerable: false, configurable: false} // foo: {value: 24, writable: false, enumerable: true, configurable: false} Object.getOwnPropertyDescriptors(copy(obj)); // 不可列舉的bar丟了,foo的屬性描述符被重置回默認了 // foo: {value: 24, writable: true, enumerable: true, configurable: true}
String padding
曾經引發npm風波 的left-pad模組,以後用不著了:
str.padStart(targetLength [, padString]) str.padEnd(targetLength [, padString])
有一些小細節,例如:
// 預設補空格(U+0020) '1'.padStart(4) === '1'.padStart(4, ' ') //也可以填充指定串 '1'.padEnd(4, 0) === '1000' // 填充串的長度不限於一個字元,太長會被裁剪掉 '1'.padEnd(4, 'abcde') === '1abc' // 不用補就不補 '1345'.padStart(2) === '1345'
Trailing commas in function parameter lists and calls
基本語法的2個小變動:
function foo( param1, param2, // 形參列表允許有多餘逗號 ) { foo( 'abc', 'def',// 實參列表允許有多餘逗號 ); }
實際上,類似的變動在ES5.1也發生過:
const object = { foo: "bar", baz: "qwerty", age: 42,// 物件字面量鍵值對兒列表允許有多餘逗號 };
除了上面3種,還有語言最初的語法規則:
const arr = [ 1, 2, 3,// 陣列字面量允許有多餘逗號 ]; arr; // [1, 2, 3] arr.length; // 3
特殊的:
const arr = [1, 2, 3,,,]; arr.length; // 5
在字面量形式的稀疏陣列中,最後一個逗號屬於trailing commas(末尾多餘逗號)被忽略掉 ,因此陣列大小是5
P.S.關於trailing commas的更多資訊,見Trailing commas
五.總結
Async functions終於在ES2017加入豪華午餐了,多執行緒方面的基礎建設也在逐步完善
此外,還有三個無關緊要的Object方法,一個字串padding方法,引數列表末尾允許有多餘逗號。對於這些錦上添花的東西,看到一個端正的態度 :
Are people seriously considering to extend the language for something that can be implemented in 7 lines of code ? Convenience matters, as does eliminating redundant code. JavaScript’s runtime library is still very spartan compared to other programming languages.
參考資料
-
ECMAScript® 2017 Language Specification (ECMA-262, 8th edition, June 2017)