Что нового в ES2016, 2017 и 2018?

JavaScript ECMAScript 6 ECMAScript 8
Что нового в ES2016, 2017 и 2018?

JavascriptСкорость обновления слишком высока, чтобы идти в ногу с темпом, и есть несколько вспомогательных руководств.Сегодня я представлю новые функции и возможности ES2016 ~ ES2018 вместе с подробными примерами кода.

Следующее представлено в порядке версий JS:

1. Array.prototype.includes

includesЭто метод экземпляра массива. Функция этого метода очень проста: он используется для определения того, существует ли элемент в массиве.indexOf, разница между нимиindexOfне в состоянии судитьNaN, как показано на рисунке:

const arr = [1, 2, 3, 4, NaN];// es5if (arr.indexOf(3) >= 0) {   console.log(true)}// es2016if (arr.includes(1)) {   console.log(true)}// 注:indexOf不支持检查`NaN`arr.indexOf(NaN) // -1arr.includes(NaN) // trueскопировать код

2. Оператор возведения в степень

оператор возведения в степень: **, который заменяет предыдущий метод возведения в степеньMath.pow,

Способ применения следующий:

// 之前Math.pow(3, 2) // 9// 现在3**2 // 9скопировать код

1. Object.values()

Object.valuesМетоды иObject.keysТочно так же возвращаемый тип — это массив, а возвращаемое значение — это набор значений объекта.Следует отметить, что оба метода возвращают свои собственные свойства, исключая любые свойства в цепочке прототипов, как показано на рисунке:

const cars = {  BMW: 3,  Tesla: 2,  Toyota: 1}// es5const vals = Object  .keys(cars)  .map(key => cars[key]) console.log(vals) // [3, 2, 1]// es2016const values = Object.values(cars)console.log(values) // [3, 2, 1]скопировать код

2. Object.entries()

Object.entries()метод немного похожObject.keysа такжеObject.valuesКомбинация , возвращаемый тип представляет собой массив, и каждый элемент массива также является массивом, включая два элемента: ключ и значение.Преимущество этого метода в том, что вы можете передатьfor ofПройдите ключ/значение один раз; возвращаемое значение (объект) Object.entries() также может быть напрямую преобразовано вMap:

пример 1, пересекая:

const cars = {  BMW: 3,  Tesla: 2,  Toyota: 1}// es5的遍历方式// 需要把`key`取出来,再遍历Object  .keys(cars)  .forEach(key => {    console.log(`key: ${key}, value: ${cars[key]}`)  })// es2017// Object.entries(carts):// [//   ['BMW', 3],//   ['Tesla', 2],//   ['Toyota', 1]// ]for (let [key, value] of Object.entries(cars)) {  console.log(`key: ${key}, value: ${cars[key]}`)}скопировать код

Пример 2, преобразовать объект непосредственно вMap:

const cars = {  BMW: 3,  Tesla: 2,  Toyota: 1}// es5const map1 = new Map()Object.keys(cars).map(key => {  map1.set(key, cars[key])})console.log(map1) // Map { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }// es2016const map2 = new Map(Object.entries(cars))console.log(map2) // Map { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }скопировать код

3. String padding

String добавляет два метода экземпляра —padStartа такжеpadEnd, эти два метода могут добавлять другие строки в начало/конец строки:

// 'someStr'.padStart(字符数, [,添加的字符])'hello'.padStart('10', 'a') // 'aaaaahello', 添加了5个字符`a`后一共`10`个字符'hello'.padEnd('10', 'b') // 'hellobbbbb''hello'.padStart('7') // '  hello', 在头部添加两个个空格скопировать код

3.1 Пример стартовой площадки

const formatted =  [ 0, 1, 12, 123, 1234, 12345 ]   .map(num =>     num.toString().padStart(10, '0')   )console.log(formatted)// 输出:// [//   '0000000000',//   '0000000001',//   '0000000012,'//   '0000000234,'//   '0000001234,'//   '0009012345'// ]скопировать код

3.2 пример padEnd

