利用原始的JS特性/对象/函数/闭包实现高级工具

130 阅读2分钟

JS以奇怪著称,其中几个重要‼️的概念值得牢记并弄清楚:

  • Class类
  • Object对象
  • Plain Object:纯粹对象
  • JSON
  • Constructor Function构造函数

Class

image.png

要点:

  1. ES6原生支持Class写法
  2. ES6的Class跟Typescript的Class有所不同,TS增加了一些特性比如 constructor(public ...args)以及一整套完整的Class体系,Abstract Class,Private/Protected/Readonly等关键字。
  3. 在ES6之前,原始人类通过constructor function构造函数 new Func()来实现Class
  4. 不管是TS还是ES6,最终的JS代码的Class还是原型链prototype chain的继承

注意:

  1. Class只是蓝图,类似房子的图纸,不是房子;房子是基于图纸的实例instance
  2. 明白了这图纸的概念,方便理解 TS中 private,protected,和public的区别,比如private:只能在本图纸使用,protected:可以在复印的图纸中使用,并修改;public:在图纸和造好的房子(instance)中都可以修改。

Object

对象可以通过object literal快速粗暴实现

image.png

image.png

注意:

  1. object本身就是一个实例,不是图纸,而是instance
  2. 如果object可以直接生成,为何还用Class?因为Class可以被无限自由生成多个instance,重复利用

如何把Class转换为Plain Object?

1) Helper 函数

toJSON(proto) {
    let jsoned = {};
    let toConvert = proto || this;
    Object.getOwnPropertyNames(toConvert).forEach((prop) => {
        const val = toConvert[prop];
        // don't include those
        if (prop === 'toJSON' || prop === 'constructor') {
            return;
        }
        if (typeof val === 'function') {
            jsoned[prop] = val.bind(jsoned);
            return;
        }
        jsoned[prop] = val;
    });

    const inherited = Object.getPrototypeOf(toConvert);
    if (inherited !== null) {
        Object.keys(this.toJSON(inherited)).forEach(key => {
            if (!!jsoned[key] || key === 'constructor' || key === 'toJSON')
                return;
            if (typeof inherited[key] === 'function') {
                jsoned[key] = inherited[key].bind(jsoned);
                return;
            }
            jsoned[key] = inherited[key];
        });
    }
    return jsoned;
}

2) 用工具 github.com/typestack/c…

image.png

JSON

JSON String


{
"name" : "john"
...
}

如何把JsonString转换为Object?

利用插件 marketplace.visualstudio.com/items?itemN…

构造函数 Function Constructor

利用闭包实现react的memo工厂函数

image.png

利用闭包实现jest的mock工厂函数

image.png

source: juejin.cn/post/708049…

利用Class实现Promise,即Promise是个Class,Promise的实例Instance才是Object (不是函数哦)

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    class Promise{
      constructor(executor){

        this.state = PENDING
        this.value = undefined
        this.reason = undefined
        //存放onFulfilled
        this.onResolvedCallbacks = []
        //存放onRejected
        this.onRejectedCallbacks = []
        const resolve = (value) => {
          if (this.state === PENDING) {
            this.value = value
            this.state = FULFILLED
            //promise实例状态改变后调用暂存的onFulfilled
            this.onResolvedCallbacks.forEach(fn => fn())
          }
        }

        const reject = (reason) => {
          if (this.state === PENDING) {
            this.reason = reason
            this.state = REJECTED
            //promise实例状态改变后调用的onRejected
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        try {
          executor(resolve,reject)
        } catch (error) {
          reject(error)
        }
      }
      then(onFulfilled, onRejected){
        if (this.state === FULFILLED) {
          onFulfilled(this.value)
        }

        if (this.state === REJECTED) {
          onRejected(this.reason)
        }

        if (this.state === PENDING) {
          //如果此时promise实例的状态还未确定,我们需要将onFulfilled与onRejected存起来,等到promise实例状态改变后再去调用
          this.onResolvedCallbacks.push(() => {
            onFulfilled(this.value)
          })
          this.onRejectedCallbacks.push(() => {
            onRejected(this.reason)
          })
        }

      }
    }

Source: juejin.cn/post/702627…

总结:无论是Jest的mock还是Promise,原理都差不多,就是在构造函数(jest.fn()其实是可以用来 new jest.fn()的)和Class的内部,注入一个property(属性)来跟踪记录调用结果:jest的calls和Promise的state等!