前言:

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钩子会触发。

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

写于vue3.0发布前夕的helloworld的更多相关文章

  1. VUE3.0发布,自己搞个文档网站

    9月19日,尤大神发表了VUE3.0版本的演说,强大且震撼,这两天一直在找网站文档,可能还未被百度收录,未找到文档网站.后来在github上面找到了中文代码. 地址为:https://github.c ...

  2. 预计2019年发布的Vue3.0到底有什么不一样的地方?

    摘要: Vue 3.0预览. 原文:预计今年发布的Vue3.0到底有什么不一样的地方? 作者:小肆 微信公众号:技术放肆聊 Fundebug经授权转载,版权归原作者所有. 还有几个月距离 vue2 的 ...

  3. 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...

  4. 纯小白入手 vue3.0 CLI - 3.3 - 路由的导航守卫

    vue3.0 CLI 真小白一步一步入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 尽量把纷繁的知识,肢解重组成为可以堆砌的知识. ...

  5. 纯小白入手 vue3.0 CLI - 2.1 - 组件 ( component )

    vue3.0 CLI 真小白入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 我的 github 地址 - vue3.0Study  ...

  6. 快速进阶Vue3.0

    在2019.10.5日发布了Vue3.0预览版源码,但是预计最早需要等到 2020 年第一季度才有可能发布 3.0 正式版. 可以直接看 github源码. 新版Vue 3.0计划并已实现的主要架构改 ...

  7. Vue3实战系列:Vue3.0 + Vant3.0 搭建种子项目

    最近在用 Vue3 写一个开源的商城项目,开源后让大家也可以用现成的 Vue3 大型商城项目源码来练练手,目前处于开发阶段,过程中用到了 Vant3.0,于是就整理了这篇文章来讲一下如何使用 Vue3 ...

  8. Rubinius 2.0 发布,Ruby 虚拟机

    Rubinius 2.0 发布了,官方发行说明请看这里. Rubinius是一个运行Ruby程序的虚拟机,其带有Ruby的核心库. Rubinius的设计决定了其调试功能的强大,使得在运行时常规的Ru ...

  9. Restful.Data v2.0发布,谢谢你们的支持和鼓励

    v1.0发布后,承蒙各位博友们的热心关注,也给我不少意见和建议,在此我真诚的感谢 @冰麟轻武 等朋友,你们的支持和鼓励,是这个开源项目最大的推动力. v2.0在除了细枝末节外,在功能上主要做了一下更新 ...

  10. 开源搜索引擎Iveely 0.7.0发布,不一样,那就让他不一样!

    2012年08月05日,Iveely Search Engine 0.1.0发布,今天,怀着对于未来的追求,终于,0.7.0如期和大家见面了,7个版本,历时2年4个月,感谢大家的支持,感谢我不离不弃的 ...

随机推荐

  1. mybatis:自定义映射关系resultMap

    创建表t_emp 定义实体类 package org.example.entity; public class Emp { private Integer empId; private String ...

  2. Solution Set - NOIP2022

    种花 枚举 C 或者 F 最左边的那一竖,考虑对于每一个这一竖上的全 \(0\) 区间 \([l,r]\) 求答案. 记每个点向右延伸最多延伸到 \(L_{i,j}\),对于 C 的情况,枚举列 \( ...

  3. 一台服务器部署ShareWAF,后面接多台Web服务器,该如何配置?

    ShareWAF做为WAF,可以不只是WAF,还可以充当负载或路由的角色. 比如可以有这样一种部署架构: 在此结构中,ShareWAF部署于一台服务器,后面接多台独立的WEB服务器. ShareWAF ...

  4. 多个pie环形图 逆时针旋转

    效果图如下  代码如下 data = [ { name: "经济目的", value: 754, }, { name: "网络安全爱好者", value: 61 ...

  5. 用到的jar包作用随笔,吼吼

    名称 版本 说明 spring spring.jar(2.5) spring基础包                   公司基础包 isskill-pro0.7.1.2.jar(0.7.1.2) 包含 ...

  6. etcdctl 安装与使用

    介绍 etcdctl是一个提供简洁命令的etcd客户端,使用etcdctl可以直接和etcd服务打交道,对etcd中的键值对进行增删改查. 安装etcdctl 下载etcdctl工具 下载地址:etc ...

  7. springboot项目的创建和兼容jsp和注解开发,详细有效(注解和配置两种)

    如果这篇博客能给你带来帮助不胜荣幸,如果有不对的地方,都是码农欢迎指正.直接进入正题 在这里首先我要声明一下我个人的感觉如果你会ssm(spring+mybatis+springmvc)的话,那么你可 ...

  8. Visual Studio Code 使用总结

      记录一下个人在使用 VS Code 中的一些插件和设置.   该配置在编写 vue + iview 项目时使用. 文件路径 用户文件路径:%AppData%/Code/User 用户设置:sett ...

  9. RabbitMQ-01-使用Java进行简单消息发送与接收

    前言 这里使用手动管理jar与使用Maven管理jar两种方式,分别演示消息的发送和接收. 手动管理jar实现消息发送与接收 添加jar amqp-client-5.7.1.jar slf4j-api ...

  10. QML与python互相通信

    解决python与QML的通信问题: QML中直接调用python函数 python发送信号,QML响应信号并进行相应处理 py文件 # This Python file uses the follo ...