vuex的源码分析系列大概会分为三篇博客来说,为何分三篇呢,由于写一篇太多了。您看着费劲,我写着也累前端
这是vuex的目录图vue
分析vuex的源码,咱们先从index入口文件进行分析,入口文件在src/store.js文件中react
export default { // 主要代码,状态存储类 Store, // 插件安装 install, // 版本 version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
一个一个来看Store是vuex提供的状态存储类,一般咱们使用Vuex就是经过建立Store的实例,
install方法是配合Vue.use方法进行使用,install方法是用来编写Vue插件的通用公式,先来看一下代码es6
export function install (_Vue) { if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== 'production') { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin(Vue) }
这个方法的做用是什么呢:当window上有Vue对象的时候,就会手动编写install方法,而且传入Vue的使用。
在install中法中有applyMixin这个函数,这个函数来自云src/mixin.js,下面是mixin.js的代码面试
export default function (Vue) { // 判断VUe的版本 const version = Number(Vue.version.split('.')[0]) // 若是vue的版本大于2,那么beforeCreate以前vuex进行初始化 if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. // 兼容vue 1的版本 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. */ // 给vue的实例注册一个$store的属性,相似我们使用vue.$route 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 } } }
这段代码的整体意思就是就是在vue的声明周期中进行vuex的初始化,而且对vue的各类版本进行了兼容。vuexInit就是对vuex的初始化。为何咱们能在使用vue.$store这种方法呢,由于在vuexInit中为vue的实例添加了$store属性
Store构造函数
先看constructor构造函数vuex
constructor (options = {}) { // Auto install if it is not done yet and `window` has `Vue`. // To allow users to avoid auto-installation in some cases, // this code should be placed here. See #731 // 判断window.vue是否存在,若是不存在那么就安装 if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) } // 这个是在开发过程当中的一些环节判断,vuex要求在建立vuex // store实例以前必须先使用这个方法Vue.use(Vuex),而且判断promise是否可使用 if (process.env.NODE_ENV !== 'production') { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `Store must be called with the new operator.`) } // 提取参数 const { plugins = [], strict = false } = options let { state = {} } = options if (typeof state === 'function') { state = state() || {} } // store internal state // 初始化store内部状态,Obejct.create(null)是ES5的一种方法,Object.create( // object)object是你要继承的对象,若是是null,那么就是建立一个纯净的对象 this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() // bind commit and dispatch to self const store = this const { dispatch, commit } = this // 定义dispatch方法 this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } // 定义commit方法 this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // strict mode // 严格模式 this.strict = strict // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters // 初始化根模块,递归注册全部的子模块,收集getters installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) // 重置vm状态,同时将getters转换为computed计算属性 resetStoreVM(this, state) // apply plugins // 执行每一个插件里边的函数 plugins.forEach(plugin => plugin(this)) if (Vue.config.devtools) { devtoolPlugin(this) } }
由于vuex是由es6进行编写的,我真以为es6的模块简直是神器,我之前就纳闷了像Echarts这种8万行的库,他们是怎么写出来的,上次还看到一个应聘百度的问Echarts的源码没有。可怕!后来才知道模块化。现在es6引入了import这种模块化语句,让咱们编写庞大的代码变得更加简单了。json
if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) }
这段代码检测window.Vue是否存在,若是不存在那么就调用install方法promise
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
这行代码用于确保在咱们实例化 Store以前,vue已经存在。浏览器
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
这一行代码用于检测是否支持Promise,由于vuex中使用了Promise,Promise是es6的语法,可是有的浏览器并不支持es6因此咱们须要在package.json中加入babel-polyfill用来支持es6
下面来看接下来的代码babel
const { plugins = [], strict = false } = options
这利用了es6的解构赋值拿到options中的plugins。es6的解构赋值在个人《前端面试之ES6》中讲过,大体就是[a,b,c] = [1,2,3] 等同于a=1,b=2,c=3。后面一句的代码相似取到state的值。
this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue()
这里主要用于建立一些内部的属性,为何要加_这是用于辨别属性,_就表示内部属性,咱们在外部调用这些属性的时候,就须要当心。
this._committing 标志一个提交状态 this._actions 用来存储用户定义的全部actions this._actionSubscribers this._mutations 用来存储用户定义全部的 mutatins this._wrappedGetters 用来存储用户定义的全部 getters this._modules this._subscribers 用来存储全部对 mutation 变化的订阅者 this._watcherVM 是一个 Vue 对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化的 commit和dispatch方法在事后再讲
installModule(this, state, [], options)
是将咱们的options传入的各类属性模块注册和安装
resetStoreVM(this, state)
resetStoreVM 方法是初始化 store._vm,观测 state 和 getters 的变化;最后是应用传入的插件
下面是installModule的实现
function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) }
这个函数的主要做用是什么呢:初始化组件树根组件、注册全部子组件,并将全部的getters存储到this._wrapperdGetters属性中
这个函数接受5个函数,store, rootState, path, module, hot。咱们来看看他们分别表明什么:
store: 表示当前Store实例
rootState: 表示根state,
path: 其余的参数的含义都比较好理解,那么这个path是如何理解的呢。咱们能够将一个store实例当作module的集合。每个集合也是store的实例。那么path就能够想象成一种层级关系,当你有了rootState和path后,就能够在Path路径中找到local State。而后每次getters或者setters改变的就是localState
module:表示当前安装的模块
hot:当动态改变modules或者热更新的时候为true
const isRoot = !path.length const namespace = store._modules.getNamespace(path)
用于经过Path的长度来判断是否为根,下面那一句是用与注册命名空间
module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) })
这句是将mutation注册到模块上,后面几句是一样的效果,就不作一一的解释了
module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })
这儿是经过遍历Modules,递归调用installModule安装模块。
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) })
}
这段用于判断若是不是根而且Hot为false的状况,将要执行的俄操做,这儿有一个函数getNestedState (state, path),函数的内容以下:
function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) : state }
这个函数的意思拿到该module父级的state,拿到其所在的moduleName,而后调用store._withCommit()方法
store._withCommit(() => { Vue.set(parentState, moduleName, module.state) })
把当前模块的state添加到parentState,咱们使用了_withCommit()方法,_withCommit的方法咱们留到commit方法再讲。