vuex 源码整体架构学习,下载量瞬秒百万

}

git clone https://github.com/vuejs/vue.git

cd vue

npm i

在 dist/vue.js 最后一行追加一行 //# sourceMappingURL=vue.js.map

npm run dev

新终端窗口

根目录下 全局安装http-server(一行命令启动服务的工具)

npm i -g http-server

hs -p 8100

在examples 文件夹中把引用的vuejs的index.html 文件 vue.min.js 改为 vue.js

或者把dist文件夹的 vue.min.js ,替换成npm run dev编译后的dist/vue.js

浏览器打开 open http://localhost:8100/examples/

打开控制面板 source 在左侧找到 src 目录 即vue.js源码文件 根据自己需求断点调试即可。

本小节大篇幅介绍调试方法。是因为真的很重要。会调试代码,看源码就比较简单了。关注主线调试代码,很容易看懂。

强烈建议克隆笔者的这个仓库,自己调试代码,对着注释看,不调试代码,只看文章不容易吸收消化。

笔者也看了文章末尾笔者推荐阅读的文章,但还是需要自己看源代码,才知道这些文章哪里写到了,哪里没有细写。

正文开始~

vuex 原理


简单说明下 vuex 原理

count {{$store.state.count}}

每个组件(也就是Vue实例)在beforeCreate的生命周期中都混入(Vue.mixin)同一个Store实例 作为属性 $store, 也就是为啥可以通过 this.$store.dispatch 等调用方法的原因。

最后显示在模板里的 $store.state.count 源码是这样的。

class Store{

get state () {

return this._vm._data.$$state

}

}

其实就是: vm.$store._vm._data.$$state.count 其中vm.$store._vm._data.$$state 是 响应式的。怎么实现响应式的?其实就是new Vue()

function resetStoreVM (store, state, hot) {

// 省略若干代码

store._vm = new Vue({

data: {

$$state: state

},

computed

})

// 省略若干代码

}

这里的 state 就是 用户定义的 state。这里的 computed 就是处理后的用户定义的 getters。而 class Store上的一些函数(API)主要都是围绕修改vm.$store._vm._data.$$statecomputed(getter)服务的。

Vue.use 安装


笔者画了一张图表示下Vuex对象,是Vue的一个插件。

看到这里,恭喜你已经了解了Vuex原理。文章比较长,如果暂时不想关注源码细节,可以克隆一下本仓库代码git clone https://github.com/lxchuan12/vuex-analysis.git,后续调试代码,点赞收藏到时想看了再看。

文档 Vue.use Vue.use(Vuex)

参数:{Object | Function} plugin 用法:

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。

该方法需要在调用 new Vue() 之前被调用。

当 install 方法被同一个插件多次调用,插件将只会被安装一次。

根据断点调试,来看下Vue.use的源码。

function initUse (Vue) {

Vue.use = function (plugin) {

var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));

// 如果已经存在,则直接返回this也就是Vue

if (installedPlugins.indexOf(plugin) > -1) {

return this

}

// additional parameters

var args = toArray(arguments, 1);

// 把 this(也就是Vue)作为数组的第一项

args.unshift(this);

// 如果插件的install属性是函数,调用它

if (typeof plugin.install === ‘function’) {

plugin.install.apply(plugin, args);

} else if (typeof plugin === ‘function’) {

// 如果插件是函数,则调用它

// apply(null) 严格模式下 plugin 插件函数的 this 就是 null

plugin.apply(null, args);

}

// 添加到已安装的插件

installedPlugins.push(plugin);

return this

};

}

install 函数

vuex/src/store.js

export function install (_Vue) {

// Vue 已经存在并且相等,说明已经Vuex.use过

if (Vue && _Vue === Vue) {

// 省略代码:非生产环境报错,vuex已经安装

return

}

Vue = _Vue

applyMixin(Vue)

}

接下来看 applyMixin 函数

applyMixin 函数

vuex/src/mixin.js

export default function (Vue) {

// Vue 版本号

const version = Number(Vue.version.split(‘.’)[0])

if (version >= 2) {

// 合并选项后 beforeCreate 是数组里函数的形式 [ƒ, ƒ]

// 最后调用循环遍历这个数组,调用这些函数,这是一种函数与函数合并的解决方案。

// 假设是我们自己来设计,会是什么方案呢。

Vue.mixin({ beforeCreate: vuexInit })

} else {

// 省略1.x的版本代码 …

}

/**

  • Vuex init hook, injected into each instances init hooks list.

*/

function vuexInit () {

const options = this.$options

// store injection

// store 注入到每一个Vue的实例中

if (options.store) {

this.$store = typeof options.store === ‘function’

? options.store()
options.store

} else if (options.parent && options.parent.$store) {

this. s t o r e = o p t i o n s . p a r e n t . store = options.parent. store=options.parent.store

}

}

}

