二、响应式原理initState(vm)
1.inState响应式入口
- initState在
import { initState } from './state'
- 同级目录下找到state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// 处理props对象 为每一个props对象上面设置响应式,并将其代理到vm的实例上
if (opts.props) initProps(vm, opts.props)
处理methods 校验每个属性的值是否为函数
和props属性比对进行判重处理,并将其代理到vm的实例上
if (opts.methods) initMethods(vm, opts.methods)
1.判重处理,data对象上的属性不能和props、methods对象上的属性相同
2.代理data到vm实例上的
3.为data对象上的数据设置响应式
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
处理conputed
1.为computed【key】 创建 watcher 实例 默认是懒执行 也就是有缓存
2.代理computed【key】,到vm实例
3.判重,computed中的key不能和data,props中的属性重复
if (opts.computed) initComputed(vm, opts.computed)
处理watch
1.处理watch对象
2.为每一watch.key 创建watcher实例,key和watcher 实例可能是一对多的关系
3.如果设置了 immediate,则立即执行 回调函数
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
computed和watch 在本质上是没有区别的,都是通过 watcher 去实现的响应式
1.watch 适用于当数据变化时执行异步函数或者开销比较大的操作使用
需要长时间等待操作可以放在watch
2. computed 其中可以使用异步函数,computed更适合做一些同步计算。
}
2.initProps方法
- 主要处理父组件传入props
- /src/core/instance/state.js
- 为props对象的每一个属性设置响应式,并代理到vm上
function initProps (vm: Component, propsOptions: Object) {
// 写法一
props: ['name']
// 写法二
props: {
name: String, // [String, Number]
}
// 写法三
props: {
name:{
type: String
}
}
propsData:父组件传入的真实props数据。
const propsData = vm.$options.propsData || {}
props:指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中。
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
缓存props 的每个key,做性能优化
const keys = vm.$options._propKeys = []
isRoot:当前组件是否为根组件
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
// 遍历props 对象
for (const key in propsOptions) {
// key 是props的key
keys.push(key)
// 收集所有的key,存到空数组里面
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
// 为props的每一个key是设置数据响应式
defineReactive(props, key, value, () => {
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
)
}
})
} else {
// 对props数据做响应式处理
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 做代理,将props上的key代理到this上面
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
3.proxy 代理方法
- 设置代理,将key代理到target上
- /src/core/instance/state.js
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
}
主要使用defineproperty 对每一个key进行代理设置
Object.defineProperty(target, key, sharedPropertyDefinition)
}
4.initMethods
- 初始化代理方法methods
- 代理到vm实例
function initMethods (vm: Component, methods: Object) {
获取 props 配置项
const props = vm.$options.props
遍历 methods 对象
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (typeof methods[key] !== 'function') {
判定methods【key】中必须是一个函数
在非生产环境下判断如果methods中某个方法只有key而没有value,
即只有方法名没有方法体时,抛出异常:提示用户方法未定义。
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
判定 methods 的 key 和 props 不能重复
判断如果methods中某个方法名与props中某个属性名重复了,就抛出异常:提示用户方法名重复了。
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
判断如果methods中某个方法名如果在实例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)
}
}
5.initData
- src/core/instance/state.js
- 初始化data
1.判重处理,data对象上属性和props、methods对象上的属性相同
2.代理 data 对象上的属性到 vm 实例
3.为data对象上的数据设置响应式
function initData (vm: Component) {
let data = vm.$options.data
获取到用户传入的data选项,赋给变量data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
将变量data作为指针指向vm。data,判断data是不是一个函数
如果是,执行getData函数获取其返回值
如果不是,就将其本身保存到vmdata中
就是传入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
做判重处理,data对象上的属性不能和props、methods对象上的属性相同
代理data对象上的属性到vm实例
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)) {
proxy(vm, `_data`, key)
}
}
// observe data
// 为 data 对象上的数据设置响应式
observe(data, true /* asRootData */)
}
6.initComputed
- 初始化computed
- 为computed【key】创建wathcer,默认是懒执行
- 代理到vm实例上
计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
定义了一个watchers,赋值给空对象,同时将其作为指针指向vm._computedWatchers
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
遍历computed
for (const key in computed) {
const userDef = computed[key]
判断userDef是不是一个函数
是函数
默认赋值给getter的取值器
不是函数
就是个对象,取对象的get属性作为取值器赋值给变量getter
const getter = typeof userDef === 'function' ? userDef : userDef.get
判断环境
if (process.env.NODE_ENV !== 'production' && getter == null) {
如果取值器getter没有取到值,则警告
提示用户计算属性必须有取值器。
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
判断是不是服务器渲染
if (!isSSR) {
创建一个watcher实例
创建的实例作为值存入watchers对象中
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
判断当前的值是否存在vm的实例上 保持key的唯一性
if (!(key in vm)) {
不存在 调用函数
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
则在非生产环境下抛出警告
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)
}
}
}
}
7.defineComputed
- computed中的key如果不存在vm的整个实例中(也就是这个key是唯一的)
- 执行defineComputed 方法
属性描述符
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
该方法接受三个参数
target、key和userDef
为target上定义一个属性key,并且属性key的getter和setter
根据userDef的值来设置
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
标识计算属性是否该有缓存
判断是否是服务器渲染
true 非服务器渲染
false 服务器渲染
const shouldCache = !isServerRendering()
判断userDef是否函数,就是判断getter的取值
if (typeof userDef === 'function') {
判断是否是服务器渲染
非服务端渲染环境下并没有直接使用userDef作为getter,而是调用createComputedGetter函数
userDef只是一个普通的getter,它并没有缓存功能,所以我们需要额外创建一个具有缓存功能的getter
服务端渲染环境不需要缓存
但是服务器渲染没有setter,所以将sharedPropertyDefinition.set设置为noop。
设置getter和setter
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 (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
调用Object.defineProperty方法将属性key绑定到target上
主要描述性就是sharedPropertyDefinition
Object.defineProperty(target, key, sharedPropertyDefinition)
}
- createComputedGetter()
function createComputedGetter (key) {
返回一个computedGetter
return function computedGetter () {
存储在当前实例上key所对应watcher实例
const watcher = this._computedWatchers && this._computedWatchers[key]
// 执行watcher.evalute 方法
// 执行computed.key的方法,得到函数的执行结果,赋值给watcher.value
// 将watcher.dirty 赋值为false
如果watcher存在,判断dirty是否为true
这个watcher只是会在数据变化后,才会变为true
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
在实例化Watcher类的时候,第四个参数传入了一个对象computedWatcherOptions = { computed: true },该对象中的computed属性标志着这个watcher实例是计算属性的watcher实例,即Watcher类中的this.computed属性,同时类中还定义了this.dirty属性用于标志计算属性的返回值是否有变化,计算属性的缓存就是通过这个属性来判断的,每当计算属性依赖的数据发生变化时,会将this.dirty属性设置为true,这样下一次读取计算属性时,会重新计算结果返回,否则直接返回之前的计算结果。
当调用watcher.depend()方法时,会将读取计算属性的那个watcher添加到计算属性的watcher实例的依赖列表中,当计算属性中用到的数据发生变化时,计算属性的watcher实例就会执行watcher.update()方法,在update方法中会判断当前的watcher是不是计算属性的watcher,如果是则调用getAndInvoke去对比计算属性的返回值是否发生了变化,如果真的发生变化,则执行回调,通知那些读取计算属性的watcher重新执行渲染逻辑。
当调用watcher.evaluate()方法时,会先判断this.dirty是否为true,如果为true,则表明计算属性所依赖的数据发生了变化,则调用this.get()重新获取计算结果最后返回;如果为false,则直接返回之前的计算结果。
export default class Watcher {
constructor (vm,expOrFn,cb,options,isRenderWatcher) {
if (options) {
// ...
this.computed = !!options.computed
// ...
} else {
// ...
}
this.dirty = this.computed
初始值为true
if (typeof expOrFn === 'function') {
this.getter = expOrFn
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
}
}
evaluate () {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
/**
* Depend on this watcher. Only for computed property watchers.
*/
depend () {
if (this.dep && Dep.target) {
this.dep.depend()
}
}
update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
}
}
getAndInvoke (cb: Function) {
const value = this.get()
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 value
const oldValue = this.value
this.value = value
this.dirty = false
if (this.user) {
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
cb.call(this.vm, value, oldValue)
}
}
}
}
const computedWatcherOptions = { computed: true }
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
8.initWatch
- 初始化watch
function initWatch (vm: Component, watch: Object) {
遍历watch的所有值
for (const key in watch) {
const handler = watch[key]
watch对象的值
if (Array.isArray(handler)) {
handler 为数组,遍历数组,获取其中的每一项,然后调用 createWatcher
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
调用createWatcher,方法
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
如果handler是对象,则获取其中 handler 选项的值
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
如果 hander 为字符串,则说明是一个 methods 方法,获取 vm[handler]
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
定义vm.$watch
创建watcher,返回一个unwatchFn
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
兼容性处理,因为用户调用 vm.$watch 时设置的 cb 可能是对象
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options.user 表示用户 watcher,还有渲染 watcher
即 updateComponent 方法中实例化的 watcher
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
如果用户设置了 immediate 为 true,则立即执行一次回调函数
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
返回一个unwatch函数,用于解除监听
return function unwatchFn () {
watcher.teardown()
}
}
}