每一个JavaScript开发人员都应该晓得的新ES2018功用(译文)

媒介

本文首发于我的个人网站:
Timbok.top

正文

ECMAScript范例的第九版,官方称为ECMAScript 2018(或简称ES2018),于2018年6月宣布。从ES2016最先,ECMAScript范例的新版本每一年宣布而不是每几年宣布一次,而且增加的功用少于主要版本之前。该范例的最新版本经由历程增加四个新RegExp功用,rest/spread属性,asynchronous iteration,和Promise.prototype.finally。另外,ES2018从标记模板中删除了转义序列的语法限定。

这些新变化将在背面的小节中诠释。

rest/spread属性

ES2015最风趣的功用之一是点差运算符。该运算符使复制和兼并数组变得越发简朴。您可以运用运算符...,而不是挪用concat()or slice()要领:

const arr1 = [10, 20, 30];

// make a copy of arr1
const copy = [...arr1];

console.log(copy);    // → [10, 20, 30]

const arr2 = [40, 50];

// merge arr2 with arr1
const merge = [...arr1, ...arr2];

console.log(merge);    // → [10, 20, 30, 40, 50]

在必需作为函数的零丁参数传入数组的情况下,扩大运算符也派上用场。比方:

const arr = [10, 20, 30]

// equivalent to
// console.log(Math.max(10, 20, 30));
console.log(Math.max(...arr));    // → 30

ES2018经由历程向对象笔墨增加扩大属性来进一步扩大此语法。运用spread属性,您可以将对象的本身可罗列属性复制到新对象上。请斟酌以下示例:

const obj1 = {
  a: 10,
  b: 20
};

const obj2 = {
  ...obj1,
  c: 30
};

console.log(obj2);    // → {a: 10, b: 20, c: 30}

在此代码中,...运算符用于检索属性obj1并将其分配给obj2。在ES2018之前,尝试如许做会激发毛病。假如有多个具有雷同称号的属性,则将运用末了一个属性:

const obj1 = {
  a: 10,
  b: 20
};

const obj2 = {
  ...obj1,
  a: 30
};

console.log(obj2);    // → {a: 30, b: 20}

Spread属性还供应了一种兼并两个或多个对象的新要领,可以将其用作要领的替代Object.assign()要领:

const obj1 = {a: 10};
const obj2 = {b: 20};
const obj3 = {c: 30};

// ES2018
console.log({...obj1, ...obj2, ...obj3});    // → {a: 10, b: 20, c: 30}

// ES2015
console.log(Object.assign({}, obj1, obj2, obj3));    // → {a: 10, b: 20, c: 30}

但请注意,spread属性并不老是发生雷同的效果Object.assign()。请斟酌以下代码:

Object.defineProperty(Object.prototype, 'a', {
  set(value) {
    console.log('set called!');
  }
});

const obj = {a: 10};

console.log({...obj});    
// → {a: 10}

console.log(Object.assign({}, obj));    
// → set called!
// → {}

在此代码中,该Object.assign()要领实行继承的setter属性。相反,流传属性完全疏忽了setter。

主要的是要记着,spread属性只复制可罗列的属性。在以下示例中,type属性不会显现在复制的对象中,因为其enumerable属性设置为false

const car = {
  color: 'blue'
};

Object.defineProperty(car, 'type', {
  value: 'coupe',
  enumerable: false
});

console.log({...car});    // → {color: "blue"}

纵然它们是可罗列的,也会疏忽继承的属性:

const car = {
  color: 'blue'
};

const car2 = Object.create(car, {
  type: {
    value: 'coupe',
    enumerable: true,
  }
});

console.log(car2.color);                      // → blue
console.log(car2.hasOwnProperty('color'));    // → false

console.log(car2.type);                       // → coupe
console.log(car2.hasOwnProperty('type'));     // → true

console.log({...car2});                       // → {type: "coupe"}

在此代码中,car2继承color属性car。因为spread属性只复制对象的本身的属性,color所以不包含在返回值中。

请记着,spread属性只能天生对象的浅表副本。假如属性包含对象,则仅复制对象的援用:

const obj = {x: {y: 10}};
const copy1 = {...obj};    
const copy2 = {...obj}; 

