ES8 新特性详解

709 阅读7分钟

上一篇总结的(ES7新特性以及es5手写实现 ),今天来看ES8的新特性

ES8(ECMAScript 2017) 是JavaScript语言的第8个版本,于2017年正式发布。
主要新特性,包括异步函数(async/await)、共享内存和原子操作、Object.values和Object.entries、字符串填充、Object.getOwnPropertyDescriptors、Rest/Spread属性等。

1. 异步函数(async/await)

使用async/await,可以将异步操作写成类似同步代码的形式,使代码更加简洁和易于理解。async函数返回一个Promise对象,可以使用await关键字等待Promise解决。实际上是generator的语法糖,下文会写出generator实现方法对比.

优势:简化异步代码的编写,解决了回调地狱问题,提高代码的可读性和可维护性。

promise普通调用实现方法:

 new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched");
    }, 2000);
  }).then((data) => {
  console.log(data);
});

使用

async/await使用:

  let data = await  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched");
    }, 2000)
   });
  console.log(data);

注意:

  1. 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行
async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
// 可以用try catch 或者添加 catch链
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {}
  await Promise.resolve('hello world');
}
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}
  1. 如果多个await操作不存在继承最好让它们同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  1. await命令只能用在async函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}
  // 不报错 会同时执行
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
  // 可以用for 或者 reduce
 for (let doc of docs) {
    await db.post(doc);
  }
 await docs.reduce(async (_, doc) => {
    await _;
    return db.post(doc);
  }, undefined);
  1. async 函数可以保留运行堆栈。
//如果`b()`或`c()`报错,错误堆栈将不包括`a()`。
const a = () => {
  b().then(() => c());
};
//错误堆栈将包括`a()`
const a = async () => {
  await b();
  c();
};

es6 实现

generator 实现方法:

function* getResult(params) {

   let a = yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1)
        }, 1000);
    })

   yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2)
            console.log(2);
            console.log('a=',a);
        }, 500);
    })
    
}
let gen = getResult()

// genrator next() 返回{value:...,done:false} 形式,getResult中 value为promise
// 调用next()会直接返回promise 

// 直接调用打印顺序是错误的
// gen.next();
// gen.next();
// 2
// a= undefined
// 1

function co(g,v) {
    // next方法参数的作用,是为上一个yield语句赋值
    const nextObj = g.next(v);
    if (nextObj.done) {
        return;
    }
    nextObj.value.then((s)=>{
        co(g,s)
    })
}

co(gen)

2. 共享内存和原子操作

  1. SharedArrayBuffer

在Web worker多线程数据传输时采用的复制机制,即一个进程将需要分享的数据复制一份,通过postMessage方法交给另一个进程。如果数据量比较大,这种通信的效率显然比较低。SharedArrayBuffer,允许 Worker 线程与主线程共享同一块内存.

  1. Atomics

多线程共享内存,最大的问题就是如何防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供Atomics对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。提供了store() load() exchange() wait() wake() add() sub() and() or() xor() **compareExchange()**等

优势:

提高多线程编程的性能和安全性。

使用方法:

  1. SharedArrayBuffer
const sharedBuffer = new SharedArrayBuffer(4);
const view = new Int32Array(sharedBuffer);
function worker() {
  Atomics.add(view, 0, 1);
}
const workerThread = new Worker("worker.js", { type: "module" });
workerThread.postMessage(sharedBuffer);
workerThread.onmessage = () => {
  console.log(Atomics.load(view, 0)); // 输出:1
};

2.Atomics

 const sab = new SharedArrayBuffer(100);
 const ia = new Int32Array(sab);
 Atomics.store(ia, 0, 1)//写入数据 返回写入的值
 Atomics.load(ia, 0)//读取数据
 Atomics.exchange(ia,0,2)//写入数据 返回写入的值
 Atomics.wait(sharedArray, arrayIndex, expectedStoredValue,timeout?)//满足条件Worker 线程进入休眠 timeout自动唤醒
 Atomics.wake(sharedArray, arrayIndex, num);//唤醒num个work线程
 Atomics.add(sharedArray, index, value) // 加 返回旧值,其他 sub减, and与,or或,xor异或
 Atomics.compareExchange(sharedArray, index, oldval, newval)//如果`sharedArray[index]`等于`oldval`,就写入`newval`,返回`oldval`

