写于vue3.0发布前夕的helloworld

前言:

vue3.0马上要来了,于今昔写一篇vue将一个字符串hellowrold渲染于页面的过程,慰藉我这几个月写vue的‘枯燥’。

源码版本是2.6.10。

开始:

我们的模板足够简单:

<div id="app">{{msg}}</div>

vue实例的配置也足够简单:

new Vue({
        el:'#app',
        data:function(){
            return{
                msg: 'hello,world'
            }
        }
    })

下面带着配置进入vue的构造函数:

function Vue (options) {
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }

构造函数足够简单,判断了一下是否用new调用,然后进入实例的_init_方法:

Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      vm._uid = uid$3++;
        
      var startTag, endTag;
      /* istanbul ignore if */
      if (config.performance && mark) {
        startTag = "vue-perf-start:" + (vm._uid);
        endTag = "vue-perf-end:" + (vm._uid);
        mark(startTag);
      }

      // a flag to avoid this being observed
      vm._isVue = true;
      // merge options
      if (options && options._isComponent) {
        // 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 */
      {
        initProxy(vm);
      }
      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');

      /* istanbul ignore if */
      if (config.performance && mark) {
        vm._name = formatComponentName(vm, false);
        mark(endTag);
        measure(("vue " + (vm._name) + " init"), startTag, endTag);
      }

      if (vm.$options.el) {
        vm.$mount(vm.$options.el);
      }
    };
  }

init函数一进来,先使用vm保存了this,然后给组件定义了一个_uid属性,每初始化一个组建这个东西就+1从0开始,下来一个

config.performance && mark 这个属性是用来记录vue的性能的相关参数,因为是根组建,所以下来的if-else走else分支,然后开始mergeOptions, mergeOptions其实就是将vue实例上缺省的属性设置成了默认值,然后merge之后我们的option长这个亚子:

"{"components":{},"directives":{},"filters":{},"beforeCreate":[null],"destroyed":[null],"el":"#app"}"

接着开始proxy:

initProxy = function initProxy (vm) {
      if (hasProxy) {
        // determine which proxy handler to use
        var options = vm.$options;
        var handlers = options.render && options.render._withStripped
          ? getHandler
          : hasHandler;
        vm._renderProxy = new Proxy(vm, handlers);
      } else {
        vm._renderProxy = vm;
      }

这个hasProxy是用于检测当前环境是否支持Proxy,如果支持之后就进行代理操作,三元运算符表示如果当前实例上已经有render就用getHandler代理操作,否则就用hasHandler,因为我们没有render故而得到了hasHandler代理操作:

var hasHandler = {
      has: function has (target, key) {
        var has = key in target;
        var isAllowed = allowedGlobals(key) ||
          (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));
        if (!has && !isAllowed) {
          if (key in target.$data) { warnReservedPrefix(target, key); }
          else { warnNonPresent(target, key); }
        }
        return has || !isAllowed
      }
    };

proxy就不用细讲了,以上的代理的作用就是检测当前vm上有某个key时,也就是 key in vm时,proxy代理了这个操作。具体细节就是 先检测当前vm上有无此属性,然后isAllowed 这个值呢 由allowedGlobals产生,allowedGlobals呢是一个由全局对象或着js语言关键词组成的闭包map函数,长这样:

首先是关键字部分:

var allowedGlobals = makeMap(
      'Infinity,undefined,NaN,isFinite,isNaN,' +
      'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
      'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
      'require' // for Webpack/Browserify
    );

makeMap就是用这个字符串产生一个map对象,然后返回一个闭包函数,makeMap长这样:

function makeMap (
    str,
    expectsLowerCase
  ) {
    var map = Object.create(null);
    var list = str.split(',');
    for (var i = 0; i < list.length; i++) {
      map[list[i]] = true;
    }
    return expectsLowerCase
      ? function (val) { return map[val.toLowerCase()]; }
      : function (val) { return map[val]; }
  }

