https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/update.html#%E6%80%BB%E7%BB%93
function Vue (options) {//不用创建临时对象,因为 new 会帮你做(你使用「this」就可以访问到临时对象);//不用绑定原型,因为 new 会帮你做(new 为了知道原型在哪,所以指定原型的名字为 prototype);//不用 return 临时对象,因为 new 会帮你做;//不要给原型想名字了,因为 new 指定名字为 prototype。//instanceof是一个判断右侧是否在左侧原型链上的过程,new做了newObj.__proto__ = constructorif (process.env.NODE_ENV !== 'production' &&!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)}
//聚合同类定义var events = {create: updateDOMListeners,update: updateDOMListeners};//总模块定义聚合var platformModules = [attrs,klass,events,domProps,style,transition];/* */// the directive module should be applied last, after all// built-in modules have been applied.var modules = platformModules.concat(baseModules);//聚合需要的钩子function createPatchFunction (backend) {var i, j;var cbs = {};var modules = backend.modules;var nodeOps = backend.nodeOps;for (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]]);}}}...}//更新组件的属性(事件,属性等prepatch: function prepatch (oldVnode, vnode) {var options = vnode.componentOptions;var child = vnode.componentInstance = oldVnode.componentInstance;updateChildComponent(child,options.propsData, // updated propsoptions.listeners, // updated listenersvnode, // new parent vnodeoptions.children // new children);},// 更新vnode节点function patchVnode (oldVnode,vnode,insertedVnodeQueue,ownerArray,index,removeOnly) {if (oldVnode === vnode) {return}if (isDef(vnode.elm) && isDef(ownerArray)) {// clone reused vnodevnode = ownerArray[index] = cloneVNode(vnode);}var elm = vnode.elm = oldVnode.elm;if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue);} else {vnode.isAsyncPlaceholder = true;}return}// reuse element for static trees.// note we only do this if the vnode is cloned -// if the new node is not cloned it means the render functions have been// reset by the hot-reload-api and we need to do a proper re-render.if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance = oldVnode.componentInstance;return}var i;var data = vnode.data;// prepatch更新组件例如slot,options prepatch 会执行 updateChildComponents// hook和prepatch属性可以追溯到createComponent内对于属性的装配if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {i(oldVnode, vnode);}var oldCh = oldVnode.children;var ch = vnode.children;// 更新节点的属性// cbs.update包括// updateAttrs// updateClass// updateDOMListeners// updateDOMProps// updateStyle// update// updateDirectivesif (isDef(data) && isPatchable(vnode)) {for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }}if (isUndef(vnode.text)) {if (isDef(oldCh) && isDef(ch)) {if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }} else if (isDef(ch)) {if (process.env.NODE_ENV !== 'production') {checkDuplicateKeys(ch);}if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);} else if (isDef(oldCh)) {removeVnodes(oldCh, 0, oldCh.length - 1);} else if (isDef(oldVnode.text)) {nodeOps.setTextContent(elm, '');}} else if (oldVnode.text !== vnode.text) {nodeOps.setTextContent(elm, vnode.text);}if (isDef(data)) {if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }}}
运行时
// 定义根节点的complile规则var createCompiler = createCompilerCreator(function baseCompile (template,options) {var ast = parse(template.trim(), options);if (options.optimize !== false) {optimize(ast, options);}var code = generate(ast, options);return {ast: ast,render: code.render,staticRenderFns: code.staticRenderFns}});//其中 generate 生成template首次对应的render代码,genElement具体的处理// render代码的功能function generate (ast,options) {// CodegenState合并配置var state = new CodegenState(options);var code = ast ? genElement(ast, state) : '_c("div")';return {render: ("with(this){return " + code + "}"),staticRenderFns: state.staticRenderFns}}function genElement (el, state) {if (el.parent) {el.pre = el.pre || el.parent.pre;}if (el.staticRoot && !el.staticProcessed) {return genStatic(el, state)} else if (el.once && !el.onceProcessed) {return genOnce(el, state)} else if (el.for && !el.forProcessed) {return genFor(el, state)} else if (el.if && !el.ifProcessed) {return genIf(el, state)} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {return genChildren(el, state) || 'void 0'} else if (el.tag === 'slot') {return genSlot(el, state)} else {// component or elementvar code;if (el.component) {code = genComponent(el.component, el, state);} else {var data;if (!el.plain || (el.pre && state.maybeComponent(el))) {data = genData$2(el, state);}var children = el.inlineTemplate ? null : genChildren(el, state, true);code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";}//根节点是template写法,此时code是_c("App") _c是处理template的createElement// module transformsfor (var i = 0; i < state.transforms.length; i++) {code = state.transforms[i](el, code);}return code}}
generate调用路径 //根据ast生成render的方法块
var ref$1 = createCompiler(baseOptions);var compile = ref$1.compile;var compileToFunctions = ref$1.compileToFunctions;function createCompileToFunctionFn (compile) {var cache = Object.create(null);return function compileToFunctions (template,options,vm){//compile根据代码块生成functionvar compiled = compile(template, options);res.render = createFunction(compiled.render, fnGenErrors);return res;}};//自动执行 autovar createCompiler = createCompilerCreator(function baseCompile (){...});function createCompilerCreator(baseCompile){return createCompiler(){function compile(){var compiled = baseCompile(template.trim(), finalOptions);return compiled;}return {compile: compile,compileToFunctions: createCompileToFunctionFn(compile)}}};//上图手动触发顺序调用描述1var ref = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this);
update&createElement流程
Watcher在get触发依赖收集的过程中会 保证操作的dep实例收集的类属性 Dep.target是当前组件
Watcher.prototype.get = function get () {pushTarget(this);//function pushTarget (target) {//targetStack.push(target);//Dep.target = target;value = this.getter.call(vm, vm);//开始依赖收集,触发set,addDep}}
createElm(vue\src\core\vdom\patch.js\patch) vdom->dom 描述 update _patch的主要功能
1,是否是常规标签ns是指它标准的特色标准如svg等
2,递归创建子元素的createElm,从底层向上生成待dom树
3, 调用create钩子invokeCreateHooks。注:接着再调用 invokeCreateHooks 方法执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue 中。
function createElm (vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index) {if (isDef(vnode.elm) && isDef(ownerArray)) {// This vnode was used in a previous render!// now it's used as a new node, overwriting its elm would cause// potential patch errors down the road when it's used as an insertion// reference node. Instead, we clone the node on-demand before creating// associated DOM element for it.vnode = ownerArray[index] = cloneVNode(vnode)}vnode.isRootInsert = !nested // for transition enter checkif (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {return}const data = vnode.dataconst children = vnode.childrenconst tag = vnode.tagif (isDef(tag)) {if (process.env.NODE_ENV !== 'production') {if (data && data.pre) {creatingElmInVPre++}if (isUnknownElement(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)}}// 是否是常规标签ns是指它标准的特色标准如svg等vnode.elm = vnode.ns? nodeOps.createElementNS(vnode.ns, tag): nodeOps.createElement(tag, vnode)setScope(vnode)/* istanbul ignore if */if (__WEEX__) {// in Weex, the default insertion order is parent-first.// List items can be optimized to use children-first insertion// with append="tree".const appendAsTree = isDef(data) && isTrue(data.appendAsTree)if (!appendAsTree) {if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}createChildren(vnode, children, insertedVnodeQueue)if (appendAsTree) {if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}} else {createChildren(vnode, children, insertedVnodeQueue)if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}if (process.env.NODE_ENV !== 'production' && 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)}}
normalizeArrayChildren 整理生成vdom
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {const res = []let i, c, lastIndex, lastfor (i = 0; i < children.length; i++) {c = children[i]if (isUndef(c) || typeof c === 'boolean') continuelastIndex = res.length - 1last = res[lastIndex]// nested// 如果是数组,递归调用normalizeArrayChildren// nestedIndex, 多个重复时的唯一keyif (Array.isArray(c)) {if (c.length > 0) {c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)// merge adjacent text nodes 如果才生成的批量节点和当前数组最后一个元素都是文字,归并if (isTextNode(c[0]) && isTextNode(last)) {res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)c.shift()}//apply的第二个参数是数组,本句功能相当于push(...c)res.push.apply(res, c)}} else if (isPrimitive(c)) {if (isTextNode(last)) {// merge adjacent text nodes// this is necessary for SSR hydration because text nodes are// essentially merged when rendered to HTML stringsres[lastIndex] = createTextVNode(last.text + c)} else if (c !== '') {// convert primitive to vnoderes.push(createTextVNode(c))}} else {if (isTextNode(c) && isTextNode(last)) {// merge adjacent text nodesres[lastIndex] = createTextVNode(last.text + c.text)} else {// default key for nested array children (likely generated by v-for)if (isTrue(children._isVList) &&isDef(c.tag) &&isUndef(c.key) &&isDef(nestedIndex)) {c.key = `__vlist${nestedIndex}_${i}__`}res.push(c)}}}return res}
observer/initState
vue初始化的构造函数_init内,会执行initState(对props、methods、data、computed和wathcer等属性做了初始化操作),其中initData部分会执行 observe(data, true)->new Observer(value)
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && 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 */)}
Observer构造方法部分
constructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this) //把当前实例defineProperty到data的__ob__上if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value) //遍历数组再次调用 observe 方法} else {this.walk(value)}}//walkwalk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]) //开始正式的依赖收集}}
设置gettter/setter 依赖收集/派发更新
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) {const dep = new Dep()const property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) {return}// cater for pre-defined getter/settersconst getter = property && property.getconst setter = property && property.setif ((!getter || setter) && arguments.length === 2) {val = obj[key]}//childOb用于递归当前value(对象/数组)进行observe,当设置一个新值时,也能派发更新let childOb = !shallow && observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value)}}}return value},// ...})}
watcher部分
// expOrFn用于执行然后getter变量触发依赖收集。cb用于$watch,如expOrFn值变动,watch会调用run,其中会执行watch的回调方法cb。constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean)updateComponent = () => {vm._update(vm._render(), hydrating)}new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)//用newDepIds,depIds两个set来维护新旧依赖,便于在不同阶段调用/懒加载。新加入的依赖统一在//newDeps系列里,然后等合适的机会,diff并固化为实际的depIds系列,然后本身置空等待下次收集。//mountComponent的调用updateComponent = () => {vm._update(vm._render(), hydrating)}new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)constructor ( //构造方法vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vmif (isRenderWatcher) {vm._watcher = this}vm._watchers.push(this)...// parse expression for getterif (typeof expOrFn === 'function') {this.getter = expOrFn // this.getter = updateComponent} else {this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = noopprocess.env.NODE_ENV !== 'production' && 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()}get(){pushTarget(this) //当前Dep.target设置为this watcher,并缓存。let valueconst vm = this.vmvalue = this.getter.call(vm, vm) // vm._update(vm._render(), hydrating)// render会生成VNode,过程中访问数据从而触发getter,此时dep.depend->Dep.target.addDep(this)// 交给watcher执行(进行一些关于dep的判断),最后交给dep.addSub,只负责收集行为。if (this.deep) {traverse(value) //遍历触发依赖收集}}cleanupDeps () {let i = this.deps.lengthwhile (i--) {const dep = this.deps[i]if (!this.newDepIds.has(dep.id)) {//如旧的依赖id,新的里面没有了,则remove掉dep.removeSub(this)}}let tmp = this.depIdsthis.depIds = this.newDepIdsthis.newDepIds = tmpthis.newDepIds.clear()tmp = this.depsthis.deps = this.newDepsthis.newDeps = tmpthis.newDeps.length = 0}//宏观理解下observer index是data数据reactive的逻辑,dep是一个data依赖的抽象或者说依赖的实体,维护了watchers,职责单一,如负责通知依赖的组件watcher update;watcher维护一个依赖的关系,然后负责具体组件的更新,取消订阅依赖等。
watch的更新
update () {/* istanbul ignore else */if (this.computed) {// A computed property watcher has two modes: lazy and activated.// It initializes as lazy by default, and only becomes activated when// it is depended on by at least one subscriber, which is typically// another computed property or a component's render function.if (this.dep.subs.length === 0) {// In lazy mode, we don't want to perform computations until necessary,// so we simply mark the watcher as dirty. The actual computation is// performed just-in-time in this.evaluate() when the computed property// is accessed.this.dirty = true} else {// In activated mode, we want to proactively perform the computation// but only notify our subscribers when the value has indeed changed.this.getAndInvoke(() => {this.dep.notify()})}} else if (this.sync) {this.run()} else {queueWatcher(this) //一般逻辑}export function queueWatcher (watcher: Watcher) {const id = watcher.idif (has[id] == null) { //has[id] = trueif (!flushing) { //flush: 冲刷queue.push(watcher)} else {// if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}//id蕴含优先级信息,遍历插入 splice第二个参数是删除个数,0表示只新增queue.splice(i + 1, 0, watcher)}// queue the flushif (!waiting) {waiting = truenextTick(flushSchedulerQueue) //一般执行nextTick中的}}}
开始flush冲刷
function flushSchedulerQueue () {flushing = truelet watcher, id// Sort queue before flush.// This ensures that:// 1. Components are updated from parent to child. (because parent is always// created before the child)// 2. A component's user watchers are run before its render watcher (because// user watchers are created before the render watcher)// 3. If a component is destroyed during a parent component's watcher run,// its watchers can be skipped.queue.sort((a, b) => a.id - b.id)// do not cache length because more watchers might be pushed// as we run existing watchersfor (index = 0; index < queue.length; index++) {watcher = queue[index]if (watcher.before) {watcher.before()}id = watcher.idhas[id] = nullwatcher.run() //在此过程中可能会发生queueWatcher插入的逻辑,所以遍历flag动 态获取length// in dev build, check and stop circular updates.if (process.env.NODE_ENV !== 'production' && has[id] != null) {circular[id] = (circular[id] || 0) + 1if (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}}}//runrun () {if (this.active) {const value = this.get() //触发vm._update(vm._render(), hydrating),重新渲染和收集依赖if (value !== this.value ||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value) ||this.deep) {// set new valueconst oldValue = this.valuethis.value = valueif (this.user) {try {this.cb.call(this.vm, value, oldValue) // $watch第二个参数的cb} catch (e) {handleError(e, this.vm, `callback for watcher "${this.expression}"`)}} else {this.cb.call(this.vm, value, oldValue)}}}}
set方法
function set (target: Array<any> | Object, key: any, val: any): any {if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) {warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)target.splice(key, 1, val)return val}if (key in target && !(key in Object.prototype)) {target[key] = valreturn val}const ob = (target: any).__ob__if (target._isVue || (ob && ob.vmCount)) {process.env.NODE_ENV !== 'production' && warn('Avoid adding reactive properties to a Vue instance or its root $data ' +'at runtime - declare it upfront in the data option.')return val}if (!ob) {target[key] = valreturn val}defineReactive(ob.value, key, val)//进入这里说明是对响应式对象,添加新属性;//这里ob是observer实例,执行let childOb = !shallow && observe(val),observe,如果已经添加过,直 //接返回ob,然后getter准备收集依赖,ob.dep.notify()//notify最终执行watch.run执行value = this.get()触发vm.update(vm.render()),render会重新收集依赖//最后update渲染return val}
nextTick
// 上面依次判断环境支持的api,对timerFunc赋值,let timeFuncif (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()timerFunc = () => {p.then(flushCallbacks)if (isIOS) setTimeout(noop)}isUsingMicroTask = true //当前的循环基于microTask还是macroTask}else{timerFunc = () => {setTimeout(flushCallbacks, 0)}}export function nextTick (cb?: Function, ctx?: Object) {let _resolve//推入队列一个闭包的functioncallbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})if (!pending) {pending = truetimerFunc()}// $flow-disable-lineif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}}//callback 执行某次flushfunction flushCallbacks () {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}}
还有一段有趣的任务执行代码
let counter = 1//观察dom各项属性的突变const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})//触发方式是执行dom内部text的变化,然后执行observer的监听timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
validateProp
function validateProp (key: string,propOptions: Object,propsData: Object,vm?: Component): any {const prop = propOptions[key]const absent = !hasOwn(propsData, key)let value = propsData[key]// boolean castingconst booleanIndex = getTypeIndex(Boolean, prop.type)if (booleanIndex > -1) {if (absent && !hasOwn(prop, 'default')) {value = false} else if (value === '' || value === hyphenate(key)) {// only cast empty string / same name to boolean if// boolean has higher priority//在type里如果boolen在string前,或者无string,则将视作const stringIndex = getTypeIndex(String, prop.type)if (stringIndex < 0 || booleanIndex < stringIndex) {value = true}}}// check default valueif (value === undefined) {value = getPropDefaultValue(vm, prop, key)// since the default value is a fresh copy,// make sure to observe it.const prevShouldObserve = shouldObservetoggleObserving(true)observe(value)toggleObserving(prevShouldObserve)}if (process.env.NODE_ENV !== 'production' &&// skip validation for weex recycle-list child component props!(__WEEX__ && isObject(value) && ('@binding' in value))) {assertProp(prop, key, value, vm, absent)}return value}
