VueX源码分析(1)

文件架构如下

  • /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 !== 'object') {
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('deepCopy: nornal structure', () => {
const original = {
a: 1,
b: 'string',
c: true,
d: null,
e: undefined
}
const copy = deepCopy(original) expect(copy).toEqual(original)
}) // 嵌套结构
it('deepCopy: nested structure', () => {
const original = {
a: {
b: 1,
c: [2, 3, {
d: 4
}]
}
}
const copy = deepCopy(original) expect(copy).toEqual(original)
}) // 循环引用结构
it('deepCopy: circular structure', () => {
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('forEachValue', () => {
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 === 'object'
}

测试用例

  it('isObject', () => {
expect(isObject(1)).toBe(false)
expect(isObject('String')).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 === 'function'
}

测试用例

  it('isPromise', () => {
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('assert', () => {
expect(assert.bind(null, false, 'Hello')).toThrowError('[vuex] Hello')
})

解析:

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

index.jsindex.esm.js

index.js

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
}

index.esm.js

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
} export {
Store,
install,
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
}

解析:

  • 区别就是index.esm.jsindex.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('.')[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 === 'function'
? 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源码分析(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. Python序列化模块pickle和json使用和区别

    这是用于序列化的两个模块: • json: 用于字符串和python数据类型间进行转换 • pickle: 用于python特有的类型和python的数据类型间进行转换 Json模块提供了四个功能:d ...

  2. 剑指Offer的学习笔记(C#篇)-- 二进制中1的个数

    题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 一 . 解题思路 新颖的解法,使得该题目运用到了二进制的位运算符.先了解一下位运算符! 此题便很好的发挥了位运算符& ...

  3. 剑指Offer的学习笔记(C#篇)-- 斐波那契数列

    题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). 一 . 理解概念 斐波那契数列概念:斐波那契数列(Fibonacci sequence), ...

  4. ES6入门教程---变量和常量

    ES6提出了两个新的声明变量的命令:let 和 const 1. 建议不再使用var,而使用let 和const .优先使用const. 在定义之后值是固定不变的,即为常量 常量的值不能修改,但是如果 ...

  5. Js 数组对象排序

    1.定义函数 /** * 数组对象排序函数 * @param {any} name 排序字段 * @param {any} order 升.降(这里事true.false记得处理下) */ var b ...

  6. mac 增加/usr/bin目录的操作无权限

    起因:想要往/usr/bin目录下拷贝文件,提示: operation not permitted 解决办法: 1. 重启,启动中长安command+r进入恢复模式 2.顶部菜单拉中打开终端 3. 输 ...

  7. 2019湘潭校赛 H(dp)

    题目传送 dp是常规的:\(m^2\)的预处理:把位置存进vector然后\(O(1)\)算出想要的:WA点:要注意特意设置一下val[i][v.size()]=0,即全天都放鸽子则花费时间为0. # ...

  8. Netty(2)Echo

    上节介绍的是discard协议,即不给客户端返回消息.本节主要说下,echo协议,即服务端收到消息后原样返回给客户端. 为了实现此需求,只需要在DiscardServerHandler中重写chann ...

  9. NSSM把.Net Core部署至 Windows 服务

    NSSM把.Net Core部署至 Windows 服务 https://www.cnblogs.com/emrys5/p/nssm-netcore.html 为什么部署至Windows Servic ...

  10. setTimout( , 0) 详解

    setTimout( , 0) 一.前言 前端工程师们工作久了,一般都会在某些地方看见过这样的代码: setTimeout(function(){ // TODO }, 0); 举个实例,移动端我们经 ...