上一篇总结的(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);
注意:
- 任何一个
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');
}
- 如果多个await操作不存在继承最好让它们同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
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);
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. 共享内存和原子操作
- SharedArrayBuffer
在Web worker多线程数据传输时采用的复制机制,即一个进程将需要分享的数据复制一份,通过postMessage
方法交给另一个进程。如果数据量比较大,这种通信的效率显然比较低。SharedArrayBuffer
,允许 Worker 线程与主线程共享同一块内存.
- Atomics
多线程共享内存,最大的问题就是如何防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供Atomics
对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。提供了store() load() exchange() wait() wake()
add() sub() and() or() xor() **compareExchange()**等
优势:
提高多线程编程的性能和安全性。
使用方法:
- 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.values
和Object.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