chenxiaochun / exploring-es2016-es2017 Goto Github PK
View Code? Open in Web Editor NEW📔Exploring ES2016 and ES2017
📔Exploring ES2016 and ES2017
在 ECMAScript2017 中添加了两个新的字符串方法:
'x'.padStart(5, 'ab')
"ababx"
'x'.padEnd(5, 'ab')
"xabab"
两个方法的使用场景:
此方法的功能是使用 fillString 给被处理字符串添加前缀,直到被处理字符串的长度正好等于 maxLength 为止。
'x'.padStart(5, 'ab')
"ababx"
如有必要,为了长度能恰好的等于 maxLength,可能也只截取应用 fillString 的一部分字符片段。
'x'.padStart(4, 'ab')
"abax"
如果被处理字符串的长度大于等于 maxLength,那么原样返回。
'abcd'.padStart(2, '#')
"abcd"
如果 maxLength 和 fillString 的长度是相同的,那么就会截取 fillString 的一部分,然后与被处理字符合成一个长度正好等于 maxLength 的新字符串。
'abc'.padStart(10, '0123456789')
"0123456abc"
如果省略了 fillString,则默认为空格。
'x'.padStart(3)
" x"
padStart()
方法的简单实现String.prototype.padStart = function (maxLength, fillString=' ') {
let str = String(this);
if (str.length >= maxLength) {
return str;
}
fillString = String(fillString);
if (fillString.length === 0) {
fillString = ' ';
}
let fillLen = maxLength - str.length;
let timesToRepeat = Math.ceil(fillLen / fillString.length);
let truncatedStringFiller = fillString
.repeat(timesToRepeat)
.slice(0, fillLen);
return truncatedStringFiller + str;
};
padEnd()
和padStart()
的工作方式类似,只不过后者是用来插入前缀字符,前者用来插入后缀字符。
示例1:
'x'.padEnd(5, 'ab')
'xabab'
示例2:
'x'.padEnd(4, 'ab')
'xaba'
示例3:
'abcd'.padEnd(2, '#')
'abcd'
示例4:
'abc'.padEnd(10, '0123456789')
'abc0123456'
示例5:
'x'.padEnd(3)
'x '
在ES2107中引入了两个新的对象:SharedArrayBuffer
和atomics
。
atomics
直译为“原子学”。它提供了若干静态方法用于对SharedArrayBuffer
进行原子上的操作。
这两个词语的意思非常相近,很容易让人混淆,所以很多地方混用它们好像也不是太大的问题。对于它们也存在很多的定义,但我是这样理解使用它们的:
指的是多个计算任务同时发生,但是在给定的时刻只会执行其中一项任务。它又分成两种模式:
Web worker
以及Unix
的生成进程模式。指的是在一个给定的时间周期内执行多个任务,并且没有先后顺序。所以,我们使用的电脑就是并发性质的,它可以在一个 CPU 时钟内执行多个任务,例如:播放音乐、接受键盘指令、刷新屏幕等。
众所周知,js是运行在一个单线程上。如果一项任务需要异步执行,浏览器通常是开启一个单独的线程来运行,之后再将运行结果以回调函数的方式返回给之前的线程。
之后的Web worker
才赋予了浏览器真正意义上的并行任务特性。它们是比较重量级的进程,每一个worker
都有自己的全局环境。默认情况下,worker
之间不会共享任何资源,后来才逐渐形成了它们之间的通信机制:
JSON
数据、数组、正则表达式、Blob
对象、ImageData
对象。而且它还能正确地处理对象之间的循环引用。但是,它无法处理error
对象、函数对象、DOM
节点等。worker
之间传送数据过程中,发送方不能在接收方访问数据时,也同时去访问数据。再一个应用领域是基于WebGL
的图形计算,可以把你的输入数据逐像素的转换成另外一张图像。
第三个是SIMD
,它是比较底层的一种应用方式。它能让你在同一时刻同时对若干整数或者浮点数进行运算(例如:加法或者乘法)。
共享数组缓冲区是并发性抽象中一个比较重要、比较高等的构建模块。它们可以让你在多个worker
或者主线程之间共享SharedArrayBuffer
的字节数据。它主要有两处好处:
worker
之间共享数据postMessage()
相比,协调worker
会变得更简单快捷创建一个共享数组缓存区和创建一个普通数组缓存区的方式是一样的,都是通过在构造函数里指定缓存区的大小(单位:字节)。使用缓存区用来在worker
之间共享数据。如果是你自己本地使用,那就可以再用类型化的数组再包裹一层即可。
// main.js
const worker = new Worker('worker.js');
// To be shared
const sharedBuffer = new SharedArrayBuffer(10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements
// Share sharedBuffer with the worker
worker.postMessage({sharedBuffer}); // clone
// Local only
const sharedArray = new Int32Array(sharedBuffer); // (B)
注意:共享数组缓存区的正确共享方式就是克隆它。但是有的引擎实现的还是旧api,所以可能需要手动转换一下。而且正在转换中的数据,是不可以访问它的。
worker.postMessage({sharedBuffer}, [sharedBuffer]);
首先把共享数组缓存区提取出来,然后用类型数组包裹一次就可以使用了
// worker.js
self.addEventListener('message', function (event) {
const {sharedBuffer} = event.data;
const sharedArray = new Int32Array(sharedBuffer); // (A)
console.log(sharedArray)
// ···
});
//输出结果:
Int32Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Atomics
幂运算符是 ES7 中的一个新特性。
6 ** 2
36
它与 Math.pow(x, y)
的计算结果一样的。
Math.pow(6, 2)
36
你也可以这样使用:
let num = 3;
num **= 2;
console.log(num);
9
现在声明函数的时候,可以在参数尾部添加逗号了。
function foo(
param1,
param2,
) {}
同理,调用函数时也可以这样用:
foo(
'abc',
'def',
);
在一个对象中使用会被忽略:
let obj = {
first: 'Jane',
last: 'Doe',
};
for(o in obj){
console.log(o)
}
'first'
'last'
同样,在数组中使用也会被忽略:
let arr = [
'red',
'green',
'blue',
];
console.log(arr.length);
3
使用函数尾部参数语法有两个好处:
foo(
'abc',
);
改成:
foo(
'abc',
'def',
);
现在以下几种async
函数的用法都是可行的。
Async函数声明:
async function foo(){}
Async函数表达式:
const foo = async function(){}
Async方法定义:
let obj = {async foo(){}}
Async箭头函数:
const foo = async () => {}
Async函数通常返回的是 Promise:
async function asyncFunc() {
return 123;
}
asyncFunc()
.then(x => console.log(x));
123
async function asyncFunc() {
throw new Error('Problem!');
}
asyncFunc()
.catch(err => console.log(err));
Error: Problem!
只有操作符await
可以放在 Async 函数里用来处理返回的 Promise 对象结果。所以await
的处理结果随着 Promise 的状态不同而不同。
简单处理一个异步结果:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc()
.then(result => {
console.log(result);
});
}
顺序处理多个异步结果:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc1()
.then(result1 => {
console.log(result1);
return otherAsyncFunc2();
})
.then(result2 => {
console.log(result2);
});
}
平行处理多个异步结果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
]);
console.log(result1, result2);
}
// 等价于:
function asyncFunc() {
return Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
])
.then([result1, result2] => {
console.log(result1, result2);
});
}
处理错误异常:
async function asyncFunc() {
try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc()
.catch(err => {
console.error(err);
});
}
async
函数在我解释async
函数之前,我想通过把Promises
和generator
结合起来,用看起来像同步代码的方式去模拟异步方式。
对于处理获取一次性结果的异步函数,Promises
是目前最流行的方式。下面是一个使用fetch
方法获取文件的示例:
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
co
是一个基于Promises
和generator
的实现,也能够让你以书写同步代码的方式来实现上面的示例。
const fetchJson = co.wrap(function* (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});
co
在generator
回调函数中每次检测到一个带有yield
操作符的方法,就会产生一个Promise
对象,co
会先暂停回调代码的执行,直到Promise
对象的状态发生变化再继续执行。无论Promise
的状态为resolved
或者rejected
,yield
都会将相应的结果值返回。
详细说明一下async
函数的执行过程:
async
函数在开始执行的时候,通常都是返回一个Promise
对象return
或者throw
直接完成执行过程。也可以使用await
暂时完成执行过程,然后根据情况再继续执行Promise
对象then
和catch
中的callback只有在当前所有代码执行完毕之后,才会被执行。从下面的输出结果可以看出函数asyncFunc
的返回值等到所有代码包括循环逻辑都执行完毕之后,才最终得以被输出async function asyncFunc() {
console.log('asyncFunc()'); // (A)
return 'abc';
}
asyncFunc().
then(x => console.log(`Resolved: ${x}`)); // (B)
console.log('main');
for(let i=0; i<5; i++){
console.log(i)
}
asyncFunc()
main
0
1
2
3
4
Resolved: abc
使用return
去Resolve
一个async
函数状态,是一种很标准的操作方式。这意味着你可以:
Promise
对象类型的值,作为Resolve
状态的参数值Promise
对象代表了当前async
的函数状态await
的若干贴示使用async
函数最常犯的一个错误就是忘记添加await
关键字,在下面示例中value
被指向了一个Promise
对象,但它可能并不是你想要的结果:
async function asyncFunc() {
const value = otherAsyncFunc(); // missing `await`!
···
}
await
可以感知到后面跟的异步函数是否返回了结果值,然后它就可以告诉调用者当前的异步函数是否已经执行完毕了。例如在下面的示例中,await
可以确保step1()
执行完之前,不会招待foo()
的剩余逻辑代码:
async function foo() {
await step1(); // (A)
···
}
有时候你仅仅是想触发一个异步函数计算,并不想知道它会何时完成。在下面示例中,我们并不关心写文件的操作何时完成,只要它们是按正确的顺序执行就可以了。最后一行的await
只是为了确保关闭写文件的操作能被成功执行即可。
async function asyncFunc() {
const writer = openFile('someFile.txt');
writer.write('hello'); // don’t wait
writer.write('world'); // don’t wait
await writer.close(); // wait for file to close
}
多个await
异步函数是顺序执行的关系,想要它们同时执行就得使用Promise.all()
了:
async function foo() {
const result1 = await asyncFunc1();
const result2 = await asyncFunc2();
}
async function foo() {
const [result1, result2] = await Promise.all([
asyncFunc1(),
asyncFunc2(),
]);
}
async
在回调函数中的应用有一个需要知道的限制是,await
操作符只会影响async
函数的直接作用域环境。因此,你不能在async
函数内的回调函数中使用await
,这也会让那些基于callback
的方法变得非常难于理解。
Array.prototype.map()
下面的示例是想下载若干url并返回其内容:
async function downloadContent(urls) {
return urls.map(url => {
// Wrong syntax!
const content = await httpGet(url);
return content;
});
}
像上面这种在普通的箭头函数中使用await
根本无法运行,会抛出语法错误。那我们应该怎么用,像下面这样吗:
async function downloadContent(urls) {
return urls.map(async (url) => {
const content = await httpGet(url);
return content;
});
}
function httpGet(url){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(url.indexOf('a') > -1){
resolve('a.com');
}else{
resolve('b.com');
}
}, 2000)
});
}
const contents = downloadContent(['http://a.com', 'http://b.com']);
console.log(contents);
contents.then((url) => {
console.log(url);
});
输出结果:
你会发现代码中有两个问题:
Promise
对象的数组,并不是我们期望的包含resolve
返回值的数组await
只能暂停箭头回调函数里的httpGet()
,map
本身的回调函数执行完成之后,并不能影响外层的downloadContent ()
也执行完成我们用Promise.all()
来修复这两个问题,把返回的 包含两个Promise
对象的数组 转换成 包含两个数组元素的Promise
对象,看如下示例:
async function downloadContent(urls) {
const promiseArray = urls.map(url => httpGet(url));
return await Promise.all(promiseArray);
}
function httpGet(url){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(url.indexOf('a') > -1){
resolve('a.com');
}else{
resolve('b.com');
}
}, 2000)
});
}
const contents = downloadContent(['http://a.com', 'http://b.com']);
console.log(contents);
contents.then((url) => {
console.log(url);
});
输出结果:
OK,现在输出结果是正确的了。但是这段代码还是有一点点低效的地方需要改进,downloadContent ()
函数里首先用await
展开了Promise.all()
的返回结果,后面又用return
包装了一次,其实我们用return
直接返回Promise.all()
即可:
async function downloadContent(urls) {
const promiseArray = urls.map(url => httpGet(url));
return Promise.all(promiseArray);
}
Array.prototype.forEach()
我们这次换成forEach()
方法来模拟输出若干文件内容。很显然,下面的示例会抛出语法错误,因为你不能在普通的箭头函数中直接使用await
:
async function logContent(urls) {
urls.forEach(url => {
// Wrong syntax
const content = await httpGet(url);
console.log(content);
});
}
那我把代码修改成如下:
async function logContent(urls) {
urls.forEach(async url => {
const content = await httpGet(url);
console.log(content);
});
// Not finished here
}
这次代码倒是运行了,但是httpGet()
方法返回resolve
状态的操作是异步的,也就是说当forEach()
方法已经返回之后,它的callback
还并没有执行完成。修复此问题,只需要将代码做一下更改:
async function logContent(urls) {
for (const url of urls) {
const content = await httpGet(url);
console.log(content);
}
}
这段示例中的httpGet()
执行顺序是线性的,每一次的调用必须要等待上一次执行完毕。如果想要改成并行的执行顺序,就得用Promise.all()
了:
async function logContent(urls) {
await Promise.all(urls.map(
async url => {
const content = await httpGet(url);
console.log(content);
}));
}
map()
方法创建了一个Promise
对象数组。我们并不关心这几个Promise
对象的履行结果,只要它们履行了即可。也就是loginContent()
方法执行完成就可以了。在这个示例中除非把Promise.all()
直接返回,否则此函数的结果只会包含若干`undefined。
async
函数的若干贴示async
函数的基础就是Promise
,所以充分理解下面的示例非常重要。尤其是在那些没有使用Promise
机制的老代码中使用async
函数时,你可能除了直接使用Promise
之外没有别的选择。
下面是一个在XMLHttpRequest
中使用Promise
的示例:
function httpGet(url, responseType="") {
return new Promise(
function (resolve, reject) {
const request = new XMLHttpRequest();
request.onload = function () {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
};
request.onerror = function () {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
};
request.open('GET', url);
xhr.responseType = responseType;
request.send();
});
}
XMLHttpRequest
的 API 设计都是基于 callback 的。使用async
函数就意味着你要在内层回调函数里使用return
和throw
返回Promise
对象的状态,但这肯定是不可能的。因此,在这情况中使用async
的风格就是:
Promise
直接构建一个异步的基元async
函数来使用这些基元在一个模块或者 script 的顶级作用域中使用await
,可以像下面这样:
async function main() {
console.log(await asyncFunction());
}
main();
或者
(async function () {
console.log(await asyncFunction());
})();
或者
(async () => {
console.log(await asyncFunction());
})();
不用太担心那些未处理rejections
,以前这种情况可能都是静默失败,不过现在大多数的现代浏览器都会抛出一个未处理的异常:
async function foo() {
throw new Error('Problem!');
}
foo();
Object.getOwnPropertyDescriptors(obj)
的功能返回一个包含 obj 自身所有属性的属性描述符数组。
const obj = {
[Symbol('foo')]: 123,
get bar() { return 'abc' },
};
console.log(Object.getOwnPropertyDescriptors(obj));
输出:
从 ES6 开始,才有了一个用来复制属性的工具方法:Object.assign()
。但是,这个方法只能简单的使用 get 和 set 操作去复制那些本身就是关键字的属性。这也意味着它并不能正确地复制那些非默认属性。
const source = {
set foo(value) {
console.log(value);
}
};
console.log(Object.getOwnPropertyDescriptor(source, 'foo'));
使用Object.assign()
复制 foo 到 target 对象:
const target1 = {};
Object.assign(target1, source);
console.log(Object.getOwnPropertyDescriptor(target1, 'foo'));
然后我们把Object.getOwnPropertyDescriptors()
和Object.defineProperties()
组合起来却能得到我们想要的结果。
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptor(target2, 'foo'));
对象的浅复制也可以使用Object.getOwnPropertyDescriptors()
。但是,这次我们用Object.create()
来尝试一下,这个方法有两个参数:
Object.getOwnPropertyDescriptors()
方法返回的描述符属性集合const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
使用对象字面量的最好方式就是创建一个对象,比如下面的示例中把内置属性__proto__
指向了另一个任意的对象prot
。
var prot = {
value: 1
}
const obj = {
__proto__: prot,
foo: 123,
};
console.log(obj.value)
//正确的输出了我们所期望的值
1
可惜的是,这种用法只是在浏览器环境中才支持。更通用的做法是使用Object.create()
和赋值操作。
var prot = {
value: 1
}
const obj = Object.create(prot);
obj.foo = 123;
console.log(obj.value)
//依然可以输出正确的值
1
还有一种实现方式就是使用Object.getOwnPropertyDescriptors()
var prot = {
value: 1
}
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);
console.log(obj.value)
//输出了正确的结果
1
通过单词意思也能看出来,此方法用来判断元素是否存在于一个数组中,存在返回true
,否则返回false
。
['a', 'b', 'c'].includes('a')
true
['a', 'b', 'c'].includes('d')
false
与includes
非常类似的一个方法是indexOf
,比如下面两种用法实现的作用是一样的。
arr.includes(x)
arr.indexOf(x) >= 0
includes
方法还可以查找NaN
。
[NaN].includes(NaN)
true
indexOf
方法却不行。
[NaN].indexOf(NaN)
-1
includes
方法不能区分+0
和-0
。
[-0].includes(+0)
true
类型化数组中也同样存在一个includes
方法。
let tarr = Uint8Array.of(12, 5, 3);
console.log(tarr.includes(5));
true
includes
,而不是contains
?contains
确实是最初的选择,但是后来某些代码库(MooTools)有了同名的实现,就被放弃了。
has
呢?因为has
方法用于判断键值对(Map.prototype.has),而includes
通常用于判断元素(String.prototype.includes)。一个集合(Set)的所有元素是可以通过key
和value
分别查看的,这也是为什么Set
中也没有includes
方法了。
String.prototype.includes
方法的工作机制是基于字符串,而不是基于字符的。可这是不是就和Array.prototype.includes
方法的工作机制不一致了?如果数组的includes
方法和String
上的保持一致,那么数组的includes
方法参数接受的应该是一个数组,而不能是一个单一元素。但是,这两个对象上的includes
方法又是模仿的indexOf
方法。字符是一种特定情况,字符串一般情况下都是拥有一定的长度。let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}
"one": 1
"two": 2
如果传给它的数据是key-value
形式的键值对,那么返回的是每一个都包含两个元素的数组列表。Object.entries(x)
的参数必须是一个对象,并且返回的是一个可权举的、转换成字符串的key
属性数组。
Object.entries({ one: 1, two: 2 })
[ [ 'one', 1 ], [ 'two', 2 ] ]
如果某些key
为Symbol
类型,则会被直接忽略。
Object.entries({ [Symbol()]: 123, foo: 'abc' });
[ [ 'foo', 'abc' ] ]
Object.entries()
带给我们的是一种遍历对象属性的方式(为什么对象默认不支持遍历)。它也可以与Maps
相配合,这比上面的使用方式更直白。
let map = new Map(Object.entries({
one: 1,
two: 2,
}));
console.log(JSON.stringify([...map]));
[["one",1],["two",2]]
它用来返回键值可枚举的、可转换成字符串类型的属性值。
Object.values({ one: 1, two: 2 })
[1, 2]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.