1. 程式人生 > >vueJs原始碼解讀0-2

vueJs原始碼解讀0-2

上篇文章已經對index.js中的基本呼叫情況做了說明,接下來的幾篇將對各個函式做仔細的分析,能力有限,文章中不足之處,希望大家能夠指正!

上篇中提到在instance/vue中使用了9個高階函式來構建(install)Vue建構函式(並不會呼叫該建構函式的進行初始化的過程),一切等在使用new Vue({….})的時候將一個全新的物件作為函式內this的值,返回該新物件作為結果(函式 呼叫中建構函式呼叫的方法)

function Vue (options) {
  this._init(options)

}

建立函式中函式申明的建立的方法(涉及知識函式宣告的提升),this為函式方法呼叫的接收者,一般為建構函式呼叫的方式 new Vue()

initMixin(Vue)

Mixin-mix in( 混入加入) 可能是作者取這個名字的原因吧(只是妄加猜測,具體已作者本人意圖為準)

import initMixin from ‘./internal/init’ 會在internal/init中就會存在default的export接下來的分析將會從這個開始著手

逐行程式碼的分析如

let uid = 0

設定了uid只在當前的塊中有效,let具體可以在預熱解讀中有說明

export default function (Vue) {
.....
}

export default 匿名函式具體用法可以在預熱解讀中找到,接下來的也即是function中的內容,接收引數為Vue

 Vue.prototype._init = function (options){
    .....
  }

Vue也即是傳入進來的引數(函式名),函式會自帶一個預設的prototype的屬性在新建立之前幾乎為空,當使用new建立Vue的例項的時候,會得到自動分配的原型物件,存在User的prototype例如我們使用 var vm=new Vue({})來初始化構造方法的時候(先查詢自身的屬性再去原型鏈中進行查詢)

這裡寫圖片描述

對於javaScript的繼承機制基於原型鏈(ES5),javaScript的例項物件由建構函式與在例項件共享的原型物件組成,對於原型用的較多的1個建立的方法(m.prototype)與2個獲取原型的方法(obj.getPrototypeOf(m)和obj. _ proto

_).其中

 options = options || {}

判斷options是否為null,0,-0,undefined,false,”,NaN等情況(以上也即是js的7大假值),當options不為假則直接執行賦值,否則為{}。(涉及賦值運算子優先順序,||運算時當左邊為假才會執行右邊;左右options的不一樣);

    this.$el = null
    this.$parent = options.parent
    this.$root = this.$parent? this.$parent.$root : this
    this.$children = []
    this.$refs = {}       // child vm references
    this.$els = {}        // element references
    this._watchers = []   // all watchers as an array
    this._directives = [] // all directives

this例項化之後也即是Vue物件,未指定呼叫接收者為undefined;先來了解下基本的含義,在後面涉及到會仔細介紹:
$parent存在的話則為父例項; $root:當前元件樹的根 Vue 例項。如果當前例項沒有父例項為自身。$children 當前例項的直接子元件。
$refs:一個物件,包含註冊有 v-ref 的子元件。\$els物件中包含註冊有 v-el 的 DOM 元素。

  // a uid
    this._uid = uid++ //上文中定義的let uid

    // a flag to avoid this being observed  設定標誌避免被檢測到
    this._isVue = true

    // events bookkeeping  事件統計
    this._events = {}            // registered callbacks
    this._eventsCount = {}      // for $broadcast optimization  //$broadcast的優化

    // fragment instance properties fragment例項屬性
    this._isFragment = false
    this._fragment =         // @type {DocumentFragment}
    this._fragmentStart =    // @type {Text|Comment}
    this._fragmentEnd = null // @type {Text|Comment}

    // lifecycle state  生命週期狀態
    this._isCompiled =
    this._isDestroyed =
    this._isReady =
    this._isAttached =
    this._isBeingDestroyed =
    this._vForRemoving = false
    this._unlinkFn = null

