VueX源码分析(1)

文件架构如下

  • /module
  • /plugins
  • helpers.js
  • index.esm.js
  • index.js
  • store.js
  • util.js

util.js

先从最简单的工具函数开始。

find函数

  1. /**
  2. * Get the first item that pass the test
  3. * by second argument function
  4. *
  5. * @param {Array} list
  6. * @param {Function} f
  7. * @return {*}
  8. */
  9. export function find (list, f) {
  10. return list.filter(f)[0]
  11. }

find函数的测试用例

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

解析:

  • 先用断言函数f过滤列表list,最后取过滤后列表的第一个元素。

deepCopy函数

  1. /**
  2. * Deep copy the given object considering circular structure.
  3. * This function caches all nested objects and its copies.
  4. * If it detects circular structure, use cached copy to avoid infinite loop.
  5. *
  6. * @param {*} obj
  7. * @param {Array<Object>} cache
  8. * @return {*}
  9. */
  10. export function deepCopy (obj, cache = []) {
  11. // just return if obj is immutable value
  12. if (obj === null || typeof obj !== 'object') {
  13. return obj
  14. }
  15. // if obj is hit, it is in circular structure
  16. const hit = find(cache, c => c.original === obj)
  17. if (hit) {
  18. return hit.copy
  19. }
  20. const copy = Array.isArray(obj) ? [] : {}
  21. // put the copy into cache at first
  22. // because we want to refer it in recursive deepCopy
  23. cache.push({
  24. original: obj,
  25. copy
  26. })
  27. Object.keys(obj).forEach(key => {
  28. copy[key] = deepCopy(obj[key], cache)
  29. })
  30. return copy
  31. }

deepCopy的测试用例

  1. // 普通结构
  2. it('deepCopy: nornal structure', () => {
  3. const original = {
  4. a: 1,
  5. b: 'string',
  6. c: true,
  7. d: null,
  8. e: undefined
  9. }
  10. const copy = deepCopy(original)
  11. expect(copy).toEqual(original)
  12. })
  13. // 嵌套结构
  14. it('deepCopy: nested structure', () => {
  15. const original = {
  16. a: {
  17. b: 1,
  18. c: [2, 3, {
  19. d: 4
  20. }]
  21. }
  22. }
  23. const copy = deepCopy(original)
  24. expect(copy).toEqual(original)
  25. })
  26. // 循环引用结构
  27. it('deepCopy: circular structure', () => {
  28. const original = {
  29. a: 1
  30. }
  31. original.circular = original
  32. const copy = deepCopy(original)
  33. expect(copy).toEqual(original)
  34. })

解析:

  • 功能:支持循环引用的深克隆函数
  • 第一个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

  1. /**
  2. * forEach for object
  3. */
  4. export function forEachValue (obj, fn) {
  5. Object.keys(obj).forEach(key => fn(obj[key], key))
  6. }

测试用例

  1. it('forEachValue', () => {
  2. let number = 1
  3. function plus (value, key) {
  4. number += value
  5. }
  6. const origin = {
  7. a: 1,
  8. b: 3
  9. }
  10. forEachValue(origin, plus)
  11. expect(number).toEqual(5)
  12. })

解析:

  • 一个遍历对象的函数(支持对象和数组)
  • fn(value, key)但是回调函数第一个参数是值,第二个参数是键值

isObject

  1. export function isObject (obj) {
  2. return obj !== null && typeof obj === 'object'
  3. }

测试用例

  1. it('isObject', () => {
  2. expect(isObject(1)).toBe(false)
  3. expect(isObject('String')).toBe(false)
  4. expect(isObject(undefined)).toBe(false)
  5. expect(isObject({})).toBe(true)
  6. expect(isObject(null)).toBe(false)
  7. expect(isObject([])).toBe(true)
  8. expect(isObject(new Function())).toBe(false)
  9. })

解析:

  • 判断是不是对象,这里没有判断是不是原生对象,数组也是通过的。
  • 由于typeof null === 'object'要先判断是不是null

isPromise

  1. export function isPromise (val) {
  2. return val && typeof val.then === 'function'
  3. }

测试用例

  1. it('isPromise', () => {
  2. const promise = new Promise(() => {}, () => {})
  3. expect(isPromise(1)).toBe(false)
  4. expect(isPromise(promise)).toBe(true)
  5. expect(isPromise(new Function())).toBe(false)
  6. })

解析:

  • 判断是不是Promise
  • 首先判断val不是undefined,然后才可以判断val.then,避免报错
  • 判断依据是val.then是不是函数

assert

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

测试用例:

  1. it('assert', () => {
  2. expect(assert.bind(null, false, 'Hello')).toThrowError('[vuex] Hello')
  3. })

解析:

  • 断言函数,断言不通过抛出一个自定义错误信息的Error

index.jsindex.esm.js

index.js

  1. import { Store, install } from './store'
  2. import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
  3. export default {
  4. Store,
  5. install,
  6. version: '__VERSION__',
  7. mapState,
  8. mapMutations,
  9. mapGetters,
  10. mapActions,
  11. createNamespacedHelpers
  12. }

index.esm.js

  1. import { Store, install } from './store'
  2. import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
  3. export default {
  4. Store,
  5. install,
  6. version: '__VERSION__',
  7. mapState,
  8. mapMutations,
  9. mapGetters,
  10. mapActions,
  11. createNamespacedHelpers
  12. }
  13. export {
  14. Store,
  15. install,
  16. mapState,
  17. mapMutations,
  18. mapGetters,
  19. mapActions,
  20. createNamespacedHelpers
  21. }