const cars = {  '🚙BMW': '10',  '🚘Tesla': '5',  '🚖Lamborghini': '0'}Object  .entries(cars)  .map(([name, count]) => {    console.log(`${name.padEnd(20, ' -')} Count: ${count.padStart(3, '0')}`)  });//输出:// 🚙BMW - - - - - - -  Count: 010// 🚘Tesla - - - - - -  Count: 005// 🚖Lamborghini - - -  Count: 000скопировать код

4.Object.getOwnPropertyDescriptors

Эффект этого метода заключается в дополненииObject.assignфункция, основанная на объекте мелкого клонирования, также скопируетgetterа такжеsetterметод:

В следующем примере используетсяObject.definePropertiesскопировать исходный объектCarна новый объектElectricCarпоказыватьObject.assignа такжеObject.getOwnPropertyDescriptorsс разница.

const Car = {  name: 'BMW',  price: 100000,  set discount(x) {    this.d = x  },  get discount() {    return this.d  }}console.log(Object.getOwnPropertyDescriptor(Car, 'discount')// 输出:// {//   get: [Function: get],//   set: [Function: set],//   enumerable: true,//   configurable: true// }const ElectricCar = Object.assign({}, Car)console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount'))// 输出:// {//   value: undefined,//   writable: true,//   enumerable: true,//   configurable: true// }// 使用`Object.assign`创建`ElectricCar`后,属性`getter`和`setter`丢失了скопировать код

использоватьObject.getOwnPropertyDescriptorsназад:

const Car = {  name: 'BMW',  price: 100000,  set discount(x) {    this.d = x  },      get discount() {    return this.d  }}const ElectricCar2 =  Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car))// 输出:// {//   get: [Function: get],  <-----👈//   set: [Function: set],  <-----👈//   enumerable: true,//   configurable: true // }скопировать код

5. Добавьте запятую в конце последнего параметра функции

Это небольшая функциональная точка, добавление запятой в конце последнего параметра параметра функции может избежатьgit blameЗапрашивать изменения, которых не было у предыдущего автора, пример кода:

// 假设这个函数由 `程序员_1` 创建// 这个函数最后一个参数`age`后没有逗号function Person(  name,  age) {  this.name = name  this.age = age}// 如果 `程序员_2` 这时有了以下修改function Person(  name,  age, /* 那么这个`,`逗号也会引起`git blame`认为 `程序员_1` 修改了这一行*/  gender /* 添加了新参数 */) { // 新添加  this.name = name  this.age = age  this.gender = gender // 新添加}// es2017对这个混淆的处理办法是:// 通过 `程序员_1`在`age`末尾添加`,`逗号// 更新如下:// 假设这个函数由 `程序员_1` 创建// 在最后一个参数`age`后添加`,`逗号function Person(  name,  age, /* 添加逗号 */) {  this.name = name  this.age = age}скопировать код

6. Async/Await

Эта функция, безусловно, самая важная функция,asyncФункция позволяет нам избежать частых вызововотвратительный callback, чтобы код оставался чистым и аккуратным.

когда компилятор входитasyncПосле функции встречаawaitКлючевое слово приостановит выполнение, вы можете поставитьawaitпоствыражение какpromise, пока не будет обещаноresolveилиrejectПосле этого функция возобновит выполнение,

В частности, посмотрите на следующий код:

// es5的`Promise`function getAmount(userId) {  getUser(userId)    .then(getBankBalance)    .then(amount => {      console.log(amount)    })}// es2017的`async`async function getAmount2(userId) {  var user = await getUser(userId)  var amount = await getBankBalance()  console.log(amount)}getAmount('1') // $1,000getAmount2('1') // $1,000function getUser(userId) {  return new Promise(resolve => {    setTimeout(() => {      resolve('张三')    }, 1000)  })}function getBankBalance() {  return new Promise((resolve, reject) => {    setTimeout(() => {      if (user === '张三') {        resolve('$1,000')      } else {        resolve('Unknown User')      }    }, 1000)  })}скопировать код

6.1 Сама функция Async возвращает обещание

Поскольку асинхронная функция возвращает обещание, чтобы получить возвращаемое значение асинхронной функции, вам нужно выполнить возвращенное обещание.thenоценивать.

В частности, посмотрите на следующий код:

