残缺代码 (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》