注意:

2018年1月5日,由于现代CPU架构中发现的漏洞攻击,SharedArrayBuffer 在所有主要浏览器中被禁用。 要实现SharedArrayBuffer ,你需要一个安全的环境,使用一个或多个响应头指令限制访问。这就是所谓的跨源隔离.

3. Object.values和Object.entries

ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for循环使用。 Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。 Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。除了返回值不一样,该方法的行为与Object.values基本一致。

优势:简化对象的遍历操作,提高代码的可读性。

之前ES版本实现方法:

const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach((key) => {
  console.log(obj[key]);
});
Object.keys(obj).forEach((key) => {
  console.log([key, obj[key]]);
});

ES8实现方法:

const obj = { a: 1, b: 2, c: 3 };
Object.values(obj).forEach((value) => {
  console.log(value);
});
Object.entries(obj).forEach(([key, value]) => {
  console.log(key, value);
});
//a 1
//b 2
//c 3

4. 字符串填充

ES8引入了String.prototype.padStart和String.prototype.padEnd方法,用于在字符串的开头或结尾填充字符。

优势:简化字符串格式化操作,提高代码的可读性。

之前ES版本实现方法:

function padStart(str, targetLength, padString) {
  padString = padString || " ";
  while (str.length < targetLength) {
    str = padString + str;
  }
  return str;
}
function padEnd(str, targetLength, padString) {
  padString = padString || " ";
  while (str.length < targetLength) {
    str += padString;
  }
  return str;
}
console.log(padStart("123", 5, "0")); // 输出:00123
console.log(padEnd("123", 5, "0")); // 输出:12300

ES8实现方法:

console.log("123".padStart(5, "0")); // 输出:00123
console.log("123".padEnd(5, "0")); // 输出:12300

5. Object.getOwnPropertyDescriptors

ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。 该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

优势:简化对象属性的复制操作,提高代码的可读性。

之前ES版本实现方法:

function getOwnPropertyDescriptors(obj) {
  const descriptors = {};
  Object.keys(obj).forEach((key) => {
    descriptors[key] = Object.getOwnPropertyDescriptor(obj, key);
  });
  return descriptors;
}
const obj = { a: 1, b: 2 };
const descriptors = getOwnPropertyDescriptors(obj);
console.log(descriptors);

ES8使用方法:

const obj = { a: 1, b: 2 };
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
//配合`Object.defineProperties()`方法,可以实现正确拷贝。
const source = {
  set foo(value) {
    console.log(value);
  }
};
const target1 = {};
Object.assign(target1, source); //foo value undefined

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
//
const shallowMerge = (target, source) => Object.defineProperties(
  target,
  Object.getOwnPropertyDescriptors(source)
);

// 配合`Object.create()`方法,将对象属性克隆到一个新对象。这属于浅拷贝。
const shallowClone = (obj) => Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

6. Rest/Spread属性

ES8引入了Rest和Spread属性,用于对象解构赋值和对象字面量扩展。 ... 作 Rest 含义时,表示将多个值收集为一个数组.箭头函数没有 arguments,所以箭头函数中不确定参数,可以使用 rest ... 作为 Spread 含义时,效果为扩散对象的属性.

优势:简化对象解构和扩展操作,提高代码的可读性。

之前ES版本实现方法:

function test() {
        var s = "";
        for (var i = 0; i < arguments.length; i++) {
            alert(arguments[i]);
            s += arguments[i] + ",";
        }
        return s;
 }
test("name", "age");


function merge(obj1, obj2) {
  const result = {};
  Object.keys(obj1).forEach((key) => {
    result[key] = obj1[key];
  });
  Object.keys(obj2).forEach((key) => {
    result[key] = obj2[key];
  });
  return result;
}
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = merge(obj1, obj2);
console.log(merged);

ES8实现方法:

const sum = (a, b, ...restOfArguments) => {
  return a + b + restOfArguments.reduce((acc, curr) => acc + curr, 0);
  //     ^   ^          ^
  //     1   2      [3, 4, 5]
};

console.log(sum(1, 2, 3, 4, 5));
// 15

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged);


参考

手写async await核心原理,再也不怕面试官问我async await原理 - 知乎 (zhihu.com) 20. async 函数 - 语法 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack