美文网首页前端实践
Vue2源码分析——处理data

Vue2源码分析——处理data

作者: forzoom | 来源:发表于2019-12-28 15:40 被阅读0次
    new Vue({
      el: '#app',
      data: {
        message: 'Hello World!',
      },
    })
    

    这是我在使用Vue时写下的第一段代码,我会好奇,在运行这段代码时,其背后Vue究竟完成了哪些操作呢?阅读并理解源码,让我们看到隐藏在框架后的那一片片齿轮,在需要性能优化,或是遇到疑难解决错误时,也许能更轻松地解决问题。

    刚开始学习编程时,一般会寻找程序运行的入口。在src/core/instance/index.js中,可以看到对Vue的定义,也是我们所寻找的入口。

    约定在先,文章中只摘录部分源码,建议在阅读之后,可以按照文章中的思路顺序看一遍源代码,所有的源码基于Vue@2.16.10版本。在引用的代码之前,都会用注释标记这段代码来自于哪个文件。

    // file: src/core/instance/index.js
    function Vue(options) {
      // ..
      this._init(options);
    }
    
    // ..
    initMixin(Vue);
    stateMixin(Vue);
    

    实际上HelloWorld项目中调用Vue构造函数时,最终会调用initState()

    // file: src/core/instance/init.js
    import { initState } from './state'
    
    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        // ..
        initState(vm)
        // ..
      }
    }
    
    // file: src/core/instance/state.js
    export function initState(vm: Component) {
      // ..
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      // 对于data的处理
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    

    处理Data数据

    在initState中,可以看到对开发过程中常接触到的props/data/computed的处理函数,我们来看看对于data的处理过程——initData()。

    // file: src/core/instance/state.js
    function initData(vm: Component) {
      // ..
      observe(data); // 最后是使用observe函数对data进行了处理
    }
    

    来查看observe函数中对于value,也就是data的处理

    // file: src/core/observer/index.js
    export function observe(value: any, asRootData: ?boolean): Observer | void {
      // ..
      let ob: Observer | void
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__
      } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
        // 参数value将是定义的data对象
        ob = new Observer(value)
      }
      // ..
      return ob
    }
    

    其中最主要的是创建了一个Observer对象,来看看Observer对象的构造流程

    // file: src/core/observer/index.js
    export class Observer {
      // ..
      constructor (value: any) {
        this.value = value
        this.dep = new Dep() // 之后会使用到
        // ..
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
          // ..
          this.observeArray(value)
        } else {
          // 因为目前定义的data是一个object,所以会执行walk函数
          this.walk(value)
        }
      }
    
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
    
      observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }
    

    在Observer构造函数中,因为我们在HelloWorld项目中传入的是一个object,最终将调用Observer了walk函数,即对于data中的每个property调用了defineReactive函数进行处理。

    调用defineReactive函数对property进行改造

    来看看defineReactive所进行的处理。其中用来到property descriptor,如果不了解的话,建议优先弄清楚再继续阅读。

    // file: src/core/observer/index.js
    export function defineReactive (
      obj: Object,
      key: string,
      val: any,
      customSetter?: ?Function,
      shallow?: boolean
    ) {
      const dep = new Dep()
    
      // 获取descriptor
      const property = Object.getOwnPropertyDescriptor(obj, key)
      // 如果descriptor定明确说明该property不能修改,那么不进行处理
      if (property && property.configurable === false) {
        return
      }
    
      // 获取已经定义的get和set函数,应该都是undefined
      const getter = property && property.get
      const setter = property && property.set
      if ((!getter || setter) && arguments.length === 2) {
        val = obj[key]
      }
    
      // ..
      // 重新定义property
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          const value = getter ? getter.call(obj) : val
          // ..
          return value
        },
        set: function reactiveSetter (newVal) {
          const value = getter ? getter.call(obj) : val
          // ..
          if (getter && !setter) return
          if (setter) {
            setter.call(obj, newVal)
          } else {
            // 将执行到这里
            val = newVal
          }
          // ..
        }
      })
    }
    

    在defineReactive中,重新将data中的message定义为accessor descriptor。当我们使用data.message时,调用reactiveGetter函数,最终返回的是val,当我们为data.message重新赋值时,调用reactiveSetter函数,实际上是为val重新赋值。

    结论

    对于data的处理可以简单地理解为,重新定义每个property,将其从value descriptor改造成accessor descriptor,至于为什么要这么做,在之后的computed/watcher的定义中会找到使用的地方。

    相关文章

      网友评论

        本文标题:Vue2源码分析——处理data

        本文链接:https://www.haomeiwen.com/subject/jtusoctx.html