回到has那个方法里,也就是说如果现在key是一个关键字那么就不用执行后面的东西啦,此时isAllowed就是true,否则,如果key是个字符串而且以_开头,而且vm.$data无法访问,那么此时isAllowed就是false啦。下来那个if就是说如果vm上没有属性key,并且isAllowed是false,就会执行下边的两个警告。最后返回结果。然后代理完成,并在当前vm实例上绑定了一个属性_renderProxy引用当前经过proxy之后的vm。接下来进入各种初始化过程,包含生命周期钩子函数,事件,render,然后在调用了第一个生命周期钩子,beforeCreate,随后初始化injection,state,以及provider。injection和provider是vue的依赖注入机制,这里我们不需要细讲,我们着重进入initState:

function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    if (opts.methods) { initMethods(vm, opts.methods); }
    if (opts.data) {
      initData(vm);
    } else {
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
    }
  }

一进来,先初始化了一个_watchers,然后开始初始化props和methods,因为这里我们没有定义这两个东西所以直接来到initData:

function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      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
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        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);
      }
    }
    // observe data
    observe(data, true /* asRootData */);
  }

首先拿到data,(敲黑板,这里拿到data之后,还会在当前实例上声明一个_data用于保存当前组建得data数据,这个_data后期会代理vm上访问data里得值)。然后进入while循环,如果data中某个key值与props或者methods上的key重复了,会予以警告,否则进入另一个代理程序,proxy:

function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }

这个代理呢实际就是将vm.key 的get和set代理到了 vm._data.key上,往后在vue实例中使用 this.msg = 'balabal'时,实际上是将这个值set到了this,_data.msg上。这是后话。代理完成之后,来到了observe:

 /**
   * Attempt to create an observer instance for a value,
   * returns the new observer if successfully observed,
   * or the existing observer if the value already has one.
   */
  function observe (value, asRootData) {
    if (!isObject(value) || value instanceof VNode) {
      return
    }
    var ob;
    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
  }

入口处的判断表明如果value是原始值或者是VNode的实例时不予以ovserve的。随后如果此对象已经被observe过了的话会有一个'__ob__'属性引用了observe当前对象之后的结果,如果有ob就用以前已经存在的否则经过一系列判断之后进入Observer,这些判断条件包括一个全局变量shouldObserve声明的默认值就是true,一个是否时服务端渲染的函数这里当然是true,然后判断是否为对象或者数组,这里value就是我们data的原始值,所以为true,然后判断当前对象是否可扩展,of course是ture,然后下边这个属性因为现在还没有,取反就是true,然后进入Observer:

 var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };

这里,observe实例先保存了传入的value,然后会有一个dep实例生成,然后下来定义了一个observe 判断时会用到的属性__ob__,接着判断是数组,还是对象,这里我们的data是一个对象走walk:

/**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };

walk遍历对象,每一个键值,使用defineReactive$$1监听其get,set方法:

 /**
   * Define a reactive property on an Object.
   */
  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
      return
    }

    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        var 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 (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();
      }
    });
  }

defineReactive$$1一进来也是生成了一个dep对象,这里看下dep长什么亚子:

{"id":3,"subs":[]}

基本属性是一个id,还有一个subs,subs里会保存依赖收集时对应组建的watcher,这个我们下面会讲到,然后原型方法上是一组关于subs的操作方法,不再细讲。接着取到属性描述符,判断是否configurable,取得已经事先配置好的getter setter,然后如果是非shallow模式,会继续observe,这里我们的val是一个字符串,非对象,所以observe会直接返回,然后我们继续走到Object.defineProperty这里,对于data上的属性,都成为响应式属性,即get方法收集依赖,(他里边这个Dep.target就是收集依赖的时候的watcher实例),set唤起更新程序。设置完毕,层层返回直到observe函数里。返回ob对象。然后observe完毕。继续返回到initState函数里,下来initState函数会继续初始化computed和watch属性,我们代码里没有设置直接返回到最开始的init函数里,随后在当前vue实例初始化完毕之后,created钩子会触发。

篇幅过长,新开一篇接着讲。

posted @ 2022-01-28 10:18  子龙_子龙  阅读(11)  评论(0编辑  收藏  举报