ECMAScript 2016, 2017, and 2018一

2018-04-11  本文已影响65人  NowhereToRun

1. Array.prototype.includes

includes是一个简单的检测元素是否在数组中的方法,包括NaN。(不像indexOf)

let a = [1, 2, 3, NaN, 4];
a.includes(NaN); // true
a.indexOf(NaN); // -1

2. 求幂中缀操作符

算数操作,例如+-都是中缀操作符,与他们类似**被用来求幂操作。

Math.pow(7,2) // 49
7**2  // 49

1. Object.values()

Object.values()Object.keys()类似,不过返回了对象的所有值。

const cars = {BMW: 3, Tesla: 2, Toyota: 1}  

// ES2015
const vals = Object.keys(cars).map(key => cars[key]);  
console.log(vals) // [3, 2, 1]

// ES2016 
const values = Object.values(cars); 
console.log(values) // [3, 2, 1]

2. Object.entries()

Object.entries()Object.keys()相关,但不像Object.keys()只返回key,这个方法连value一起返回。
Example 1

const cars = {BMW: 3, Tesla: 2, Toyota: 1}  

// ES2015
Object.keys(cars).forEach(key => {
    console.log('key: ' + key + ' value: ' + cars[key])
})

// ES2017
for (let [key, value] of Object.entries(cars)){
    console.log(`key: ${key} value: ${value}`)
}

Example 2

const cars = {BMW: 3, Tesla: 2, Toyota: 1}  

// ES2015
const map1 = new Map();
Object.keys(cars).forEach(key => {
    map1.set(key, cars[key]);
});

// ES2017
const map2 = new Map(Object.entries(cars)); 
// Map(3) {"BMW" => 3, "Tesla" => 2, "Toyota" => 1}

Object.entries返回二维数组,把key value全返回回来。

Object.entries返回值

3. String padding

String.prototype.padStartString.prototype.padEnd允许在字符串头或尾添加空白或指定字符串。(一共这么长,而不是添加这么长)

'someString'.padStart(numberOfCharcters [,stringForPadding]); 
'5'.padStart(10) // '          5'
'5'.padStart(10, '=*') //'=*=*=*=*=5'
'5'.padEnd(10) // '5         '
'5'.padEnd(10, '=*') //'5=*=*=*=*='

总有一些情况下我们想要补齐输出,美化效果,这是一个很好用的方法

Example 1

const formated = [1, 12, 123, 1234, 12345].map(
  num => num.toString().padStart(10, '0')
)
// 输出
// "0000000001"
// "0000000012"
// "0000000123"
// "0000001234"
// "0000012345"

Example 2

const cars = {BMW: 10, Tesla: 5, Toyota: 0}  

Object.entries(cars).map(([name, count]) => {
    console.log(`${name.padEnd(20, '-')} Count: ${count.toString().padStart(3, '0')} `)
})  

// BMW----------------- Count: 010 
// Tesla--------------- Count: 005 
// Toyota-------------- Count: 000 

需要注意的问题
在Emoji和其他双字节字符上,此方法有问题。

'heart'.padStart(10, "❤️"); // prints.. '❤️❤️❤heart'

并没如预期添加五个❤️,这是因为❤️由两字节 ('\u2764\uFE0F' )组成,最终添加上去了两个'\u2764\uFE0F'和一个'\u2764'

4. Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
这个方法返回对象属性的所有细节(包括setter和getter)。这个api的动机就是浅拷贝时拷贝getter和setter(与Object.assign不同)。

Object.assign浅拷贝原对象除了getter和setter之外的所有属性

var Car = {
  name: 'BMW',
  price: 1000000,
  set discount(x) {
    this.d = x;
  },
  get discount() {
    return this.d;
  },
};
//Print details of Car object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(Car, 'discount'));
//prints..
// { 
//   get: [Function: get],
//   set: [Function: set],
//   enumerable: true,
//   configurable: true
// }
//Copy Car's properties to ElectricCar using Object.assign
const ElectricCar = Object.assign({}, Car);
//Print details of ElectricCar object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount'));
//prints..
// { 
//   value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true 

