Closed baixiaoji closed 4 years ago
2018-11-12 vue挂载的实现: init -> $mount -> compiler -> render -> patch -> DOM
$mount
首先主要判断了挂载的根节点不能是根元素(html/body
),然后判断有没有render
方法,如果没有将对el
或是template
转化为render
函数,调用了compileToFunctions
方法。
...
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
...
mount
-> mountComponet
-> updateComponent
// 初始化的 updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._render() : VNode
,而这个函数主要通过调用$createElement
的方法将对应上面生成的render
函数转化VNode
// vm
vnode = render.call(vm._renderProxy, vm.$createElement)
就像自己写render
函数一样:
render(createElement) {
return createElement("div", {
attrs: {
id: 'app',
},
}),
'hello world'
}
而使用createElement
创建VNode
时,对children
的规范做了一些特殊的处理,主要目的是拍平整个children
数据,使用了两个不同的方法normalizationType
和simpleNormalizeChildren
,前者是递归拍平数组,而后者仅处理一层层级。
现在整体才处理完vm._render()
。接下来要开始运行vm._update
。
该函数执行的过程中会不断的调用patch
函数,而patch
函数是一个通过createPatchFunction
函数创建的闭包函数:
// src/platforms/web/runtime/patch.js
export const patch: Function = createPatchFunction({ nodeOps, modules })
// src/core/vdom/patch.js
// 为什么会要一层闭包?
// 因为这个可以在一开始就给patch函数设定一些平台信息,如:浏览器环境,挂载节点
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
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]])
}
}
}
// ...
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
然后patch
函数通过不同VNode
节点调用createElm
函数创建DOM元素,然后根据vm.$el
位置调用insert
函数插入节点,然后删除掉原本DOM上的对应的vm.$el
节点。
2018-11-13
如果 Vue
挂载的是组件,同样最后返回一个VNode
,但是在此之前会先创建子类的构造器Ctor
,是由Vue.extend
方法构成的,以及会加入一些hook
函数,然后在返回VNode。
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
2018-11-14
开始的new Vue
实例化以及组件的实例化是有不同的地方,在实例化过程中都是调用了_init
方法,但合并option
规则却是不同。
Vue.prototype._init = function (options?: Object) {
// merge 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
)
}
// ...
}
new Vue
过程中是直接将自身的option
以及Vue
默认的option
参数进行合并,但是option的每一个key值得合并是有对应的策略,如想自定义策略可以使用Vue.config.optionMergeStrategies
方法去自定义义,同样的Vue本身就有默认的自定义规则,所以mergeOptions
函数会将对应合并好的option
返回给vm.$option
// file: src/core/util/options.js
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
但是组件化的合并会有所不同,首先我们知道组件的创建是由Vue.extend
创建对应的构造器,在创建构造器的时候已经将Vue
默认的option
与本身的option
进行了合并,但是这些option
还存在构造器上。
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
// ...
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// ...
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// ...
return Sub
}
然后当子组件初始化的过程的时候,实例化了上述的构造器,然后走到了_init
的方法,由于是组件走到了initInternalComponent
的方法上。
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// ...
return new vnode.componentOptions.Ctor(options)
}
// _init 进到下面这个方法
initInternalComponent(vm, options)
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const 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
}
}
而这个方法将包含的父节点信息的option带入,再次与实例化子组件的option进行合并,然后就有了完整的子组件的option
。
组件注册相关:
上面option
合并过程中,我们知道Vue自身是存在默认的option
的。
全局注册组件时候,调用的是Vue.componet
方法去注册组件,通过这个方法实际上是将Vue.option.components
上进行挂载对应的组件的构造器。对应的方法详情如下:
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && 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
}
}
})
}
而组件注册的时候,我们知道在组件创建的时候通过Vue.extend
方法创建组件的构造器Sub
,而在option
合并上会将自身的option
和Vue
的option
进行合并。
// 这一整块就是自身的option
export default {
name: 'app',
components: {
HelloWorld,
}
}
Sub.options = mergeOptions(
Super.options, // Vue 的option
extendOptions
)
然后子组件的componets
属性下就有了自身的注册的组件,也有了全局注册的组件。顺便说一下,在子组件合并componets
属性的时候,是通过合并在Object.create(Vue.option.components)
的,所以子组件访问全局注册组件的时候是通过子组件components.__proto__
的原型链进行访问。
响应原理
Vue
响应式原理是使用率Object.defineProperty
方法,主要对get
和set
方法进行了劫持。其源码层面是在initState
过程中,初始化的时候会将Vue
对应的属性全部初始化上去。其中对props
和data
设置代理和响应式。
initProps和initData中都要一行,proxy函数的代理,其主要目标是通过这一层代理方便访问,但底层实际访问的原理依旧是:this[target][key]
的方式。
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
最终都是调用了defineRactive
的函数去将对应的props
和Data
转化为对应的响应式对应。但是不同的是Data
的设置方式,首先调用observer
方法中增加Observer
实例,实例中会初始化一个__ob__
的属性,如果拥有改属性,则到原型上直接获取Observer
的实例。根据value的不同,调用不过的遍历方式,最终依旧调用defineRactive
函数,设置劫持对应的set
或get
方法。
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
但是如对应的遍历的val
依旧是一个对象或是数组,在defineRactive
函数会递归的调用observer
函数去将对应的值设置为响应式的。
/**
* Define a reactive property on an Object.
*/
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/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
依赖收集
我们对应数据执行defineReactive
的时候,都会有一个Dep
的实例,而只有当vm.__render
函数执行的时候VNode
创建时才访问各个data
中的数据时会出发getter
,才会进行依赖收集。
那我们慢慢来分析一下全流程呗。当组件挂载的时候会出发mountComponent
,这里面会定义好对应的的updateComponent
方法以及初始化一个渲染watcher
实例。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
实例化最后会执行this.get()
,首先执行pushTarget(this)
,将Dep.target
指向了这个Watcher实例,然后会执行this.getter.call(vm, vm)
,而这个getter
函数就是updateComponent
函数。而当执行updateComponent方法的时候,首先触发对应的__render函数渲染处对应节点的Vnode,在渲染Vnode的时候触发对应响应对象中的getter方法,调用dep.depend()
,就执行了Dep.target.addDep(this)方法,将对应的dep和其id存入watcher的newDeps和newDepIds中,并将当前watcher存入dep的subs中。
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
computed: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
dep: Dep;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.computed // for computed watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
// ...
}
其实这就是依赖收集主要流程,但是没有结束。其后会递归的将value
值进行递归访问,去触发子项的getter
。
if (this.deep) {
traverse(value)
}
然后执行 popTarget()
,因为当前vm的依赖手机已经完成,将Dep.target
恢复为上一个watcher
实例。
popTarget()
Dep.target = targetStack.pop()
最后执行this.cleanupDeps()
。
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
黄老师例子 因为不清楚对于一些渲染Vnode详情,比如黄老师例子中提到的根据v-if的指令就可以在__render层面不进行依赖收集,所以在清除的时候,将老的依赖进行移除,好像自己回答自己这个问题了。
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
派发更新
当响应式数据改变的时候,会触发当初设置好的setter
,然后回触发对应的dep
的notify
执行,而从而通知存在subs
中的watcher
执行update
。
class Dep {
// ...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
而对应的update
中调用了queueWatcher
函数去将所有的watcher
放在一个队列当中,然后在下一次nextTick
中去执行flushSchedulerQueue
函数。注:这个队列中的watcher
不会重复。
const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
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 - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
flushSchedulerQueue
函数在执行的时候,先将watcher
由父到子进行排序,这里有部分的性能优化的地方。然后遍历queue
中所有的watcher
执行run
函数。遍历完之后在执行resetSchedulerState
函数,清除对应的watcher
。
let flushing = false
let index = 0
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
flushing = true
let 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 watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && 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
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
其实只是run
实际上是执行getAndInvoke
函数,而该函数先执行了this.get()
获取到当前的值,然后将新老值放进当前的回调用执行,所以为什么自定义的watch
中会有两个参数,是这里传入的。
而此时执行的get
,会触发依赖收集的过程,然后执行updateComponent
。
这是什么东西?(明白该想project的定义)
为什么要做这个东西?(想想做这个东西的动机)
如何做这个东西?(拆解细节)
回顾(项目过程中的执行、以及心态的变化)
ref: #8