残缺代码 (https://github.com/mfaying/si...

事件相干的实例办法

在eventsMixin中挂载到Vue构造函数的prototype中

vm.$on

将回调fn注册到事件列表中即可,_events在实例初始化时创立。

Vue.prototype.$on = function(event, fn) {  const vm = this;  if (Array.isArray(event)) {    for (let i = 0, l = event.length; i < l; i++) {      this.$on(event[i], fn);    }  } else {    (vm._events[event] || (vm._events[event] = [])).push(fn);  }  return vm;};

vm.$off

反对offoff('eventName')off('eventName', fn)off(['eventName1', 'eventName2'])off(['eventName1', 'eventName2'], fn)多种状况

Vue.prototype.$off = function(event, fn) {  const vm = this;  if (!arguments.length) {    vm._events = Object.create(null);    return vm;  }  if (Array.isArray(event)) {    for (let i = 0, l = event.length; i < l; i++) {      this.$off(event[i], fn);    }    return vm;  }  const cbs = vm._events[event];  if (!cbs) {    return vm;  }  if (!fn) {    vm._events[event] = null;    return vm;  }  if (fn) {    const cbs = vm._events[event];    let cb;    let i = cbs.length;    while (i--) {      cb = cbs[i];      if (cb === fn || cb.fn === fn) {        cbs.splice(i, 1);        break;      }    }  }  return vm;};

vm.$once

先移除事件监听,再执行函数。

Vue.prototype.$once = function(event, fn) {  const vm = this;  function on() {    vm.$off(event, on)    fn.apply(vm, arguments)  }  on.fn = fn;  vm.$on(event, on);  return vm;};

vm.$emit

取出对应event回调函数列表,再遍历执行

Vue.prototype.$emit = function(event) {  const vm = this;  let cbs = vm._events[event];  if (cbs) {    const args = Array.from(arguments).slice(1)    for (let i = 0, l = cbs.length; i < l; i ++) {      try {        cbs[i].apply(vm, args)      } catch (e) {        console.error(e, vm, `event handler for "${event}"`)      }    }  }  return vm;};

生命周期相干的实例办法

vm.$forceUpdate

执行_watcher.update(后面介绍过原理),手动告诉实例从新渲染

Vue.prototype.$forceUpdate = function() {  const vm = this;  if (vm._watcher) {    vm._watcher.update();  }};

vm.$destroy

vm.$destroy能够销毁一个实例

  1. 先触发beforeDestroy生命周期
  2. 删除以后组件与父组件之间的连贯
  3. 从状态的依赖列表中将watcher移除
  4. 销毁用户应用vm.$watch所创立的watcher实例
  5. 将模板中所有指令解绑vm.__patch__(vm._vnode, null)
  6. 触发destroyed生命周期
  7. 移除所有事件监听器
Vue.prototype.$destroy = function() {  const vm = this;  if (vm._isBeingDestroyed) {    return;  }  // callHook(vm, 'beforeDestroy')  vm._isBeingDestroyed = true;  const parent = vm.$parent;  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {    remove(parent.$children, vm);  }  if (vm._watcher) {    vm._watcher.teardown();  }  let i = vm._watchers.length;  while (i--) {    vm._watchers[i].teardown();  }  vm._isDestroyed = true;  // vm.__patch__(vm._vnode, null);  // callHook(vm, 'destroyed')  vm.$off();};

vm.$nextTick

nextTick接管一个回调函数作为参数,它的作用是将回调提早到下次DOM更新周期之后执行。如果没有提供回调且反对Promise的环境中,则返回一个Promise。

应用示例:

new Vue({  // ...  methods: {    example: function() {      this.msg = 1;      this.$nextTick(function () {        // DOM当初更新了      })    }  }})

异步更新队列

在同一轮事件循环中即便有数据产生了两次雷同的变动,也不会渲染两次。因为Vue.js会将受到告诉的watcher实例增加到队列中缓存起来,增加到队列之前会查看是否曾经存在雷同的watcher,只有不存在,才会将watcher实例增加到队列中。下次事件循环会让队列里的watcher触发渲染流程并清空队列。

什么是事件循环

JavaScript是单线程的脚本语言,任何时候都只有一个主线程来解决工作。当解决异步工作时,主线程会挂起这工作,当工作处理完毕,JavaScript会将这个事件退出一个队列,咱们叫事件队列,被放入事件队列中的事件不会立刻执行其回调,而是期待以后执行栈中的所有工作执行结束后,主线程会去查找事件队列中是否有工作。

异步工作有两种类型:微工作和宏工作。当执行栈中的所有工作都执行结束后,会去查看微工作队列中是否有事件存在,如果有则一次执行微工作队列中事件对应的回调,直到为空。而后去宏工作队列中取出一个事件,把对应的回调退出以后执行栈,当执行栈中的所有工作都执行结束后,查看微工作队列,如此往返,这个循环就是事件循环

属于微工作的事件有:

  1. Promise.then
  2. MutationObserver
  3. Object.observe
  4. process.nextTick
  5. ...

属于宏工作的事件有

  1. setTimeout
  2. setInterval
  3. setImmediate
  4. MessageChannel
  5. requestAnimationFrame
  6. I/O
  7. UI交互事件
  8. ...

下次DOM更新周期其实是下次微工作执行时更新DOM。vm.$nextTick其实是将回调增加到微工作中。只有非凡状况下才会降级成宏工作。

nextTick的实现

nextTick个别状况会应用Promise.then将flushCallbacks增加到微工作队列中
withMacroTask包裹的函数所应用的nextTick办法会将回调增加到宏工作中。

const callbacks = [];let pending = false;function flushCallbacks() {  pending = false;  const copies = callbacks.slice(0);  callbacks.length = 0;  for (let i = 0; i < copies.length; i++) {    copies[i]();  }}let microTimerFunc;let macroTimerFunc;function isNative() {  // 实现疏忽  return true;}if (typeof setImmediate !== "undefined" && isNative(setImmediate)) {  macroTimerFunc = () => {    setImmediate(flushCallbacks);  };} else if (  typeof MessageChannel !== "undefined" &&  (isNative(MessageChannel) ||    MessageChannel.toString() === "[object MessageChannelConstructor]")) {  const channel = new MessageChannel();  const port = channel.port2;  channel.port1.onmessage = flushCallbacks;  macroTimerFunc = () => {    port.postMessage(1);  };} else {  macroTimerFunc = () => {    setTimeout(flushCallbacks, 0);  };}let useMacroTask = false;if (typeof Promise !== "undefined" && isNative(Promise)) {  const p = Promise.resolve();  microTimerFunc = () => {    p.then(flushCallbacks);  };} else {  microTimerFunc = macroTimerFunc;}export function withMacroTask(fn) {  return (    fn._withTask ||    (fn._withTask = function() {      useMacroTask = true;      const res = fn.apply(null, arguments);      useMacroTask = false;      return res;    })  );}export function nextTick(cb, ctx) {  let _resolve;  callbacks.push(() => {    if (cb) {      cb.call(ctx);    } else if (_resolve) {      _resolve(ctx);    }  });  if (!pending) {    pending = true;    if (useMacroTask) {      macroTimerFunc();    } else {      microTimerFunc();    }  }  if (!cb && typeof Promise !== "undefined") {    return new Promise(resolve => {      _resolve = resolve;    });  }}

vm.$mount

想让Vue.js实例具备关联的DOM元素,只有应用vm.$mount办法这一种途经。

  Vue.prototype.$mount = function(el) {    el = el && query(el);    const options = this.$options;    if (!options.render) {      let template = options.template;      if (template) {        if (typeof template === "string") {          if (template.charAt(0) === "#") {            template = idToTemplate(template);          }        } else if (template.nodeType) {          template = template.innerHTML;        } else {          if (process.env.NODE_ENV !== "production") {            console.warn("invalid template option:" + template, this);          }          return this;        }      } else if (el) {        template = getOuterHTML(el);      }      if (template) {        const { render } = compileToFunctions(template, options, this);        options.render = render;      }    }    return mountComponent(this, el);    // return mount.call(this, el);  };}function mountComponent(vm, el) {  if (!vm.$options.render) {    vm.$options.render = createEmptyVNode;    if (process.env.NODE_ENV !== "production") {      // 在开发环境收回正告    }    callHook(vm, "beforeMount");    // 挂载    // _update 调用patch办法执行节点的比对和渲染操作    // _render 执行渲染函数,失去一份最新的VNode节点树    // vm._watcher = new Watcher(    //   vm,    //   () => {    //     vm._update(vm._render());    //   },    //   noop    // );    callHook(vm, "mounted");    return vm;  }}function createEmptyVNode() {}function callHook() {}function idToTemplate(id) {  const el = query(id);  return el && el.innerHTML;}function query(el) {  if (typeof el === "string") {    const selected = document.querySelector(el);    if (!selected) {      return document.createElement("div");    }    return selected;  } else {    return el;  }}function getOuterHTML(el) {  if (el.outerHTML) {    return el.outerHTML;  } else {    const container = document.createElement("div");    container.appendChild(el.cloneNode(true));    return container.innerHTML;  }}const cache = {};function compile() {  // 03章节介绍过的生成代码字符串  return {    render: ""  };}function compileToFunctions(template, options, vm) {  // options = extend({}, options);  // 查看缓存  const key = options.delimiters    ? String(options.delimiters) + template    : template;  if (cache[key]) {    return cache[key];  }  const compiled = compile(template, options);  const res = {};  res.render = createFunction(compiled.render);  return (cache[key] = res);}function createFunction(code) {  return new Function(code);}

Vue.extend

应用根底Vue结构器创立一个"子类"

let cid = 1;const ASSET_TYPES = ["component", "directive", "filter"];exports.extend = function(Vue) {  Vue.extend = function(extendOptions) {    extendOptions = extendOptions || {};    const Super = this;    const SuperId = Super.cid;    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});    if (cachedCtors[SuperId]) {      return cachedCtors[SuperId];    }    // const name = extendOptions.name || Super.options.name;    const name = extendOptions.name;    if (process.env.NODE_ENV !== "production") {      if (!/^[a-zA-Z][\w-]*$/.test(name)) {        console.warn("");      }    }    const Sub = function VueComponent(options) {      this._init(options);    };    Sub.prototype = Object.create(Super.prototype);    Super.prototype.constructor = Sub;    Sub.cid = cid++;    Sub.options = { ...Super.options, ...extendOptions };    Sub["super"] = Super;    if (Sub.options.props) {      initProps(Sub);    }    if (Sub.options.computed) {      initComputed(Sub);    }    Sub.extend = Super.extend;    Sub.mixin = Super.mixin;    Sub.use = Super.use;    ASSET_TYPES.forEach(type => {      Sub[type] = Super[type];    });    if (name) {      Sub.options.components[name] = Sub;    }    Sub.superOptions = Super.options;    Sub.extendOptions = extendOptions;    Sub.sealedOptions = Object.assign({}, Sub.options);    cachedCtors[SuperId] = Sub;    return Sub;  };};function initProps(Comp) {  const props = Comp.options.props;  for (const key in props) {    proxy(Comp.prototype, `_props`, key);  }}function proxy(target, sourceKey, key) {  sharedPropertyDefinition.get = function proxyGetter() {    return this[sourceKey][key];  };  sharedPropertyDefinition.set = function proxySetter(val) {    this[sourceKey][key] = val;  };  Object.defineProperties(target, key, sharedPropertyDefinition);}function initComputed(Comp) {  const computed = Comp.options.computed;  for (const key in computed) {    definedComputed(Comp.prototype, key, computed[key]);  }}

Vue.nextTick

和后面介绍过的原理一样

Vue.set

和后面介绍过的原理一样

Vue.delete

和后面介绍过的原理一样

Vue.directive、Vue.filter、Vue.component

// Vue.filter、Vue.component、Vue.directive原理const ASSET_TYPES = ["component", "directive", "filter"];function isPlainObject() {}exports.filterAndOther = function(Vue) {  Vue.options = Object.create(null);  ASSET_TYPES.forEach(type => {    Vue.options[type + "s"] = Object.create(null);  });  ASSET_TYPES.forEach(type => {    Vue.directive = function(id, definition) {      ASSET_TYPES.forEach(type => {        Vue.options[type + "s"] = Object.create(null);      });      ASSET_TYPES.forEach(type => {        Vue[type] = function(id, definition) {          if (!definition) {            return this.options[type + "s"][id];          } else {            if (type === "component" && isPlainObject(definition)) {              definition.name = definition.name || id;              definition = Vue.extend(definition);            }            if (type === "directive" && typeof definition === "function") {              definition = { bind: definition, update: definition };            }            this.options[type + "s"][id] = definition;            return definition;          }        };      });    };  });};

Vue.use

会调用install办法,将Vue作为参数传入,install办法会被同一个插件屡次调用,插件只会装置一次。

exports.use = function(Vue) {  Vue.use = function(plugin) {    const installedPlugins =      this._installedPlugins || (this._installedPlugins = []);    if (installedPlugins.indexOf(plugin) > -1) {      return this;    }    const args = Array.from(arguments).slice(1);    args.unshift(this);    if (typeof plugin.install === "function") {      plugin.install.apply(plugin, args);    } else if (typeof plugin === "function") {      plugin.apply(null, args);    }    installedPlugins.push(plugin);    return this;  };};

Vue.mixin

全局注册一个混入(mixin),影响注册之后创立的每个Vue.js实例。插件作者能够应用混入向组件注入自定义行为(例如:监听生命周期钩子)。不举荐在利用代码中应用

function mergeOptions() {}exports.mixin = function(Vue) {  Vue.mixin = function(mixin) {    this.options = mergeOptions(this.options, mixin);    return this;  };};

Vue.compile

后面介绍过的将模板编译成渲染函数的原理

Vue.version

返回Vue.js装置版本号,从Vue构建文件配置中取
残缺代码 (https://github.com/mfaying/si...

参考

《深入浅出Vue.js》