各個例項到底什麼意思,相信也很困惑,這裡只要稍微有印象即可在之後的分析與學習中會逐步解釋

  // context:
    // if this is a transcluded component, context
    // will be the common parent vm of this instance
    // and its host.
    如果這是一個嵌入式的元件,上下文將是這個例項共有父例項(或宿主)
    this._context = options._context || this.$parent

    // scope:
    // if this is inside an inline v-for, the scope
    // will be the intermediate scope created for this
    // repeat fragment. this is used for linking props
    // and container directives.
    如果這是在一個內聯的v-for,將由這個迴圈的片段產生中間的作用域範圍,被用在連結父元件的資料和指令容器
    this._scope = options._scope

    // fragment:
    // if this instance is compiled inside a Fragment, it
    // needs to reigster itself as a child of that fragment
    // for attach/detach to work properly.
    如果這個例項在某個片段裡已經編譯,需要在該片段上進行註冊,利於attach或detach的正常工作
    this._frag = options._frag
    if (this._frag) {
      this._frag.children.push(this)
    }

    // push self into parent / transclusion host
    如果存在父例項則將其建立雙方的連結
    if (this.$parent) {
      this.$parent.$children.push(this)
    }

    // merge options.
    合併options,含有一個mergeOptions的函式
    options = this.$options = mergeOptions(
      this.constructor.options,
      options,
      this
    )

import { mergeOptions } from ‘../../util/index’

export * from './lang'
export * from './env'
export * from './dom'
export * from './options'  //options
export * from './component'
export * from './debug'
export { defineReactive } from '../observer/index'

export * 也即是將所有的標記過的均匯出

這裡寫圖片描述

在options.js中可以看到

  /**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 * 主要用於在例項化與繼承
 * @param {Object} parent
 * @param {Object} child
 * @param {Vue} [vm] - if vm is present, indicates this is
 *                     an instantiation merge. 
 *
  options = this.$options = mergeOptions(
      this.constructor.options,
      options,
      this
    )
 */
export function mergeOptions (parent, child, vm) {

}

下面均是該函式內的程式碼片段

  guardComponents(child)
  guardProps(child)
  function guardComponents (options){
   ...
  }

對於guardComponents主要用作options中的元件構造,下文的程式碼為guardComponents中的程式碼

var vm = new Vue({
  el: '...',
  data:{},
  components: {
  'a':{},
  'b':{}
  }
})
 if (options.components) {
    ......
  }

如果在options中存在components的存在,則會進行下部分的程式碼

 var components = options.components =
      guardArrayAssets(options.components)

賦值語句從右至左,使用guardArrayAssets函式將陣列形式的轉化為鍵值對的形式

guardArrayAssets:

function guardArrayAssets (assets) {
//assets 也即是傳遞過來的options.components
//1.components:{'s':{},'d':{}}
//2.componets:[{'name':'...','id':'...'}]
//3.?
  if (isArray(assets)) {
    var res = {}
    var i = assets.length
    var asset
    //陣列迴圈取值組成鍵值對的形式 key值由id決定
    while (i--) {
      asset = assets[i]
      var id = typeof asset === 'function'
        ? ((asset.options && asset.options.name) || asset.id)
        : (asset.name || asset.id)
        //id異常情況
      if (!id) {
        process.env.NODE_ENV !== 'production' && warn(
          'Array-syntax assets must provide a "name" or "id" field.'
        )
      } else {
      //規整為key-value的形式
        res[id] = asset
      }
    }
    return res
  }
  return assets
}

可以看出有3種方式填寫的option.components,主要目的是規整為字典的形式便於後面的直接呼叫

下面回到guardComponents

 var components = options.components =
      guardArrayAssets(options.components)

var ids = Object.keys(components)

這裡用到了一個Object.keys方法,獲取規整後的components的鍵值陣列

The Object.keys() method returns an array of a given object’s own enumerable properties, in the same order as that provided by a for…in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
返回一個列舉所有物件屬性的陣列,類似於for-in 列舉(並不保證按物件的順序輸各個屬性 ,不可預測的順序unpredicted order)

接下來飄逸與自然的for迴圈如下:


 for (var i = 0, l = ids.length; i < l; i++) {
      var key = ids[i]
      if (commonTagRE.test(key) || reservedTagRE.test(key)) {
        process.env.NODE_ENV !== 'production' && warn(
          'Do not use built-in or reserved HTML elements as component ' +
          'id: ' + key
        )
        continue
      }
      // record a all lowercase <-> kebab-case mapping for
      // possible custom element case error warning
      if (process.env.NODE_ENV !== 'production') {
        map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
      }
      def = components[key]
      if (isPlainObject(def)) {
        components[key] = Vue.extend(def)
      }
    }