// }
//⚠️Notice that getters and setters are missing in ElectricCar object for 'discount' property !👎👎
//Copy Car's properties to ElectricCar2 using Object.defineProperties 
//and extract Car's properties using Object.getOwnPropertyDescriptors
const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car));
//Print details of ElectricCar2 object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount'));
//prints..
// { get: [Function: get],  👈🏼👈🏼👈🏼
//   set: [Function: set],  👈🏼👈🏼👈🏼
//   enumerable: true,
//   configurable: true 
// }
// Notice that getters and setters are present in the ElectricCar2 object for 'discount' property!

5. 在函数参数中添加尾随逗号

这个主要是用来帮助git,让显示更加美观,在添加属性后只改变添加的那一行,而不是上一行也改变。如下面的例子

//问题
//    #1 开发者创建
function Person(
  name,
  age
) {
  this.name = name;
  this.age = age;
}
//#2 开发者添加address 
function Person(
  name,
  age, // 添加尾随逗号     
  address //添加新的参数
) {
  this.name = name;
  this.age = age;
  this.address = address; //添加的这行
}


//ES2017 解决
//允许#1开发者添加尾随逗号
//#1 创建如下
function Person(
  name,
  age, //
) {
  this.name = name;
  this.age = age;
}

6. Async/Await

这个是目前为止最重要,最有用的特性。Async使我们不必处理回调地狱,可以让这个代码看起来更整洁。
async关键词告诉JS引擎采用不同的方法处理这个函数。编译器一旦在函数中遇见await就暂停。它假定await后的表达式返回一个Promise,于是一直等待这个Promise直到resolved或者rejected。
在下面的例子中,getAmount方法调用了两个异步函数getUsergetBankBalance。我们可以使用Promise完成这个功能,但是使用async await更优雅更简单。

// ES2015
function getAmount1(userId) {
  getUser(userId)
    .then(getBankBalance)
    .then(amount => {
      console.log(amount);
    });
}

// ES2017
// 译者注: 这个async不可省略
// 否则报错 Syntax Error: await is a reserved word
async function getAmount2(userId) {
  var user = await getUser(userId);
  var amount = await getBankBalance(user);
  console.log(amount);
}



function getUser(userId) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('john');
    }, 1000)
  })
}

function getBankBalance(user) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (user === 'john') {
        resolve('$1000');
      } else {
        resolve('unknown user');
      }
    }, 1000)

  })
}
getAmount1('1') // $1000
getAmount2('1') // $1000

6.1 Async函数本身返回Promise

如果你想使用async函数的返回值,你需要使用Promise的then来获取。
在下面的例子,我们想要在doubleAndAdd函数外输出他的结果,所以我们需要使用then语法。

// async本身返回promise!
async function doubleAndAdd(a, b) {
  a = await doubleAfter1Sec(a);
  b = await doubleAfter1Sec(b);
  return a + b;
}

function doubleAfter1Sec(param) {
  return new Promise(resolve => {
    setTimeout(resolve(param * 2), 1000);
  })
}

doubleAndAdd(1, 2).then(console.log); // 6

6.2 并行调用 async/await

上面面的例子调用await两遍,每次等待1秒(共两秒)。但是,我们其实可以使用Promise.all并行的调用a和b,因为他们互不依赖,这样时间就缩短为1秒。

// async本身返回promise!
async function doubleAndAdd(a, b) {
  // a = await doubleAfter1Sec(a);
  // b = await doubleAfter1Sec(b);
  [a, b] = await Promise.all([doubleAfter1Sec(a), doubleAfter1Sec(b)])
  return a + b;
}

function doubleAfter1Sec(param) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(param * 2)
    }, 1000);
  })
}

doubleAndAdd(1, 2).then(console.log); // 6

6.3 错误处理

使用async await时有多种错误处理方法。

