一。特性概覽
2 個主要特性:
4 個小特性:
二。Async functions
一個里程碑式的特性,標誌著 JS 異步編程體驗上升到了一個新高度,具體見 [從 Generator 到 Async function](/articles/從 generator 到 async-function/)
三。Shared memory and atomics
算是在多線程並行能力方面的基礎建設,分為 2 部分:
-
SharedArrayBuffer允許主線程、及 [WebWorkers](/articles/理解 web-workers/) 之間共享數據 -
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 使用,所以也可以共享 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](/articles/集合(set 和 map)-es6 筆記 8/#articleHeader3) 的工作:
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](/articles/symbol-es6 筆記 7/) 類型屬性,用Symbol-only表示僅針對 Symbol 類型屬性
JS 裡圍繞對象屬性的這 3 個特點提供了很多工具方法,除了上面提到的 Object.keys()、Object.values()、Object.entries() 外,還有:
-
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.
暫無評論,快來發表你的看法吧