vue2源码学习(3)-响应式原理(一)

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

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值