史上最全的vue.js源码解析(四)
虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下。vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到。所以我近期会对vue.js的源码进行解读,分享值得去学习的代码片段,这篇文章将会持续更新。
一、4000~6200代码有哪些内容?:
1.initState:初始化实例状态(initProps,initData,initComputed,initMethods,initWatch,initMixin等)
2.initAssetRegisters:创建组件、指令、过滤器
3.initGlobalAPI:初始化全局api
4.renderClass,mergeClassData,stringifyClass:对 class 的转码、合并和其他二次封装的工具函数
5.ref的注册和实现原理(ref是给元素或者子组件注册引用信息的)
二.4000~6200行代码的重点:
1.Watcher(重点)
在 Watcher 的原型链上定义了get、addDep、cleanupDeps、update、run、evaluate、depend、teardown 方法,进行新增依赖、清除、更新视图等操作。
每个Vue组件都有一个对应的watcher,这个watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染
2.proxy代理
通过 vm 对象(即this)来代理 data 对象中所有属性的操作,更方便的操作 data 中的数据
通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符,所有添加的属性都包含 getter/setter,getter/setter 内部去操作 data 中对应的属性数据
3、keep-alive的 缓存原理
keep-alive是 vue 内置组件,在源码定义中,也具有自己的组件选项如 data 、 method 、 computed 、 props 等,具有抽象组件标识 abstract,通常会与动态组件一同使用,包裹动态组件时,会缓存不活动的组件实例,将它们停用,而不是销毁它们;缓存的组件会触发 activated 或 deactivated 生命周期钩子;会缓存组件实例的 vnode 对象 ,和真实 dom 节点,所以会有 max 属性设置;不会在函数式组件中正常工作,因为它们没有缓存实例
4、VNode的diff与更新
销毁一个DOM节点并创建一个新的再插入是消耗非常大的,无论是DOM对象本身的复杂性还是操作引起的重绘重排,所以虚拟DOM的目标是尽可能复用现有DOM进行更新
三、4000~6200行的代码解读:
var activeInstance = null;var isUpdatingChildComponent = false;//设置当前的Vue实例 其返还结果为恢复上一个Vue实例,类似栈的思想来进行树状结构的构建function setActiveInstance(vm) {var prevActiveInstance = activeInstance;activeInstance = vm;return function () {activeInstance = prevActiveInstance;}}//方法主要用来初始化生命周期相关的属性,以及为parent,child属性赋值function initLifecycle (vm) {// 定义 options,它是 vm.$options 的引用,后面的代码使用的都是 options 常量var options = vm.$options;// 查找第一个非抽象父级// 定义 parent,它引用当前实例的父实例var parent = options.parent;// 如果当前实例有父组件,且当前实例不是抽象的if (parent && !options.abstract) {//循环查找第一个非抽象的父组件while (parent.$options.abstract && parent.$parent) {parent = parent.$parent;}// 经过上面的 while 循环后,parent 应该是一个非抽象的组件,将它作为当前实例的父级,// 所以将当前实例 vm 添加到父级的 $children 属性里parent.$children.push(vm);}// 设置当前实例的 $parent 属性,指向父级vm.$parent = parent;// 设置 $root 属性,有父级就是用父级的 $root,否则 $root 指向自身vm.$root = parent ? parent.$root : vm;// 设置当前实例的 $children 属性为空数组vm.$children = [];// 设置当前实例的 $ref 属性为空对象vm.$refs = {};// 设置当前实例的 _watcher 属性为nullvm._watcher = null;vm._inactive = null;vm._directInactive = false;vm._isMounted = false;vm._isDestroyed = false;vm._isBeingDestroyed = false;}//混入生命周期相关属性和方法function lifecycleMixin (Vue) {Vue.prototype._update = function (vnode, hydrating) {// 存储数据做以后update用var vm = this;var prevEl = vm.$el;var prevVnode = vm._vnode;var restoreActiveInstance = setActiveInstance(vm);vm._vnode = vnode;//第一次渲染会调用vm.__patch__方法if (!prevVnode) {// 初始渲染vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);} else {// 更新vm.$el = vm.__patch__(prevVnode, vnode);}restoreActiveInstance();// 更新参考if (prevEl) {prevEl.__vue__ = null;}if (vm.$el) {vm.$el.__vue__ = vm;}// 如果parent是HOC,则也更新其$elif (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {vm.$parent.$el = vm.$el;}// 调度程序调用更新的挂钩,以确保在父级的更新挂钩中更新子级};// 强制刷新的作用//迫使Vue实例重新渲染,注意它仅仅影响实例本身喝插入插槽内容的子组件,而不是所有的子组件Vue.prototype.$forceUpdate = function () {var vm = this;if (vm._watcher) {vm._watcher.update();}};// $destroy是组件内部销毁自己。// 清理它与其它实例的连接,解绑它的全部指令及事件监听器,// 断掉虚拟dom和真实dom之间的联系,并没有真正地回收这个vue实例Vue.prototype.$destroy = function () {var vm = this;if (vm._isBeingDestroyed) {return}//回调触发beforeDestroy钩子函数callHook(vm, 'beforeDestroy');vm._isBeingDestroyed = true;//从父对象中删除vmvar parent = vm.$parent;if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm);}//拆卸观察者,调用teardown方法移出watcherif (vm._watcher) {vm._watcher.teardown();}var i = vm._watchers.length;while (i--) {vm._watchers[i].teardown();}// 从数据中删除引用对象// 可能没有观察者if (vm._data.__ob__) {vm._data.__ob__.vmCount--;}// call the last hook...vm._isDestroyed = true;//在当前渲染树上调用销毁挂钩vm.__patch__(vm._vnode, null);//回调触发destroyed钩子函数callHook(vm, 'destroyed');//关闭所有实例的侦听器vm.$off();if (vm.$el) {vm.$el.__vue__ = null;}if (vm.$vnode) {vm.$vnode.parent = null;}};}// $mount函数会调用mountComponent方法function mountComponent (vm,el,//当前挂载的元素hydrating //和服务端渲染相关) {vm.$el = el;// 如果没有!vm.$options.render方法,就创建一个空的VNODE,if (!vm.$options.render) {vm.$options.render = createEmptyVNode;{if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm);} else {warn('Failed to mount component: template or render function not defined.',vm);}}}//回调触发beforeMount钩子函数callHook(vm, 'beforeMount');var updateComponent;/* istanbul ignore if */// 如果config中配置了performance(记录性能)if (config.performance && mark) {updateComponent = function () {var name = vm._name;var id = vm._uid;var startTag = "vue-perf-start:" + id;var endTag = "vue-perf-end:" + id;//开始标记mark(startTag);var vnode = vm._render();//开始结束mark(endTag);// measure方法是在执行了Performance的measure方法后,把开始结束两个mark值删除measure(("vue " + name + " render"), startTag, endTag);mark(startTag);vm._update(vnode, hydrating);mark(endTag);measure(("vue " + name + " patch"), startTag, endTag);};} else {updateComponent = function () {vm._update(vm._render(), hydrating);};}//将其设置为vm._watcher在watcher的构造函数中//因为观察者的初始补丁可能会调用$forceUpdate(例如,在child内部组件的挂载钩子),它依赖于已定义的vm.\u监视程序new Watcher(vm, updateComponent, noop, {// vm._isMounted 为 true,表示这个实例已经挂载了,执行 beforeUpdate 钩子函数before: function before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate');}}}, true);hydrating = false;//手动装入实例,调用自行装入//在插入的钩子中为渲染创建的子组件调用mountedif (vm.$vnode == null) {vm._isMounted = true;callHook(vm, 'mounted');}return vm}//对占位符 vm.$vnode 的更新、slot的更新,listeners 的更新,props 的更新等function updateChildComponent (vm,propsData,listeners,parentVnode,renderChildren) {{isUpdatingChildComponent = true;}// 确定组件是否具有插槽子级var newScopedSlots = parentVnode.data.scopedSlots;var oldScopedSlots = vm.$scopedSlots;var hasDynamicScopedSlot = !!((newScopedSlots && !newScopedSlots.$stable) ||(oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||(newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||(!newScopedSlots && vm.$scopedSlots.$key));// 来自父级的任何静态插槽子级在父级的更新。动态作用域插槽也可能已更改。// 在这种情况下,需要强制更新以确保正确性var needsForceUpdate = !!(renderChildren || // 有一个新的静态插槽vm.$options._renderChildren || // 有一个旧的静态插槽hasDynamicScopedSlot);vm.$options._parentVnode = parentVnode;// 更新vm的占位符节点而不重新渲染vm.$vnode = parentVnode;// 更新子节点的父节点if (vm._vnode) { vm._vnode.parent = parentVnode;}vm.$options._renderChildren = renderChildren;// 更新$attrs和$listeners散列它们也是被动的,因此如果子对象在渲染期间使用它们,// 它们可能会触发子对象更新vm.$attrs = parentVnode.data.attrs || emptyObject;vm.$listeners = listeners || emptyObject;// 更新propsif (propsData && vm.$options.props) {//禁止掉根组件 props的依赖收集toggleObserving(false);var props = vm._props;var propKeys = vm.$options._propKeys || [];for (var i = 0; i < propKeys.length; i++) {var key = propKeys[i];var propOptions = vm.$options.props;//校验propsprops[key] = validateProp(key, propOptions, propsData, vm);}toggleObserving(true);// 保留一份原始的propsDatavm.$options.propsData = propsData;}// 更新listenerslisteners = listeners || emptyObject;var oldListeners = vm.$options._parentListeners;vm.$options._parentListeners = listeners;updateComponentListeners(vm, listeners, oldListeners);// 解决插槽和强制更新if (needsForceUpdate) {vm.$slots = resolveSlots(renderChildren, parentVnode.context);vm.$forceUpdate();}{isUpdatingChildComponent = false;}}//判断是否直接激活function isInInactiveTree (vm) {while (vm && (vm = vm.$parent)) {if (vm._inactive) { return true }}return false}// 激活子组件function activateChildComponent (vm, direct) {if (direct) {vm._directInactive = false;//判断是否直接激活if (isInInactiveTree(vm)) {return}} else if (vm._directInactive) {return}if (vm._inactive || vm._inactive === null) {vm._inactive = false;// 循环激活 vm.$childrenfor (var i = 0; i < vm.$children.length; i++) {activateChildComponent(vm.$children[i]);}// 调用 callHook(vm, 'activated')callHook(vm, 'activated');}}// 不激活组件function deactivateChildComponent (vm, direct) {if (direct) {vm._directInactive = true;if (isInInactiveTree(vm)) {return}}// 判断是否是直接不激活if (!vm._inactive) {vm._inactive = true;// 循环不激活 vm.$childrenfor (var i = 0; i < vm.$children.length; i++) {deactivateChildComponent(vm.$children[i]);}// 调用 callHook(vm, "deactivated")callHook(vm, 'deactivated');}}// 先入栈操作,拿到 options.hook,处理错误问题,vm.$emit('hook:' + hook),出栈操作function callHook (vm, hook) {// 在调用生命周期钩子时禁用dep收集pushTarget();var handlers = vm.$options[hook];var info = hook + " hook";if (handlers) {for (var i = 0, j = handlers.length; i < j; i++) {invokeWithErrorHandling(handlers[i], vm, null, vm, info);}}if (vm._hasHookEvent) {vm.$emit('hook:' + hook);}popTarget();}/* */var MAX_UPDATE_COUNT = 100;var queue = [];var activatedChildren = [];var has = {};var circular = {};var waiting = false;var flushing = false;var index = 0;/*** 重置scheduler的状态.*/function resetSchedulerState () {index = queue.length = activatedChildren.length = 0;has = {};{circular = {};}waiting = flushing = false;}// 需要在附加事件监听器时保存时间戳。但是,调用performance.now()会带来性能开销,// 尤其是当页面有数千个事件监听器时。相反,我们在调度程序每次刷新时获取一个时间戳,并将其用于刷新期间附加的所有事件监听器。var currentFlushTimestamp = 0;// 异步边缘情况修复需要存储事件侦听器的附加时间戳var getNow = Date.now;// 确定浏览器正在使用的事件时间戳。恼人的是,时间戳可以是高分辨率(相对于页面加载),// 也可以是低分辨率(相对于UNIX epoch),所以为了比较时间,// 我们必须在保存刷新时间戳时使用相同的时间戳类型。所有IE版本都使用低分辨率的事件时间戳,并且时钟实现存在问题if (inBrowser && !isIE) {var performance = window.performance;if (performance &&typeof performance.now === 'function' &&getNow() > document.createEvent('Event').timeStamp) {// 如果事件时间戳(尽管在Date.now()之后计算)小于它,则意味着事件使用的是高分辨率时间戳,// 我们也需要为事件侦听器的时间戳使用高分辨率版本。getNow = function () { return performance.now(); };}}/*** 清空两个队列并运行监视器。* 据变化最终会把flushSchedulerQueue传入到nextTick中执行* 遍历执行watcher.run()方法,watcher.run()方法最终会完成视图更新*/function flushSchedulerQueue () {currentFlushTimestamp = getNow();flushing = true;var watcher, id;//在刷新之前对队列进行排序。为了确保:// 1。组件从父组件更新到子组件。(因为父母总是//在child之前创建// 2。组件的用户监视器在它的呈现监视器之前运行(因为//用户观察者在渲染观察者之前创建)// 3。如果一个组件在父组件的监视程序运行期间被销毁,//它的观察者可以被跳过。queue.sort(function (a, b) { return a.id - b.id; });//不要缓存长度,因为可能会有更多的监视器被推送//我们运行现有的观察者for (index = 0; index < queue.length; index++) {watcher = queue[index];if (watcher.before) {watcher.before();}id = watcher.id;has[id] = null;watcher.run();// 在开发构建中,检查并停止循环更新。if (has[id] != null) {circular[id] = (circular[id] || 0) + 1;if (circular[id] > MAX_UPDATE_COUNT) {warn('You may have an infinite update loop ' + (watcher.user? ("in watcher with expression \"" + (watcher.expression) + "\""): "in a component render function."),watcher.vm);break}}}// 在重置状态之前保留帖子队列的副本var activatedQueue = activatedChildren.slice();var updatedQueue = queue.slice();resetSchedulerState();// 执行actived钩子函数callActivatedHooks(activatedQueue);//执行updated钩子函数callUpdatedHooks(updatedQueue);// devtool hook/* istanbul ignore if */if (devtools && config.devtools) {devtools.emit('flush');}}// 按索引递减的顺序执行_watcher关联实例的updated钩子function callUpdatedHooks (queue) {var i = queue.length;while (i--) {var watcher = queue[i];var vm = watcher.vm;if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {callHook(vm, 'updated');}}}/***队列一个在补丁期间激活的保持活动的组件。*队列将在整个树被修补后进行处理。*/function queueActivatedComponent (vm) {//将_inactive设置为false,这样渲染函数就可以//依赖于检查它是否在一个非活动的树(例如路由器视图)vm._inactive = false;activatedChildren.push(vm);}function callActivatedHooks (queue) {for (var i = 0; i < queue.length; i++) {queue[i]._inactive = true;activateChildComponent(queue[i], true /* true */);}}/*** 将一个观察者推入观察者队列。具有重复id的作业将被跳过,除非它是在队列刷新时被推入。*/function queueWatcher (watcher) {var id = watcher.id;if (has[id] == null) {has[id] = true;if (!flushing) {queue.push(watcher);} else {// 如果已经刷新,则根据其id拼接监视程序// 如果已经超过了它的id,它将立即运行下一个var i = queue.length - 1;while (i > index && queue[i].id > watcher.id) {i--;}queue.splice(i + 1, 0, watcher);}if (!waiting) {waiting = true;if (!config.async) {// 头尾插入代码来获取耗费时间flushSchedulerQueue();return}nextTick(flushSchedulerQueue);}}}/* */var uid$2 = 0;/*** 观察者解析表达式,收集依赖项,并在表达式值改变时触发回调。这用于$watch() api和指令* Watcher 用于初始化数据的watcher的实列,他的原型上有个update用于派发更新*/var Watcher = function Watcher (vm,expOrFn,cb,options,isRenderWatcher) {this.vm = vm;if (isRenderWatcher) {vm._watcher = this;}vm._watchers.push(this);// optionsif (options) {this.deep = !!options.deep;this.user = !!options.user;this.lazy = !!options.lazy;this.sync = !!options.sync;this.before = options.before;} else {this.deep = this.user = this.lazy = this.sync = false;}this.cb = cb;this.id = ++uid$2; // uid for batchingthis.active = true;this.dirty = this.lazy; // 用于懒惰的观察者this.deps = [];this.newDeps = [];this.depIds = new _Set();this.newDepIds = new _Set();this.expression = expOrFn.toString();// getter的解析表达式if (typeof expOrFn === 'function') {this.getter = expOrFn;} else {this.getter = parsePath(expOrFn);if (!this.getter) {this.getter = noop;warn("Failed watching path: \"" + expOrFn + "\" " +'Watcher only accepts simple dot-delimited paths. ' +'For full control, use a function instead.',vm);}}this.value = this.lazy? undefined: this.get();};/*** 评估getter,并重新收集依赖项。*/Watcher.prototype.get = function get () {//将当前用户watch保存到Dep.target中pushTarget(this);var value;var vm = this.vm;try {//执行用户wathcer的getter()方法,此方法会将当前用户watcher作为订阅者订阅起来value = this.getter.call(vm, vm);} catch (e) {if (this.user) {handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));} else {throw e}} finally {//"touch"每一个属性,所以他们都被跟踪//深度监视的依赖项if (this.deep) {traverse(value);}//恢复之前的watcherpopTarget();this.cleanupDeps();}return value};/***给这个指令添加一个依赖项.添加依赖(重点)*/Watcher.prototype.addDep = function addDep (dep) {var id = dep.id;if (!this.newDepIds.has(id)) {//将依赖的每个dep,添加到 watcher 的 deps集合中,完成数据的收集this.newDepIds.add(id);this.newDeps.push(dep);if (!this.depIds.has(id)) {dep.addSub(this);}}};/*** 清理依赖项集合.*/Watcher.prototype.cleanupDeps = function cleanupDeps () {var i = this.deps.length;while (i--) {var dep = this.deps[i];if (!this.newDepIds.has(dep.id)) {dep.removeSub(this);}}var tmp = this.depIds;this.depIds = this.newDepIds;this.newDepIds = tmp;this.newDepIds.clear();tmp = this.deps;// 把 this.deps = this.newDeps,缓存到 deps 里,然后清空newDeps,来做下一次的收集this.deps = this.newDeps;this.newDeps = tmp;this.newDeps.length = 0;};/*** 更新数据的方法,在派发更新的时候会用到。 computed 更新数据的时候,用 dep 的 notify 方法进 * 行更新数据,更新数据主要调用的是 run 方法*/Watcher.prototype.update = function update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run();} else {queueWatcher(this);}};/*** 在这个阶段主要是运行get方法,拿到数据*/Watcher.prototype.run = function run () {if (this.active) {var value = this.get();if (value !== this.value ||// 深度观察者和对象/数组的观察者应该发射,当值相同时,因为值可能有突变。isObject(value) ||this.deep) {//设置新的值var oldValue = this.value;this.value = value;if (this.user) {var info = "callback for watcher \"" + (this.expression) + "\"";invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info);} else {this.cb.call(this.vm, value, oldValue);}}}};// 用来重新计算,更新缓存值,并重置 dirty 为false,表示缓存已更新Watcher.prototype.evaluate = function evaluate () {this.value = this.get();// 执行完更新函数之后,立即重置标志this.dirty = false;};/*** 深度收集依赖,computed 可以收集 computed 数据就是依靠这个方法*/Watcher.prototype.depend = function depend () {var i = this.deps.length;while (i--) {//注意这里的 depend 方法是 Dep 原型上的方法,不是Watcher 的法this.deps[i].depend();}};/*** 从所有依赖项的订阅者列表中删除self*/Watcher.prototype.teardown = function teardown () {if (this.active) {// 从vm的监视列表中移除self这是一个有点昂贵的操作,所以我们跳过它,如果vm正在被销毁。if (!this.vm._isBeingDestroyed) {remove(this.vm._watchers, this);}var i = this.deps.length;while (i--) {this.deps[i].removeSub(this);}this.active = false;}};//***************************************代理proxy*************************** *//* 初始化 */var sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop};// 定义代理函数,target:当前对象,sourceKey:代理对象的名称,key:要访问的属性function proxy (target, sourceKey, key) {sharedPropertyDefinition.get = function proxyGetter () {//访问vm[message],就返回vm[data][message]return this[sourceKey][key]};//设值vm[message],就变成设值vm[data][message]sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val;};//通过defineProperty对vm[message]进行拦截,并将拦截的set和get方法通过sharedPropertyDefinition传进去Object.defineProperty(target, key, sharedPropertyDefinition);}//初始化实例状态function initState (vm) {vm._watchers = [];var opts = vm.$options;//初始化props和methodsif (opts.props) { initProps(vm, opts.props); }if (opts.methods) { initMethods(vm, opts.methods); }if (opts.data) {//把data属性注入到Vue实例上initData(vm);} else {// 调用observe(data)将data对象转化成响应式的对象observe(vm._data = {}, true /* asRootData */);}//初始化computedif (opts.computed) { initComputed(vm, opts.computed); }if (opts.watch && opts.watch !== nativeWatch) {//初始化watchinitWatch(vm, opts.watch);}}function initProps (vm, propsOptions) {var propsData = vm.$options.propsData || {};var props = vm._props = {};// 缓存props的key,以便将来的道具更新可以使用Array进行迭代,而不是使用动态对象键枚举。var keys = vm.$options._propKeys = [];var isRoot = !vm.$parent;// 根实例道具需要被转换if (!isRoot) {toggleObserving(false);}var loop = function ( key ) {keys.push(key);//用来处理校验规范,设置默认值,取值传递过来得参数值等操作var value = validateProp(key, propsOptions, propsData, vm);/* istanbul ignore else */{var hyphenatedKey = hyphenate(key);if (isReservedAttribute(hyphenatedKey) ||config.isReservedAttr(hyphenatedKey)) {warn(("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),vm);}// 来处理props中参数得响应式defineReactive$$1(props, key, value, function () {if (!isRoot && !isUpdatingChildComponent) {warn("Avoid mutating a prop directly since the value will be " +"overwritten whenever the parent component re-renders. " +"Instead, use a data or computed property based on the prop's " +"value. Prop being mutated: \"" + key + "\"",vm);}});}// 在Vue.extend()期间,静态道具已经被代理到组件的原型上了,只需要在这里实例化时定义的代理道具。if (!(key in vm)) {proxy(vm, "_props", key);}};for (var key in propsOptions) loop( key );toggleObserving(true);}// 运行 observe 函数深度遍历数据中的每一个属性,进行数据劫持function initData (vm) {var data = vm.$options.data;data = vm._data = typeof data === 'function'? getData(data, vm): data || {};if (!isPlainObject(data)) {data = {};warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm);}// proxy data on instancevar keys = Object.keys(data);var props = vm.$options.props;var methods = vm.$options.methods;var i = keys.length;while (i--) {var key = keys[i];{if (methods && hasOwn(methods, key)) {warn(("Method \"" + key + "\" has already been defined as a data property."),vm);}}if (props && hasOwn(props, key)) {warn("The data property \"" + key + "\" is already declared as a prop. " +"Use prop default value instead.",vm);} else if (!isReserved(key)) {proxy(vm, "_data", key);}}// observe dataobserve(data, true /* asRootData */);}function getData (data, vm) {// 在调用数据获取器时禁用dep收集pushTarget();try {return data.call(vm, vm)} catch (e) {handleError(e, vm, "data()");return {}} finally {popTarget();}}var computedWatcherOptions = { lazy: true };// 始化computed的时候(initComputed),会监测数据是否已经存在data或props上,如果存在则抛出警告,// 否则调用defineComputed函数,监听数据,为组件中的属性绑定getter及setter。如果computed中属性的值是一个函数,// 则默认为属性的getter函数。此外属性的值还可以是一个对象,他只有三个有效字段set、get和cache,// 分别表示属性的setter、getter和是否启用缓存,其中get是必须的,cache默认为truefunction initComputed (vm, computed) {var watchers = vm._computedWatchers = Object.create(null);// 计算属性只是SSR时期的gettervar isSSR = isServerRendering();for (var key in computed) {var userDef = computed[key];var getter = typeof userDef === 'function' ? userDef : userDef.get;if (getter == null) {warn(("Getter is missing for computed property \"" + key + "\"."),vm);}if (!isSSR) {// 为computed属性创建内部监视器。watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions);}// 如果定义的计算属性不在组件实例上,对属性进行数据劫持if (!(key in vm)) {defineComputed(vm, key, userDef);} else {// 如果定义的计算属性在data和props有,抛出警告if (key in vm.$data) {warn(("The computed property \"" + key + "\" is already defined in data."), vm);} else if (vm.$options.props && key in vm.$options.props) {warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);} else if (vm.$options.methods && key in vm.$options.methods) {warn(("The computed property \"" + key + "\" is already defined as a method."), vm);}}}}
// 监听数据,为组件中的属性绑定getter及settefunction defineComputed (target,key,userDef) {var shouldCache = !isServerRendering();if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef);sharedPropertyDefinition.set = noop;} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noop;sharedPropertyDefinition.set = userDef.set || noop;}if (sharedPropertyDefinition.set === noop) {sharedPropertyDefinition.set = function () {warn(("Computed property \"" + key + "\" was assigned to but it has no setter."),this);};}Object.defineProperty(target, key, sharedPropertyDefinition);}
// 创建计算属性的getter函数computedGetterfunction createComputedGetter (key) {return function computedGetter () {var watcher = this._computedWatchers && this._computedWatchers[key];if (watcher) {if (watcher.dirty) {//watcher.evaluate中更新watcher的值,并把watcher.dirty设置为false//这样等下次依赖更新的时候才会把watcher.dirty设置为true,然后进行取值的时候才会再次运行这个函数watcher.evaluate();}//依赖追踪if (Dep.target) {watcher.depend();}return watcher.value}}}function createGetterInvoker(fn) {// 没有缓存则直接执行computed的getter函数return function computedGetter () {return fn.call(this, this)}}
// 绑定了所有method的this为vmfunction initMethods (vm, methods) {var props = vm.$options.props;for (var key in methods) {{if (typeof methods[key] !== 'function') {warn("Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +"Did you reference the function correctly?",vm);}if (props && hasOwn(props, key)) {warn(("Method \"" + key + "\" has already been defined as a prop."),vm);}if ((key in vm) && isReserved(key)) {warn("Method \"" + key + "\" conflicts with an existing Vue instance method. " +"Avoid defining component methods that start with _ or $.");}}vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);}}// vm.$watch函数会直接使用Watcher构建观察者对象。watch中属性的值作为watcher.cb存在,在观察者update的时候,在watcher.run函数中执行function initWatch (vm, watch) {//遍历watch,为每一个属性创建侦听器for (var key in watch) {var handler = watch[key];//如果属性值是一个数组,则遍历数组,为属性创建多个侦听器//createWatcher函数中封装了vm.$watch,会在vm.$watch中创建侦听器if (Array.isArray(handler)) {for (var i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i]);}} else {//为属性创建侦听器createWatcher(vm, key, handler);}}}function createWatcher (vm,expOrFn,handler,options) {//如果属性值是一个对象,则取对象的handler属性作为回调if (isPlainObject(handler)) {options = handler;handler = handler.handler;}//如果属性值是一个字符串,则从组件实例上寻找if (typeof handler === 'string') {handler = vm[handler];}//为属性创建侦听器return vm.$watch(expOrFn, handler, options)}function stateMixin (Vue) {// 在使用object . defineproperty时,flow在直接声明定义对象方面有一些问题,所以我们必须在这里过程地构建对象。// 定义了dataDef和propsDef,主要是用来设置响应式d a t a 和 data和data和props的get和set方法var dataDef = {};dataDef.get = function () { return this._data };var propsDef = {};propsDef.get = function () { return this._props };{dataDef.set = function () {warn('Avoid replacing instance root $data. ' +'Use nested data properties instead.',this);};propsDef.set = function () {warn("$props is readonly.", this);};}Object.defineProperty(Vue.prototype, '$data', dataDef);Object.defineProperty(Vue.prototype, '$props', propsDef);Vue.prototype.$set = set;Vue.prototype.$delete = del;Vue.prototype.$watch = function (expOrFn,cb,options) {var vm = this;if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {};options.user = true;options.user = true;//为需要观察的 expOrFn 添加watcher ,expOrFn的值有改变时执行cb,//在watcher的实例化的过程中会对expOrFn进行解析,并为expOrFn涉及到的data数据下的def添加该watchervar watcher = new Watcher(vm, expOrFn, cb, options);if (options.immediate) {var info = "callback for immediate watcher \"" + (watcher.expression) + "\"";pushTarget();invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);popTarget();}//取消观察函数return function unwatchFn () {watcher.teardown();}};}/* */var uid$3 = 0;function initMixin (Vue) {Vue.prototype._init = function (options) {var vm = this;// a uidvm._uid = uid$3++;var startTag, endTag;/* istanbul ignore if */if (config.performance && mark) {startTag = "vue-perf-start:" + (vm._uid);endTag = "vue-perf-end:" + (vm._uid);mark(startTag);}// 如果是Vue的实例,则不需要被observevm._isVue = true;// options参数的处理if (options && options._isComponent) {// optimize internal component instantiation// since dynamic options merging is pretty slow, and none of the// internal component options needs special treatment.initInternalComponent(vm, options);} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm);}/* istanbul ignore else */{initProxy(vm);}// expose real selfvm._self = vm;// vm的生命周期相关变量初始化initLifecycle(vm);// vm的事件监听初始化initEvents(vm);// vm的编译render初始化initRender(vm);// vm的beforeCreate生命钩子的回调callHook(vm, 'beforeCreate');// vm在data/props初始化之前要进行绑定initInjections(vm); // resolve injections before data/props// vm的sate状态初始化initState(vm);// vm在data/props之后要进行提供initProvide(vm); // resolve provide after data/props// vm的created生命钩子的回调callHook(vm, 'created');/* istanbul ignore if */if (config.performance && mark) {vm._name = formatComponentName(vm, false);mark(endTag);measure(("vue " + (vm._name) + " init"), startTag, endTag);}if (vm.$options.el) {vm.$mount(vm.$options.el);}};}// Vue初始化中的选项合并// 通过循环递归,找到Component这个类的继承链,然后把所有的配置都进行融合function initInternalComponent (vm, options) {var opts = vm.$options = Object.create(vm.constructor.options);// 该组件实例的vnode对象var parentVnode = options._parentVnode;opts.parent = options.parent;opts._parentVnode = parentVnode;var vnodeComponentOptions = parentVnode.componentOptions;opts.propsData = vnodeComponentOptions.propsData;opts._parentListeners = vnodeComponentOptions.listeners;opts._renderChildren = vnodeComponentOptions.children;opts._componentTag = vnodeComponentOptions.tag;if (options.render) {opts.render = options.render;opts.staticRenderFns = options.staticRenderFns;}}function resolveConstructorOptions (Ctor) {var options = Ctor.options;// 首先需要判断该类是否是Vue的子类if (Ctor.super) {var superOptions = resolveConstructorOptions(Ctor.super);var cachedSuperOptions = Ctor.superOptions;// 来判断父类中的options 有没有发生变化if (superOptions !== cachedSuperOptions) {// 超级选项改变,需要解决新的选项Ctor.superOptions = superOptions;// 检查是否有任何后期修改/附加的选项var modifiedOptions = resolveModifiedOptions(Ctor);// update base extend optionsif (modifiedOptions) {// 当为Vue混入一些options时,superOptions会发生变化,此时于之前子类中存储的cachedSuperOptions已经不相等,所以下面的操作主要就是更新sub.superOptionsextend(Ctor.extendOptions, modifiedOptions);}options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);if (options.name) {options.components[options.name] = Ctor;}}}return options}function resolveModifiedOptions (Ctor) {var modified;var latest = Ctor.options;// 执行Vue.extend时封装的"自身"options,这个属性就是方便检查"自身"的options有没有变化var sealed = Ctor.sealedOptions;// 遍历当前构造器上的options属性,如果在"自身"封装的options里没有,则证明是新添加的。执行if内的语句。// 调用dedupe方法,最终返回modified变量(即”自身新添加的options“)for (const key in latest) {for (var key in latest) {if (latest[key] !== sealed[key]) {if (!modified) { modified = {}; }modified[key] = latest[key];}}return modified}
}function Vue (options) {if (!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword');}this._init(options);}initMixin(Vue);// 初始化 MixinstateMixin(Vue);// 状态 MixineventsMixin(Vue);// 事件 MixinlifecycleMixin(Vue);// 生命周期 MixinrenderMixin(Vue);// 渲染 Mixin/* Vue.use() 的注册本质上就是执行了一个 install 方法 */function initUse (Vue) {// 在全局api Vue 上定义了 use 方法,接收一个 plugin 参数Vue.use = function (plugin) {var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));if (installedPlugins.indexOf(plugin) > -1) {return this}// 用来判断该插件是不是已经注册过,防止重复注册var args = toArray(arguments, 1);args.unshift(this);// 判断 Vue.use() 传入的第一个参数是 Object 还是 Functionif (typeof plugin.install === 'function') {plugin.install.apply(plugin, args);} else if (typeof plugin === 'function') {plugin.apply(null, args);}installedPlugins.push(plugin);return this};}/* 将options和mixin合并 */function initMixin$1 (Vue) {Vue.mixin = function (mixin) {this.options = mergeOptions(this.options, mixin);return this};}/*** Vue.extend的原理以及初始化过程*/function initExtend (Vue) {// 每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够为原型继承创建包装子构造函数并缓存它们。Vue.cid = 0;var cid = 1;Vue.extend = function (extendOptions) {// extendOptions就是我我们传入的组件optionsextendOptions = extendOptions || {};var Super = this;var SuperId = Super.cid;// 每次创建完Sub构造函数后,都会把这个函数储存在extendOptions上的_Ctor中// 下次如果用再同一个extendOptions创建Sub时// 就会直接从_Ctor返回var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});if (cachedCtors[SuperId]) {return cachedCtors[SuperId]}//校验组件 名var name = extendOptions.name || Super.options.name;if (name) {validateComponentName(name);}// 创建Sub构造函数var Sub = function VueComponent (options) {this._init(options);};// 继承Super,如果使用Vue.extend,这里的Super就是VueSub.prototype = Object.create(Super.prototype);Sub.prototype.constructor = Sub;Sub.cid = cid++;Sub.options = mergeOptions(Super.options,extendOptions);// 将组件的options和Vue的options合并,得到一个完整的options// 可以理解为将Vue的一些全局的属性,比如全局注册的组件和mixin,分给了SubSub['super'] = Super;// 将props和computed代理到了原型上if (Sub.options.props) {initProps$1(Sub);}if (Sub.options.computed) {initComputed$1(Sub);}// 继承Vue的global-apiSub.extend = Super.extend;Sub.mixin = Super.mixin;Sub.use = Super.use;// 继承assets的api,比如注册组件,指令,过滤器ASSET_TYPES.forEach(function (type) {Sub[type] = Super[type];});// 在components里添加一个自己if (name) {Sub.options.components[name] = Sub;}// 这些options保存起来Sub.superOptions = Super.options;Sub.extendOptions = extendOptions;Sub.sealedOptions = extend({}, Sub.options);// 设置缓存cachedCtors[SuperId] = Sub;return Sub};}//初始化自定义代理函数function initProps$1 (Comp) {var props = Comp.options.props;for (var key in props) {proxy(Comp.prototype, "_props", key);}}//为组件的属性绑定setter和getter属性function initComputed$1 (Comp) {var computed = Comp.options.computed;for (var key in computed) {defineComputed(Comp.prototype, key, computed[key]);}}/* 创建组件、指令、过滤器 */function initAssetRegisters (Vue) {//遍历 ASSET_TYPES 数组,为Vue 定义相应的方法// ASSET_TYPES 包含 directive component filterASSET_TYPES.forEach(function (type) {Vue[type] = function (id,definition) {// 判断是否有第二个参数,没有的话去之前的option的组件或者指令if (!definition) {return this.options[type + 's'][id]} else {//判断是否是一个组件,校验组件名字if (type === 'component') {validateComponentName(id);}// 判断传入的是不是一个原始对象if (type === 'component' && isPlainObject(definition)) {definition.name = definition.name || id;// 把组件配置转换为组件的构造函数definition = this.options._base.extend(definition);}if (type === 'directive' && typeof definition === 'function') {definition = { bind: definition, update: definition };}// 全局注册,存储资源赋值this.options[type + 's'][id] = definition;return definition}};});}
//获取组件的名字function getComponentName (opts) {return opts && (opts.Ctor.options.name || opts.tag)}// 检测name是否匹配function matches (pattern, name) {if (Array.isArray(pattern)) {return pattern.indexOf(name) > -1} else if (typeof pattern === 'string') {return pattern.split(',').indexOf(name) > -1} else if (isRegExp(pattern)) {return pattern.test(name)}/* istanbul ignore next */return false}function pruneCache (keepAliveInstance, filter) {//获取keepAliveInstance的缓存var cache = keepAliveInstance.cache;var keys = keepAliveInstance.keys;var _vnode = keepAliveInstance._vnode;for (var key in cache) {var entry = cache[key];if (entry) {var name = entry.name;// name不符合filter条件的,同时不是目前渲染的vnode时,销毁vnode对应的组件实例(Vue实例),并从cache中移除if (name && !filter(name)) {//pruneCache 函数的核心就是去调用pruneCacheEntrypruneCacheEntry(cache, key, keys, _vnode);}}}}function pruneCacheEntry (cache,key,keys,current) {// 通过cached$$1 = cache[key]` 获取头部数据对应的值 `vnode`,执行 `cached$$1.componentInstance.$destroy() 将组件实例销毁var entry = cache[key];if (entry && (!current || entry.tag !== current.tag)) {// 销毁vnode对应的组件实例entry.componentInstance.$destroy();}// 清空组件对应的缓存节点cache[key] = null;// 删除缓存中的头部数据 keys[0]remove(keys, key);}var patternTypes = [String, RegExp, Array];var KeepAlive = {name: 'keep-alive',// 抽象组件,判断当前组件虚拟dom是否渲染成真实dom的关键abstract: true,// 定义include、exclude及max属性// include - 字符串或正则表达式。只有名称匹配的组件会被缓存。// exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。// max - 数字。最多可以缓存多少组件实例。props: {include: patternTypes,exclude: patternTypes,max: [String, Number]},methods: {cacheVNode: function cacheVNode() {var ref = this;var cache = ref.cache;var keys = ref.keys;var vnodeToCache = ref.vnodeToCache;var keyToCache = ref.keyToCache;if (vnodeToCache) {var tag = vnodeToCache.tag;var componentInstance = vnodeToCache.componentInstance;var componentOptions = vnodeToCache.componentOptions;cache[keyToCache] = {name: getComponentName(componentOptions),tag: tag,componentInstance: componentInstance,};keys.push(keyToCache);// prune oldest entryif (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode);}this.vnodeToCache = null;}}},//在 keep-alive 的创建阶段, created钩子会创建一个cache对象,用来保存vnode节点created: function created () {this.cache = Object.create(null);this.keys = [];},// 在销毁阶段,destroyed 钩子则会调用pruneCacheEntry方法清除cache缓存中的所有组件实例destroyed: function destroyed () {for (var key in this.cache) {// 销毁vnode对应的组件实例pruneCacheEntry(this.cache, key, this.keys);}},mounted: function mounted () {var this$1 = this;this.cacheVNode();// 通过 watch 来监听 include 和 exclude,在其改变时调用 pruneCache 以修改 cache 缓存中的缓存数据this.$watch('include', function (val) {pruneCache(this$1, function (name) { return matches(val, name); });});this.$watch('exclude', function (val) {pruneCache(this$1, function (name) { return !matches(val, name); });});},updated: function updated () {this.cacheVNode();},// 渲染阶段render: function render () {var slot = this.$slots.default;// 得到slot插槽中的第一个组件var vnode = getFirstComponentChild(slot);var componentOptions = vnode && vnode.componentOptions;if (componentOptions) {//获取组件名称,优先获取组件的name字段,否则是组件的tagvar name = getComponentName(componentOptions);var ref = this;var include = ref.include;var exclude = ref.exclude;if (// 不需要缓存,则返回 vnode(include && (!name || !matches(include, name))) ||// excluded(exclude && name && matches(exclude, name))) {return vnode}var ref$1 = this;var cache = ref$1.cache;var keys = ref$1.keys;var key = vnode.key == null// 同一个构造函数可能会被注册为不同的本地组件,所以单独使用cid是不够的? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : ''): vnode.key;if (cache[key]) {// 有缓存则取缓存的组件实例vnode.componentInstance = cache[key].componentInstance;// make current key freshestremove(keys, key);keys.push(key);} else {// 无缓存则创建缓存this.vnodeToCache = vnode;this.keyToCache = key;}// keepAlive标记vnode.data.keepAlive = true;}return vnode || (slot && slot[0])}};var builtInComponents = {KeepAlive: KeepAlive};// 初始化全局APIfunction initGlobalAPI (Vue) {// configvar configDef = {};// 给configDef添加了一个get属性,这个属性返回得是一个config对象,这个cofig对象里面,有n个属性configDef.get = function () { return config; };{// 给configDef添加了一个set属性,返回的一个警告configDef.set = function () {warn('Do not replace the Vue.config object, set individual fields instead.');};}// 为Vue的构造函数,添加一个要通过Object.defineProperty监听的属性configObject.defineProperty(Vue, 'config', configDef);// 公开util// 设置了一个公开的util对象,但是它不是公共的api,避免依赖Vue.util = {warn: warn,extend: extend,mergeOptions: mergeOptions,defineReactive: defineReactive$$1};// 绑定全局API——Vue.set,Vue.delete,Vue.nextTickVue.set = set;Vue.delete = del;Vue.nextTick = nextTick;// 2.6 explicit observable APIVue.observable = function (obj) {observe(obj);return obj};Vue.options = Object.create(null);ASSET_TYPES.forEach(function (type) {Vue.options[type + 's'] = Object.create(null);});// 这用于标识“基”构造函数,以便在Weex的多实例场景中扩展所有普通对象组件Vue.options._base = Vue;extend(Vue.options.components, builtInComponents);// 初始化Vue.extend,Vue.mixin,Vue.extend// AssetRegisters就是component,directive,filter三者initUse(Vue);initMixin$1(Vue);initExtend(Vue);initAssetRegisters(Vue);}initGlobalAPI(Vue);// vue.prototype上挂载$isServer、$ssrContext、FunctionalRenderContextObject.defineProperty(Vue.prototype, '$isServer', {get: isServerRendering});Object.defineProperty(Vue.prototype, '$ssrContext', {get: function get () {/* istanbul ignore next */return this.$vnode && this.$vnode.ssrContext}});//为ssr运行时助手安装暴露FunctionalRenderContextObject.defineProperty(Vue, 'FunctionalRenderContext', {value: FunctionalRenderContext});// 在vue 上挂载 version 属性Vue.version = '2.6.14';//检验是否存在'style','class'字符串var isReservedAttr = makeMap('style,class');// 应该使用props进行绑定的属性//检验是否存在'input','textarea','option','select','progress'字符串var acceptValue = makeMap('input,textarea,option,select,progress');//校验元素值是否通过prop指定var mustUseProp = function (tag, type, attr) {return ((attr === 'value' && acceptValue(tag)) && type !== 'button' ||(attr === 'selected' && tag === 'option') ||(attr === 'checked' && tag === 'input') ||(attr === 'muted' && tag === 'video'))};
//检验是否存在'contenteditable','draggable','spellcheck'字符串var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck');
//检验是否存在'events','caret','typing','plaintext-only'字符串var isValidContentEditableValue = makeMap('events,caret,typing,plaintext-only');var convertEnumeratedValue = function (key, value) {return isFalsyAttrValue(value) || value === 'false'? 'false'// 允许content - itable的任意字符串值: key === 'contenteditable' && isValidContentEditableValue(value)? value: 'true'};
//校验是否包含以下字符串var isBooleanAttr = makeMap('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +'required,reversed,scoped,seamless,selected,sortable,' +'truespeed,typemustmatch,visible');var xlinkNS = 'http://www.w3.org/1999/xlink';var isXlink = function (name) {return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'};var getXlinkProp = function (name) {return isXlink(name) ? name.slice(6, name.length) : ''};// 通过isFalsyAttrValue方法判断值是否是false与null,如果isFalsyAttrValue返回为true则表示传入的值var isFalsyAttrValue = function (val) {return val == null || val === false};/* class 转码获取vonde 中的staticClass 静态class 和class动态class转义成真实dom需要的class格式。然后返回class字符串 */function genClassForVnode (vnode) {var data = vnode.data;var parentNode = vnode;var childNode = vnode;while (isDef(childNode.componentInstance)) {childNode = childNode.componentInstance._vnode;if (childNode && childNode.data) {data = mergeClassData(childNode.data, data);}}while (isDef(parentNode = parentNode.parent)) {if (parentNode && parentNode.data) {data = mergeClassData(data, parentNode.data);}}//return renderClass(data.staticClass, data.class)}function mergeClassData (child, parent) {return {staticClass: concat(child.staticClass, parent.staticClass),class: isDef(child.class)? [child.class, parent.class]: parent.class}}// 渲染calss 这里获取到已经转码的calssfunction renderClass (staticClass,dynamicClass) {if (isDef(staticClass) || isDef(dynamicClass)) {// 转码 class,把数组格式,对象格式的calss 全部转化成 字符串格式return concat(staticClass, stringifyClass(dynamicClass))}/* istanbul ignore next */return ''}function concat (a, b) {return a ? b ? (a + ' ' + b) : a : (b || '')}// 转码 class,把数组格式,对象格式的calss 全部转化成 字符串格式function stringifyClass (value) {if (Array.isArray(value)) {// 数组字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串return stringifyArray(value)}if (isObject(value)) {// 对象字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串return stringifyObject(value)}if (typeof value === 'string') {return value}/* istanbul ignore next */return ''}// 数组字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串function stringifyArray (value) {var res = '';var stringified;for (var i = 0, l = value.length; i < l; i++) {if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {if (res) { res += ' '; }res += stringified;}}return res}
// 对象字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串function stringifyObject (value) {var res = '';for (var key in value) {if (value[key]) {if (res) { res += ' '; }res += key;}}return res}/* */var namespaceMap = {svg: 'http://www.w3.org/2000/svg',math: 'http://www.w3.org/1998/Math/MathML'};
//判断html标签var isHTMLTag = makeMap('html,body,base,head,link,meta,style,title,' +'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +'s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +'embed,object,param,source,canvas,script,noscript,del,ins,' +'caption,col,colgroup,table,thead,tbody,td,th,tr,' +'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +'output,progress,select,textarea,' +'details,dialog,menu,menuitem,summary,' +'content,element,shadow,template,blockquote,iframe,tfoot');//判断svg 标签var isSVG = makeMap('svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +'foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',true);// 判断tag是不是prevar isPreTag = function (tag) { return tag === 'pre'; };// 判断tag是不是html标签或者svg标签var isReservedTag = function (tag) {return isHTMLTag(tag) || isSVG(tag)};function getTagNamespace (tag) {if (isSVG(tag)) {return 'svg'}// 对MathML的基本支持注意,它不支持其他MathML元素作为组件根if (tag === 'math') {return 'math'}}var unknownElementCache = Object.create(null);// 检查dom 节点的tag标签 类型 是否是VPre 标签 或者是判断是否是浏览器自带原有的标签function isUnknownElement (tag) {/* istanbul ignore if */if (!inBrowser) {return true}// 判断tag是不是html标签或者svg标签if (isReservedTag(tag)) {return false}tag = tag.toLowerCase();/* istanbul ignore if */if (unknownElementCache[tag] != null) {return unknownElementCache[tag]}var el = document.createElement(tag);if (tag.indexOf('-') > -1) {// http://stackoverflow.com/a/28210364/1070244return (unknownElementCache[tag] = (el.constructor === window.HTMLUnknownElement ||el.constructor === window.HTMLElement))} else {return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))}}var isTextInputType = makeMap('text,number,password,search,email,tel,url');/* *//*** 查询一个元素选择器,如果他不是元素选择器则创建div*/function query (el) {if (typeof el === 'string') {var selected = document.querySelector(el);if (!selected) {warn('Cannot find element: ' + el);return document.createElement('div')}return selected} else {return el}}//创建一个真实的domfunction createElement$1 (tagName, vnode) {var elm = document.createElement(tagName);if (tagName !== 'select') {return elm}// False或null将删除该属性,但undefined不会if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {elm.setAttribute('multiple', 'multiple');}return elm}// 创建由tagName 指定的HTML元素function createElementNS (namespace, tagName) {return document.createElementNS(namespaceMap[namespace], tagName)}// 创建文本节点function createTextNode (text) {return document.createTextNode(text)}// 创建一个注释节点function createComment (text) {return document.createComment(text)}// 插入节点 在某个元素前面插入一个节点function insertBefore (parentNode, newNode, referenceNode) {parentNode.insertBefore(newNode, referenceNode);}// 删除子节点function removeChild (node, child) {node.removeChild(child);}//添加子节点 尾部function appendChild (node, child) {node.appendChild(child);}// 获取父亲子节点domfunction parentNode (node) {return node.parentNode}//获取下一个兄弟节点function nextSibling (node) {return node.nextSibling}//获取dom标签名称function tagName (node) {return node.tagName}//设置dom 文本function setTextContent (node, text) {node.textContent = text;}//设置组建样式的作用域function setStyleScope (node, scopeId) {node.setAttribute(scopeId, '');}// Object.freeze冻结对象var nodeOps = /*#__PURE__*/Object.freeze({createElement: createElement$1,createElementNS: createElementNS,createTextNode: createTextNode,createComment: createComment,insertBefore: insertBefore,removeChild: removeChild,appendChild: appendChild,parentNode: parentNode,nextSibling: nextSibling,tagName: tagName,setTextContent: setTextContent,setStyleScope: setStyleScope});/* ref是给元素或者子组件注册引用信息的 */var ref = {create: function create (_, vnode) {registerRef(vnode);},update: function update (oldVnode, vnode) {if (oldVnode.data.ref !== vnode.data.ref) {registerRef(oldVnode, true);registerRef(vnode);}},destroy: function destroy (vnode) {registerRef(vnode, true);}};// ref模块初始化时会执行registerRef函数function registerRef (vnode, isRemoval) {var key = vnode.data.ref;//如果没有定义ref属性,则直接返回if (!isDef(key)) { return }//当前的根Vue实例var vm = vnode.context;//优先获取vonde的组件实例(对于组件来说),或者el(该Vnode对应的DOM节点,非组件来说)var ref = vnode.componentInstance || vnode.elm;var refs = vm.$refs;if (isRemoval) {if (Array.isArray(refs[key])) {remove(refs[key], ref);} else if (refs[key] === ref) {refs[key] = undefined;}} else {//如果不是移除,当在v-for之内时,则保存为数组形式if (vnode.data.refInFor) {if (!Array.isArray(refs[key])) {refs[key] = [ref];} else if (refs[key].indexOf(ref) < 0) {// $flow-disable-linerefs[key].push(ref);}//不是在v-for之内时,直接保存到refs对应的key属性上} else {refs[key] = ref;}}}/*** Virtual DOM patching algorithm based on Snabbdom by* Simon Friis Vindum (@paldepind)* Licensed under the MIT License* https://github.com/paldepind/snabbdom/blob/master/LICENSE** modified by Evan You (@yyx990803)** Not type-checking this because this file is perf-critical and the cost* of making flow understand it is not worth it.*/var emptyNode = new VNode('', {}, []);var hooks = ['create', 'activate', 'update', 'remove', 'destroy'];// 判断当前VNode可复用,销毁一个DOM节点并创建一个新的再插入是消耗非常大的,// 无论是DOM对象本身的复杂性还是操作引起的重绘重排,所以虚拟DOM的目标是尽可能复用现有DOM进行更新function sameVnode (a, b) {return (a.key === b.key &&a.asyncFactory === b.asyncFactory && ((a.tag === b.tag &&a.isComment === b.isComment &&// 这个涉及属性的更新,如果一个节点没有任何属性,即data为undefined,与一个有data属性的节点进行更新不如直接渲染一个新的isDef(a.data) === isDef(b.data) &&// 这个主要是input标签type属性异同判断,不同的type相当于不同的tagsameInputType(a, b)) || (isTrue(a.isAsyncPlaceholder) &&isUndef(b.asyncFactory.error))))}//这个主要是input标签type属性异同判断,不同的type相当于不同的tagfunction sameInputType (a, b) {if (a.tag !== 'input') { return true }var i;var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)}// 接收一个 children 数组,生成 key 与 index 索引对应的一个 map 表function createKeyToOldIdx (children, beginIdx, endIdx) {var i, key;var map = {};for (i = beginIdx; i <= endIdx; ++i) {key = children[i].key;if (isDef(key)) { map[key] = i; }}return map}// patch核心函数// backend的nodeOps是节点的功能函数,包括createElement创建元素、removeChild删除子元素,// tagName获取到标签名等,backend的modules是vue框架用于分别执行某个渲染任务的功能函数function createPatchFunction (backend) {var i, j;var cbs = {};var modules = backend.modules;var nodeOps = backend.nodeOps;// 循环hooks和modulesfor (i = 0; i < hooks.length; ++i) {cbs[hooks[i]] = [];for (j = 0; j < modules.length; ++j) {if (isDef(modules[j][hooks[i]])) {cbs[hooks[i]].push(modules[j][hooks[i]]);}}}// 将原有的节点,同时也是DOM节点包装成虚拟节点function emptyNodeAt (elm) {return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)}// 创建remove函数// remove$$1函数作为一个对象,第一个参数是vnode所属的dom元素,第二个参数是监听器个数。// 内部实现remove函数拥有listeners属性,等到这个属性的值每一次减少直到0时将直接移除节点function createRmCb (childElm, listeners) {function remove$$1 () {if (--remove$$1.listeners === 0) {removeNode(childElm);}}remove$$1.listeners = listeners;return remove$$1}// 移除节点,先找到父节点,然后通过removeChild移除掉这个节点function removeNode (el) {var parent = nodeOps.parentNode(el);// 元素可能已经由于v-html / v-text而被删除if (isDef(parent)) {nodeOps.removeChild(parent, el);}}function isUnknownElement$$1 (vnode, inVPre) {return (!inVPre &&!vnode.ns &&!(config.ignoredElements.length &&config.ignoredElements.some(function (ignore) {return isRegExp(ignore)? ignore.test(vnode.tag): ignore === vnode.tag})) &&// 检测是否未知elconfig.isUnknownElement(vnode.tag))}var creatingElmInVPre = 0;// 创建新节点function createElm (vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index) {if (isDef(vnode.elm) && isDef(ownerArray)) {//这个vnode在以前的渲染中使用过,现在它被用作一个新节点,覆盖它的榆树将导致//当它被用作插入时,潜在的补丁错误,引用节点。相反,我们在创建节点之前按需克隆节点对应的DOM元素。vnode = ownerArray[index] = cloneVNode(vnode);}vnode.isRootInsert = !nested; //过渡进入检查if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {return}var data = vnode.data;var children = vnode.children;var tag = vnode.tag;if (isDef(tag)) {{if (data && data.pre) {creatingElmInVPre++;}if (isUnknownElement$$1(vnode, creatingElmInVPre)) {warn('Unknown custom element: <' + tag + '> - did you ' +'register the component correctly? For recursive components, ' +'make sure to provide the "name" option.',vnode.context);}}vnode.elm = vnode.ns? nodeOps.createElementNS(vnode.ns, tag): nodeOps.createElement(tag, vnode);setScope(vnode);/* istanbul ignore if */{createChildren(vnode, children, insertedVnodeQueue);if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue);}insert(parentElm, vnode.elm, refElm);}if (data && data.pre) {creatingElmInVPre--;}} else if (isTrue(vnode.isComment)) {vnode.elm = nodeOps.createComment(vnode.text);insert(parentElm, vnode.elm, refElm);} else {vnode.elm = nodeOps.createTextNode(vnode.text);insert(parentElm, vnode.elm, refElm);}}function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {var i = vnode.data;if (isDef(i)) {var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;if (isDef(i = i.hook) && isDef(i = i.init)) {i(vnode, false /* hydrating */);}// 调用init钩子后,如果vnode是子组件它应该创建一个子实例并挂载它。这个子组件也设置了占位符vnode的榆树。//在这种情况下,我们只需要返回元素就可以了。if (isDef(vnode.componentInstance)) {initComponent(vnode, insertedVnodeQueue);insert(parentElm, vnode.elm, refElm);if (isTrue(isReactivated)) {reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);}return true}}}function initComponent (vnode, insertedVnodeQueue) {if (isDef(vnode.data.pendingInsert)) {insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);vnode.data.pendingInsert = null;}vnode.elm = vnode.componentInstance.$el;if (isPatchable(vnode)) {// div#app的创建时会调用invokeCreateHooksinvokeCreateHooks(vnode, insertedVnodeQueue);setScope(vnode);} else {// 根空的组件。跳过所有元素相关的模块,除了refregisterRef(vnode);// 确保调用了插入钩子insertedVnodeQueue.push(vnode);}}function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {var i;// 重新激活的内部转换组件不触发,因为内部节点创建的钩子没有被调用一次。var innerNode = vnode;while (innerNode.componentInstance) {innerNode = innerNode.componentInstance._vnode;if (isDef(i = innerNode.data) && isDef(i = i.transition)) {for (i = 0; i < cbs.activate.length; ++i) {cbs.activate[i](emptyNode, innerNode);}insertedVnodeQueue.push(innerNode);break}}// 与新创建的组件不同,重新激活的keep-alive组件不插入自己insert(parentElm, vnode.elm, refElm);}// 通过insertBefore或者appendChild添加元素function insert (parent, elm, ref$$1) {if (isDef(parent)) {if (isDef(ref$$1)) {if (nodeOps.parentNode(ref$$1) === parent) {nodeOps.insertBefore(parent, elm, ref$$1);}} else {nodeOps.appendChild(parent, elm);}}}
史上最全的vue.js源码解析(四)相关推荐
- 【Vue.js源码解析 一】-- 响应式原理
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...
- js define函数_不夸张,这真的是前端圈宝藏书!360前端工程师Vue.js源码解析
优秀源代码背后的思想是永恒的.普适的. 这些年来,前端行业一直在飞速发展.行业的进步,导致对从业人员的要求不断攀升.放眼未来,虽然仅仅会用某些框架还可以找到工作,但仅仅满足于会用,一定无法走得更远.随 ...
- 【Vue.js源码解析 三】-- 模板编译和组件化
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 模板编译 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) <div> ...
- 【Vue.js源码解析 二】-- 虚拟 DOM
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 虚拟 DOM 基本介绍 什么是虚拟 DOM 虚拟 DOM(Virtual DOM) 是使用 JavaScript 对象 ...
- 史上最全基于vue的图片裁剪vue-cropper使用
史上最全基于vue的图片裁剪vue-cropper使用 基于vue的图片裁剪vue-cropper 新的需求 vue-cropper官网 代码拷贝 最后 基于vue的图片裁剪vue-cropper 最 ...
- 从template到DOM(Vue.js源码角度看内部运行机制)
写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(github.com/answershuto-)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些产出也会对同样想要学习Vue.j ...
- Vue.js 框架源码与进阶 - Vue.js 源码剖析 - 响应式原理
文章目录 一.准备工作 1.1 Vue 源码的获取 1.2 源目录结构 1.3 了解 Flow 1.4 调试设置 1.5 Vue 的不同构建版本 1.6 寻找入口文件 1.7 从入口开始 二.Vue ...
- Vue.js 源码目录设计
Vue.js 源码目录设计 Vue.js 的源码都在 src 目录下,其目录结构如下. src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # ...
- vue源码解析(3)—— Vue.js 源码构建
Vue.js 源码构建 Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json ...
最新文章
- c语言断链隐藏dll,通过断链隐藏模块(DLL)
- iPhone销量低迷,或导致苹果放弃自动驾驶项目?
- MSSQL 漏洞利用与提权
- 可以获取python整数类型帮助的是什么-下列选项中可以获取Python整数类型帮助的是...
- centos关闭php服务,linux(centos)防火墙的开启与关闭的方法
- 【Flink】Buffer pool is destroyed
- 开始学习C#.Net
- 模块是python中普通的文件吗_python 包和模块
- 大数据,原来可以这么“玩”
- JAVA-Switch语句
- 最全最新cpu显卡天梯图_电脑显卡天梯图2019排行榜——2019显卡CPU天梯图排行榜...
- Android 平台 Native 代码的崩溃捕获机制及实现
- 关于unity easy touch 再次进入场景后报错问题
- 线性代数学习笔记(一):线性空间的理解
- 构建 Web 应用之 Service Worker 初探
- HTML给汉字头部添加拼音的标签
- 分享一个超好用的项目进度模板(包括计划进度与实际进度对比甘特图)
- python爬取今日头条瀑布流_火车头采集今日头条教程,含视频教程!自行下载
- Python 闭包 Closures
- doxygen 命令_Doxygen