Option 1. 方法内使用try catch
async function doubleAndAdd(a, b) {
  try {
    a = await doubleAfter1Sec(a);
    b = await doubleAfter1Sec(b);
  } catch (e) {
    console.log(e);
    return "Error!!";
  }
  // [a, b] = await Promise.all([doubleAfter1Sec(a), doubleAfter1Sec(b)])
  return a + b;
}

function doubleAfter1Sec(param) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let val = param * 2;
      isNaN(val) ? reject('error msg') : resolve(val);
    }, 1000);
  })
}

doubleAndAdd('one', 2).then(console.log); // Error!! 和log 'error msg'
doubleAndAdd(1, 2).then(console.log); // 6
Option 2. 每个await表达式都Catch
// 对每一个await catch
// 因为每一个await内部都是一个Promise
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;
}
//🚀Usage:
doubleAndAdd('one', 2).then(console.log); // NaN  and logs:  "a" is NaN
doubleAndAdd(1, 2).then(console.log); // 6
function doubleAfter1Sec(param) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      let val = param * 2;
      isNaN(val) ? reject(NaN) : resolve(val);
    }, 1000);
  });
}
Option 3. 对整个的async-await函数Catch
// 除了在函数外部之外,不做任何处理
// 因为async await返回一个Promise 我们可以对他整体进行处理
async function doubleAndAdd(a, b) {
  a = await doubleAfter1Sec(a);
  b = await doubleAfter1Sec(b);
  return a + b;
}
//🚀Usage:
doubleAndAdd('one', 2)
  .then(console.log)
  .catch(console.log); // 👈👈🏼<------- use "catch"

function doubleAfter1Sec(param) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      let val = param * 2;
      isNaN(val) ? reject(NaN) : resolve(val);
    }, 1000);
  });
}

ECMAScript最近已经进入终稿,2018年6或7月会推出。所有下面的features都是在Stage-4中提到的,而且将会是ECMAScript2018的一部分。

1. 共享内存和atomics

这是一个大的先进的提升,而且是对JS引擎的核心提升。

核心观点是为JavaScript带来某种意义上(some sort of)的多线程特性,从而允许JS开发人员未来可通过自己管理内存而不是靠JS引擎来管理内存,从而写一些高性能的并发代码

这是通过一个新类型的全局变量SharedArrayBuffer,在共享内存空间上存储数据。这样这些数据就可以被JS主线程和web-werker线程共同使用。
目前为止,如果我们想在主线程和worker间共享变量只能通过拷贝变量然后通过postMessage传递。再也不用这样了!
只需要简单的使用SharedArrayBuffer,数据就会瞬间被主线程和web-worker访问到。
但是共享内存会带来竞争条件(race conditions)。为了避免它,引入了“Atomics”全局变量。当一个线程使用共享内存中的数据时,Atomics提供了多种方式来锁住内存。同时它也提供了多种方式在共享内存中安全地更新数据。

推荐使用类库来使用这个特性,但是目前没有类库能兼容这个特性。
如果你感兴趣,推荐阅读

  1. From Workers to Shared Memory — lucasfcosta
  2. A cartoon intro to SharedArrayBuffers — Lin Clark
  3. Shared memory and atomics — Dr. Axel Rauschmayer

2. 移除标记模板字面量(Tagged Template literal)的限制

首先,我们需要解释一下什么叫做“Tagged Template literal”,这样才能更好的理解这个特性。

在ES2015+,有一个特性叫做标签模板字面量(Tagged Template literal),他允许开发人员定制字符串将如何被插入。例如,标准的方式,我们这样插入字符串。

const firstName = 'NowhereToRun';
const greetings = `Hello ${firstName}`;
console.log(greetings); // Hello NowhereToRun

在标签字面量中,你可以写一个方法接收硬编码的字符串,例如['hello', '!'],和一个替换的变了,例如:['NowhereToRun'],返回任何你想让这个function返回的东西。
下面的例子展示了我们的“Tag”函数greet在输入的字符串后添加当时的时间。

