vue2源码学习-响应式原理
Posted ~往无前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码学习-响应式原理相关的知识,希望对你有一定的参考价值。
initState
/src/core/instance/state.js
/**
* 数据响应式的入口:分别处理props、methods、data、computed、watch
* 优先级:props、methods,data、compouted 对象中的属性不能出现重复,优先级和列出顺序一致
* 其中 computed中的key 不能和 props、data中的key重复,methods不影响
*/
export function initState (vm:Component)
vm._watchers = []
const opts = vm.$options
//处理props对象,为props对象的每个属性设置响应式,并将其代理到vm实例上
if(opts.props) initProps(vm,opts.props)
//处理methods对象,校验每个属性的值是否为函数,和props属性比对进行判重处理,最后得到vm[key]=methods[key]
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 */)
/**
* 1.为 computed[key] 创建watcher实例,默认是懒执行
* 2.代理computed[key]到vm实例
* 3.判重,computed中的key不能和data、props中的属性重复
*/
if(opts.computed) initComputed(vm,opts.computed)
/**
* 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更适合做一些同步计算
*/
initProps
//处理props对象,为props对象的每个属性设置响应式,并将其代理到vm实例上
function initProps (vm:Component,propsOptions:Object)
const propsData = vm.$options.propsData ||
const props = vm._props =
//缓存props的每个key,性能优化
const keys = vm.$options._propKeys=[]
//判断是否为根组件
const isRoot = !vm.$parent
if(!isRoot)
toggleObserving(false)
//遍历props对象
for(const key in propsOptions)
//缓存key
keys.push(key)
//获取props[key]的默认值
const value = validateProps(key,propsOptions,propsData,vm)
//为props的每个key都设置数据响应式
defineReactive(props,key,value)
//静态属性已经在Vue.extend()期间代理到组件的原型上。我们只需要代理在这里实例化时定义的props
if(!(key in vm))
//代理key到vm对象上
proxy(vm,`_props`,key)
toggleObserving(true)
proxy
/src/core/instance/state.js
//设置代理,将key代理到targe上
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
//并非直接将key绑定在了vm实例上,而是通过Object.defineProperty()方法来绑定
Object.defineProperty(target, key, sharedPropertyDefinition)
initMethods
/src/core/instance/state.js
/**
* 1.校验 methods[key],必须是一个函数
* 2.判重
* methods中的key不能和props中的key相同
* methods中的key与Vue实例上已有的方法重叠,一般是一些内置方法,比如以$ 和 _开头的方法
* 3.将methods[key]放到vm实例上,得到vm[key]=methods[key]
*/
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')
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' ? bind(methods[key],vm)
initData
src/core/instance/state.js
/**
* 1.判重处理,data对象上的属性不能和props、methods对象上的属性相同
* 2.代理data对象上的属性到vm实例
* 3.为data对象上的数据设置响应式
*/
function initData (vm: Component)
//得到data对象
let data =vm.$options.data
data = 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
/**
* 1.判重处理,data对象上的属性不能和props、methods对象上的属性相同
* 2. 代理data对象傻姑娘的属性到vm实例
*/
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))
proxy(vm, `_data`,key)
//为data对象上的数据设置响应式
observe(data, true /* asRootData */)
export function getData (data: Function, vm: Component): any
pushTarget()
try
return data.call(vm, vm)
catch(e)
handleError(e, vm,`data()`)
return
finally
popTarget()
initComputed
/src/core/instance/state.js
const computedWatcherOptions = lazy: true
/**
* 1.为computed[key]创建watcher实例,默认是懒执行
* 2.代理computed[key]到vm实例
* 3.判重,computed中的key不能和data、props中的属性重复
* @param * computed =
* key1:function()return xx,
* key2:
* get: function() return xx,
* set: function(val)
*
*/
function initComputed (vm: Component, computed:Object)
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// 计算属性在服务器渲染时调用getters
const isSSR = isServerRendering()
// 遍历computed对象
for(const key in computed)
//获取对应的值,即getter函数
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 如果userDef为null,则表示该属性并没有Getter属性
if(process.env.NODE_ENV !== 'production' && getter==null)
warn(`Getter is missing for computed property "$key`),
vm
if(!isSSR)
//为computed属性创建watcher实例
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
//配置项,computed 默认是懒执行
computedWatcherOptions
)
if(!(key in vm))
//代理computed对象中的属性到vm实例
//这样就可以使用vm.computedKey 访问计算属性了
defineComputed(vm, key, userDef)
else if(process.env.NODE_ENV !== 'production')
//非生产环境有一个判重处理,computed 对象中的属性不能和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)
/**
* 代理computed对象中的key到target(vm)上
*/
export function deinedComputed(
target: any,
key: string,
userDef: Object | Function
)
const shouldCache = !isServerRendering()
//构造属性描述符(get,set)
if(typeof userDef === 'function')
sharedPropertyDefinition.get=shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertDefinition.set = userDef.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 is has no setter`,
this
)
//拦截对target.key的访问和设置
Object.defineProperty(target, key, sharedPropertyDefinition)
/**
* @return 返回一个函数,这个函数在访问vm.computedProperty 时会执行,然后返回执行结果
*/
function createComputedGetter (key)
//computed 属性会缓存的原理也是在这里结合watcher.dirty、watcher.evalaute、watcher.update实现的
return function computedGetter ()
//得到当前key对应的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if(watcher)
//计算key对应的值,通过执行computed.key 的回掉函数
//watcher.dirty属性就是大家说的computed计算结果会缓存的原理
//<template>
// <div>computedProperty</div>
// <div>computedProperty</div>
//</template>
// 像这种情况下,在页面的一次渲染中,两个dom中的computedProperty 只有第一个会执行
//computed.computedProperty 的回掉函数计算实际的值
//即执行 watcher.evalaute,而第二个就不走计算过程了
//因为上一次执行 watcher.evalaute时把watcher.dirty 置为了false
//待页面更新后,watcher.update 方法会将watcher.dirty重新置为true
//供下次页面更新时重新计算computed.key的结果
if(watcher.dirty)
watcher.evaluate()
if(Dep.target)
watcher.depend()
return watcher.value
/**
*功能同createComputedGetter 一样
*/
function createGetter(fn)
return function computedGetter()
return fn.call(this,this)
initWatch
/src/core/instance/state.js
/**
*处理watch对象的入口,做了两件事
* 1.遍历watch对象
* 2.调用createWatcher函数
*@param * watch =
* 'key1':function(val,oldval),
* 'key2':'this.methodName',
* 'key3':
* handler:function(val, oldVal),
* deep:true
* ,
* 'key4':[
* 'this.methodName',
* function handler1(),
*
* handler:function(),
* immediate:true
*
* ],
* 'key.key5' ...
*
*/
function initWatch (vm:Component,watch:Object)
//遍历watch对象
for(const key in watch)
const handler = watch[key]
if(Array.isArray(handler))
//handler为数组,遍历数组,获取其中的每一项,然后调用createWather
for(let i=0;i < handler.length;i++)
createWatcher(vm, key, handler[i]
else
createWatcher(vm, key, handler)
/**
*1.兼容性处理: 保证handler肯定是一个函数
*2.调用$watch
*@returns
*/
function createWatcher
vm: Component,
exOrFn: string | Function,
handler: any,
options?: Object
//如果handler为对象,则获取handler选项的值
if(isPlainObject(handler))
options = handler
handler = handler.handler
//如果handler为字符串,则说明是一个methods方法,获取vm[handler]
if(typeof handler ==== 'string')
handler = vm[handler]
return vm.$watch(expOrFn, handler, options)
/**
*创建watcher,返回unwatch,共完成如下五件事
* 1.兼容性处理,保证最后new Watcher时的cb为函数
* 2.标示用户watcher
* 3.创建watcher实例
* 4.如果设置immediate,则立即执行一次cb
*@param * expOrFn key
*@param * cb回掉函数
*@param * options配置项,用户直接调用this.$watch时可能会传递一个配置项
*@return 返回 wnwatch函数,用于取消watch监听
*/
Vue.prototype.$watch = function(
exOrFn:string | Function,
cb: any,
options? Object
):Function
const vm:Component = this
//兼容性处理,因为用户调用vm.$watch时设置的cb可能是对象
if(isPlainObject(cb))
return createWatcher(vm, exOrFn, cb, options)
//options.user 表示用户watcher,还有渲染watcher,即updateComponent 方法中实例化的watcher
options = options ||
options.user = true
//创建watcher
const watcher = new Watcher(vm,exOrFn,cb,options)
//如果设置了immediate为true,则立即执行一次回掉函数
if(options.immediate)
try
cb.call(vm, watcher.value)
catch (error)
handlerError(error, vm, `callback for immediate watcher "$watcher.expression`
//返回一个unwatch函数,用户解除监听
return function unwatchFn()
watcher.teardown()
以上是关于vue2源码学习-响应式原理的主要内容,如果未能解决你的问题,请参考以下文章