The complete code
Github.com/mfaying/sim…
Event-related instance methods
Mount to the Vue constructor’s Prototype in eventsMixin
vm.$on
Just register the callback FN in the event list, and _Events is created when the instance is initialized.
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;
};
Copy the code
vm.$off
Off ([‘eventName1’, ‘eventName2’]), 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;
};
Copy the code
vm.$once
Remove the event listener before executing the function.
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;
};
Copy the code
vm.$emit
Fetch the corresponding event callback function list, and then iterate the execution
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;
};
Copy the code
Lifecycle related instance methods
vm.$forceUpdate
Execute _watcher.update(the principle described earlier) to manually notify the instance to re-render
Vue.prototype.$forceUpdate = function() {
const vm = this;
if(vm._watcher) { vm._watcher.update(); }};Copy the code
vm.$destroy
Vm.$destroy destroys an instance
- Trigger the beforeDestroy lifecycle first
- Deletes the connection between the current component and its parent component
- Remove watcher from the state dependency list
- Destroy the watcher instance created by the user using vm.$watch
- Unbind all instructions in the template
vm.__patch__(vm._vnode, null)
- The life cycle of the Destroyed is triggered
- Remove all event listeners
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();
};
Copy the code
vm.$nextTick
NextTick takes a callback function as an argument, and its purpose is to delay the callback until after the next DOM update cycle. If no callback is provided and a Promise is supported, a Promise is returned.
Example:
new Vue({
// ...
methods: {
example: function() {
this.msg = 1;
this.$nextTick(function () {
// The DOM is now updated}}}})Copy the code
Asynchronous update queue
Even if the data changes twice in the same round of the event loop, it will not be rendered twice. Because vue.js will cache the watcher instance that has been notified to the queue, it will check to see if the same Watcher already exists before adding it to the queue. If not, it will add the watcher instance to the queue. The next event loop causes the watcher in the queue to trigger the render process and clear the queue.
What is an event loop
JavaScript is a single-threaded scripting language, with only one main thread handling tasks at any one time. When working with asynchronous task, the main thread will suspend this task, when the task is processed, JavaScript will join this event a queue, we call the event queue and are placed in events in the queue will not immediately implement the callback, but wait for the currently executing after all the tasks in the stack, the main thread if there is a task to find the event queue.
There are two types of asynchronous tasks: microtasks and macro tasks. When all tasks in the execution stack are completed, the system checks whether there are events in the microtask queue. If there are, the callback corresponding to the events in the microtask queue is executed until it is empty. Then take out an event from the macro task queue and add the corresponding callback to the current execution stack. When all tasks in the execution stack are completed, check the microtask queue, and so on, this loop is the event loop.
Events that belong to a microtask are:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
- .
The events that belong to a macro task are
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI Interaction Events
- .
The next DOM update cycle is actually an update to the DOM the next time a microtask is executed. Vm.$nextTick actually adds a callback to a microtask. Demoted to a macro task only in exceptional cases.
The realization of the nextTick
Then the nextTick method used by the function wrapped withMacroTask adds the callback to the macro task.
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() {
// Implement ignore
return true;
}
if (typeofsetImmediate ! = ="undefined" && isNative(setImmediate)) {
macroTimerFunc = (a)= > {
setImmediate(flushCallbacks);
};
} else if (
typeofMessageChannel ! = ="undefined" &&
(isNative(MessageChannel) ||
MessageChannel.toString() === "[object MessageChannelConstructor]")) {const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = flushCallbacks;
macroTimerFunc = (a)= > {
port.postMessage(1);
};
} else {
macroTimerFunc = (a)= > {
setTimeout(flushCallbacks, 0);
};
}
let useMacroTask = false;
if (typeof Promise! = ="undefined" && isNative(Promise)) {
const p = Promise.resolve();
microTimerFunc = (a)= > {
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;
returnres; })); }export function nextTick(cb, ctx) {
let _resolve;
callbacks.push((a)= > {
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; }); }}Copy the code
vm.$mount
The only way to get the vue. js instance to have an associated DOM element is to use the vm.$mount method.
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") {
// Issue a warning in the development environment
}
callHook(vm, "beforeMount");
/ / a mount
// _update calls the patch method to compare and render nodes
// _render executes the render function to get a copy of the latest VNode tree
// vm._watcher = new Watcher(
// vm,
// () => {
// vm._update(vm._render());
/ /},
// noop
// );
callHook(vm, "mounted");
returnvm; }}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 {
returnel; }}function getOuterHTML(el) {
if (el.outerHTML) {
return el.outerHTML;
} else {
const container = document.createElement("div");
container.appendChild(el.cloneNode(true));
returncontainer.innerHTML; }}const cache = {};
function compile() {
// The generated code string described in section 03
return {
render: ""
};
}
function compileToFunctions(template, options, vm) {
// options = extend({}, options);
// Check the cache
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);
}
Copy the code
Vue.extend
Create a “subclass” using the base Vue constructor
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 incomputed) { definedComputed(Comp.prototype, key, computed[key]); }}Copy the code
Vue.nextTick
Same principle as described earlier
Vue.set
Same principle as described earlier
Vue.delete
Same principle as described earlier
Directive, vue.filter, and Vue.component
// Principles of vue. filter, Vue.component, and 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;
returndefinition; }}; }); }; }); };Copy the code
Vue.use
The install method will be called, passing Vue as an argument. The install method will be called multiple times by the same plug-in, and the plug-in will only be installed once.
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;
};
};
Copy the code
Vue.mixin
Global registration of a mixin affects every vue.js instance created after registration. Plug-in authors can use mixins to inject custom behavior into components (for example, listening for lifecycle hooks). Not recommended for use in application code
function mergeOptions() {}
exports.mixin = function(Vue) {
Vue.mixin = function(mixin) {
this.options = mergeOptions(this.options, mixin);
return this;
};
};
Copy the code
Vue.compile
The rationale for compiling templates into render functions was described earlier
Vue.version
Return the vue.js installation version number and take the full code from the Vue build file configuration (github.com/mfaying/sim…)
reference
Vue.js