共计 9654 个字符,预计需要花费 25 分钟才能阅读完成。
残缺代码 (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
反对 off
、off('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 能够销毁一个实例
- 先触发 beforeDestroy 生命周期
- 删除以后组件与父组件之间的连贯
- 从状态的依赖列表中将 watcher 移除
- 销毁用户应用 vm.$watch 所创立的 watcher 实例
- 将模板中所有指令解绑
vm.__patch__(vm._vnode, null)
- 触发 destroyed 生命周期
- 移除所有事件监听器
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 会将这个事件退出一个队列,咱们叫 事件队列
,被放入事件队列中的事件不会立刻执行其回调,而是期待以后执行栈中的所有工作执行结束后,主线程会去查找事件队列中是否有工作。
异步工作有两种类型:微工作和宏工作。当执行栈中的所有工作都执行结束后,会去查看微工作队列中是否有事件存在,如果有则一次执行微工作队列中事件对应的回调,直到为空。而后去宏工作队列中取出一个事件,把对应的回调退出以后执行栈,当执行栈中的所有工作都执行结束后,查看微工作队列,如此往返,这个循环就是 事件循环
。
属于微工作的事件有:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
- …
属于宏工作的事件有
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI 交互事件
- …
下次 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》