}
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.$$state
和computed(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)
取出plugins
和strict
参数。
// 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
实例对象一些内部变量。用于存放处理后用户自定义的actions
、mutations
、getters
等变量。
提一下
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)
,如果暂时不想看,可以直接看返回结果。installModule
,resetStoreVM
函数则可以断点调试。
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
中mapState
、mapGetters
、mapMutations
、mapActions
四个辅助函数使用的。
生成本地的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前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
总结
阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
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)]
总结
阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。