虽然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源码解析(四)相关推荐

  1. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  2. js define函数_不夸张,这真的是前端圈宝藏书!360前端工程师Vue.js源码解析

    优秀源代码背后的思想是永恒的.普适的. 这些年来,前端行业一直在飞速发展.行业的进步,导致对从业人员的要求不断攀升.放眼未来,虽然仅仅会用某些框架还可以找到工作,但仅仅满足于会用,一定无法走得更远.随 ...

  3. 【Vue.js源码解析 三】-- 模板编译和组件化

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 模板编译 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) <div> ...

  4. 【Vue.js源码解析 二】-- 虚拟 DOM

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 虚拟 DOM 基本介绍 什么是虚拟 DOM 虚拟 DOM(Virtual DOM) 是使用 JavaScript 对象 ...

  5. 史上最全基于vue的图片裁剪vue-cropper使用

    史上最全基于vue的图片裁剪vue-cropper使用 基于vue的图片裁剪vue-cropper 新的需求 vue-cropper官网 代码拷贝 最后 基于vue的图片裁剪vue-cropper 最 ...

  6. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(github.com/answershuto-)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些产出也会对同样想要学习Vue.j ...

  7. Vue.js 框架源码与进阶 - Vue.js 源码剖析 - 响应式原理

    文章目录 一.准备工作 1.1 Vue 源码的获取 1.2 源目录结构 1.3 了解 Flow 1.4 调试设置 1.5 Vue 的不同构建版本 1.6 寻找入口文件 1.7 从入口开始 二.Vue ...

  8. Vue.js 源码目录设计

    Vue.js 源码目录设计 Vue.js 的源码都在 src 目录下,其目录结构如下. src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # ...

  9. vue源码解析(3)—— Vue.js 源码构建

    Vue.js 源码构建 Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json ...

最新文章

  1. c语言断链隐藏dll,通过断链隐藏模块(DLL)
  2. iPhone销量低迷,或导致苹果放弃自动驾驶项目?
  3. MSSQL 漏洞利用与提权
  4. 可以获取python整数类型帮助的是什么-下列选项中可以获取Python整数类型帮助的是...
  5. centos关闭php服务,linux(centos)防火墙的开启与关闭的方法
  6. 【Flink】Buffer pool is destroyed
  7. 开始学习C#.Net
  8. 模块是python中普通的文件吗_python 包和模块
  9. 大数据,原来可以这么“玩”
  10. JAVA-Switch语句
  11. 最全最新cpu显卡天梯图_电脑显卡天梯图2019排行榜——2019显卡CPU天梯图排行榜...
  12. Android 平台 Native 代码的崩溃捕获机制及实现
  13. 关于unity easy touch 再次进入场景后报错问题
  14. 线性代数学习笔记(一):线性空间的理解
  15. 构建 Web 应用之 Service Worker 初探
  16. HTML给汉字头部添加拼音的标签
  17. 分享一个超好用的项目进度模板(包括计划进度与实际进度对比甘特图)
  18. python爬取今日头条瀑布流_火车头采集今日头条教程,含视频教程!自行下载
  19. Python 闭包 Closures
  20. doxygen 命令_Doxygen

热门文章

  1. codeves天梯 合唱队形
  2. MPE环境安装-强化学习的小demo
  3. 【真人手势动画制作软件】万彩手影大师教程 | 自定义动画移动路径
  4. 自我介绍以及我的今后打算
  5. 电脑格式化后文件怎么恢复?
  6. 集群挖矿脚本排查日记
  7. 除了 UCAN 发布的鹿班和普惠体,这些设计工具也来自阿里
  8. html 阅读模式,最纯粹的阅读 体验浏览器清爽阅读模式
  9. U盘里的文件莫名损坏,copy进去的文件夹也是空的
  10. Geomagic Freeform Plus2019中文版