// A "Tag" function returns a custom string literal.
// In this example, greet calls timeGreet() to append Good //Morning/Afternoon/Evening depending on the time of the day.
function greet(hardCodedPartsArray, ...replacementPartsArray) {
  console.log(hardCodedPartsArray); //["Hello", ", I'm", "!", raw: Array(3)]
  console.log(replacementPartsArray); //["Raja", "Eason"]
  let str = '';
  hardCodedPartsArray.forEach((string, i) => {
    if (i < replacementPartsArray.length) {
      str += `${string} ${replacementPartsArray[i] || ''}`;
    } else {
      str += `${string} ${timeGreet()}`; //<-- append Good morning/afternoon/evening here
    }
  });
  return str;
}
// Usage:
const firstName = 'Raja';
const you = 'Eason'
const greetings = greet `Hello${firstName}, I'm${you}!`; // <-- Tagged literal
console.log(greetings); // 'Hello  Raja! Good Morning!'
function timeGreet() {
  const hr = new Date().getHours();
  return hr < 12 ?
    'Good Morning!' :
    hr < 18 ? 'Good Afternoon!' : 'Good Evening!';
}

现在我们来讨论什么是"Tagged"函数,很多人想在不同的环境下使用这个特性,例如命令行终端,或获取HTTP请求的URIs,等等。

Tagged String literal存在的问题

ES2015和ES2016不逊于使用转义字符(escape characters)。例如“\u” (unicode),“\x”(hexadecimal) 除非他们看起来像一个准确的字符,例如\u00A9 或 \u{2F804} or \xA9。

所以如果你的Tagged函数内部有其他领域的规则(例如终端的),他可能需要使用类似** \ubla123abla**的字符,并不是像\u0049 或 \u{@F804}一样准确,你会被报语法错误。

在ES2018中,这个规则被去掉了,只要Tagged函数返回一个对象,包含cooked属性(非法变量存储为"undefined"),和raw属性(保存任意你想保存的)就允许使用看起来不合法的转义字符。

function myTagFunc(str) { 
 return { "cooked": "undefined", "raw": str.raw[0] }
} 

var str = myTagFunc `hi \ubla123abla`; //call myTagFunc

str // { cooked: "undefined", raw: "hi \\unicode" }

3. 正则表达式“dotall”标记

目前正则表达式中,即时dot(".")表示匹配任意单个字符,他还是不能匹配换行符,例如\n \r \f等。

// Before  
console.log(/first.second/.test('first\nsecond'));  // false

这个特性使得dot操作符可以匹配所有单字符。为了使这个不造成非常大的影响,我们需要在创建正则的时候添加\s标志。

//ECMAScript 2018
/first.second/s.test('first\nsecond'); //true   Notice: /s 
ECMAScript 2018 — 允许通过\s标记来使 . 操作符匹配换行符

4. RegExp命名组捕获

这个增强为JS带来了和其他编程例如Java,Python语言一样的“命名组(Named Groups)”。这个特性允许开发者写正则表达式时以(?<name>...)为不同的组提供名称(标识符)。可以使用这个名称去方便地获取捕获。

4.1 基本的命名组的例子

在下面的例子中,我们使用(?<year>) (?<month>) and (?day)去命名日期的不同部分。匹配结果返回值会有一个property属性,其中有属性yearmonthday,值为他们对应的匹配值。

let re1 = /(\d{4})-(\d{2})-(\d{2})/;
let result1 = re1.exec('2015-01-02');
console.log(result1);
// ["2015-01-02", "2015", "01", "02", index: 0, input: "2015-01-02", groups: undefined]

let re2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result2 = re2.exec('2015-01-02');
console.log(result2);
// ["2015-01-02", "2015", "01", "02", index: 0, input: "2015-01-02", groups: {year: "2015", month: "01", day: "02"}]

4.2 在正则表达式内部使用命名组的值