console.log(copy1.x === copy2.x);    // → true

这里copy1copy2的x是指在内存中的统一对象,所以全等运算返回true

ES2015中增加的另一个有用功用是rest参数,它使JavaScript顺序员可以运用它...来示意值作为数组。比方:

const arr = [10, 20, 30];
const [x, ...rest] = arr;

console.log(x);       // → 10
console.log(rest);    // → [20, 30]

这里,arr的第一个值被分配给对应的x,而盈余的元素被分配给rest变量。这类称为阵列解构的情势变得云云受欢迎,以至于Ecma手艺委员会决定为对象带来相似的功用:

const obj = {
  a: 10,
  b: 20,
  c: 30
};

const {a, ...rest} = obj;

console.log(a);       // → 10
console.log(rest);    // → {b: 20, c: 30}

此代码运用解构赋值中的其他属性将盈余的本身可罗列属性复制到新对象中。请注意,rest属性必需一直出现在对象的末端,否则会激发毛病:

const obj = {
  a: 10,
  b: 20,
  c: 30
};

const {...rest, a} = obj;    // → SyntaxError: Rest element must be last element

还要记着,在对象中运用多个rest会致使毛病,除非它们是嵌套的:

const obj = {
  a: 10,
  b: {
    x: 20,
    y: 30,
    z: 40
  }
};

const {b: {x, ...rest1}, ...rest2} = obj;    // no error

const {...rest, ...rest2} = obj;    // → SyntaxError: Rest element must be last element

Support for Rest/Spread

ChromeFirefoxSafariEdge
605511.1No
Chrome AndroidFirefox AndroidiOS SafariEdge MobileSamsung InternetAndroid Webview
605511.3No8.260

Node.js

  • 8.0.0(运行时须要加-harmony
  • 8.3.0(完全支撑)

Asynchronous Iteration(异步迭代)

迭代数据集是编程的主要部份。此前ES2015,供应的JavaScript语句如forfor...inwhile,和要领map()filter()以及forEach()都用于此目标。为了使顺序员可以一次一个地处置惩罚鸠合中的元素,ES2015引入了迭代器接口。

假如对象具有Symbol.iterator属性,则该对象是可迭代的。在ES2015中,字符串和鸠合对象(如Set,Map和Array)带有Symbol.iterator属性,因而可以迭代。以下代码给出了怎样一次接见可迭代元素的示例:

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

Symbol.iterator是一个尽人皆知的标记,指定一个返回迭代器的函数。与迭代器交互的主要要领是next()要领。此要领返回具有两个属性的对象:valuedonevalue属性为鸠合中下一个元素的值。done属性的值为truefalse示意鸠合是不是迭代完成。

默许情况下,平常对象不可迭代,但假如在其上定义Symbol.iterator属性,则它可以变成可迭代,以下例所示:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.iterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return {
          value: this[values[i++]],
          done: i > values.length
        }
      }
    };
  }
};

const iterator = collection[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

此对象是可迭代的,因为它定义了一个Symbol.iterator属性。迭代器运用该Object.keys()要领猎取对象属性称号的数组,然后将其分配给values常量。它还定义了一个计数器变量i,并给它一个初始值0.当实行迭代器时,它返回一个包含next()要领的对象。每次挪用next()要领时,它都返回一对{value, done}value坚持鸠合中的下一个元素并done坚持一个布尔值,指导迭代器是不是已达到鸠合的须要。

虽然这段代码十全十美,但却不必要。运用天生器函数可以大大简化历程:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.iterator]: function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

在这个天生器中,for...in轮回用于罗列集兼并发生每一个属性的值。效果与前一个示例完全雷同,但它大大缩短了。

迭代器的瑕玷是它们不适合示意异步数据源。ES2018的弥补解决方案是异步迭代器和异步迭代。异步迭代器与传统迭代器的差别的地方在于,它不是以情势返回平常对象{value, done},而是返回推行的许诺{value, done}。异步迭代定义了一个返回异步迭代器的Symbol.asyncIterator要领(而不是Symbol.iterator)。

一个例子让这个更清晰:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return Promise.resolve({
          value: this[values[i++]], 
          done: i > values.length
        });
      }
    };
  }
};

const iterator = collection[Symbol.asyncIterator]();
  
