Vue 源碼分析系列一:new Vue()

Vue從何處來 (import Vue from 'vue') ?

  1. 首先分析一下vue的相關文件
\ UMD CommonJS ES Module (基於構建工具使用) ES Module (直接用於瀏覽器)
完整版 vue.js vue.common.js vue.esm.js vue.esm.browser.js
只包含運行時版 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js -
完整版 (生產環境) vue.min.js - - vue.esm.browser.min.js
只包含運行時版 (生產環境) vue.runtime.min.js - - -

詳 情 戳 這 裏html


在利用webpack的Vue項目中,在main.js中經過import Vue from 'vue'導入的vue包以下圖所示。(在node_modules/vue/package.json中配置了main屬性)vue

這個包功能實際上是不完整的,只有runtime-only的功能。來建立 Vue 實例、渲染並處理虛擬 DOM 等的代碼。基本上就是除去編譯器的其它一切。 解決辦法有一下三種:node

  1. import Vue from '../node_modules/vue/dist/vue.js'
  2. 在Vue包的package.json文件中main屬性指定的入口文件修改成【"main": "dist/vue.js"】(或者其餘功能完善的包)
  3. 在項目的webpack.config.js中添加resolve屬性,以下圖所示

webpack


源碼分析(只看主線)

import Vue from 'vue'過程當中,Vue 初始化主要就幹了幾件事情,合併配置,初始化生命週期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。目前主要注意一下initMixin方法 webpack

initMixin()

有刪減,目前主要看主線,其餘的功能逐步拆開來分析web

function initMixin (Vue) {
  Vue.prototype._init = function (options) { // 在Vue的原型上定義 _init 方法
    var vm = this;
    vm._self = vm;
    initLifecycle(vm); // 初始化生命週期
    initEvents(vm); // 事件
    initRender(vm); // render 方法
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm); // 初始化狀態
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');

    if (vm.$options.el) { // 掛載dom元素
      vm.$mount(vm.$options.el);
    }
  };
}
複製代碼

new Vue()

經過initMixin方法以後,執行 new Vue() 操做,在 Vue構造函數內部,調用了vm原型上的 _init()方法,在this._init()方法中咱們目前主要關注的是 initState()方法json

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options); // 這是在Vue原型上定義了的方法(initMixin方法中),使構建出來的Vue實例調用
}

new Vue({
  el: '#app',
  render: h => h(App)
})

複製代碼

initState()

咱們深刻到 initData 方法中瀏覽器

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);
  }
}
複製代碼

initData()

在這個方法中,須要注意的是 proxy() 方法bash

function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function' // 在vm 上定義一個 '_data' 屬性
    ? getData(data, vm) // getData()方法返回的就是咱們在vue data()方法中定義了的屬性和方法
    : data || {};
  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
  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 (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    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)) {
      proxy(vm, "_data", key); // 數據代理
    }
  }
  // observe data
  observe(data, true /* asRootData */);
}
複製代碼

proxy()

在這個方法中,經過Object.defineProperty()來實現數據劫持,當咱們經過 this.xxx 訪問咱們定義的數據時,其實就是訪問的 this._data.xxx,從而達到數據代理的目的。app

var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy (target, sourceKey, key) {
// target是vm, sourceKey是'_data', key 是咱們定義的data中的鍵
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
複製代碼

總結

在初始化的最後,檢測到若是有 el 屬性,則調用 vm.$mount 方法掛載 vm,掛載的目標就是把模板渲染成最終的 DOM,在下個系列中將分析Vue實例掛載的實現dom

相關文章
相關標籤/搜索