我们可以使用\k<group name>的格式向前寻找刚才正则匹配的值。

let sameWorks = /(?<fruit>apple|orange)==\k<fruit>/u;
sameWorks.test('apple==apple')   // true
sameWorks.test('orange==apple')  //false
sameWorks.test('orange==orange')  //true

4.3 在String.prototype.replace中使用命名组

命名组的特性已经集成到了String的replace实例方法中。我们可以方便地使用它交换字符位置。
例如,把“firstName,lastName”变成“lastName,firstName”。

let re = /(?<firstName>[A-Za-z]+) (?<lastName>[A-Za-z]+$)/u;
'HaHa NowhereToRun'.replace(re, '$<lastName>, $<firstName>');
// "NowhereToRun, HaHa"

5. 对Object获取剩余属性(Rest properties)

剩余操作符(Rest operator) ...(或者称三点操作符)可以用来获取Object的没有被解析出的属性。

5.1 使用...获取你想要的属性

// 提取出firstName和age, 把剩下的存储到'remaining'变量中
let {
  firstName,
  age,
  ...remaining
} = {
  firstName: 'haha',
  lastName: 'NowhereToRun',
  age: 20,
  height: '5.10',
  race: 'martian'
}
console.log(firstName) // haha
console.log(age) // 20
console.log(remaining) // {lastName: "NowhereToRun", height: "5.10", race: "martian"}

5.2 使用...移除不想要的元素

例如我想要一个新的对象不包含SSN属性,我们不必遍历旧对象的每一个属性,直接可以用... 来获取去除不需要的属性之外的属性

let {
  SSN,
  ...cleanObj
} = {
  firstName: 'haha',
  lastName: 'NowhereToRun',
  age: 20,
  height: '5.10',
  race: 'martian',
  SSN: '123-45-67890'
}

console.log(cleanObj); // {firstName: "haha", lastName: "NowhereToRun", age: 20, height: "5.10", race: "martian"}

6. 对象的扩展属性 (Spread properties for Objects)

可以使用 ...操作符来展开对象的属性。与Object.assign等操作类似,后面的会覆盖前面的同名属性。

扩展操作符(本节)用于等号的右侧。Rest操作符(上节)用于等号的左侧

// person 和 account 合并
const person = { fName: 'john', age: 20, name: 'haha' };
const account = { name: 'bofa', amount: '$1000' };

// 通过扩展运算符提取person和account的属性,并将其添加到一个新对象中。
const personAndAccount = { ...person, ...account };
console.log(personAndAccount); // {fName: 'john', age: 20, name: 'bofa', amount: '$1000' }

7. RegExp向后断言

这是对RegExp的增强,它允许我们确保某些字符串立即存在于其他字符串之前。

可以使用一个组(?<=...)(问号,小于,等于)来查找肯定的声明。
此外使用(?<!...)(问号,小于,感叹号)来查看否定声明。
从本质上讲,只要-ve声明通过,就会匹配。(-ve是啥... 查查)

肯定断言
比如我们希望#号后面紧跟着winner,即#winner。而且希望正则表达式只返回winner不包含#号。

console.log(/(?<=#).*/.test('winner')); // false
console.log(/(?<=#).*/.test('#winner'));  // true

//before
console.log('#winner'.match(/#.*/)[0]);  //'#winner' 匹配值 包含 # 
//After ECMAScript 2018
console.log('#winner'.match(/(?<=#).*/)[0]); // 'winner'

否定断言
假设我们想从具有€符号之后提取数字而不是$符号之后提取数字。

image.png
(这个例子Chrome中执行结果并不一样,Chrome还没支持?)
'A gallon of milk is €3.00'.match(/(?<!\$)\d+\.?\d+/)[0]; 
//3.00 

'A gallon of milk is $3.00'.match(/(?<!\$)\d+\.?\d+/);
// 输出  ["00", index: 23, input: "A gallon of milk is $3.00", groups: undefined]

原文链接

上一篇下一篇

猜你喜欢

热点阅读