console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 10, done: false}
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 20, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 30, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: undefined, done: true} 
}));

请注意,不可运用promises的迭代器来完成雷同的效果。虽然平常的同步迭代器可以异步肯定值,但它依然须要同步肯定done的状况。

一样,您可以运用天生器函数简化历程,以下所示:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]: async function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.asyncIterator]();
  
console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 10, done: false}
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 20, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 30, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: undefined, done: true} 
}));

平常,天生器函数返回带有next()要领的天生器对象。当挪用next()时,它返回一个{value,done},其value属性保留了yield值。异步天生器实行雷同的操纵,除了它返回一个推行{value,done}的promise。

迭代可迭代对象的一种简朴要领是运用for...of语句,然则for...of不能与async iterables一同运用,因为valuedone不是同步肯定的。因而,ES2018供应了for...await...of。我们来看一个例子:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]: async function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

(async function () {
  for await (const x of collection) {
    console.log(x);
  }
})();

// logs:
// → 10
// → 20
// → 30

在此代码中,for...await...of语句隐式挪用Symbol.asyncIterator鸠合对象上的要领以猎取异步迭代器。每次轮回时,都邑挪用迭代器的next()要领,它返回一个promise。一旦剖析了promise,就会将效果对象的value属性读取到x变量中。轮回继承,直到返回的对象的done属性值为true

请记着,该for...await...of语句仅在异步天生器和异步函数中有用。违背此规则会致使一个SyntaxError报错。

next()要领可能会返回谢绝的promise。要文雅地处置惩罚被谢绝的promise,您可以将for...await...of语句包装在语句中try...catch,以下所示:

const collection = {
  [Symbol.asyncIterator]() {
    return {
      next: () => {
        return Promise.reject(new Error('Something went wrong.'))
      }
    };
  }
};

(async function() {
  try {
    for await (const value of collection) {}
  } catch (error) {
    console.log('Caught: ' + error.message);
  }
})();

// logs:
// → Caught: Something went wrong.

Support for Asynchronous Iterators

ChromeFirefoxSafariEdge
635712No
Chrome AndroidFirefox AndroidiOS SafariEdge MobileSamsung InternetAndroid Webview
635712No8.263

