본문으로 건너뛰기

ES2017

무료2018-11-11#JS#ES8#ServiceWorker共享数据#JS线程锁#JS原子操作#JS transaction

Async functions 드디어 ES2017 에서 호화 런치에 추가되었고, 멀티스레드 방면의 기초 건설도 점차 정비되고 있습니다

一.특성 개관

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.

게다가, SharedArrayBufferArrayBuffer 로서 사용할 수 있으므로, 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() 외에, 이하가 있습니다:

및 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 에서 호화 런치에 추가되었고, 멀티스레드 방면의 기초 건설도 점차 정비되고 있습니다

게다가, 3 개의 중요하지 않은 Object 메서드, 1 개의 문자열 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.

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성