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. VRTK3.3.0-002获取手柄事件

    1.首先创建VRScripts空物体,用来存放脚本,在其下创建Right空物体并添加VRTK_ControllerEvents脚本 2.Right作为右手手柄,拖拽到[VRTK_SDKManager] ...

  2. argparse 在深度学习中的应用

    argparse 介绍 argparse模块主要用来为脚本传递命令参数功能,使他们更加灵活. 代码: parser = argparse.ArgumentParser() #建立解析器,必须写 par ...

  3. 转 DataGuard环境搭建 (一主一备一级联)

    DataGuard环境搭建 (一主一备一级联) http://blog.itpub.net/30130773/viewspace-2116985/ 1.--------- primary_role / ...

  4. 第十五章 提升用户体验 之 设计实现MVC controllers 和 actions

    1. 概述 controllers 和 actions 是 ASP.NET MVC4中非常重要的组成部分. controller管理用户和程序间的交互,使用action作为完成任务的方式. 如果是包含 ...

  5. SQL数据库基础二

  6. Vmware Player 比较

    .wiz-todo, .wiz-todo-img {width: 16px; height: 16px; cursor: default; padding: 0 10px 0 2px; vertica ...

  7. 告别CMD.windows终端神器conemu设置

    前言 一种刘姥姥进大观园的感觉,现在是见啥啥新鲜.因为之前不怎么接触到命令操作,平时偶尔用用cmd也没觉得什么不妥.直到现在经常调试脚本,使用git越发感觉不方便.看见同事使用的terminal绚丽夺 ...

  8. Android 自定义Adapter中实现startActivityForResult的分析

    最近几天在做文件上传的时候,想在自定义Adapter中启动activity时也返回Intent数据,于是想到了用startActivityForResult,可是用mContext怎么也调不出这个方法 ...

  9. Centos 7 搭建git服务器及使用gitolite控制权限

    一.安装git yum install git git --version #查看git版本 二.升级git(可选,如果之前已经安装git,需要升级git到最新版本) git clone https: ...

  10. Jmeter监控内存及CPU等

    在进行性能测试时需要查看内存和CPU等信息来判断系统瓶颈,关于CPU和内存的监控,goole开发了一款专门的jmeter插件,弥补了Jmeter这方面的不足,下面来介绍这款插件-JmeterPlugi ...