最终每个Vue的实例对象,都有一个$store属性。且是同一个Store实例。

用购物车的例子来举例就是:

const vm = new Vue({

el: ‘#app’,

store,

render: h => h(App)

})

console.log('vm. s t o r e = = = v m . store === vm. store===vm.children[0]. s t o r e ′ , v m . store', vm. store,vm.store === vm. c h i l d r e n [ 0 ] . children[0]. children[0].store)

// true

console.log(‘vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 0 ] . children[0]. children[0].store’, vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 0 ] . children[0]. children[0].store)

// true

console.log(‘vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 1 ] . children[1]. children[1].store’, vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 1 ] . children[1]. children[1].store)

// true

Vuex.Store 构造函数


先看最终 new Vuex.Store 之后的 Store 实例对象关系图:先大致有个印象。

export class Store {

constructor (options = {}) {

// 这个构造函数比较长,这里省略,后文分开细述

}

}

if (!Vue && typeof window !== ‘undefined’ && window.Vue) {

install(window.Vue)

}

如果是 cdn script 方式引入vuex插件,则自动安装vuex插件,不需要用Vue.use(Vuex)来安装。

// asset 函数实现

export function assert (condition, msg) {

if (!condition) throw new Error([vuex] ${msg})

}

if (process.env.NODE_ENV !== ‘production’) {

// 可能有读者会问:为啥不用 console.assert,console.assert 函数报错不会阻止后续代码执行

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.)

}

条件断言:不满足直接抛出错误

1.必须使用 Vue.use(Vuex) 创建 store 实例。

2.当前环境不支持Promise,报错:vuex 需要 Promise polyfill

3.Store 函数必须使用 new 操作符调用。

const {

// 插件默认是空数组

plugins = [],

// 严格模式默认是false

strict = false

} = options

从用户定义的new Vuex.Store(options) 取出pluginsstrict参数。

// store internal state

// store 实例对象 内部的 state

this._committing = false

// 用来存放处理后的用户自定义的actoins

this._actions = Object.create(null)

// 用来存放 actions 订阅

this._actionSubscribers = []

// 用来存放处理后的用户自定义的mutations

this._mutations = Object.create(null)

// 用来存放处理后的用户自定义的 getters

this._wrappedGetters = Object.create(null)

// 模块收集器,构造模块树形结构

this._modules = new ModuleCollection(options)

// 用于存储模块命名空间的关系

this._modulesNamespaceMap = Object.create(null)

// 订阅

this._subscribers = []

// 用于使用 $watch 观测 getters

this._watcherVM = new Vue()

// 用来存放生成的本地 getters 的缓存

this._makeLocalGettersCache = Object.create(null)

声明Store实例对象一些内部变量。用于存放处理后用户自定义的actionsmutationsgetters等变量。

提一下 Object.create(null) 和 {} 的区别。前者没有原型链,后者有。即 Object.create(null).__proto__是 undefined ({}).__proto__ 是 Object.prototype

// bind commit and dispatch to self

const store = this

const { dispatch, commit } = this

this.dispatch = function boundDispatch (type, payload) {

return dispatch.call(store, type, payload)

}

this.commit = function boundCommit (type, payload, options) {

return commit.call(store, type, payload, options)

}

给自己 绑定 commit 和 dispatch

为何要这样绑定 ?

说明调用 commit 和 dispach 的 this 不一定是 store 实例

这是确保这两个函数里的 this 是 store 实例

// 严格模式,默认是false

this.strict = strict

// 根模块的state

const state = this._modules.root.state

// init root module.

// this also recursively registers all sub-modules

// and collects all module getters inside this._wrappedGetters

installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity

// (also registers _wrappedGetters as computed properties)

resetStoreVM(this, state)

上述这段代码 installModule(this, state, [], this._modules.root)

初始化 根模块。

并且也递归的注册所有子模块。

并且收集所有模块的 getters 放在 this._wrappedGetters 里面。

resetStoreVM(this, state)

初始化 store._vm 响应式的

并且注册 _wrappedGetters 作为 computed 的属性

plugins.forEach(plugin => plugin(this))

插件:把实例对象 store 传给插件函数,执行所有插件。

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools

if (useDevtools) {

devtoolPlugin(this)

}

初始化 vue-devtool 开发工具。

参数 devtools 传递了取 devtools 否则取Vue.config.devtools 配置。

初读这个构造函数的全部源代码。会发现有三个地方需要重点看。分别是:

this._modules = new ModuleCollection(options)

installModule(this, state, [], this._modules.root)

resetStoreVM(this, state)

阅读时可以断点调试,赋值语句this._modules = new ModuleCollection(options),如果暂时不想看,可以直接看返回结果。installModuleresetStoreVM函数则可以断点调试。

