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()
}
}