async function doubleAndAdd(a, b) {  a = await doubleAfter1Sec(a)  b = await doubleAfter1Sec(b)  return a + b}doubleAndAdd(1, 2).then(console.log) // 5 async function doubleAfter1Sec(param) {  return new Promise(resolve => {    setTimeout(() => {      resolve(param * 2)    }, 1000)  })}скопировать код

6.2 Параллельный вызов async/await

предыдущая функцияdoubleAndAddВызывали двоих по очередиasyncфункция, но приходится ждать 1 секунду для каждого вызова, низкая производительность; потому что параметрaи параметрыbМежду ними нет связи, поэтому мы можем использоватьPromise.allчтобы выполнить эти два вызова параллельно:

async function doubleAndAdd(a, b) {  // 使用`Promise.all`  // 这个地方使用数组`解构`  // 来得到两次调用的结果  const [a, b] = Promise.all([    doubleAfter1Sec(a),    doubleAfter1Sec(b)  ])  return a + b}doubleAndAdd(1, 2).then(console.log) // 5 async function doubleAfter1Sec(param) {  return new Promise(resolve => {    setTimeout(() => {      resolve(param * 2)    }, 1000)  })}скопировать код

6.3 Обработка ошибок async/await

async/awaitСуществует множество способов обработки ошибок:

1. Используйте try/catch внутри функции

async function doubleAndAdd(a, b) {  try {    a = await doubleAfter1Sec(a)    b = await doubleAfter1Sec(b)  } catch (e) {    return NaN  }  return a + b}doubleAndAdd('one', 2).then(console.log) // NaNdoubleAndAdd(1, 2).then(console.log) // 5 async function doubleAfter1Sec(param) {  return new Promise(resolve => {    setTimeout(() => {      const val = param * 2      isNaN(val) ? reject(NaN) : resolve(val)    }, 1000)  })}скопировать код

2. Уловить всеawaitвыражение

потому чтоawaitвыражение возвращаетpromise, так что мы можемawaitВыполнять сразу после выраженияcatchобрабатывать ошибки

async function doubleAndAdd(a, b) {  a = await doubleAfter1Sec(a).catch(e => console.log(`'a' is NaN`)  b = await doubleAfter1Sec(b).catch(e => console.log(`'b' is NaN`))  if (!a || !b) return NaN  return a + b}doubleAndAdd('one', 2).then(console.log) // NaN, "a" is NaNdoubleAndAdd(1, 2).then(console.log) // 5 async function doubleAfter1Sec(param) {  return new Promise(resolve => {    setTimeout(() => {      const val = param * 2      isNaN(val) ? reject(NaN) : resolve(val)    }, 1000)  })}скопировать код

3. поймать всю функцию асинхронного ожидания

async function doubleAndAdd(a, b) {  a = await doubleAfter1Sec(a)  b = await doubleAfter1Sec(b)  return a + b}doubleAndAdd('one', 2)  .then(console.log)  .catch(console.log) // 使用catchскопировать код

ECMAScript в настоящее время находится на стадии финального проекта и будет официально запущен в июне или июле 2018 года. Все функции, описанные ниже, принадлежатstage-4, который вскоре станет частью ECMAScript 2018.

1. Общая память и атомарность

Это расширенная функция JS и основное улучшение движка JS.

Основная идея разделяемой памяти заключается в том, чтобы привнести многопоточность в JS, чтобы улучшить производительность и высокий параллелизм кода, предыдущий движок JS управляет памятью для управления самой памятью.

Эта возможность реализована новым глобальным объектом SharedArrayBuffer, который находится в блокеразделяемая область памятиДля хранения данных основной поток JS иweb-workerПотоки разделяют эту часть данных.

В настоящее время, если мы хотим обмениваться данными между основным потоком JS и потоком веб-воркеров, мы должны использоватьpostMessageПередача данных между разными потоками сSharedArrayBufferПозже различные потоки могут напрямую обращаться к этому объекту для обмена данными.

Однако общая память между несколькими потоками вызовет состояние гонки.Чтобы избежать этой ситуации, JS вводит原子性глобальный объект. Этот объект предоставляет различные методы для обеспечения того, чтобы память, к которой обращается поток, была заблокирована для безопасности памяти.

2. Ограничение Tagged Template literal (tagged template literal?) снято

Сначала разберитесь с концепцией: что такое литерал тегированного шаблона?

tagged template literalПоявившийся в es2015 и более поздних версиях, позволяет разработчикам настраивать значение встроенной строки. Например, стандартный способ вставить значение в строку:

const userName = '张三'const greetings = `hello ${userName}!`console.log(greetings) // "hello 张三!"скопировать код

существуетtagged template literal, Вы можете использовать функцию для получения жесткокодируемых частей строки через параметры, такие как: [«Hello», '!'] и переменная ['Zhang San'], которая позже заменяется значением, и, наконец, верните любое значение через функцию. Вы хотите результат, функция называетсяTaggedфункция, нижеTaggedфункцияgreetЧтобы расширить приветствия в приведенном выше примере:

const userName = '张三'const greetings = `hello ${userName}!`console.log(greetings) // "hello 张三!早上好!"// hardCodedPartsArray: 字符串写死的各部分,  [ "hello ", "!" ]// replacementPartsArray: 字符串里嵌入的变量, [ "张三" ]function greet(hardCodedPartsArray, ...replacementPartsArray) {  let str = ''  hardCodedPartsArray.forEach((part, i) => {    if (i < replacementPartsArray.length) {      str += `${part}${replacementPartsArray[i] || ''}`    } else {      str += `${part} ${timeGreet()}` // 在结尾添加问候语    }  })  return str}function timeGreet() {  const hr = new Date().getHours()  return hr < 12    ? '早上好!'    : hr < 18 ? '下午好!' : '晚上好!'}скопировать код

3. В регулярных выражениях.соответствовать всем символам

Хотя в текущих регулярных выражениях.считается, что точка представляет все символы, на самом деле она не будет соответствовать чему-то вроде\n,\rа также\fДождитесь новой строки.

Например:

// 之前/first.second/.test('first\nsecond'); // falseскопировать код

Это улучшение делает.Оператор точки соответствует любому одиночному символу. Для того, чтобы следующий код работал корректно в любой версии JS, добавим в конце/sмодификатор

//ECMAScript 2018/first.second/s.test('first\nsecond'); // true   注意: /s 👈🏼скопировать код

4. Захват регулярного выражения Именованная группа

Это улучшение приносит полезные функции регуляризации, которые уже поддерживаются в других языках, таких как Java, Python и т. д. Эта функция позволяет разработчикам записывать формат для разных групп в регулярном выражении как(<?name>)Идентификатор имени , а затем соответствующее значение может быть получено группой, идентифицированной по имени в результате сопоставления.

4.1 Базовый пример

// 之前const re1 = /(\d{4})-(\d{2})-(\d{2})/const result1 = re1.exec('2015-01-08')console.log(result1)// 输出:// [//   "2015-01-08",//   "2015",//   "01",//   "08",//   index: 0,//   input: "2015-01-08",//   groups: undefined// ]// 现在 (es2018)const re2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/uconst result2 = re2.exec('2015-01-08')console.log(result2)// 输出:// [//   "2015-01-08",//   "2015",//   "01",//   "08",//   groups: {//     day: "08",//     month: "01",//     year: "2015"//   },//   index: 0,//   input: "2015-01-08",// ]скопировать код

4.2 Использование именованных групп в самом регулярном выражении

мы можем использовать формат\k<group name>перед самим регулярным, чтобы обратиться к предыдущей группе. Следующий пример использования показывает:

// 在这个例子里,我们有一个命名组`fruit`,// 它可以匹配`apple`或者`orange`,我们可以用`\k<group name>`(\k<fruit>)// 来引用之前匹配的这个组// 所以等号两边的值是相等的const sameWords = /(?<fruit>apple|orange)==\k<fruit>/usameWords.test('apple==apple') // truesameWords.test('orange==orange') // truesameWords.test('apple==orange') // falseскопировать код

4.3 Использование именованных групп в String.prototype.replace

Функция именованной группы была добавлена ​​вreplaceметод, поэтому мы можем легко заменить строки.

Пример: изменить «имя, фамилия» на «фамилия, имя»:

const re = /(?<firstName>[A-Za-z]+) (?<lastName>[A-Za-z]+)/u'John Lennon'.replace(re, '$<lastName>, $<firstName>') // Lennon Johnскопировать код

5. Остальные свойства объектов

Оператор отдыха...(три точки) позволяет убрать остальные свойства объекта

5.1 Используйте свойства Rest, чтобы получить свойства, которые вы хотите использовать

let { name, age, ...remaining} = {  name: '张三',  age: 20,  gender: '男',  address: 'xxxxx'}name // '张三'age // 20скопировать код

5.2 Вы даже можете удалить ненужные свойства

// 如果我们想要删除address属性,// 但是我们又不想要遍历对象重新创建新对象// 我们只要简单的解构出这个要移除的属性// 剩下的没有解构的对象// 就是我们想要留下的对象let {address, ...cleanObj} = {  name: 'john',  address: '北京市海淀区',  gender: '男'}cleanObj // {name, gender}скопировать код

6. Распространяйте свойства объектов

Свойство Spread похоже на свойство Rest, и это тоже три точки....оператор, за исключением того, что Spread используется для создания новых объектов.

const person = {name: 'john', age: 20}const address = {city: 'Beijing', country: 'china'}const personWithAddress = {  ...person,  ...address}personWithAddress // {name, age, city, country}скопировать код

7. Регулярное утверждение просмотра назад

Это улучшение регуляризации позволяет нам гарантировать, что некоторые строки существуют до некоторых строк.

можно использовать набор(?<=...)(вопросительный знак, меньше или равно) Найдите следующее положительное утверждение.

Делая шаг вперед, вы можете использовать(?<!...(знак вопроса, меньше восклицательного знака) Найдите последующее отрицательное утверждение.

Положительное утверждение: например, мы хотим быть уверены, что символ#появляется в словеwinningраньше, т.е.#winning, только возвращаетwinning:

/(?<=#).*/.test('winning') // false/(?<=#).*/.test('#winning') // true// 之前'#winning'.match(/#.*/)[0] // '#winning'// es2018'#winning'.match(/(?<=#).*/)[0] // 'winning', 没有 #, #只是为了验证скопировать код

Отрицательное утверждение: например, мы хотим убрать число со знаком # вместо $

'this is a test signal $1.23'.match(/(?<!\$)\d+\.\d+/) // null'this is a test signal #2.43'.match(/(?<!\$)\d+\.\d+/)[0] // 2.43скопировать код

8. Обычный escape-символ свойства Unicode

Сопоставить все символы Юникода с регулярным выражением сложно. картина\w,\W,\dи т. д. могут соответствовать только английским символам и цифрам, но что нам делать с числами, которые появляются на других языках, таких как греческий?

Экранирующий символ свойства Unicode предназначен для решения этой проблемы. Это заставляет Unicode добавлять описание к каждому символу.metadata.

Пример: база данных Unicode группирует все символы хинди в одно значение.DevanagariсвойстваScriptи другое значение также дляDevanagariсвойстваScript_Extensionsпод группой, чтобы мы могли искатьScript_Extensionsчтобы получить все символы хинди.

Starting in ECMAScript 2018, we can use \p to escape characters along with {Script=Devanagari} to match all those Indian characters. That is, we can use: \p{Script=Devanagari} in the RegEx to match all Devanagari characters.

Начиная с ES2018 мы можем использовать\pСопоставьте все символы хинди с escape-символом {Script=Devanagari}, то есть используйте escape-символ\p{Script=Devanagari}чтобы соответствовать всем символам Деванагари.

// 下面的正在匹配多个北印度语字符// ps: 这里一共有三个北印度语字符/^p{Script=Devanagari}+$/u.test('हिन्दी') // trueскопировать код

Точно так же Unicode присваивает атрибуту все греческие символы.Script_Extensions(а такжеScript) значениеGreekсгруппировать, чтобы мы могли использоватьScript_Extensions=GreekилиScript=Greekдля поиска всех греческих символов.

То есть мы можем использовать escape-символы\p{Script=Greek}чтобы соответствовать всем греческим символам:

/\p{Script_Extensions=Greek}/u.test('π') // trueскопировать код

Кроме того, в базе данных Unicode хранится множество типов символов Emoji с логическими свойствами.Emoji,Emoji_Component,Emoji_Presentation,Emoji_Modifierа такжеEmoji_Modifier_Base, значениеtrueЧтобы сгруппировать по, мы можем сделать это, используяEmojiДля поиска всех символов эмоджи.

То есть, экранируя символ\p{Emoji}чтобы соответствовать различным символам Emoji

/\p{Emoji}/u.test('❤️')// 下面的例子匹配失败,因为黄色的emoji字符不需要`Emoji_Modifier`/\p{Emoji}\p{Emoji_Modifier}/u.test('✌️'); //false// 下面的匹配一个emoji字符,`\p{Emoji}`跟着一个`\p{Emoji_Modifier}`/\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //true// 解释:// 默认情况下`胜利`的emoji字符是黄色的,// 如果我们使用棕色、黑色或者其他颜色的变种emoji,// 它们被当做为原始Emoji字符的变种,使用两个unicode字符来表示,// 一个代表原始的emoji字符,跟着的一个unicode字符表示颜色//// 所以在下面的例子里,即使我们只看到了一个棕色的胜利emoji图标,// 但是它实际上使用了两个unicode字符,一个是emoji,另一个是棕色。//// 在Unicode数据库里,这些颜色有`Emoji_Modifier`属性。// 所以我们需要使用`\p{Emoji}`和`\p{Emoji_Modifier}`// 来完整的匹配棕色emoji/\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //trueскопировать код

Наконец, мы можем использовать escape-символ заглавной буквы «P» (\P) вместо маленькой p (\p), чтобы отменить совпадения.

Наконец, мы можем использовать заглавную P (\P) escape-символ для соответствия и нижний регистр\pСоответствует противоположному содержанию.

8. Promise.prototype.finally()

finally()— это метод экземпляра, недавно добавленный в Promise. Основное использование вresolveилиrejectПосле выполнения функции обратного вызова выполняется задача очистки.

finallyФункция обратного вызова не принимает никаких параметров и будет выполняться независимо от обстоятельств.

// Resolve示例let started = truelet myPromise = new Promise((resolve, reject) => {  resolve('all good')}).then(val => {  console.log(val) // 'all good'}).catch(e => {  console.log(e) // 跳过}).finally(() => {  console.log('这个函数总是会被执行')  started = false // 清理})// Reject示例let started = truelet myPromise = new Promise((resolve, reject) => {  reject('reject apple')}).then(val => {  console.log(val) // 'reject apple'}).catch(e => {  console.log(e) // 跳过}).finally(() => {  console.log('这个函数总是会被执行')  started = false // 清理})// 错误case 1// 从Promise抛出错误let started = truelet myPromise = new Promise((resolve, reject) => {  throw new Error('error')}).then(val => {  console.log(val) // 跳过}).catch(e => {  console.log(e) // 因为有error,所以catch被调用}).finally(() => {  console.log('这个函数总是会被执行')  started = false // 清理})// 错误case 2// 从`catch` case 抛出错误let started = truelet myPromise = new Promise((resolve, reject) => {  throw new Error('something happened')}).then(val => {  console.log(val) // 跳过}).catch(e => {  throw new Error('throw another error')}).finally(() => {  console.log('这个函数总是会被执行')  started = false // 清理  // 注意,从*catch*里抛出的错误需要在其他地方处理})скопировать код

9. Асинхронный цикл

Этооченьполезная функция. По сути, это позволяет нам легко создавать циклы в асинхронных функциях.

Эта функция добавляет новыйfor-await-ofЦиклы позволяют нам вызывать асинхронную функцию, которая возвращает промис (или массив промисов) в цикле.

Круто то, что этот цикл ждет каждого промисаresolveЗатем переходите к следующему циклу.

const promises = [  new Promise(resolve => resolve(1)),  new Promise(resolve => resolve(2)),  new Promise(resolve => resolve(3)),]// 之前// for-of使用正常的同步循环// 不会等待promise resolveasync function test1() {  for (const obj of promises) {    console.log(obj) // 输出3个promise对象  }}// 之后// for-await-of 使用Async循环// 为每个循环等待promise resolveasync function test2() {  for await (const obj of promises) {    console.log(obj) // 输出1, 2, 3  }}test1() // promise, promise, promisetest2() // 1, 2, 3скопировать код