class ModuleCollection

收集模块,构造模块树结构。

注册根模块 参数 rawRootModule 也就是 Vuex.Store 的 options 参数

未加工过的模块(用户自定义的),根模块

export default class ModuleCollection {

constructor (rawRootModule) {

// register root module (Vuex.Store options)

this.register([], rawRootModule, false)

}

}

/**

  • 注册模块

  • @param {Array} path 路径

  • @param {Object} rawModule 原始未加工的模块

  • @param {Boolean} runtime runtime 默认是 true

*/

register (path, rawModule, runtime = true) {

// 非生产环境 断言判断用户自定义的模块是否符合要求

if (process.env.NODE_ENV !== ‘production’) {

assertRawModule(path, rawModule)

}

const newModule = new Module(rawModule, runtime)

if (path.length === 0) {

this.root = newModule

} else {

const parent = this.get(path.slice(0, -1))

parent.addChild(path[path.length - 1], newModule)

}

// register nested modules

// 递归注册子模块

if (rawModule.modules) {

forEachValue(rawModule.modules, (rawChildModule, key) => {

this.register(path.concat(key), rawChildModule, runtime)

})

}

}

class Module

// Base data struct for store’s module, package with some attribute and method

// store 的模块 基础数据结构,包括一些属性和方法

export default class Module {

constructor (rawModule, runtime) {

// 接收参数 runtime

this.runtime = runtime

// Store some children item

// 存储子模块

this._children = Object.create(null)

// Store the origin module object which passed by programmer

// 存储原始未加工的模块

this._rawModule = rawModule

// 模块 state

const rawState = rawModule.state

// Store the origin module’s state

// 原始Store 可能是函数,也可能是是对象,是假值,则赋值空对象。

this.state = (typeof rawState === ‘function’ ? rawState() : rawState) || {}

}

}

经过一系列的注册后,最后 this._modules = new ModuleCollection(options) this._modules 的值是这样的。笔者画了一张图表示:

installModule 函数

function installModule (store, rootState, path, module, hot) {

// 是根模块

const isRoot = !path.length

// 命名空间 字符串

const namespace = store._modules.getNamespace(path)

if (module.namespaced) {

// 省略代码:模块命名空间map对象中已经有了,开发环境报错提示重复

// module 赋值给 _modulesNamespaceMap[namespace]

store._modulesNamespaceMap[namespace] = module

}

// … 后续代码 移出来 待读解释

}

注册 state

// set state

// 不是根模块且不是热重载

if (!isRoot && !hot) {

// 获取父级的state

const parentState = getNestedState(rootState, path.slice(0, -1))

// 模块名称

// 比如 cart

const moduleName = path[path.length - 1]

// state 注册

store._withCommit(() => {

// 省略代码:非生产环境 报错 模块 state 重复设置

Vue.set(parentState, moduleName, module.state)

})

}

最后得到的是类似这样的结构且是响应式的数据 实例 Store.state 比如:

{

// 省略若干属性和方法

// 这里的 state 是只读属性 可搜索 get state 查看,上文写过

state: {

cart: {

checkoutStatus: null,

items: []

}

}

}

const local = module.context = makeLocalContext(store, namespace, path)

module.context 这个赋值主要是给 helpers 中 mapStatemapGettersmapMutationsmapActions四个辅助函数使用的。

生成本地的dispatch、commit、getters和state。

主要作用就是抹平差异化,不需要用户再传模块参数。

遍历注册 mutation

module.forEachMutation((mutation, key) => {

const namespacedType = namespace + key

registerMutation(store, namespacedType, mutation, local)

})

/**

  • 注册 mutation

  • @param {Object} store 对象

  • @param {String} type 类型

  • @param {Function} handler 用户自定义的函数

  • @param {Object} local local 对象

*/

function registerMutation (store, type, handler, local) {

// 收集的所有的mutations找对应的mutation函数,没有就赋值空数组

const entry = store._mutations[type] || (store._mutations[type] = [])

// 最后 mutation

entry.push(function wrappedMutationHandler (payload) {

/**

  • mutations: {

  • pushProductToCart (state, { id }) {

  •    console.log(state);
    
  • }

  • }

  • 也就是为什么用户定义的 mutation 第一个参数是state的原因,第二个参数是payload参数

*/

handler.call(store, local.state, payload)

})

}

遍历注册 action

module.forEachAction((action, key) => {

const type = action.root ? key : namespace + key

const handler = action.handler || action

registerAction(store, type, handler, local)

})

/**

  • 注册 mutation

  • @param {Object} store 对象

  • @param {String} type 类型

  • @param {Function} handler 用户自定义的函数

  • @param {Object} local local 对象

*/