解析:

  • 区别就是index.esm.jsindex.js多了个一个导入模式
  • import Vuex, { mapState } from 'index.esm.js':有两种方式导入
  • import Vuex from 'index.js':只有一种方式导入

mixin.js

  1. export default function (Vue) {
  2. const version = Number(Vue.version.split('.')[0])
  3. if (version >= 2) {
  4. Vue.mixin({ beforeCreate: vuexInit })
  5. } else {
  6. // override init and inject vuex init procedure
  7. // for 1.x backwards compatibility.
  8. const _init = Vue.prototype._init
  9. Vue.prototype._init = function (options = {}) {
  10. options.init = options.init
  11. ? [vuexInit].concat(options.init)
  12. : vuexInit
  13. _init.call(this, options)
  14. }
  15. }
  16. /**
  17. * Vuex init hook, injected into each instances init hooks list.
  18. */
  19. function vuexInit () {
  20. const options = this.$options
  21. // store injection
  22. if (options.store) {
  23. this.$store = typeof options.store === 'function'
  24. ? options.store()
  25. : options.store
  26. } else if (options.parent && options.parent.$store) {
  27. this.$store = options.parent.$store
  28. }
  29. }
  30. }

解析:

  • 为什么每个组件都拥有\(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源码分析(1)的更多相关文章

  1. VueX源码分析(5)

    VueX源码分析(5) 最终也是最重要的store.js,该文件主要涉及的内容如下: Store类 genericSubscribe函数 resetStore函数 resetStoreVM函数 ins ...

  2. VueX源码分析(3)

    VueX源码分析(3) 还剩余 /module /plugins store.js /plugins/devtool.js const devtoolHook = typeof window !== ...

  3. VueX源码分析(4)

    VueX源码分析(4) /module store.js /module/module.js import { forEachValue } from '../util' // Base data s ...

  4. VueX源码分析(2)

    VueX源码分析(2) 剩余内容 /module /plugins helpers.js store.js helpers要从底部开始分析比较好.也即先从辅助函数开始再分析那4个map函数mapSta ...

  5. 逐行粒度的vuex源码分析

    vuex源码分析 了解vuex 什么是vuex vuex是一个为vue进行统一状态管理的状态管理器,主要分为state, getters, mutations, actions几个部分,vue组件基于 ...

  6. vuex源码分析3.0.1(原创)

    前言 chapter1 store构造函数 1.constructor 2.get state和set state 3.commit 4.dispatch 5.subscribe和subscribeA ...

  7. vuex 源码分析(七) module和namespaced 详解

    当项目非常大时,如果所有的状态都集中放到一个对象中,store 对象就有可能变得相当臃肿. 为了解决这个问题,Vuex允许我们将 store 分割成模块(module).每个模块拥有自己的 state ...

  8. vuex 源码分析(六) 辅助函数 详解

    对于state.getter.mutation.action来说,如果每次使用的时候都用this.$store.state.this.$store.getter等引用,会比较麻烦,代码也重复和冗余,我 ...

  9. vuex 源码分析(五) action 详解

    action类似于mutation,不同的是Action提交的是mutation,而不是直接变更状态,而且action里可以包含任意异步操作,每个mutation的参数1是一个对象,可以包含如下六个属 ...

随机推荐

  1. ESQL 查询数据报 参数类型“Edm.Decimal”和“Edm.Double”不兼容

    ESQL 查询数据报 参数类型“Edm.Decimal”和“Edm.Double”不兼容 System.Data.Entity.Core.Objects.ObjectQuery<TEntity& ...

  2. JavaScript进阶 - 第1章 系好安全带,准备启航

    第1章 系好安全带,准备启航 1-1让你认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂 ...

  3. JMeter(7) 优化判断返回类型和返回值

    之前判断接口类型和返回值用beanshell,可见JMeter(5)-JMeter之BeanShell使用 优化点 接口测试里面对返回字段的类型验证和值验证为基本需求,将方法导成jar文件,导入之后方 ...

  4. UVaLive6443(线段树)

    要点 题意--题意往往是个大坎Orz:输入操作 p 则在区间\([x_1,x_2]\)插入一个三次函数, t 则先查询区间\([x_1,x_2]\)的函数值的和,然后按题目要求得到新的\(x_1\). ...

  5. python之字典的相关操作

    一.什么是字典 dict 用{}表示,用来存放键值对数据 {key:value} 键:具有唯一性,不能重复,不可变 必须是可哈希的(不可变的数据类型) 字典是无序的,没有索引 值: 没有任何限制 已知 ...

  6. CoreRT

    使用CoreRT将.NET Core发布为Native应用程序 在上一篇文章<使用.NET Core快速开发一个较正规的命令行应用程序>中我们看到了使用自包含方式发布的.NET Core应 ...

  7. LINUX中IPTABLES防火墙使用

    对于有公网IP的生产环境VPS,仅仅开放需要的端口,即采用ACL来控制IP和端口(Access Control List). 这里可以使用Linux防火墙netfilter的用户态工具 iptable ...

  8. OpenCV图像处理之 Mat 介绍

    我记得开始接触OpenCV就是因为一个算法里面需要2维动态数组,那时候看core这部分也算是走马观花吧,随着使用的增多,对Mat这个结构越来越喜爱,也觉得有必要温故而知新,于是这次再看看Mat. Ma ...

  9. WebStorm技巧-集成命令行工具插件

    打开菜单项 File -> Settings-   搜索插件 CMD Support,并安装.   重启WebStorm,在你的项目中新建一个Cmd script 文件,命名为build.cmd ...

  10. jquery阻止网页中右键的点击

    <body onmousedown="whichElement(event)"> </body> function whichElement(e) { if ...