首頁  >  文章  >  web前端  >  淺析VueX的原始碼內容

淺析VueX的原始碼內容

不言
不言原創
2018-07-20 15:30:471786瀏覽

Vuex是一個專門為Vue.js應用程式開發的狀態管理模式。針對組件繁多互動複雜的單一頁面應用,Vuex提供了一種便利、準確和可預測的狀態管理方式,方便組件之間的資料共享和修改。

檔案架構如下

  • /module

  • /plugins

  • helpers.js

  • index.esm.js

  • index.js

  • #store.js

  • util.js

util.js

先從最簡單的工具函數開始。

find函數

/**
 * Get the first item that pass the test
 * by second argument function
 *
 * @param {Array} list
 * @param {Function} f
 * @return {*}
 */
export function find (list, f) {
  return list.filter(f)[0]
}

find函數的測試案例

it('find', () => {
  const list = [33, 22, 112, 222, 43]
  expect(find(list, function (a) { return a % 2 === 0 })).toEqual(22)
})

解析:

  • 先用斷言函數f過濾列表list,最後取過濾後列表的第一個元素。

deepCopy函數

/**
 * Deep copy the given object considering circular structure.
 * This function caches all nested objects and its copies.
 * If it detects circular structure, use cached copy to avoid infinite loop.
 *
 * @param {*} obj
 * @param {Array<Object>} cache
 * @return {*}
 */
export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typeof obj !== &#39;object&#39;) {
    return obj
  }

  // if obj is hit, it is in circular structure
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

deepCopy的測試案例

  // 普通结构
  it(&#39;deepCopy: nornal structure&#39;, () => {
    const original = {
      a: 1,
      b: &#39;string&#39;,
      c: true,
      d: null,
      e: undefined
    }
    const copy = deepCopy(original)

    expect(copy).toEqual(original)
  })
  
  // 嵌套结构
  it(&#39;deepCopy: nested structure&#39;, () => {
    const original = {
      a: {
        b: 1,
        c: [2, 3, {
          d: 4
        }]
      }
    }
    const copy = deepCopy(original)

    expect(copy).toEqual(original)
  })
  
  // 循环引用结构
  it(&#39;deepCopy: circular structure&#39;, () => {
    const original = {
      a: 1
    }
    original.circular = original

    const copy = deepCopy(original)

    expect(copy).toEqual(original)
  })

解析:

  • 功能:支援循環引用的深克隆函數

  • 第一個if判斷obj === null || typeof obj !== 'object'判斷如果不是引用型別直接回傳(基本型別是值拷貝),也是遞迴的一個出口。

  • 第二個判斷hit是判斷是否是循環引用,由於是循環引用,在cache中應該有快取到一份拷貝,直接取cache的,避免再重複拷貝一份。

  • 什麼是循環引用看測試案例第三個original.circular = original,循環引用和被引用的內容是一樣的,用快取就是避免重複的複製(內容一樣)

  • original.circular是循環引用,original是被迴圈引用

  • 先把cope放到cache#中,是在遞歸的時候,如果遇到循環引用,要確保cache中有一份被循環引用的copy ,但是copy必須是引用型別。

  • 為什麼cope必須是參考型別? 循環引用保存的是引用不是內容(這時候還沒拷貝完),在遞歸的時候並未完成拷貝,只有遞歸跑完了才完成拷貝,這樣未來被循環引用的內容改變時(拷貝完),循環引用的內容同步改變

  • #所以const copy = Array.isArray(obj) ? [] : {}必須是引用型別。

  • 最後Object.keys可以遍歷物件和陣列的所有鍵名(只傳回實例的屬性,不包含原型鍊和Symbol),實現遞歸複製。

  • 總共兩個出口,一個是基本型,另一個是循環引用。

forEachValue

/**
 * forEach for object
 */
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

測試案例

  it(&#39;forEachValue&#39;, () => {
    let number = 1

    function plus (value, key) {
      number += value
    }
    const origin = {
      a: 1,
      b: 3
    }

    forEachValue(origin, plus)
    expect(number).toEqual(5)
  })

解析:

  • ##一個遍歷物件的函數(支援物件和陣列)

  • fn(value, key)但是回呼函數第一個參數是值,第二個參數是鍵值

isObject

export function isObject (obj) {
  return obj !== null && typeof obj === &#39;object&#39;
}

測試案例

  it(&#39;isObject&#39;, () => {
    expect(isObject(1)).toBe(false)
    expect(isObject(&#39;String&#39;)).toBe(false)
    expect(isObject(undefined)).toBe(false)
    expect(isObject({})).toBe(true)
    expect(isObject(null)).toBe(false)
    expect(isObject([])).toBe(true)
    expect(isObject(new Function())).toBe(false)
  })

解析:

  • 判斷是不是對象,這裡沒有判斷是不是原生對象,數組也是通過的。

  • 由於typeof null === 'object'要先判斷是不是null

isPromise#

export function isPromise (val) {
  return val && typeof val.then === &#39;function&#39;
}

測試案例

  it(&#39;isPromise&#39;, () => {
    const promise = new Promise(() => {}, () => {})
    expect(isPromise(1)).toBe(false)
    expect(isPromise(promise)).toBe(true)
    expect(isPromise(new Function())).toBe(false)
  })

解析:

  • 判斷是不是Promise

  • 先判斷val不是undefined,然後才可以判斷val.then,避免報錯誤

  • 判斷依據是val.then是不是函數

assert

export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

測試案例:

  it(&#39;assert&#39;, () => {
    expect(assert.bind(null, false, &#39;Hello&#39;)).toThrowError(&#39;[vuex] Hello&#39;)
  })

解析:

  • #斷言函數,斷言不透過拋出一個自訂錯誤訊息的Error

#index.jsindex.esm.js

#index.js##<pre class="brush:js;toolbar:false;">import { Store, install } from &amp;#39;./store&amp;#39; import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from &amp;#39;./helpers&amp;#39; export default { Store, install, version: &amp;#39;__VERSION__&amp;#39;, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }</pre>

index.esm.js

<pre class="brush:js;toolbar:false;">import { Store, install } from &amp;#39;./store&amp;#39; import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from &amp;#39;./helpers&amp;#39; export default { Store, install, version: &amp;#39;__VERSION__&amp;#39;, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } export { Store, install, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }</pre>解析:

    #差異就是
  • index.esm.js

    index. js多了一個導入模式

  • import Vuex, { mapState } from 'index.esm.js'

    :有兩種方式導入

  • import Vuex from 'index.js'

    :只有一種方式導入

  • mixin.js
export default function (Vue) {
  const version = Number(Vue.version.split(&#39;.&#39;)[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = typeof options.store === &#39;function&#39;
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

解析:

    為什麼每個元件都有
  • \(store屬性,也也就是每個元件都能拿到\)

    store

  • Vue2直接用mixin和鉤子函數beforeCreate,Vue1用外觀(裝飾者)模式重寫Vue._init函數。
  • vuexInit

    是將全域註冊的store注入到目前元件中,在建立該元件之前

  • # #\(options是`new Vue(options)`的options,\)
  • options中有store

  • 由於beforeCreateVue的週期鉤子,this指向目前元件實例,所以this.$store可以把store直接注入目前元件

  • 所有元件都是繼承於一個全域Vue的,全域mixin元件週期鉤子beforeCreate,這樣每個元件都能自動注入store,也也就是每個元件都能直接透過$store拿到全域Vuenew Vue({ el: 'app', store, router })store


相關推薦:

##談談我對vuex的理解

Vue.js之vuex(狀態管理)

以上是淺析VueX的原始碼內容的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn