【Vue】那些东西

131 阅读6分钟

参考链接

观察者模式

class Observer {
    constructor() {

    }
    update(val) {

    }
}
class ObserverList {
    constructor() {
        this.observerList = []
    }
    add(observer) {
        return this.observerList.push(observer);
    }
    remove(observer) {
        this.observerList = this.observerList.filter(ob => ob !== observer);
    }
    count() {
        return this.observerList.length;
    }
    get(index) {
        return this.observerList[index];
    }
}
class Subject {
    constructor() {
        this.observers = new ObserverList();
    }
    addObserver(observer) {
        this.observers.add(observer);
    }
    removeObserver(observer) {
        this.observers.remove(observer);
    }
    notify(...args) {
        let obCount = this.observers.count();
        for (let index = 0; index < obCount; index++) {
            this.observers.get(i).update(...args);
        }
    }
}

发布订阅模式

class PubSub {
    constructor() {
        this.subscribers = {}
    }
    subscribe(type, fn) {
        if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
          this.subscribers[type] = [];
        }
        
        this.subscribers[type].push(fn);
    }
    unsubscribe(type, fn) {
        let listeners = this.subscribers[type];
        if (!listeners || !listeners.length) return;
        this.subscribers[type] = listeners.filter(v => v !== fn);
    }
    publish(type, ...args) {
        let listeners = this.subscribers[type];
        if (!listeners || !listeners.length) return;
        listeners.forEach(fn => fn(...args));        
    }
}

let ob = new PubSub();
ob.subscribe('add', (val) => console.log(val));
ob.publish('add', 1);

观察者模式和发布订阅模式

  • 观察者模式中,观察者被订阅者通知,订阅者同时也维护了观察者的一个记录。发布订阅模式下发布者和订阅者互不相识,他们只是在消息队列或者代理的帮助下进行通信
  • 发布订阅模式下组件的耦合程度比观察者模式更松
  • 观察者模式一般是同步的实现方式,发布订阅模式一般是异步的实现方式
  • 观察者模式需要在单个应用程序中实现,而发布订阅模式更多的是跨应用模式下使用。

defineProperty

参考链接

function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

VUE原理

Vue能够让一个数据变更时视图就进行刷新,而且用到这个数据的其他地方也会同步变更。

所以需要考虑两个问题

  • 视图模板templatecomputedwatch里用了哪些数据,因为这些数据是需要响应式(reactive)的。
  • 数据变更了怎么告诉render函数。

解决方案:

  • 模板渲染的时候要用到某个数据,必然要访问它,所以可以通过getter拦截,然后做出相应的处理。
  • 同时在值变更的时候,可以通过setter拦截,从而告诉render函数进行重新计算

数据响应式(observerdefineReactive )数据拦截gettersetter

** Observer Dep Watcher **

  • Observer会将数据响应式,并且每个数据都会有一个Dep实例
  • Dep实例中有个subs队列,保存着依赖本数据的观察者(Watcher对象实例),dep.notify()会通知观察者。

Observer对象针对数组和普通对象进行响应式,生产每个组件的Component类的构造函数里,会进行数据的响应式处理,watcher的实例化(依赖收集)。

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    <!--将Observer实例挂载到对象或数组的__ob__上,提供后续观测数据使用-->
    <!--只为对象或数组 实例一个Observer类的实例,而且就只会实例化一次-->
    def(value, "__ob__", this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk(obj: Object) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
        // 但是每个key都会响应式处理,每个key都有一个dep实例
      defineReactive(obj, keys[i]);
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  }
}

/**
 * Define a reactive property on an Object.
 */
 // 在对象上把属性响应式
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep();

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        // 收集依赖
        // template被编译后,会形成AST,在执行render()函数过程中就会触发data.a的getter,并且这个过程是惰性收集的
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    },
  });
}

vue 是如何监听数组的

问题:数组的一些方法,比如(push,unshift)会改变数组的值,但是无法在set中被拦截,所以无法通知更新。

解决办法:Vue针对那些不会被set拦截的方法进行重写。用AOP的思想,先把数组原来方法执行一遍拿到结果result,并且对入参进行监听,同时notify变更,返回result。

hasProto = '__proto__' in {}
/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

增强数组的方法,让数组的方法调用时会去notify观察者

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse",
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted);
    // notify change 重点
    ob.dep.notify();
    return result;
  });
});

保留数组原来的操作 - push、unshift、splice这些方法,会带来新的数据元素,而新带来的数据元素,我们是有办法得知的(即为传入的参数) - 那么新增的元素也是需要被配置为可观测数据的,这样子后续数据的变更才能得以处理。所以要对新增的元素调用observer实例上的observeArray方法进行一遍观测处理 - 由于数组变更了,那么就需要通知观察者,所以通过ob.dep.notify()对数组的观察者watchers进行通知

依赖收集

Observer Dep Watcher

依赖收集器:const dep = new Dep() 观察者怎么观察:this._watcher = new Watcher(this, render, this._update)

  • 记录了某个key被哪些watcher watch了,哪些watcher订阅了key,它还有一些对添加、删除watcher的方法。

  • 它有一个静态属性 target ,是一个 watcher , 这是全局唯一的 watcher ,因为同一时间只能有一个全局 watcher 被计算。

  • 执行mountComponent函数中,传入uddateComponent函数,实例化watcher,此时进入到watcher方法中,执行constructor,执行this.get(),首先pushTarget(this)

  • Observer的构造函数先对data作类型校验和__ob__属性检测,没有__ob__时在对data实例化一个Observer,返回该实例

  • vuedata初始化为一个Observer并对对象中的每个值,重写了其中的getsetdata中的每个key,都有一个独立的Dep实例。

  • get中,向依赖收集器添加了监听,dep.depend()

  • mount时,实例了一个Watcher,将收集器的目标指向了当前Watcher

  • data值发生变更时,触发set,触发了依赖收集器中的所有监听的更新,来触发Watcher.update

派发更新

  • setter派发更新dep.notify()
  • watcher.update
  • queueWatcher
  • flushSchedulerQueue