# JavaScript 各种手写源码实现·下篇

# 实现 EventEmitter

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(type, listener) {
    // 同类型事件归纳在一起,触发的时候挨个触发
    let cb = this._events[type] || [];
    cb.push(listener);
    this._events[type] = cb;
    return this;
  }

  off(type, listener) {
    const callbacks = this._events[type];
    // 如果是删除所有事件,一次性删除,否则删除对应事件
    if (!listener) {
      callbacks.length = 0;
    } else {
      this._events[type] =
        callbacks && callbacks.filter((cb) => cb !== listener);
    }
    return this;
  }

  once(type, listener) {
    // 绑定一个事件:执行完毕就解除
    const cb = (...args) => {
      listener.call(this, ...args);
      this.off(type, cb);
    };
    this.on(type, cb);
    return this;
  }

  emit(type, ...args) {
    const callbacks = this._events[type];
    if (Array.isArray(callbacks)) {
      callbacks.forEach((cb) => cb.call(this, ...args));
    }
  }
}

# 发布-订阅模式与观察者模式的区别

发布-订阅模式:

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(type, listener) {
    this._events[type] = listener;
  }

  emit(type, ...args) {
    if (this._events[type]) {
      this._events[type].call(this, ...args);
    }
  }
}

观察者模式:

class Subject {
  constructor() {
    this.observers = [];
  }

  add(observer) {
    this.observers.push(observer);
  }

  notify(...args) {
    this.observers.forEach((observer) => observer.log(...args));
  }
}

class Observer {
  log(...args) {
    console.log(...args);
  }
}

const ob1 = new Observer();
const ob2 = new Observer();
const sub = new Subject();
sub.add(ob1);
sub.add(ob2);
sub.notify("Event Fire");

可以看出同样是在事件被触发时打印出 Event Fire 字符串,在观察者模式中只存在两个角色:Subject 和 Observer,Subject 通知(notify)事件,然后所有的 observers 都触发回调函数;而在 发布-订阅 模式中,存在三个角色:Publisher(emit 方法)、Subscriber(on 方法)和 Event Channel(EventEmitter 本身),如下图所示:

发布订阅

观察者模式中,Subject 和 Observer 是互相耦合的 (Subject 要直接 addObserver),而在 发布-订阅 模式中,由于 Event Channel 扮演了一个数据通道的角色,Publisher 和 Subscriber 是解耦的,这也使得 发布-订阅模式 相对于观察者模式更加灵活。可以说,发布-订阅模式是一种特殊的观察者模式

原文地址:22 常见设计模式

# 实现一个简易版 Promise

Promise 三大法宝: 回调函数延迟绑定回调返回值穿透错误冒泡

function Promise(excutor) {
  var self = this;
  self.onResolvedCallback = [];
  function resolve(value) {
    setTimeout(() => {
      self.data = value;
      self.onResolvedCallback.forEach((callback) => callback(value));
    });
  }
  excutor(resolve.bind(self));
}
Promise.prototype.then = function(onResolved) {
  var self = this;
  return new Promise((resolve) => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data);
      if (result instanceof Promise) {
        result.then(resolve);
      } else {
        resolve(result);
      }
    });
  });
};

Promise 的本质是一个有限状态机,存在三种状态:

  • PENDING(等待)
  • FULFILLED(成功)
  • REJECTED(失败)

对于 Promise 而言,状态的改变不可逆,即由等待态变为其他的状态后,就无法再改变了。

//定义三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor) {
  let self = this; // 缓存当前promise实例
  self.value = null;
  self.error = null;
  self.status = PENDING;
  self.onFulfilled = null; //成功的回调函数
  self.onRejected = null; //失败的回调函数

  const resolve = (value) => {
    if (self.status !== PENDING) return;
    setTimeout(() => {
      self.status = FULFILLED;
      self.value = value;
      //resolve时执行成功回调
      self.onFulfilledCallback.forEach((callback) => callback(self.value));
    });
  };

  const reject = (error) => {
    if (self.status !== PENDING) return;
    setTimeout(() => {
      self.status = REJECTED;
      self.error = error;
      //resolve时执行成功回调
      self.onRejectedCallback.forEach((callback) => callback(self.error));
    });
  };
  executor(resolve, reject);
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  if (this.status === PENDING) {
    this.onFulfilledCallbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  } else if (this.status === FULFILLED) {
    //如果状态是fulfilled,直接执行成功回调,并将成功值传入
    onFulfilled(this.value);
  } else {
    //如果状态是rejected,直接执行失败回调,并将失败原因传入
    onRejected(this.error);
  }
  return this;
};

