聊一聊 Vue 里的 Object.defineProperty

今天小新独自一人在家学习Vue,十分好奇上次跟小兰探讨的双向数据绑定依赖的响应式,这回他决定仔细看看是怎么回事。

让我们来看一下 new Vue() 发生了些什么:

  1. new Vue() 调用构造函数创建实例 this 及执行 _init,同时把options 通过new的动作传入。

    function Vue(options) {
      if (__DEV__ && !(this instanceof Vue)) {
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options) // 执行init
    }
    new Vue({ // options 
      el: '#app',
      data(){
        return {
          name: 'Vue'
        }
      }
    })
    
  2. _init 初始化options、事件、声明周期。

    vue_init
    export function initMixin(Vue: typeof Component) {
      Vue.prototype._init = function (options?: Record<string, any>) {
        const vm: Component = this
        // a uid
        vm._uid = uid++ // 给当前组件添加标识
       
        // 初始化事件、声明周期等
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate', undefined, false /* setContext */)
        initInjections(vm) // resolve injections before data/props
        initState(vm) // 初始化 data
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
    
        /* istanbul ignore if */
        if (__DEV__ && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }
    		
        // 判断是否有root
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }
    
  3. 在第2步骤时有这么一个函数initState,这个函数就是对data 的属性进行响应性关键。

    export function initState(vm: Component) {
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
    
      // Composition API
      initSetup(vm)
    
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm) // 就是这个地方,把当前实例传入初始化data
      } else {
        const ob = observe((vm._data = {}))
        ob && ob.vmCount++
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    
    function initData(vm: Component) {
      let data: any = vm.$options.data
      // 判断data是函数还是对象,同时返回data里的内容
      data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
     
      // observe data 处理data
      const ob = observe(data)
      ob && ob.vmCount++
    }
    
    
  4. observe 方法对数组进行单独处理

    export function observe(
      value: any,
      shallow?: boolean,
      ssrMockReactivity?: boolean
    ): Observer | void {
      return new Observer(value, shallow, ssrMockReactivity)
    }
    
    export class Observer {
      dep: Dep
      vmCount: number // number of vms that have this object as root $data
    
      constructor(public value: any, public shallow = false, public mock = false) {
        
        for (let i = 0, l = arrayKeys.length; i < l; i++) {
          const key = arrayKeys[i]
          def(value, key, arrayMethods[key])
        }
        if (isArray(value)) {
          if (!shallow) {
            // 处理数组
            this.observeArray(value)
          }
        } else {
          // 处理对象
          const keys = Object.keys(value)
          for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
          }
        }
      }
    
      /**
       * Observe a list of Array items. 数组循环调用
       */
      observeArray(value: any[]) {
        for (let i = 0, l = value.length; i < l; i++) {
          observe(value[i], false, this.mock)
        }
      }
    }
    
  5. 数组原型方法的处理。

    const arrayProto = Array.prototype
    export const arrayMethods = Object.create(arrayProto)
    
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
      // cache original method
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator(...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        if (__DEV__) {
          ob.dep.notify({
            type: TriggerOpTypes.ARRAY_MUTATION,
            target: this,
            key: method
          })
        } else {
          ob.dep.notify()
        }
        return result
      })
    })
    
    
  6. $set 给目标对象添加响应式属性。

    export function set(
      target: any[] | Record<string, any>,
      key: any,
      val: any
    ): any {
      const ob = (target as any).__ob__
      if (isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        // when mocking for SSR, array methods are not hijacked
        if (ob && !ob.shallow && ob.mock) {
          observe(val, false, true)
        }
        return val
      }
      
      defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock)
      
      return val
    }
    

结语:

经过上面5个步骤我们不难看出只有在data 里定义的属性才能被Object.defineProperty编辑,除此之外的是不能被响应式,解决方法是通过实例方法$set

提示:文章展示的源码有删节,只保留说明此次研学的内容。

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值