FrankKai.github.io
FrankKai.github.io copied to clipboard
es6+ 常用语法
入职新公司后,前端老大非常提倡使用新技术,终于在8102年遇到了项目中全覆盖es6这种2015年就发布了的前端技术了,当然其中包括一些es6+的内容,比如async,await,但是应用最广泛的还是es6,let,const,Symbol,arrow function,class。
之前也自学过一段时间es6,仅仅在vue层使用了一些,而且也没有使用es6写过非常底层的工具,对于es6的理解其实比较差。
下面我将记录自己在新项目中遇到的es6+的语法。
- Symbol
- Template String
- async
- class
- Promise若是存在多个then,但是只有一个catch,catch会捕捉哪一个错误?
- Promise的 return 和 resolve 有什么区别?
- Promise的catch与顺序有关系吗?
- es6的关键词static
- es6的BigInt类型
- export 与 export default的区别是什么?
- 一个async函数可以被await吗?
Symbol
带着问题去学习:
什么时候需要用到Symbol?
知识点:
- Symbol是一个不完整的constructor,new Symbol()不支持
- 通过Symbol()返回的符号(symbol)是唯一的。
- Symbol可以用作对象的key
- 这是上述Symbol这种数据类型的唯一目的
- typeof Symbol('foo') 输出"symbol"
- Symbol('foo') === Symbol('foo') 输出false
问题答案已清晰:
- Symbol数据类型是为了作为对象属性的识别器而产生的,也就是对象的key
思考:
- 为什么es6要引入Symbol这种数据类型? 使用传统的String类型不也是可以作为对象的key吗? 当然是可以的,但是Symbol更好的原因在于
Symbol唯一性更强。可以理解为js基础数据类型中的uuid
Template String
Q:
为什么es6引入Template String?
A: 使javascript的字符串拼接更加优雅。
举个例子: Before Template String
const url = "/api?" + "name=" + name + "&"+ "age=" + age ;
After Template String
const url = `/api?name=${name}&age=${age}`
这还仅仅是为url添加几个query parameter,假设需要DOM拼接,或者是很长的字符串拼接呢,使用Template String将会事半功倍。
优点:
- 写法简单,避免了频繁键入引号
- 代码可读性高
其它:
- require中也可以使用模板字符串
async
es6 class方法为什么可以添加async关键字?
因为本质上class的方法是一个function,为function添加async关键字之后,使之成为AsyncFunction。
这里主要是异步思想的部分,可以参考 https://blog.risingstack.com/node-js-async-best-practices-avoiding-callback-hell-node-js-at-scale/ 文章去理解异步。
async本质上是对callback hell的一种解决方案,除了async/await,还有第三方库async,bluebird和Promise的解决方案。深入下去是对异步的理解。
理解异步,需要理解event loop,它又包括以下内容
- call stack
- (macro)task queue
- microTask queue
- background thread
其中前三部分属于main thread,可以阅读node源码一探究竟。最后的background thread属于libuv的部分,可以去深入libuv源码(这是一个专门处理异步的c语言库)理解其实现机制。
但是阅读源码需要非常好的基础。这里推荐一篇囊括了以上知识点的非常走心的文章:https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/
现在推荐的异步写法async/await,而使用这种写法还需要借助Promise和Timer。
推荐的异步方案
- async/await
- Promise
- setTimeout
await 后面紧跟一个返回Promise对象的函数。 Promise对象中传入一个包含setTimeout的函数。
举个例子: async/await
const makeTea = async function (){
const boiledWater = await boilWater();
}
Promise && setTimeout
const boilWater = function () {
return new Promise(function(resolve){
setTimeout(function(){
resolve('boiledWater');
}, 10*60*1000); //10分钟
});
};
浏览器端的事件callback hell 解决办法 (以img元素加载远程图片资源为例)
请求在线图片作为img元素地址,作为canvas绘制源头。
const target = new CanvasImage(url, 80, 80).imageElement;
const main = new MainCanvas(source, target, 'right').canvasElement;
context.drawImage(main, 0, 0, 500, 500);
但是上述代码在slow 3G,fast 3G等情况下,会绘制失败。 因此需要保证target img元素的src资源加载完毕后,再进行绘制canvas。
class CanvasImage {
constructor(url, width, height) {
this.width = width || 500;
this.height = height || 500;
this.url = url || '';
this.element = new Image(this.width, this.height);
}
get imageElement() {
return this.element;
}
}
callback hell方式
source.onload = function() {
const target = new CanvasImage(url, 80, 80).imageElement;
target.onload = function() {
const main = new MainCanvas(source, target, 'right').canvasElement;
context.drawImage(main, 0, 0, 500, 500);
};
};
Promise方式
const sourcePromise = new Promise((resolve) => {
setTimeout(() => {
const target = new CanvasImage(url, 80, 80).imageElement;
resolve(target);
}, 0);
});
source.onload = function() {
sourcePromise.then((target) => {
const main = new MainCanvas(source, target, 'right').canvasElement;
context.drawImage(main, 0, 0, 500, 500);
});
}
async/await方式
function sourceCanvasImage() {
return new Promise((resolve) => {
setTimeout(() => {
const target = new CanvasImage(url, 80, 80).imageElement;
resolve(target);
}, 0);
});
}
async function mergeCanvas() {
const targetElement = await sourceCanvasImage();
const main = new MainCanvas(source, targetElement, 'right').canvasElement;
context.drawImage(main, 0, 0, 500, 500);
}
source.onload = function() {
mergeCanvas();
};
class
es6 class方法除了可以添加async,还可以添加get?
是的,大佬的代码就是这样写的。
由此我们提出疑问:
除了可以添加async,get,class方法前还能添加什么关键字?意义是什么?
这其实并不是class特有的,而是所有的Object共有的特性,是es2015对类和对象方法定义的一种升级。
完整形式如下:
var obj = {
// 普通属性
property( parameters… ) {},
*generator( parameters… ) {},
async property( parameters… ) {},
async* generator( parameters… ) {},
// 计算属性
[property]( parameters… ) {},
*[generator]( parameters… ) {},
async [property]( parameters… ) {},
// getter/setter语法
get property() {},
set property(value) {}
};
其中property( parameters… ) {}的形式在vue和react中是很常见的,比如mounted(){},componentDidMount(){}。
async多用于异步的场景,与await结合使用,可以很好地解决各种异步请求的顺序问题。而generator的方式不是很推荐,原因是每次都要执行next()显得很麻烦。
set,get可能在编写一些基础类的时候会比较重要。
其中async较为不好理解: 通常的写法:
const obj = {
f: async function () {
await some_promise;
}
};
缩写的写法:
const obj = {
async f(){
await some_promise;
}
};
Promise若是存在多个then,但是只有一个catch,catch会捕捉哪一个错误?
同时抛出3个错误,只捕捉第一个,后续promise不会执行。
var promise1 = new Promise(function(resolve, reject) {
throw 'promise1:foo';
});
promise1
.then((data)=>{
throw 'promise2:bar';
})
.then((data)=>{
throw 'promise3:baz';
})
.catch(function(error) {
console.log('error:', error); // 'promise1:foo'
});
同时抛出2个错误,只捕捉第一个,后续promise不会执行。
var promise1 = new Promise(function(resolve, reject) {
// throw 'promise1:foo';
resolve('resolved');
});
promise1
.then((data)=>{
throw 'promise2:bar';
})
.then((data)=>{
throw 'promise3:baz';
})
.catch(function(error) {
console.log('error:', error); // 'promise2:bar'
});
抛出1个错误,理所当然捕捉这一个。
var promise1 = new Promise(function(resolve, reject) {
// throw 'promise1:foo';
resolve('resolved');
});
promise1
.then((data)=>{
// throw 'promise2:baz'
return 'resolved';
})
.then((data)=>{
throw 'promise3:baz';
})
.catch(function(error) {
console.log('error:', error); // 'promise3:baz'
});
所以我们得出结论:promise的链式调用上,只捕捉第一个error!
Promise的 return 和 resolve 有什么区别?
先看下Promise的两种常见状态: resolved,pending。
resolved状态
var promise1 = new Promise(function(resolve, reject) {
resolve('resolved');
});
promise1; // Promise{<resolved>: "resolved"}
pending状态
var promise1 = new Promise(function(resolve, reject) {
return 'resolved';
});
promise1; // Promise{<pending>}
return和resolve对比
return方式
var promise1 = new Promise(function(resolve, reject) {
resolve('resolved');
});
promise1
.then((data)=>{
return 'return test';
})
.then((data)=>{
console.log(data); // return test
})
结果:
return test
Promise {
resolve方式
var promise1 = new Promise(function(resolve, reject) {
resolve('resolved');
});
promise1
.then((data)=>{
return new Promise(function(resolve, reject) {
resolve('resolve test');
})
})
.then((data)=>{
console.log(data);
})
结果:
resolve test
Promise {
这样看不出什么,但是若是异步callback呢?
异步return
var promise1 = new Promise(function(resolve, reject) {
resolve('resolved');
});
promise1
.then((data) => {
setTimeout(() => {
return 'return test';
},1000)
})
.then((data)=>{
console.log(data);
})
结果:
undefined
Promise{
异步resolve
var promise1 = new Promise(function(resolve, reject) {
resolve('resolved');
});
promise1
.then((data)=>{
return new Promise(function(resolve, reject) {
setTimeout(()=>{
resolve('resolve test');
},1000)
})
})
.then((data)=>{
console.log(data);
})
结果:
Promise{
所以我们可以得出"向下一个Promise传值的2种不同方式的对比": 对于同步方法,可以直接return;对于异步方法,必须要使用Promise的resolve!
Promise的catch与顺序有关系吗?
这与Promise的第3种状态有关了,rejected状态。
catch位于异常抛出之前,出现问题!
var promise1 = new Promise(function(resolve, reject) {
resolve('foo');
});
promise1
.then((data)=>{
return 'bar';
})
.catch(function(error) {
console.log('error:', error);
})
.then((data)=>{
throw 'promise3:baz';
})
结果:
Promise{
catch位于异常抛出之后,一切OK!
var promise1 = new Promise(function(resolve, reject) {
resolve('foo');
});
promise1
.then((data)=>{
return 'bar';
})
.then((data)=>{
throw 'promise3:baz';
})
.catch(function(error) {
console.log('error:', error);
})
结果:
error: promise3:baz
Promise{
因此我们得出结论:catch最好放置在尾部,否则会有promise为未被捕获。
es6的关键词static
static
定义了class的一个静态方法,该方法只能在class内部调用,不能在由class spawn出的实例调用。
java的static能不能用来指定方法仅能在class内调用暂时未知,但是可以肯定的是:java的static可以用来指定一个类常量,而js的static不能。
典型的代表是Math
和Reflect
,因为Math和Reflect这两个js全局内建对象的所有方法和属性都是static的,因为它们都不是constructor,都不能生成实例,只能在Math和Reflect这两个类的内部调用。
注意:
内部指的并不是在Math类定义的内部,因为Math.max()
其实也是Math类调用了自己作用域内的static max()方法。
而不是说生成的了一个全新的const math = new Math()
实例,在这个Math实例的作用域内去调用。
es6的BigInt类型
在学习Java的过程中,遇到了这个long类型hold不住大数的BigInt类型,没想到js也有。
- 在js中,大于2的53次方的数据用BigInt类型进行存储
- 不能用new操作符生成
- 可以在整数后加一个n表示BigInt类型,也可以直接调用BigInt函数
- 可以直接使用typeof 进行检测,类型就为'bigint'
export 与 export default的区别是什么?
- export是具名导出,export default是默认导出
- export可以导出多个具名内容,export default只能导出一个
- export导出的内容,import时必须名称一致;export default导出的内容,import时可以自定义名称
- 可以同时使用export和export default,导入时这样写
import { default as foo, bar, baz } from './module';
,但不建议
https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
一个async函数可以被await吗?
可以。 async函数的执行结果也是[object Promise],因此也可以被await。
const foo = async () => {
return true;
}
console.log(Object.prototype.toString.call(foo())); // [object Promise]
await foo(); // true
foo(); // Promise {<fulfilled>: true}