学习资料

# async/await 实现

来自:手写 async await 的最简实现(20 行)

function asyncToGenerator(generatorFunc) {
  // 返回的是一个新的函数
  return function() {
    // 先调用generator函数 生成迭代器
    // 对应 var gen = testG()
    const gen = generatorFunc.apply(this, arguments);

    // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
    // var test = asyncToGenerator(testG)
    // test().then(res => console.log(res))
    return new Promise((resolve, reject) => {
      // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
      // key有next和throw两种取值,分别对应了gen的next和throw方法
      // arg参数则是用来把promise resolve出来的值交给下一个yield
      function step(key, arg) {
        let generatorResult;

        // 这个方法需要包裹在try catch中
        // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }

        // gen.next() 得到的结果是一个 { value, done } 的结构
        const { value, done } = generatorResult;

        if (done) {
          // 如果已经完成了 就直接resolve这个promise
          // 这个done是在最后一次调用next后才会为true
          // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
          // 这个value也就是generator函数最后的返回值
          return resolve(value);
        } else {
          // 除了最后结束的时候外,每次调用gen.next()
          // 其实是返回 { value: Promise, done: false } 的结构,
          // 这里要注意的是Promise.resolve可以接受一个promise为参数
          // 并且这个promise参数被resolve的时候,这个then才会被调用
          return Promise.resolve(
            // 这个value对应的是yield后面的promise
            value
          ).then(
            // value这个promise被resove的时候,就会执行next
            // 并且只要done不是true的时候 就会递归的往下解开promise
            // 对应gen.next().value.then(value => {
            //    gen.next(value).value.then(value2 => {
            //       gen.next()
            //
            //      // 此时done为true了 整个promise被resolve了
            //      // 最外部的test().then(res => console.log(res))的then就开始执行了
            //    })
            // })
            function onResolve(val) {
              step("next", val);
            },
            // 如果promise被reject了 就再次进入step函数
            // 不同的是,这次的try catch中调用的是gen.throw(err)
            // 那么自然就被catch到 然后把promise给reject掉啦
            function onReject(err) {
              step("throw", err);
            }
          );
        }
      }
      step("next");
    });
  };
}

# 双向绑定实现

  • 首先在 observer 的过程中会注册 get 方法,该方法用来进行「依赖收集」。
  • 在它的闭包中会有一个 Dep 对象,这个对象用来存放 Watcher 对象的实例。
  • 其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。
  • get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,
  • 在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。
class Dep {
  constructor() {
    /* 用来存放Watcher对象的数组 */
    this.subs = [];
  }

  /* 在subs中添加一个Watcher对象 */
  addSub(sub) {
    this.subs.push(sub);
  }

  /* 通知所有Watcher对象更新视图 */
  notify(val) {
    this.subs.forEach((sub) => {
      sub.update(val);
    });
  }
}

class Watcher {
  constructor() {
    /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
    Dep.target = this;
  }

  /* 更新视图的方法 */
  update(val) {
    console.log("视图更新啦~", val);
  }
}

Dep.target = null;

function defineReactive(obj, key, val) {
  /* 一个Dep类对象 */
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      /* 将Dep.target(即当前的Watcher对象存入dep的subs中) */
      dep.addSub(Dep.target);
      return val;
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) return;
      /* 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 */
      dep.notify(newVal);
    },
  });
}

// 实现 observer(可观察的)
function observer(value) {
  if (!value || typeof value !== "object") {
    return;
  }
  Object.keys(value).forEach((key) => {
    defineReactive(value, key, value[key]);
  });
}

class Vue {
  constructor(options) {
    this._data = options.data;

    observer(this._data);

    /* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象 */
    new Watcher();

    /* 在这里模拟render的过程,为了触发test属性的get函数 */
    console.log("render~", this._data.test);
  }
}

let myvue = new Vue({
  data: {
    test: "I am test data.",
  },
});

myvue._data.test = "TTTest";

# 参考资料

Last Updated: 5/22/2020, 5:01:49 PM