FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

es6+ 常用语法 #76

Open FrankKai opened 6 years ago

FrankKai commented 6 years ago

入职新公司后,前端老大非常提倡使用新技术,终于在8102年遇到了项目中全覆盖es6这种2015年就发布了的前端技术了,当然其中包括一些es6+的内容,比如async,await,但是应用最广泛的还是es6,let,const,Symbol,arrow function,class。

之前也自学过一段时间es6,仅仅在vue层使用了一些,而且也没有使用es6写过非常底层的工具,对于es6的理解其实比较差。

下面我将记录自己在新项目中遇到的es6+的语法。

FrankKai commented 6 years ago

Symbol

带着问题去学习:

什么时候需要用到Symbol?

知识点:

问题答案已清晰:

思考:

FrankKai commented 6 years ago

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将会事半功倍。

优点:

其它:

FrankKai commented 6 years ago

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,它又包括以下内容

其中前三部分属于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。

推荐的异步方案

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();
    };
FrankKai commented 6 years ago

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;
    }
};
FrankKai commented 5 years ago

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!

FrankKai commented 5 years ago

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 {: undefined}

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 {: undefined}

这样看不出什么,但是若是异步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{:undefined}

异步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{} 一秒钟后 resolve test

所以我们可以得出"向下一个Promise传值的2种不同方式的对比": 对于同步方法,可以直接return;对于异步方法,必须要使用Promise的resolve!

FrankKai commented 5 years ago

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{: "promise3:baz"} Uncaught(in promise) promise3:baz

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{: undefined}

因此我们得出结论:catch最好放置在尾部,否则会有promise为未被捕获。

FrankKai commented 5 years ago

es6的关键词static

static定义了class的一个静态方法,该方法只能在class内部调用,不能在由class spawn出的实例调用。

java的static能不能用来指定方法仅能在class内调用暂时未知,但是可以肯定的是:java的static可以用来指定一个类常量,而js的static不能。

典型的代表是MathReflect,因为Math和Reflect这两个js全局内建对象的所有方法和属性都是static的,因为它们都不是constructor,都不能生成实例,只能在Math和Reflect这两个类的内部调用。

注意: 内部指的并不是在Math类定义的内部,因为Math.max()其实也是Math类调用了自己作用域内的static max()方法。 而不是说生成的了一个全新的const math = new Math()实例,在这个Math实例的作用域内去调用。

FrankKai commented 5 years ago

es6的BigInt类型

在学习Java的过程中,遇到了这个long类型hold不住大数的BigInt类型,没想到js也有。

FrankKai commented 3 years ago

export 与 export default的区别是什么?

  1. export是具名导出,export default是默认导出
  2. export可以导出多个具名内容,export default只能导出一个
  3. export导出的内容,import时必须名称一致;export default导出的内容,import时可以自定义名称
  4. 可以同时使用export和export default,导入时这样写import { default as foo, bar, baz } from './module';,但不建议

https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export

FrankKai commented 3 years ago

一个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}