vue2.0 的响应式原理网络上有很多的资源,这里就学习一下 源码中响应式的执行过程(这里还包括了 vue 首次渲染的过程)
如果实在没基础的话,可以先看这一篇: 实现乞丐版的 vue data + method + computed
需要注意的是,defineProperty 的 get set 是这一棵大树的基石,如果在 某个步骤不理解的话,想一想这个也许你就能想明白
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
// 不使用 class 是为了方便使用 prototyp
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用 init
this._init(options)
}
// 注入 _init
initMixin(Vue)
// 注册 $data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$destory
eventsMixin(Vue)
// 初始化生命周期的混入
lifecycleMixin(Vue)
// 混入render $nextTick _render
renderMixin(Vue)
export default Vue
这里各种 mixin 的作用都有注释,重点来看 这里的 _init 的过程
说句题外话,无论是 react 还是 vue ,初始化的函数 都是 function ,而不是 class ,这是为了方便 可以使用 prototype 进行拓展
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
// 当前 的唯一标识符
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
// 当前实例是VUE实例,防止以后被 observe ,也就是不做处理
vm._isVue = true
// 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 {
// 把传入的 options 和 构造函数的 options 合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 建立父子、祖先关系
initLifecycle(vm)
// 用来构建事件响应
initEvents(vm)
// 挂载 h 函数
initRender(vm)
// 触发 beforeCreate 生命周期函数
callHook(vm, 'beforeCreate')
// 循环遍历 parent 的 _provided,然后获取 inject
initInjections(vm) // resolve injections before data/props
// 初始化props methods data
initState(vm)
// 给当前节点加上 _provided
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 调用 created 钩子函数
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && 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)
}
}
}
主要来看 initState vm.$mount 这里的其他函数都不在这里次的学习范围之内
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // 初始化 props
if (opts.methods) initMethods(vm, opts.methods) // 初始化 methods
if (opts.data) {
initData(vm) // 初始化 data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed) // 初始化 computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // 初始化 watch
}
}
initProp 和 initMethods 就不深入了,这里讲一下就好了
initProp
initProps 的作用 设置 这个值为 响应式的
但是 在 set 的函数中,设置一个 报错,也就是禁止 给 props 赋值,所以这个值 只能获取,不能 修改
在 vm 上挂载 props的 key
initMethods
- 检查在 methods 中有没有存在 和 props 同名的,存在就报错
- 如果 当前的对象中,不是方法,就返回一个空方法,否则就绑定到 vm
function initData (vm: Component) {
let data = vm.$options.data
// 当前 data 如果是一个函数,就执行 这个函数,并获取 返回值
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 如果 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 instance
// 判断 是否重名
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (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)) {
// 注入到 vm 实例中去
proxy(vm, `_data`, key)
}
}
// observe data
// 响应式处理
observe(data, true /* asRootData */)
}
- 如果 data 是一个函数,则执行 并获取
- 检查 和 props 和 methods 是否是 重名
- 对 data 进行响应式处理 执行 observe 函数
// 创建 observe 对象
export function observe (value: any, asRootData: ?boolean): Observer | void {
// value 是否是 对象 或者 vm 的 对象
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 是否有 __ob__ ,这里的 __ob__ 会挂载 当前对象的 响应式 对象
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
// 是否是 vue 实例
!value._isVue
) {
// 创建一个响应式对象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
export class Observer {
// 观测对象
value: any;
// 依赖对象
dep: Dep;
// 实力计数器
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 给 value 挂载 __ob__ 属性
def(value, '__ob__', this)
// 处理数组的响应式
if (Array.isArray(value)) {
if (hasProto) {
// 让原型指向 自定义的方法
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历数组中的每个成员,并给其增加响应式
this.observeArray(value)
} else {
// 遍历对象中的每一个对象
this.walk(value)
}
}
/**
* Walk through all properties 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) {
// 遍历 数组,然后 对其 进行 响应式处理
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
- 重点 还在 Observer 上面,这里也就是创建一个响应式对象
- 然后 给当前 的对象 附加上 __ob__ 属性,指向 当前的响应式对象
- 分别处理 数组的 响应式 和 对象的响应式处理
...
if (Array.isArray(value)) {
if (hasProto) {
// 让原型指向 自定义的方法
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历数组中的每个成员,并给其增加响应式
this.observeArray(value)
}
...
observeArray (items: Array) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
这里对于数组的响应式处理也很简单,就是 先给 当前数组的 __proto__ 进行修改,然后 遍历数组,给每一个数组成员进行响应式处理,接下来看一下 arrayMethods 是什么
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) // 将 arrayMethod 的原型指向 数组
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
// 遍历 methodsToPatch 数组,给对应的数组函数 进行重新的定义
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
// 修改了之后,发送通知
ob.dep.notify()
return result
})
})
到了这里 ,可以发现 代码中 将 数组的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' 进行了重新的定义,最后主要是执行了 一个 ob.dep.notify() ,也就是 通知当前的 watcher 去更新节点
walk (obj: Object) {
const keys = Object.keys(obj)
// 遍历每一个属性,设置为响应式数据
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
// 定义一个响应式 的属性
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// 获取 obj 的属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// 不可配置的话,说明不能使用 defineProperty,返回
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 缓存用户的 get set
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 判断是否递归观察子对象,并将子对象属性都转换为 getter/setter
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) { // 就是 Watcher, 每个 watcher 初始化的时候,就会进行一次赋值
// 收集依赖
dep.depend()
if (childOb) {
// 为子对象添加依赖
// 当子对象发生了删除 和 增加的时候,发送通知
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 如果 用户设置了 getter ,就是用 用户的
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 新的值和旧的值相等,或者 是 NaN 则不执行
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
// 是只读的 ,没有 setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新值是对象,那么观察新对象,并返回
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
纠正一个很大的误区,这里对于 defineProperty 的设置仅仅是一个设置,还没有进行响应式收集,所以到目前为止,响应式依旧没有建立完成,需要 对 模块或者 methods 等区域里面,执行了 对应响应式数据 的 getter 或者 setter,这里的 设置才会执行
- 获取 当前对象的 属性描述符,如果 configurable 为false 的话,就不进行响应式处理 (所以有一个 性能优化的地方,也就是 给 只展示的数据 设置 configurable 为 false,就能减少 接下来很多的响应式操作)(再ps 自己试了一下,7K条数组的数据,注册响应式花的时间 从 500ms+ -> 200ms+ 的华丽转变)
- 递归观察 对象的 value
- 通过 defineProperty 设置 对象的属性的 get 和 set 属性
- 当执行 get 的时候,先执行 用户设置的getter 函数,如果 Dep.target 静态属性有值,就进行依赖收集
- 当执行 set 的时候,限制性用户设置的 getter 函数,接着 执行用户设置的 setter 函数,如果 set 的 新值是 对象,那对该对象进行监听,最后通知 依赖更新
这一部分 以及接下里的内容 建议 结合 Vue2.0 中模板编译的过程学习 来一起看,味道更好哦
回到 2.Vue.prototype._init 中,这里的最后一步执行了 vm.$mount(vm.$options.el)
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
...
if (vm.$options.el) {
// 挂载整个页面
vm.$mount(vm.$options.el)
}
}
}
这里的 $mount 主要是执行了 公共的 $mount 以及Vue2.0 中模板编译的过程学习 中 所说的 重新定义过的 $mount
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
所以让我们跳过模板编译的过程,最后到了 mountComponent 这一步
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 如果当前没有 render 函数,就返回一个 创建空 vnode 的函数
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
// 调用 beforeMount 回调函数
callHook(vm, 'beforeMount')
let updateComponent
// vue 性能检测属性
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
// 生成 虚拟 dom
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
// 对比节点的差异,然后更新
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
// 对比节点的差异,然后更新
vm._update(vm._render(), hydrating)
}
}
// 创建 watcher 对象,渲染 watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 这里的数据变化 如果在 mounted 之后,destory 之前,就会调用 beforeUpdate
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
// 调用 mounted 钩子函数
callHook(vm, 'mounted')
}
return vm
}
这里的 updateComponent 主要就是 包裹了 update 以及rander 函数,然后创建了当前组件的 watcher 函数,然后调用并注入钩子函数(),而注入的 回调函数,就是 updateComponent函数
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;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array;
newDeps: Array;
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 // 侦听器里的属性 deep: true
this.user = !!options.user // user ,是否是 用户 watcher
this.lazy = !!options.lazy // 只有 computed 时候 lazy
this.sync = !!options.sync // 是否是 同步跟新的,在 vue 全局有一个 sync 属性可以使用
this.before = options.before // 这里传递的 是 beforeUpdate
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching 唯一的 id 标识符
this.active = true
this.dirty = this.lazy // for lazy 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') { // updateComponent 函数
this.getter = expOrFn
} else {
// 到这里的话,就是一个字符串,主要是 侦听器里的内容
// 例如: watch: {'person.name': funcion ... }
// 用来解析 这里的 字符串,获取对应的值
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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
)
}
}
this.value = this.lazy // 在计算属性里面,这里 就是 true ,不会在 第一时间被调用
? undefined
: this.get() // 把当前 的 对象 赋值给 Dep.target
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 给 Dep.target 赋值
pushTarget(this)
let value
const vm = this.vm
try {
// 执行了 updateComponent,进行页面的渲染
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) { // 是否是深度监听 ,就是 侦听器里 watch 属性的 deep: true
// 是的话,就把整个 对象 给遍历一遍
// 这样 如果 该数据注册了监听,就会被 同一个 watcher 之内执行一遍 getter
traverse(value)
}
popTarget() // 把当前的 Target 弹出
this.cleanupDeps() // 清除 Dep 的 watcher
}
return value
}
/**
* Add a dependency to this directive.
*/
// 缓存 对应的 Dep,然后 让 dep 缓存 当前的 watcher
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.
*/
// 清除 和 新的 dep 不同的 监听器,主要是 执行了 类似于 v-if 之类的属性之后,内部的 绑定就没有意义了
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
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
// 给 dep 和 forceUpdate 调用的
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true // 依赖的 参数改变之后,computed 被调用更新的
} else if (this.sync) { //如果当前是 同步的,立即执行
this.run()
} else {
// 把当前的 watcher 放到函数队列里面
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
// 执行 当前传入的 cb,一般是 当前组件的 watcher ,或者是 侦听器的 handler
run () {
if (this.active) {
// 调用 get,渲染 watcher 的 get是 updateComponent
const value = this.get()
if (
value !== this.value ||
// 如果是需要深度监听的,并且是 用户的watcher ,也就是监听器的时候
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) { // 如果是 用户 watcehr ,调用回调函数,而且有可能出现异常,所以调用 try catch
try {
// 执行 handler,并且 将新旧两个值 给传进去
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
// 用来给 computed 调用的,用来获取当前的值
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
// 调用 dep 收集依赖
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
// 清除当前的 watcher
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
- watcher 分为三种类型,一种是 当前组件的 渲染watcher,调用的就是当前组件的 render 函数
- computed 的 watcher ,有 lazy 属性,也就是 当 依赖的数据改变之后,dirty 才允许 对当前computed 执行 get
- 侦听器 watch 的watcher,主要是有传入 deep的话,就把当前对象 整个遍历一般,就会触发 前面定义的 getter,接着执行 传入的handler 函数
如果对 vue 熟悉的话,就会知道,如果执行 多次赋值操作,最终只会改变一次页面
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true // 当前的 watcher 已经被处理了
// 把 watcher 放到队列里面去
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)
}
if (!waiting) {
waiting = true
// 如果是开发环境的话,直接调用
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 否则到 nextTick 里面去执行
nextTick(flushSchedulerQueue)
}
}
}
- 这里可以看做是调用了 节流
- 当前只需要传入一个 render,对当前的render 进行缓存,但是不立即执行(nextTick)
- 然后 如果 在一次事件循环中,调用了 多次,只会插入一次render ,但是 render 函数 依旧会获取最新的 value
vue 2.0 将 template 转换为 vnode 的 render 函数 这里就有一个简单地render 函数
- 通过 with 函数,将当前vue 实例 注入到render 当中,这样 能直接获取对应的数据
- 在获取数据的过程中,就会执行 第5 点中说道的 getter,然后 当前 Dep 有 第 9 点中的 watcher 函数
- 执行以来收集,这里的依赖就是当前 组件的 渲染函数,render 函数
- 接下来每次 执行,都会 调用 render 函数,来进行整个组件的重新渲染
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
// 获取 当前的 get
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
// 当前 没有 set 函数
sharedPropertyDefinition.set = noop
} else {
// 如果是以 一个 对象形式定义的 computed,那获取里面的 get 和 set
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
// 如果没有 定义set,而且执行了 computed 的赋值,那就报个错
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
// 将 相关 属性 定义到 响应式当中
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 在 初始化,以及被 内部依赖 调用更新的时候,dirty会被置为 true,所以内部 依赖被调用,仅仅是打开 这个可以更新的钥匙
if (watcher.dirty) {
// 需要 在 dirty 被改变之后,调用 这个 compute 的 get 函数,才会 调用 对应 watcher 的 get ,获得正确的值,然后 dirty 被置为 false
watcher.evaluate()
}
// 进行依赖收集
if (Dep.target) {
watcher.depend()
}
// 返回 函数计算出来的 值
return watcher.value
}
}
}
- computed 的响应式是独立于 data 的
- 首先获取 用户定义的 get 和 set 属性,可以作为 响应式的 getter 和 setter
- 如果 用户没有 定义 set 函数,然后 给其赋值的话,报错
- 在 创建 Watcher 的时候,传入的 lazy 是 true ,dirty 也是 true,导致了不会第一时间执行
- 然后 当 依赖的 数据被改变之后,当前 computed 的 watcher 的 dirty被置为 true
- 然后 如果 执行了 当前 computed 的 getter的话,就会 触发 上面的 computedGetter
- 执行 对应 watcher 的 evaluate 函数,获取 计算属性的值的同时,将 dirty 置为 false
- 这样 下次再去 获取 computed 的值的时候,就会 直接将缓存的值 返回,不再进行计算,这样就进行了 数据的缓存
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// 如果 handler 是一个数组的话,遍历进行处理
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]) // 创建 用户的 watcher 对象
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// 如果是一个对象的话,把 handler 取出来
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options) // 最后还是调用了 $watch 函数
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
// 如果传入的是对象,就重新解析
const vm: Component = this
if (isPlainObject(cb)) { // 如果是个对象,再次 调用
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// 用户 watcher ,设置为 true
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) { // 是否立即执行
try {
// 当前监听属性的值传入了函数
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
// 返回了一个函数,可以取消监听
return function unwatchFn () {
watcher.teardown()
}
}
如果 handler 是一个数组的话,遍历进行处理
如果传入的 执行函数 是一个对象的话,把 handler 取出来
调用 vue.prototype.$watch
如果传入的 handler 是一个函数,再次调用 createWatcher (这里是因为 vue.prototype.$watch 是可以在全局 使用的函数,所以要额外做一次校验)
创建一个 watcher,同时传入 的参数之中将 user 设置为 true ,这里也就是为什么前面将 Watcher 分为 用户 watcher 和渲染 watcher 的原因
如果传入了 immediate 参数,将对应的 handler 函数执行一遍
最后返回 一个 unwatchfn,作用就是取消监听
能看到这里的,对于首次渲染的过程肯定了清楚了
- 首先 执行 vue 实例的 _init 函数
- _init 函数给当前实例 注入 父子关系、响应事件、h 函数、执行 钩子函数、设置数据的响应式、注入provide
- 挂载 整个页面,如果传入 render 函数的话,获取 render 函数,如果没有的话,就 将 template 或者 el 的outerHTML 编译成 render 函数,同时 生成 当前组件的 渲染 Watcher ,将 render 函数 传入
- 渲染 Watcher 被创建的时候,就会开始执行 对应的 get ,也就是 render 函数
- 在 render 函数中,会获取当前 data 中的数据,这样就会触发 数据响应式中的 getter 函数,进行依赖收集
- 这样在以后的数据更新中,就会执行 对应的 渲染watcher 了
- 至于 怎么更新的,以及挂载的,可以参考 vue 中 patch、patchVnode 函数(更新节点)createElm 函数 的学习