function registerAction (store, type, handler, local) {

const entry = store._actions[type] || (store._actions[type] = [])

// payload 是actions函数的第二个参数

entry.push(function wrappedActionHandler (payload) {

/**

  • 也就是为什么用户定义的actions中的函数第一个参数有

  • { dispatch, commit, getters, state, rootGetters, rootState } 的原因

  • actions: {

  • checkout ({ commit, state }, products) {

  •    console.log(commit, state);
    
  • }

  • }

*/

let res = handler.call(store, {

dispatch: local.dispatch,

commit: local.commit,

getters: local.getters,

state: local.state,

rootGetters: store.getters,

rootState: store.state

}, payload)

/**

  • export function isPromise (val) {

return val && typeof val.then === ‘function’

}

  • 判断如果不是Promise Promise 化,也就是为啥 actions 中处理异步函数

也就是为什么构造函数中断言不支持promise报错的原因

vuex需要Promise polyfill

assert(typeof Promise !== ‘undefined’, vuex requires a Promise polyfill in this browser.)

*/

if (!isPromise(res)) {

res = Promise.resolve(res)

}

// devtool 工具触发 vuex:error

if (store._devtoolHook) {

// catch 捕获错误

return res.catch(err => {

store._devtoolHook.emit(‘vuex:error’, err)

// 抛出错误

throw err

})

} else {

// 然后函数执行结果

return res

}

})

}

遍历注册 getter

module.forEachGetter((getter, key) => {

const namespacedType = namespace + key

registerGetter(store, namespacedType, getter, local)

})

/**

  • 注册 getter

  • @param {Object} store Store实例

  • @param {String} type 类型

  • @param {Object} rawGetter 原始未加工的 getter 也就是用户定义的 getter 函数

  • @examples 比如 cartProducts: (state, getters, rootState, rootGetters) => {}

  • @param {Object} local 本地 local 对象

*/

function registerGetter (store, type, rawGetter, local) {

// 类型如果已经存在,报错:已经存在

if (store._wrappedGetters[type]) {

if (process.env.NODE_ENV !== ‘production’) {

console.error([vuex] duplicate getter key: ${type})

}

return

}

// 否则:赋值

store._wrappedGetters[type] = function wrappedGetter (store) {

/**

  • 这也就是为啥 getters 中能获取到 (state, getters, rootState, rootGetters) 这些值的原因

  • getters = {

  •  cartProducts: (state, getters, rootState, rootGetters) => {
    
  •    console.log(state, getters, rootState, rootGetters);
    
  •  }
    
  • }

*/

return rawGetter(

local.state, // local state

local.getters, // local getters

store.state, // root state

store.getters // root getters

)

}

}

遍历注册 子模块

module.forEachChild((child, key) => {

installModule(store, rootState, path.concat(key), child, hot)

})

resetStoreVM 函数

resetStoreVM(this, state, hot)

初始化 store._vm 响应式的

并且注册 _wrappedGetters 作为 computed 的属性

function resetStoreVM (store, state, hot) {

// 存储一份老的Vue实例对象 _vm

const oldVm = store._vm

// bind store public getters

// 绑定 store.getter

store.getters = {}

// reset local getters cache

// 重置 本地getters的缓存

store._makeLocalGettersCache = Object.create(null)

// 注册时收集的处理后的用户自定义的 wrappedGetters

const wrappedGetters = store._wrappedGetters

// 声明 计算属性 computed 对象

const computed = {}

// 遍历 wrappedGetters 赋值到 computed 上

forEachValue(wrappedGetters, (fn, key) => {

// use computed to leverage its lazy-caching mechanism

// direct inline function use will lead to closure preserving oldVm.

// using partial to return function with only arguments preserved in closure environment.

/**

  • partial 函数

  • 执行函数 返回一个新函数

export function partial (fn, arg) {

return function () {

return fn(arg)

}

}

*/

computed[key] = partial(fn, store)

// getter 赋值 keys

Object.defineProperty(store.getters, key, {

get: () => store._vm[key],

// 可以枚举

enumerable: true // for local getters

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结

阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

lead to closure preserving oldVm.

// using partial to return function with only arguments preserved in closure environment.

/**

  • partial 函数

  • 执行函数 返回一个新函数

export function partial (fn, arg) {

return function () {

return fn(arg)

}

}

*/

computed[key] = partial(fn, store)

// getter 赋值 keys

Object.defineProperty(store.getters, key, {

get: () => store._vm[key],

// 可以枚举

enumerable: true // for local getters

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-7WctBUCR-1710697157675)]
[外链图片转存中…(img-MMudJYie-1710697157675)]
[外链图片转存中…(img-b8sPjkX5-1710697157676)]
[外链图片转存中…(img-G2OYzNRC-1710697157676)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-OD4V4x1n-1710697157677)]

总结

阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值