Node.js

  • 8.0.0(运行时须要加-harmony
  • 8.3.0(完全支撑)

Promise.prototype.finally

ES2018的另一个令人兴奋的补充是finally()要领。一些JavaScript库之前已完成了相似的要领,这在很多情况下证实是有用的。这勉励了Ecma手艺委员会正式增加finally()到范例中。运用这个要领,顺序员将能不论promise的效果怎样,都能实行一个代码块。我们来看一个简朴的例子:

fetch('https://www.google.com')
  .then((response) => {
    console.log(response.status);
  })
  .catch((error) => { 
    console.log(error);
  })
  .finally(() => { 
    document.querySelector('#spinner').style.display = 'none';
  });

finally()不管操纵是不是胜利,当您须要在操纵完成后举行一些清算时,该要领会派上用场。在此代码中,该finally()要领只是在猎取和处置惩罚数据后隐蔽加载微调器。代码不是在then()catch()要领中复制终究逻辑,而是在promise被fulfilled或rejected后注册要实行的函数。

你可以运用promise.then(func,func)而不是promise.finally(func)来完成雷同的效果,但你必需在fulfillment处置惩罚顺序和rejection处置惩罚顺序中反复雷同的代码,或许为它声明一个变量:

fetch('https://www.google.com')
  .then((response) => {
    console.log(response.status);
  })
  .catch((error) => { 
    console.log(error);
  })
  .then(final, final);

function final() {
  document.querySelector('#spinner').style.display = 'none';
}

then()catch()一样,finally()要领老是返回一个promise,因而可以链接更多的要领。平常,您愿望运用finally()作为末了一个链,但在某些情况下,比方在发出HTTP要求时,最好链接另一个catch()以处置惩罚finally()中可能发生的毛病。

Support for Promise.prototype.finally

ChromeFirefoxSafariEdge
635811.118
Chrome AndroidFirefox AndroidiOS SafariEdge MobileSamsung InternetAndroid Webview
635811.1No8.263

Node.js

  • 10.0.0(完全支撑)

新的RegExp功用

ES2018为该RegExp对象增加了四个新功用,进一步提高了JavaScript的字符串处置惩罚才能。这些功用以下:

  • S(DOTALL)标志
  • Named Capture Groups(定名捕捉组)
  • Lookbehind Assertions(后向断言)
  • Unicode Property Escapes(Unicode属性转义)

S(DOTALL)标志

点(.)是正则表达式情势中的特别字符,它婚配除换行符以外的任何字符,比方换行符(\n)或回车符(\r)。婚配一切字符(包含换行符)的解决要领是运用具有两个相反短字的字符类,比方[\d\D]。此字符类通知正则表达式引擎找到一个数字(\d)或非数字(\D)的字符。因而,它婚配任何字符:

console.log(/one[\d\D]two/.test('one\ntwo'));    // → true

ES2018引入了一种情势,个中点可用于完成雷同的效果。可以运用s标志在每一个正则表达式的基础上激活此情势:

console.log(/one.two/.test('one\ntwo'));     // → false
console.log(/one.two/s.test('one\ntwo'));    // → true

运用标志来挑选新行动的优点是向后兼容性。因而,运用点字符的现有正则表达式情势不受影响。

Named Capture Groups(定名捕捉组)

在一些正则表达式情势中,运用数字来援用捕捉组可能会令人困惑。比方,运用/(\d{4})-(\d{2})-(\d{2})/与日期婚配的正则表达式。因为美式英语中的日期标记与英式英语差别,因而很难晓得哪一个组指的是哪一天,哪一个组指的是月份:

const re = /(\d{4})-(\d{2})-(\d{2})/;
const match= re.exec('2019-01-10');

console.log(match[0]);    // → 2019-01-10
console.log(match[1]);    // → 2019
console.log(match[2]);    // → 01
console.log(match[3]);    // → 10

ES2018引入了运用(?<name>...)语法的定名捕捉组。因而,婚配日期的情势可以用不那么隐约的体式格局编写:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec('2019-01-10');

console.log(match.groups);          // → {year: "2019", month: "01", day: "10"}
console.log(match.groups.year);     // → 2019
console.log(match.groups.month);    // → 01
console.log(match.groups.day);      // → 10

您可以运用\k<name>语法在情势中稍后挪用定名的捕捉组。比方,要在句子中查找一连的反复单词,您可以运用/\b(?<dup>\w+)\s+\k<dup>\b/

const re = /\b(?<dup>\w+)\s+\k<dup>\b/;
const match = re.exec('Get that that cat off the table!');        

console.log(match.index);    // → 4
console.log(match[0]);       // → that that

要将定名的捕捉组插进去到要领的替代字符串中replace(),您须要运用$<name>组织。比方:

const str = 'red & blue';

console.log(str.replace(/(red) & (blue)/, '$2 & $1'));    
// → blue & red

console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$<blue> & $<red>'));    
// → blue & red

Lookbehind Assertions(后向断言)

ES2018为JavaScript带来了后向性断言,这些断言已在其他正则表达式完成中可用多年。之前,JavaScript只支撑超前断言。后向断言用示意(?<=...),并使您可以婚配基于情势之前的子字符串的情势。比方,假如要在不捕捉钱银标记的情况下以美圆,英镑或欧元婚配产物的价钱,则可以运用/(?<=\$|£|€)\d+(\.\d*)?/

const re = /(?<=\$|£|€)\d+(\.\d*)?/;

console.log(re.exec('199'));     
// → null

console.log(re.exec('$199'));    
// → ["199", undefined, index: 1, input: "$199", groups: undefined]

console.log(re.exec('€50'));     
// → ["50", undefined, index: 1, input: "€50", groups: undefined]

另有一个lookbehind的否认版本,用(?<!...),只有当情势前面没有lookbehind中的情势时,负lookbehind才许可您婚配情势。比方,情势/(?<!un)available/婚配没有“un”前缀的可用词

这段翻译的不好,放上原文

There is also a negative version of lookbehind, which is denoted by (?<!...). A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(?<!un)available/ matches the word available if it does not have a “un” prefix:

Unicode Property Escapes(Unicode属性转义)

ES2018供应了一种称为Unicode属性转义的新范例转义序列,它在正则表达式中供应对完全Unicode的支撑。假定您要在字符串中婚配Unicode字符㉛。虽然㉛被认为是一个数字,然则你不能将它与\d速记字符类婚配,因为它只支撑ASCII [0-9]字符。另一方面,Unicode属性转义可用于婚配Unicode中的任何十进制数:

const str = '㉛';

console.log(/\d/u.test(str));    // → false
console.log(/\p{Number}/u.test(str));     // → true

一样,假如要婚配任何Unicode字母字符,你可以运用\p{Alphabetic}

const str = 'ض';

console.log(/\p{Alphabetic}/u.test(str));     // → true

// the \w shorthand cannot match ض
  console.log(/\w/u.test(str));    // → false

另有一个否认版本\p{...},示意为\P{...}

console.log(/\P{Number}/u.test('㉛'));    // → false
console.log(/\P{Number}/u.test('ض'));    // → true

console.log(/\P{Alphabetic}/u.test('㉛'));    // → true
console.log(/\P{Alphabetic}/u.test('ض'));    // → false

除了字母和数字以外,另有几个属性可以在Unicode属性转义中运用。您可以在当前范例提案中找到支撑的Unicode属性列表。

Support for New RegExp

  • | Chrome | Firefox | Safari | Edge
S(DOTALL)标志62No11.1No
定名捕捉组64No11.1No
后向断言62NoNoNo
Unicode属性转义64No11.1No
  • | Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview
S(DOTALL)标志62No11.3No8.262
定名捕捉组64No11.3NoNo64
后向断言62NoNoNo8.262
Unicode属性转义64No11.3NoNo64

Node.js

  • 8.3.0 (运行时须要加-harmony)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (完全支撑)

模板字符串

当模板字符串紧跟在表达式以后时,它被称为标记模板字符串。当您想要运用函数剖析模板笔墨时,标记的模板会派上用场。请斟酌以下示例:

function fn(string, substitute) {
  if(substitute === 'ES6') {
    substitute = 'ES2015'
  }
  return substitute + string[1];
}

const version = 'ES6';
const result = fn`${version} was a major update`;

console.log(result);    // → ES2015 was a major update

在此代码中,挪用标记表达式(它是通例函数)并通报模板笔墨。该函数只是修正字符串的动态部份并返回它。

在ES2018之前,标记的模板字符串具有与转义序列相干的语法限定。反斜杠后跟某些字符序列被视为特别字符:\x诠释为十六进制转义符,\u诠释为unicode转义符,\后跟一个数字诠释为八进制转义符。其效果是,字符串,比方”C:\xxx\uuu“或许”\ubuntu“被认为是由诠释无效转义序列,并会抛出SyntaxError。

ES2018从标记模板中删除了这些限定,而不是抛出毛病,示意无效的转义序列以下undefined

function fn(string, substitute) {
  console.log(substitute);    // → escape sequences:
  console.log(string[1]);     // → undefined
}

const str = 'escape sequences:';
const result = fn`${str} \ubuntu C:\xxx\uuu`;

请记着,在通例模板笔墨中运用不法转义序列仍会致使毛病:

const result = `\ubuntu`;
// → SyntaxError: Invalid Unicode escape sequence

Support for Template Literal Revision

ChromeFirefoxSafariEdge
625611No
Chrome AndroidFirefox AndroidiOS SafariEdge MobileSamsung InternetAndroid Webview
625611No8.262

Node.js

  • 8.3.0 (运行时须要加-harmony
  • 8.10.0(完全支撑)

总结

我们已细致研讨了ES2018中引入的几个症结特征,包含异步迭代,rest/spread属性Promise.prototype.finally()以及RegExp对象的增加。虽然个中一些浏览器供应商还没有完全完成个中一些功用,但因为像Babel如许的JavaScript转换器,它们本日依然可以运用。

ECMAScript正在迅速发展,而且每隔一段时间就会引入新功用,因而请检察已完成提案的列表,相识新功用的全部内容。

第一次翻译文章,才能有限,程度平常,翻译不妥的地方,还望斧正。谢谢。

    原文作者:Timbok
    原文地址: https://segmentfault.com/a/1190000017903388
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