美文网首页
ECMAScript 2016, 2017, and 2018一

ECMAScript 2016, 2017, and 2018一

作者: NowhereToRun | 来源:发表于2018-04-11 01:15 被阅读65次

    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]
    

    原文链接

    相关文章

      网友评论

          本文标题:ECMAScript 2016, 2017, and 2018一

          本文链接:https://www.haomeiwen.com/subject/zxbohftx.html