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源码学习-响应式原理的主要内容,如果未能解决你的问题,请参考以下文章

vue2源码学习-响应式原理

vue2源码学习-响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理