美文网首页Vuevue收藏
48.vue响应式系统构建过程->initData

48.vue响应式系统构建过程->initData

作者: wo不是黄蓉 | 来源:发表于2022-02-10 22:13 被阅读0次

    我的vue版本 -> Vue.js v2.6.11

    概述:vue响应式系统构建过程主要是在Init阶段,在initState()方法中会对propsmethodsdatacomputedwatcher等内容进行初始化,在初始化data阶段会对传入的options中的data进行一些校验,接下来就是使用new Observer()data中的数据进行响应化处理

    1.src\core\instance\index.js -> vue源码的入口文件

    //入口
    function Vue(options) {
      //初始化vue
      this._init(options);
    }
    

    2.src\core\instance\init.js -> 初始化文件,主要关注initState()这个方法,其他的后面再做研究

    export function initMixin(Vue: Class<Component>) {
      //在initMinxin里面定义Vue的原型方法_init
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this;
        // a uid
        vm._uid = uid++;
    
        let startTag, endTag;
    
        // a flag to avoid this being observed
        vm._isVue = true;
        //合并选项
        // merge options
        if (options && options._isComponent) {
          //判断是不是一个组件,是的话执行initInternalComponent,否则合并options
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options);
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          );
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== "production") {
          initProxy(vm);
        } else {
          vm._renderProxy = vm;
        }
        // expose real self
        vm._self = vm; //把vm放在_self属性上暴露出去
        initLifecycle(vm); //初始化生命周期:$parent,$root,$children,$refs
        initEvents(vm); //对父组件传入事件添加监听,初始化事件
        initRender(vm); //生命$slots,$createElemnet,渲染相关的
        callHook(vm, "beforeCreate");
        initInjections(vm); // resolve injections before data/props,注入数据
        //数据初始化
        initState(vm); //初始化props、data、watch、methods、computed等属性,因此在beforeCreate的钩子函数中获取不到前面的这些定义的属性和方法
        initProvide(vm); // resolve provide after data/props,提供数据
        callHook(vm, "created");
        //最终挂载方法
        if (vm.$options.el) {
          vm.$mount(vm.$options.el);
        }
      };
    }
    

    3.src\core\instance\state.js

    export function initState(vm: Component) {
      vm._watchers = [];
      const opts = vm.$options;
      //初始化props
      if (opts.props) initProps(vm, opts.props);
      //初始化methods
      if (opts.methods) initMethods(vm, opts.methods);
      //初始化data
      if (opts.data) {
        initData(vm);
      } else {
        //没有创建则创建一个空对象,并设置位响应式
        observe((vm._data = {}), true /* asRootData */);
      }
      //初始化computed
      if (opts.computed) initComputed(vm, opts.computed);
      //初始化watch
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
      }
    }
    

    initData方法

    可以看到我们平时常见的一些报错提示信息

    • 校验传入的data是否是一个object类型
    • 遍历所有的keys(校验keys的合法性)
      • 是否有和methods重名的属性
      • 是否有和props重名的属性
    • 校验通过调用proxy方法,将所有的属性都挂载到_data属性上,vue中提供了使用$xxx直接访问vue实例属性的方法
    • 数据响应化处理
    function initData(vm: Component) {
      let data = vm.$options.data;
      data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
      //校验data必须是一个对象,其他的类似Array,Function等也算是对象类型,但是有着更精确的数据类型
      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
          );
      }
      // proxy data on instance
      //获取所有的data中的Key
      const keys = Object.keys(data);
      //获取配置对象上的props属性
      const props = vm.$options.props;
      //获取配置对象上的methods属性,所有的方法都在Methods对象中
      const methods = vm.$options.methods;
      let i = keys.length;
      while (i--) {
        const key = keys[i];
        if (process.env.NODE_ENV !== "production") {
          //校验methods->Key的唯一性
          if (methods && hasOwn(methods, key)) {
            warn(
              `Method "${key}" has already been defined as a data property.`,
              vm
            );
          }
        }
        //校验props->Key的唯一性
        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)) {
     //校验key是否是使用$或者下划线进行定义的,因为vue中定义了很多的$data,$el等来直接操作vue对象。把所有的key都代理到vm的_data属性上面
          proxy(vm, `_data`, key);
            //此处的proxy代理和observe中的walk代理有什么区别?
            //proxy代理的是将自定义的data中的所有属性定义到_data上,而walk中的Object.defineProperty则是针对data的值
        }
      }
      // observe data
      observe(data, true /* asRootData */);
    }
    

    4.src\core\observer\index.js
    关键一步:ob = new Observer(value),开始构建observe对象

    export function observe(value: any, asRootData: ? boolean): Observer | void {
      if (!isObject(value) || value instanceof VNode) {
        return
      }
      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
      ) {
        ob = new Observer(value)
      }
      if (asRootData && ob) {
        ob.vmCount++
      }
      return ob
    }
    

    src\core\observer\index.js
    在vue2中不能直接处理数组的方法,需要重写数组的方法,怎样重写数组的方法?看这里->监听数组变化的方法 原理是将常用的操作数组的方法列下来,替换原来数组的方法,同时也方便扩展。

    export class Observer {
      value: any;
      dep: Dep;
      vmCount: number; // number of vms that have this object as root $data
    
      constructor(value: any) {
        this.value = value
        //创建依赖收集的容器
        this.dep = new Dep()
        this.vmCount = 0
        //设置一个__ob__属性引用当前observer实例
        def(value, '__ob__', this)
        //判断类型
        if (Array.isArray(value)) {
          //如果是数组,替换数组对象的原型
          if (hasProto) {
            protoAugment(value, arrayMethods)
          } else {
            copyAugment(value, arrayMethods, arrayKeys)
          }
          //如果数组里面元素是对象还需要做响应化处理
          this.observeArray(value)
        } else {
          //walk方法,将每个值进行响应化处理
          this.walk(value)
        }
      }
      /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       */
      walk(obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
      /**
       * Observe a list of Array items.
       */
      observeArray(items: Array < any > ) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }
    

    defineReactive()可以看到我们常说的Object.defineProperty了,至此响应化data的部分就已经完成了。

    export function defineReactive(
      obj: Object,
      key: string,
      val: any,
      customSetter ? : ? Function,
      shallow ? : boolean
    ) {
      //和Key一一对应
      const dep = new Dep()
      //childOb,属性拦截,只要是对象类型都会返回childobj
      let childOb = !shallow && observe(val)
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          const value = getter ? getter.call(obj) : val
          //如果存在依赖
          if (Dep.target) {
            //依赖收集
            dep.depend()
            if (childOb) {
              //如果存在子obj,子obj也收集这个依赖?为什么要这么做?作用:Obj和父变了和子改变了都会通知进行更新。例:在访问时{obj.foo}无论是Obj变了还是obj.foo的值变了都会通知进行更新。
              childOb.dep.depend()
              if (Array.isArray(value)) {
                dependArray(value)
              }
            }
          }
          return value
        },
        set: function reactiveSetter(newVal) {
          const value = getter ? getter.call(obj) : val
          /* eslint-disable no-self-compare */
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return
          }
          /* eslint-enable no-self-compare */
          if (process.env.NODE_ENV !== 'production' && customSetter) {
            customSetter()
          }
          // #7981: for accessor properties without setter
          if (getter && !setter) return
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = !shallow && observe(newVal)
          dep.notify()
        }
      })
    }
    
    

    以上是我对vue初始化data的理解,有不对之处欢迎指证,共同学习!

    相关文章

      网友评论

        本文标题:48.vue响应式系统构建过程->initData

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