其中commonTagRE與reservedTagRE為options.js中匯入的兩個屬性
import { commonTagRE, reservedTagRE } from './component'

export const commonTagRE = /^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/i

export const reservedTagRE = /^(slot|partial|component)$/i

const為es6中的關鍵字,表示不可以修改常量只在當前模組中有效,想要在其他模組中引用也即是利用前面提到的export命令,不會提升,必須先申明後使用

變數的提升:某一作用域範圍內
console.info(v) ==> var v
var v=’tev’ console.info(v)
v=’tev’

正則表示式中:

/i (忽略大小寫)
/g (全文查找出現的所有匹配字元)
/m (多行查詢)
/gi(全文查詢、忽略大小寫)
/ig(全文查詢、忽略大小寫)
鍵值中

 process.env.NODE_ENV !== 'production' && warn(
          'Do not use built-in or reserved HTML elements as component ' +
          'id: ' + key
        )

不要使用保留的slot,partial,component與Html的標籤作為鍵值

def = components[key]
      if (isPlainObject(def)) {
        components[key] = Vue.extend(def)
      }

使用vue.extend定義元件,如下例子將更好解釋

 components:{
    'my-component':{
      template:'<div>A custom component!</div>'
    }
  },

html頁面中使用<\my-component><\/\my-component>等同於

 components:[{
    // 'id':'my-component',
    'name':'my-component',
    'template':'<div>A custom component!</div>'
    }
  ],

等同於:

var MyComponent = Vue.extend({
  template: '<div>A custom component!</div>'
})
Vue.component('my-component', MyComponent)

這裡寫圖片描述

上面的程式碼中,這裡涉及到兩個isplainObject與Vue.extend,x下面將對其進分析

 if (isPlainObject(def)) {
        components[key] = Vue.extend(def)
      }
import {
  isArray,
  isPlainObject,
} from './lang'
/**
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 *
 * @param {*} obj
 * @return {Boolean}
 */
//使用toString()方法判斷型別,可以表面toString對null的判斷方法,如下圖所示

var toString = Object.prototype.toString
var OBJECT_STRING = '[object Object]'
export function isPlainObject (obj) {
  return toString.call(obj) === OBJECT_STRING
}

/**
 * Array type check.
 *
 * @param {*} obj
 * @return {Boolean}
 */
//也即是呼叫Array方法中的isArray方法
export const isArray = Array.isArray

這裡寫圖片描述

Vue.extend在global-api.js中在接下來的中會分析

感覺跑偏了很遠這樣流水式的分析要知道自己要回到哪個地方

mergeOptions

export function mergeOptions (parent, child, vm) {
//在Options之前將options:components與props定義好
  guardComponents(child)
  guardProps(child)
  ....
}

Vue.prototype._init

 Vue.prototype._init = function (options){
 ...
 options = this.$options = mergeOptions(
      this.constructor.options,
      options,
      this
    )
}

props的定義

A list/hash of attributes that are exposed to accept data from the parent component(從父元件中獲得資料). It has a simple Array-based syntax (陣列形式)and an alternative Object-based(物件形式) syntax that allows advanced configurations such as type checking, custom validation and default values(物件形式用於高階的設定如 型別檢查,自定義驗證,預設值等).

 guardProps(child)將所有的props規格化為基於物件的格式(雖然支援陣列與物件的兩種形式),child也即是為init中傳入的options
 props: ['size', 'myMessage']
  props: [{'name':'size'},{'name':'myMessage'}],
  props: {
    // 只檢測型別
    size: Number,
    // 檢測型別 + 其它驗證
    name: {
      type: String,
      required: true,
      // 雙向繫結
      twoWay: true
    }
  }
function guardProps (options) {
 var props = options.props
  var i, val
  if (isArray(props)) { //為陣列型別
    options.props = {}
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
      //為String型別的時候將其值設定為空 'size':null
        options.props[val] = null

      } else if (val.name) {
      //取val.name
        options.props[val.name] = val
      }
    }
  } else if (isPlainObject(props)) {
    var keys = Object.keys(props)
    i = keys.length
    while (i--) {

      val = props[keys[i]]
      if (typeof val === 'function') { //{ 初始為Object型別 {} }
        props[keys[i]] = { type: val }
      }
    }
  }
 }