编程导航源码话题讨论

源码

7 参与
分享

源码

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

vue-router 源码分析——2. router-link 组件是如何实现导航的

小伙伴们晚上好,今天继续发vue-router的源码分析。 另外今天我所在的小区超市被零元购了哈哈,超市老板欠债,债权人来超市搬货,最终JC介入。 可见现在的经济有多差。 ### 官网例子 * 在官网例子中,对<router-link>做了三个注释:这是个组件、传入to属性,渲染成<a>标签。 * 以我们主要分析 router-link 组件如果利用必要的数据来实现导航的。 * 这里先解释一下,**渲染html页面的功能,是vue-router调用了vue的render函数,这个是vue的核心功能不做分析**。所以这里是分析router是如何定位到对应路由以及做了哪些信息收集和处理的。 `````<script src="https://unpkg.com/vue@2/dist/vue.js"></script> <script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script> <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> </div>````` ### Vue实例挂载link组件,从而可以使用<router-link>标签 * 这里的install.js实际上是一个VUE的插件,这样当创建和挂载根实例时自动导入了插件。 * 这里面还涉及到VUE的混入(Vue.mixin),实现了在任意组件内通过this.$ router和this.$route来访问路由器和当前路由。感兴趣的小伙伴可以去看VUE官网的解释。 `// ./install.js import Link from './components/link' export function install(Vue) { Vue.component('RouterLink', Link) }` ### link.js源码内容 * 传入了一个to变量,对应'/foo',tag默认为'a',即<a>标签. * render函数是vue用来描述如果构建虚拟DOM的,即如何按某个定义来构建组件。 * 如何渲染为html是vue中的核心内容,这里不做讨论,只分析vue-router中的源码内容,即resolve方法。 `const { location, route, href } = router.resolve( this.to, current, this.append ) ... const classes = {} const data: any = { class: classes } ... return h(this.tag, data, this.$slots.default) } }` ### 路由实例的 resolve 方法 * resolve入参的to是我们传入的字符串'/foo',current是当前路由this.$route,append是undefine。 * resolve 方法内部又调用了很多函数和方法,得到需要的数据并返回。 * 让我们接着分析调用的函数(normalizeLocation,this.match, createHref)。 `// ./router.js export default class VueRouter { ... resolve ( to: RawLocation, current?: Route, append?: boolean ) { ... const location = normalizeLocation(to, current, append, this) const route = this.match(location, current) const fullPath = route.redirectedFrom || route.fullPath const base = this.history.base const href = createHref(base, fullPath, this.mode) // this.mode 看做 'hash' 即可 return { location, route, href ... } } }` ### normalizeLocation 函数分析 * 入参说明:raw = ‘/foo', current = this.route, append = undifune, router = this.$router * parsePath函数暂时没有额外影响,这里不做分析,只是说明 parsePath 的具体内容 * 最终返回一个对象,里面有 \_normalized: true 和 path: '/foo'相关内容。 `// ./util/location.js export function normalizeLocation( raw: RawLocation, current: ?Route, append: ?boolean, router: ?VueRouter ): Location { let next: Location = typeof raw === 'string' ? { path: raw } : raw if (next._normalized) { return next } ... // parsePath 的实际内容为 {'path': '/foo', 'query': '', 'hash': ''} const parsedPath = parsePath(next.path || '') // basePath 为当前的路由的路径 const basePath = (current && current.path) || '/' // resolvePath函数对'/foo'也没有额外影响,可以理解直接返回了'/foo'赋值给path const path = parsedPath.path ? resolvePath(parsedPath.path, basePath, append || next.append) : basePath ... return { _normalized: true, path, ... } }` ### this.match方法获得的route是什么 * 对应代码 const route = this.match(location, current),current = this.$route。 * match方法和resolve方法一样,是定义在VueRouter中的,它直接返回了路由匹配器的match方法。 * 路由匹配器中的match方法会遍历pathList和pathMap,利用正则表达式查看对应的path是否匹配,如果匹配,这里则返回 \_createRoute 函数调用。 * * pathList和pathMap是在初始化router时生成的,这里为方便理解,再说明一下。 * * pathList是一个数组,记录着我们初始化时定义的各个url path,例如'/foo','/bar' * * pathMap是一个哈希结构,key为path,value为相关的数据(比如path, component, regex等等)。 * \_createRoute 函数又调用了 ./util/route.js 的 createRoute 函数。它返回了一个被冻结的对象,里面主要有path和matched属性,matched在这里可以看做等于 \[record\]。 * 所以this,match方法返回的是一个和我们输入的路径to匹配的一个route对象,里面有我们需要的path,record等内容。 `// ./router.js export default class VueRouter { ... match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route { return this.matcher.match(raw, current, redirectedFrom) } } // ./create-matcher.js export function createMatcher(...) { ... function match { raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location }: Route { // 由于传入的raw是已经标准化过的,所以这里的location和raw没有任何区别 const location = normalizedLocation(raw, currentRoute, false, router) const { name } = location if (name) { ... } else if (location.path) { location.params = {} for (let i = 0; i < pathList.length; i++) { const path = pathList[i] const record = pathMap[path] if (matchRoute(record.regex, location.path, location.param)) { return _createRoute(record, location, redirectedFrom) } } } } function matchRoute( regex: RouteRegExp, path: string, params: Object ): boolean { const m = path.match(regex) if (!m) { return false } else if (!params) { return true } ... return true } function _createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { ... return createRoute(record, location. redirectedFrom, router) } } // ./util/route.js export function createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: ?Location, router?: VueRouter ): Route { const route: Route = { path: location.path || '/', matched: record ? formatMatch(record) :[], // 这里可以先简单理解为 [record] ... } return Object.freeze(route) }` ### createHref函数分析 * 对应代码 href = createHref(base, fullPath, this.mode),对应base = undefine, fullPath = '/foo', this.mode = 'hash'。 * 在vue-route中,默认为hash模式,所以所有的的path前面都会带一个#号 * 这个#号就是在这个函数中体现的 `// ./router.js function createHref(base: string, fullPath: string, mode) { var path = mode === 'hash' ? '#' + fullPath : fullPath return base ? cleanPath(base + '/' + path) : path }` ### 总结 * recolve返回的对象里面的内容主要为location, route , href。 * location是标准化后的to,并且打上了标记表示已标准化,防止多次标准化,提升效率。 * route是通过遍历pathList和pathMap,利用正则表达式找到的和to匹配的路由对象,里面包含很多需要的内容。 * href在默认的hash模式下,会在to的前面加上#号,例如这里的'#/foo'。 `// ./router.js export default class VueRouter { ... resolve(to, current, append) { const location = normalizeLocation(to, current, append, this) const route = this.match(location, current) const fullPath = route.redirectedFrom || route.fullPath const base = this.history.base const href = createHref(base, fullPath, this.mode) return { location, route, href, ... } } }` * 后续的内容就是vue利用h函数将link组件渲染为html的内容,例如设置类名,定义handler函数处理跳转,绑定点击事件,指定 a 标签等等。

vue-router 源码分析——1. 路由匹配

小伙伴们晚上好,今天开一个新坑——vue-router的源码分析。 vuex的源码分析已经发布完了,感兴趣的小伙伴可以看我之前发的文章。 源码分析步骤也是和之前一样的,积沙成塔式地学习源码。根据官网的内容来分析,重点关注某一功能是如何实现的。 ### 创建vue-router 实例时内部发生了什么 * 假设最基本的情况,即官网中的入门示例: `const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] const router = new VueRouter({ routes // (缩写) 相当于 routes: routes })` * 在实例化的过程中创建 matcher 匹配器,调用了 createMatcher 函数 `// ./router.js import type { Matcher } from './create-matcher' export default class VueRouter { matcher: Matcher constructor (options: RouterOptions = {}) { this.matcher = createMatcher(options.routes || [], this) } }` * 在 createMatcher 函数中调用 createRouteMap 函数创建了一系列和path相关的数据,并返回包含自己内容定义的一些函数组成的对象。 `// createMatcher函数 ./create-matcher.js import { createRouteMap } from './create-route-map' export function createMatcher(routes, route): { const {pathList, pathMap, nameMap } = createRouteMap(routes) function match() {} function addRoute() {} function getRoutes() {} function addRoutes() {} return { match, addRoute, getRoutes, addRoutes } }` * createRouteMap函数利用routes数据得出上面需要的对象内容(pathList, pathMap, nameMap),对应 ./create-route-map.js 文件。 * 内部遍历routes => route,并调用 addRouteRecord 函数。 * 对每个route的path,调用 normalizePath 函数规范化。 * 构建一个 record 对象,基本内容还是和route一样,比如path, component。component这里有一个逻辑或运算符,是用来处理命名视图的,可以先不管。 * pathList 记录每个record的path。 * pathMap 记录record的path和record的映射关系。 * nameMap 记录name(命名路由)和recoed的映射关系。 `export function createRouteMap( routes: Array<RouteConfig>, // 下面的其他参数可先不管,因为只传了routes入参,都可看做undefine or any oldPathList?: Array<string>, oldPathMap?: Dictionary<RouteRecord>, oldNameMap?: Dictionary<RouteRecord>, parentRoute?: RouteRecord ): { pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord> } { // 初始化都是空数组和空字典 const pathList: Array<string> = oldPathList || [] const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null) const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null) routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route, parentRoute) }) return { pathList, pathMap, nameMap } } function addRouteRecord( pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord>, route: RouteConfig, parent?: RouteRecord, matchAs?: string ) { const { path, name } = route const pathToRegexpOptions = {} // path 规范化 const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict) const record: RouteRecord = { path: normalizedPaath, components: route.components || { default: route.component }, ... } // 记录 pathList 和 pathMap 的内容 if (!pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record } } function normalizePath( path: string, parent?: RouteRecord, strict?: boolean ) { if (!strict) path = path.replace(/\/$/, '') // 将path尾部的 / 去掉(如果有) if (path[0] == '/') return path }` * 至此,router在初始化时,创建了一个 VueRouter类,里面有一个 matcher属性,包含了用户定义的路由的path、components和相关的匹配逻辑函数,以及三张路由记录表。 * 下一章会分析 router-link 组件是如何实现导航的。

VUEX 3 源码分析——4. 理解Action

好久不见了小伙伴们,今天继续发我的Vuex源码分析。 这段时间我现在已经做完了vuex和vue-router的源码分析笔记,不过还是按功能一节一节地来发吧。 另外我现在还在学习鱼皮大佬的《伙伴匹配系统》项目(但是后端使用Python-Flask实现),里面的一些知识狠狠地弥补了我技术上的不足(主要是因为我的工作是重业务的,反而偏基础的一些东西不是很熟)。 活不多说,开始理解vuex的action吧! ### 官网示例 * 先看一下官网示例: * 在actions上定义函数,函数接受一个context对象,这个对象可以调用commit,state和getters。(官方说context对象不是store实例本身,等会看内部的实现源码就能更直观地感受到了) * action通过dispatch方法触发 * 辅助函数 mapActions * 如果你看过我之前分析的两篇文章(Getter和Mutation),就能发现action的功能和getter,尤其是和mutation大同小异,只是action能进行异步操作嘛。那JavaScript里面,哪种语法适合异步操作能——当然是Promise啦。 * 所以通过深入分析开源项目中的特定功能,我们可以洞察到项目背后的统一设计思想。 * 理解这一设计思想不仅有助于我们掌握单个功能的具体实现,还能帮助我们预测和理解其他功能的设计模式。 * 此外,阅读和学习优秀的开源项目,能够显著提升我们的代码理解能力和审美水平,从而在软件开发实践中,更好地应用这些设计原则和模式。 `const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } }) store.dispatch('increment')` ### 初始化Actions * 之前的Getter和Mutation初始化工作,都是在installModule函数中执行的,action也必然在此。 * Store类初始化一个无原型链对象 this\_actions:this.\_actions = Object.create(null) * 将用户定义的action按key-value格式,存储进 this.\_actions\[key\] 中,其中的每个key对应一个数组,数组中的元素为函数,每个函数都返回一个执行action的Promise `// store-util.js export function installModule(store, rootState, path, module, hot) { ... module.forEachAction((action, key) => { const type = action.root ? key : namespace + key; const handler = action.handler || action; registerAction(store, type, handler, local) }) } function registerAction(store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []); entry.push(function wrappedActionHandler(payload) { const store_obj = { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootSatte: store.state } // 这就是官方手册说的action函数接受的context对象不是store实例本身 let res = handler.call(store, store_obj, payload) // 如果res不是promise,将被封装在一个Promise对象中 if (!isPromise(res)) { res = Promise.resolve(res) } return res }) } // ./module/module.js export default class Module { ... forEachAction(fn) { if (this._rawModule.actions) { forEachValue(this._rawModule.actions, fn) } } }` ### 实现dispatch方法 * 在Store类上定义dispatch方法。将传入的参数格式化后,以**Promise.all**的方式调用 this.\_actions\[type\] 中的所有promise,返回一个新的Promise。 `// ./store.js export class Store { ... dispatch(_type, _payload) { // unifyObjectStyle函数让 Actions 支持同样的载荷方式和对象方式进行分发 const {type, payload} = unifyObjectStyle(_type, _payload) const action = {type, payload} // 分发 sub action 用的 ... const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { ... // 插件的处理 resolve(res) }, error => { ... // 插件的处理 reject(error) }) }) } } // ./store-util.js export function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } return { type, payload, options } }` * 最后将dispatch方法以类似隔离的方式,绑定store为 dispatch 的指定上下文。 * 确保在不同的执行环境中调用dispatch时,它总是与创建它的store实例相关联。 `// ./store.js export class Store { const store = this; const { dispatch, commit } = this; this.dispatch = function boundDispatch(type, payload) { return dispatch.call(store, type, payload) } }` ### mapActions 辅助函数 * 实现方式和mapMutations一模一样,只是将commit换成了dispatch `// ./helper.js export const mapActions = normalizeNamespace((namespace, actions) => { const res = {} ... normalizeMap(actions).forEach(({ key, val }) => { res[key] = function mappedAction (...args) { // get dispatch function from store let dispatch = this.$store.dispatch ... return typeof val === 'function' ? val.apply(this, [dispatch].concat(args)) : dispatch.apply(this.$store, [val].concat(args)) } }) return res })` 以上就是官网上Action的相关源码,下一遍会分析Module的实现源码。

VUEX 3.x源码分析——3. 理解Mutations

这是对vuex3.x版本的源码分析。 本次分析会按以下方法进行: * 1. 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。 * 2. 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。 * 3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。 本章只讲解vuex中的Mutations,这也是vuex官网中“核心概念”的第三个。 ### 初始化 * 在 installModule 函数中,将每个定义的mutation按key-value遍历,存入store.\_mutations中. * 注意这里的每个store.\_mutations\[k\]都是一个数组,数组中的元素是一个函数(wrappedMutationHandler,接收payload参数),此函数负责执行mutation。 `// store-util.js export function installModule(store, rootState, path, module, hot) { ... // forEachMutation方法和getter中的forEachGetter类似 module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) } function registerMutation(store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) //短路求值 entry.push(function wrappedMutationHandler(payload) { handler.call(store, local.state, payload) }) }` ### commit的调用 * 在Store类上,定义了commit方法。 * 调用commit方法后,按传入的参数\_type从store.\_mutations\[type\]中取出所有的执行函数 * 调用\_withCommit方法依次执行mutation函数 `// store.js export class Store { ... commit(_type, _payload, _options) { const {type, payload, options} = unifyObjectStyle(_type, _payload, _options) const mutation = {type, payload} const entry = this._mutations[type] if (!entry) {...} // _withCommit 是添加了额外的逻辑,这里先不管具体细节,等于执行entry.forEach this._withCommit(() => { entry.forEach(function commitItertor(handler) { handler(payload) }) }) } } // store-util.js export function unifyObjectStyle(type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) {assert(...)} return {type, payload, options} // 给外层赋值解构用 }` 至此,可以对官网中的所有内容做相关解释: * 1. 为什么mutation有两种提交方式: unifyObjectStyle 函数处理了两种提交逻辑 `store.commit({ type: 'increment', amount: 10 }) mutations: { increment (state, payload) { state.count += payload.amount } } // store-util.js export function unifyObjectStyle(type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) {assert(...)} return {type, payload, options} // 给外层赋值解构用 }` * 1. 为什么mutation必须是同步函数 mutation在内部是没有针对异步函数做特殊处理,如常见的async-awailt promise等。 * 2. 为什么只能使用commit的形式提交mutation,而不是直接调用mutation来提交: store初始化时没有mutation属性,只有一个\_mutations数据结构,这个数据并不执行mutation,只是存储相关的函数。 另外,我们还能通过源码发现一些官网没有写出,但实际存在的功能: * 在执行mutation时制定了上下文为store,所以必然可以使用getter和其他内容, `// store-util.js function registerMutation(store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) //短路求值 entry.push(function wrappedMutationHandler(payload) { // hander 就是我们定义的某个mutation,它使用了call指定上下文为store handler.call(store, local.state, payload) }) }` ### 如何开发一个mutation的订阅插件 * 在执行commit时,将对应的mutation执行完毕后,还执行了相关的订阅函数。 * store定义了subscribe方法,方便用户来订阅某些内容,执行一些额外的操作,比如日志记录、时间旅行调试等。 * genericSubscribe 函数主要有两个功能:1. 将用户定义的额外操作(即fn)加入到this.\_subscribers中。2.返回一个函数,这个函数的作用是取消订阅,即将fn从this.\_subscribers删除。 `// ./store.js export class Store { ... constructor(options = {}) { this._subscribers = [] } commit(_type, _payload, _options) { const {type, payload, options} = unifyObjectStyle(_type, _payload, _options) const mutation = {type, payload} const entry = this._mutations[type] if (!entry) {...} this._withCommit(() => { entry.forEach(function commitItertor(handler) { handler(payload) }) }) this._subscribers.slice().forEach(sub => sub(mutation, this.state)) } subscribe(fn, options) { return genericSubscribe(fn, this._subscribers, options) } } // store-util.js export function genericSubscribe (fn, subs, options) { if (subs.indexOf(fn) < 0) { options && options.prepend ? subs.unshift(fn) : subs.push(fn) } return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } } }` * 了解了上述源码后,就可以开发一个订阅mutation的插件 * 这里也解释了为什么vuex不建议直接修改state的内容,而是通过mutation来修改。因为vuex有订阅的功能,方便用户开发额外功能或者测试调试。而如果指定修改state则不会触发这些功能。 `const createLoggingPlugin = (mutationType) => { return (store) => { store.subscribe((mutation, state) => { if (mutation.type === mutationType) { console.log(111); } }) } } // 使用插件 const store = new Vuex.Store({ ... plugins: [createLoggingPlugin('increment')] })` 以上就是官网上Mutation的相关源码,下一遍会分析Mutations的实现源码。

VUEX 3.x源码分析——1. 理解state

这是对vuex3.x版本的源码分析。 本次分析会按以下方法进行: * 1. **按官网的使用文档顺序**,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。 * 2. 在围绕某个功能展开讲解时,**所有不相干的内容都会暂时去掉**,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以...表示。 * 3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。 本章只讲解vuex中的state,这也是vuex官网中“核心概念”的第一个。 ### State的初始化 当我们创建了一个store时,state发生了什么。 假设我们按官网的内容,初始化了一个最基础的store: `import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 } // 这里去掉了 mutations 的内容,只关注state })` * 这里实例上是创建了一个Store类,并将count内容作为参数传入 `// ./index.js import { Store } from './store' // Vuex.Store 就是这里的Store,它是从 ./store 导入的 export default { Store, ... } // ./store.js import ModuleCollection from './module/module-collection' export class Store{ constructor(options = {}) { ... this._modules = New ModuleCollection(options) const state = this._modules.root.state // 可以看出是来自于ModuleCollection类的,那接着看下一段 ModuleCollection 的代码 } } // ./module/module-collection.js import Module from './module' export default class ModuleCollection { constructor(rawRootModule) { this.register([], rawRootModule, false) } ... register(path, rawModule, runtime=true) { const newModule = new Module(rawModule, runtime) if (path.length === 0) { this.root = newModule } else {...} ... } } // ./module/module.js export default class Module { constructor(rawModule, runtime) { this._rawModule = rawModule // 最后在这里处理我们传入的state,Module.state const rawState = rawModule.state // 这里能让我们定义的state除了可以是对象,还可以是函数 this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} } }` * 至此,可以看出store在初始化时,将定义的state内容赋值到了module.state上, * 同时将module绑定到 ModuleCollection.root, * 并将实例化的ModuleCollection赋值给Store.\_modules * 最后,为了让vue能监测到state的变化并更新响应的组件,调用了vue中的reactive函数,创建了一个**响应式对象** store.\_state = {data: state},代码如下: `// ./store.js import { resetStoreState } from './store-util' export class Store { constructor(options = {}) { ... this._modules = new ModuleCollection(options) ... const state = this._modules.root.state ... // 在这里生成响应式对象 this._state resetStoreState(this, state) } } // ./store-util.js import { reactive } from 'vue' export function resetStoreState(store, state, hot) { ... store._state = reactive({data: state}) ... }` * 在这里还没有结束,因为如果按上面的内容,我们要使用定义的count属性的话,代码应该是 store.\_state.data.count * 但实际上是 store.state.count。vuex还做了一些简单的处理,就是**定义了一个getter方法**, * 另外还**对state设置了setter方法**,来阻止用户直接修改state的值 * 下面是store.js文件中目前我们所接触的所有代码 ```// ./store.js import ModuleCollection from './module/module-collection' import { resetStoreState } from './store-util' export class Store { constructor(options = {}) { ... this._modules = new ModuleCollection(options) const state = this._modules.root.state resetStoreState(this, state) } get state() { return this._state.data } set state(v) { if (__DEV__) { assert(false, `use store.replaceState() to explicit replace store state.`) } } }``` * 之前使用vuex时,经常有人说直接修改state的属性会告警且不能修改,原因就在这里。 * 但我们通过源码分析可以发现,vuex其实并没有阻止使用者修改state,只是阻止了开发环境的修改,如果部署到生成环境就可以直接修改且不会告警。至于官方建议使用mutation方式来提交修改,说更明确地追踪到状态的变化,等后续源码分析完后就能深刻地理解了。 ### mapState 辅助函数 * 先看下官方的示例: `````// 在单独构建的版本中辅助函数为 Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭头函数可使代码更简练 count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', // 为了能够使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } }) } // 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])````` * 让我们看下vuex的源码,是如何实现辅助生成计算属性这个功能的,同时还保证了多种定义计算属性的方法,同时既可以传入对象,又可以传入数组。 * 首先 mapState 函数定义在 ./helpers.js 中 * normalizeNamespace是一个高阶函数,它接受一个函数作为参数,并返回一个函数。在它的返回函数中,接受namespace和map作为参数,返回normalizeNamespace接受的函数的执行结果。 * 在normalizeNamespace的代码中,如果namespace不是字符串类型的话,就将两个参数的值互换,这样保证我们传入的第一个参数(实例中的对象或数组)会被赋值给第二个参数。但是我感觉这里一股别扭感,按道理map在前,namespace在后更合理一些,也不用交换两个参数的值了。可能是为了保持某种使用习惯的统一吧。 * 所以 mapState 就是 normalizeNamespace 返回的函数。当按上面官方的例子执行类似 mapState(...)的时候,就是将...作为参数传入normalizeNamespace接受的函数,并执行这个函数,返回结果。 * vuex还有其他地方都有使用这种类似的高阶函数,大致就是接受的参数是一个函数A,同时返回的也是一个函数B,在返回的函数B里执行接受的函数A的操作。 * 之后 normalizeMap 函数将我们传入的state做了格式化处理,这里的代码解释了为什么mapState传入的参数既可以是对象,也可以是数组。 * 格式化参数后states后,遍历新的数组,得到res对象。这个对象的key是我们传参时的对象的key或者数组的每个元素,value是一个函数,或者我们定义的函数。 `// ./helper.js export const mapState = normalizeNamespace((namespace, states) => { const res = {} normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState() { let state = this.$store.state let getters = this.$store.getters ... // 这里保证我们在使用mapState时,既可以直接使用state的某个属性, // 也可以函数式地定义来获得某个计算属性 // 同时在使用函数式定义时, // 还能获得当前组件的相关内容(this),以及store中的state和getters return typeof val === 'function' ? val.call(this, state, getters) : state[val] } }) return res }) // normalizeNamespace()括号包裹住的整个部分就是normalizeNamespace它接受的参数,也是一个函数,mapState就是它返回的函数 function normalizeNamespace(fn) { // 下面就是 mapState 会执行的函数内容 return (namespace, map) => { if (typeof namespace !== 'string') { map = namespace namespace = '' } else {...} return fn(namespace, map) } } function normalizeMap(map) { if (!isValidMap(map)) { return [] } return Array.isArray(map) ? map.map(key => ({key, val: key}) : Object.keys(map).map(key => ({key, val: map[key]})) }` * 所以,**mapState是一个高阶函数的返回函数**。 * 执行mapState后,会返回一个对象res,如果我们传入mapState的参数是对象,那res的key就是传参对象的key;如果传入的参数是数组,那这个res的key就是数组中的每个元素。 * **res的value都是一个待执行的mappedState函数**。 * 这个res对象作为当前组件的计算属性。 * 当计算属性所依赖的响应式数据发生变化时,对应的mappedState函数才会执行,然后根据最新的state和getter来计算并返回相应的值。 * 这种设计模式使得状态的映射可以延迟到组件的计算属性被访问时执行。这样做的好处是可以确保状态映射总是基于最新的 state 和 getters,并且可以利用 Vue 的响应式系统来自动更新。 * 同时通过分析源码,还发现了除了可以利用state以外,还能访问到getters的内容,这个是官网没写的,例如,我们可以这样修改官方的例子: ```import { mapState } from 'vuex' export default { // ... computed: mapState({ // 为了能够使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } // 函数还可以接受getters,例如: countPlusLocalState_2(state, getters) { return state.count + this.localCount + getters.doubleCount } }) }``` * 最后就是官网的“对象展开运算符” * 只要理解了mapState是返回一个对象,以及展开运算符的意义,就知道必然能这样使用了 `computed: { localComputed () { /* ... */ }, // 使用对象展开运算符将此对象混入到外部对象中 ...mapState({ // ... }) }` 以上就是官网上state的相关源码,下一遍会分析getters的实现源码。

axios源码分析及设计思路

大家好我是zest,加入星球有一段时间了,之前说想分享一些自己写的源码分析,现在总算有时间写一写了。这是我最早的一篇草稿,比较粗糙,前几天看github上的axios源码早就添加了很多代码逻辑,比如fetch的适配器。但是大体设计没有太多的改变,也就懒得重头再改啦。 欢迎各位交流和指点! 先行知识 ==== 注意可能因为版本不同,导致源码的某些细节不一致,但是只要设计思路是一致的就不会有太割裂的改动。 另外在源码分析中只会**聚焦于实现某一目的的相关代码**,不会贴出全部代码。这样更加精简也便于理解。 XMLHttpRequest和Fetch -------------------- ### XMLHttpRequest是一个早期的浏览器 API,用于执行异步请求。 **兼容性**:XHR 在所有现代浏览器中都得到了支持,并且对于旧版浏览器的支持要比 fetch 好。 **事件处理**:XHR 提供了一个基于事件的接口,可以监听多个事件,如 progress、error、load 等。 **状态信息**:XHR 在请求过程中提供了更多的状态信息,例如可以通过 readyState 属性了解请求的当前状态。 **复杂性**:XHR 的 API 相对较老,使用起来比较繁琐,需要处理状态变化、事件监听等。 **可读性**:XHR 的代码往往不够直观,尤其是在处理错误和异步逻辑时。 **JSON支持**:XHR 不直接支持 JSON,需要手动将响应文本转换为 JSON。 ### Fetch 是一个现代的、基于 Promise 的 API,用于替代 XMLHttpRequest。 **Promise 语法**: fetch 使用 Promise,这使得异步代码更容易编写和理解。 **语法简洁**: 提供了一个更为简洁和直观的 API。 **流式数据**:支持流式数据,可以在数据到达时逐块处理,而不必等待全部数据。 **默认异步**:默认就是异步的,不需要额外的设置。 **更好的 JSON 支持**:直接将响应解析为 JSON。 **兼容性**:在旧版浏览器中可能不被支持(往往实际中的打包会做降级处理)。 **默认不发送或接收 cookies**: 除非明确设置。 **没有timeout**:需要手写代码 文件结构 ==== * 1. adapters/:适配器文件夹,用于存放适配器处理文件 i. xhr.js * 2. core/:核心业务文件夹 i. Axios.js ii. dispatchRequest.js iii. interceptorManager.js * 3. helper/:辅助用文件夹,存放工具函数 * 4. axios.js:对外暴露的实例化入口,使用了工厂设计模式来创建实例 * 5. defaults.js:Axios类的初始化配置内容 * 6. utils.js:常用的工具函数 adapters/xhr.js 分析 ------------------ 对XMLHttpRequest进行封装,实现一个xhr的适配器文件。 返回一个promise,函数参数为config配置对象。 在这个返回的promise中进行XMLHttpRequest的请求处理。 `export default function xhrAdapter(config) { let xhr_promise = new Promise(function dispatchXhrRequest(resolve, reject) { let request = new XMLHttpRequest(); // XMLHttpRequest的请求 request.open(config.method.toUpperCase(), config.url, true); request.send(config.data) let responseData = request.responseText; resolve(response); request = null } return xhr_promise }` 构建一个axios的默认配置对象 ---------------- 包含适配器和其他请求内容,用于给axios实例化时使用。对应文件为./defaults.js `import utils from './utils' function getDefaultAdapter() { let adapter = require('./adapters/xhr').default; return adapter } let defaults = { adapter: getDefaultAdapter(), transformRequest: [], transformResponse: [ function transformResponse(data) { if (typeof data === 'string') { try {data = JSON.parse(data);} catch (error) {console.log(error)} } return data } ], timeout: 5000, xsrfCookieName: 'XSRF-TOKEN', //xsrfCookieName 表示存储 token 的 cookie 名称 xsrfHeaderName: 'X-XSRF-TOKEN', //xsrfHeaderName 表示请求 headers 中 token 对应的 header 名称 maxContentLength: -1, // 表示ASP支持的最大请求大小 maxBodyLength: -1, // 允许的http请求内容的最大字节数 validateStatus: function validateStatus(status) { return status >= 200 && status <= 299; }, headers: { 'common': { 'Accept': 'application/json, text/plain, */*' } } } export default defaults` 构建核心的Axios类,对应 ./core/Axios.js ------------------------------ * 1. Axios类有两个属性,分别是defaults配置内容和拦截器 * 2. 构建一个request方法,调用了适配器中的xmh请求,并返回一个promise * 3. 构建Http请求方法(get, post),所有的方法都是对request方法的回调 `import utils from "../utils"; import { buildURL } from '../helpers/buildURL' import { mergeConfig } from './mergeConfig' import { dispatchRequest } from './dispatchRequest' import InterceptorManager from './InterceptorManager.js' function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } Axios.prototype.request = function request(config) { if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } config = mergeConfig(this.defaults, config) if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); } else { config.method = 'get' } let chain = [dispatchRequest, undefined]; let promise = Promise.resolve(config); promise = promise.then(chain.shift(), chain.shift()); return promise; } utils.forEach( ['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(mergeConfig( config || {}, {method: method, url: url} )) } } ) utils.forEach( ['post', 'put', 'patch'], function forEachMethodWithData(method) { Axios.prototype[method] = function(url, data, config) { return this.request(mergeConfig( config || {}, { method: method, url: url, data: data, } )) } } ) export default Axios` 构建axios.js 对应文件 ./axios.js -------------------------- * 1. 按工厂模式暴露实例 * 2. createInstance函数中的bind和extend内容,保证实例能以函数的方式调用,也能以对象的方式调用 `import utils from './utils' import defaults from './defaults.js'; import Axios from './core/Axios.js'; import bind from './helpers/bind.js'; import {mergeConfig} from './core/mergeConfig.js'; function createInstance(defaultConfig) { let context = new Axios(defaultConfig); // 为了让实例既能像对象一样调用 axios.get 也能像函数一样调用 axios(config) let instance = bind(Axios.prototype.request, context); utils.extend(instance, Axios.prototype, context); utils.extend(instance, context); return instance } let axios = createInstance(defaults); axios.create = function create(inputConfig) { return createInstance(mergeConfig(axios.defaults, inputConfig)) } export default axios;` 这里说一些我自己对为什么需要让axios既能像对象一样调用 axios.get 也能像函数一样调用 axios(config)的理解。我刚开始看的时候是不太理解,保持使用方式一致不应该很标准很统一吗。但作为一个开源项目,肯定需要考虑方方面面,因为**开发者不知道使用者的实际环境是什么样的**,所以需要一个更高更全面的视角来**确保项目的适用性**。**这一思维在很多开源项目中都有体现**。 构建拦截管理器文件 对应 ./core/InterceptorManager.js ----------------------------------------- * 1. 有一个handlers属性,数组类型,用于存储所有的拦截器处理函数 * 2. 设置了use, eject, forEach 方法 `import utils from '../utils.js'; function InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }) return this.handlers.length-1; } InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null } } InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h) } }) } export default InterceptorManager` 在 ./core/Acios.js中的Axior.request方法中,实现拦截器promise ------------------------------------------------ * 1. 初始化一个处理链,有两个节点,分别是当前请求的适配器promise和undefined * 2. 遍历请求拦截器,将其属性handlers中的fulfilled和rejected内容放入处理链的头部 * 3. 遍历响应拦截器,将其属性handlers中的fulfilled和rejected内容放入处理链的尾部 * 4. 每一次向处理链中取出两个头部节点,分别用于promise.then中的resolve和reject `let chain = [dispatchRequest, undefined]; let promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected) }) this.interceptors.response.forEach(function pushRequestInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected) }) while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise;` 以上的内容基本就是axios的核心内容。 axios是对XMLHttpRequest的一层包装,但是又开放了adapter接口预留了后续的开发余地。 axios的拦截器本质上是一个数组,每次取出数组中的两个元素作为promise.then的fulfilled和rejected。 axios项目的utils.js文件值得一看,里面的工具函数不仅仅适合它本身,也适合我们自己拿来在自己的项目中使用。 最后补充一下我当时开发的基于fetch的axios适配器 ---------------------------- `import axios from 'axios'; const fetchAdapter = (config) => { config.body = config.body ? JSON.stringify(config.body) : config.data return fetch(config.url, { method: config.method, body: config.body, }) .then(response => { return response }) .catch(error => { return Promise.reject(error) }) } const fetch_service = axios.create({ adapter: fetchAdapter, }); fetch_service.interceptors.response.use( response => { if (response.ok) { // 处理兼容 response.data = await response.json(); return response } else { return {} } }, error => { console.log(error); return Promise.reject(); } ); export default fetch_service` axios体量比较小,逻辑也比较简单,但是里面涉及的思想是和其他开源项目相同的。后续我会更新vuex的源码,也会体现和axios相同的开发思想。

如何成为一个源码高手

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是yes。</p> <p><br></p> <p>从业多年,我研读过非常多开源项目的源码,比如:RocketMQ、Kafka、Dubbo 、Netty、Tomcat、Redis、Linux 、Spring 等等。</p> <p><br></p> <p>期间也有过迷茫,也走过很多弯路,今天就来分享一些心得体会,希望对大家有点帮助。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">疯狂劝退</strong></h1> <p><br></p> <p>在实习时期,我非常羡慕组里架构师,他总能丝滑地帮助同事处理各种各样的问题,甚至于别的项目组的同事都经常来请教他。</p> <p><br></p> <p>我非常狗腿的黏在他身边,希望他传授一点“功力”给我,每天问东问西的,直到有天他跟我说:”其实这些东西不难,只要你多看看源码,知晓里面的原理,从报错就能直接看出问题所在。“</p> <p><br></p> <p>说出来不怕人笑话,我还去上网查了下。</p> <p><br></p> <p>查啥?查叫源码。</p> <p><br></p> <p>当天我就去下了 Spring 的源码,在打开源码那一刻,我血脉喷张,感觉自己立马就要成为人人敬仰的大牛。</p> <p><br></p> <p>背景音乐已经响起,却又戛然而止,妈的,idea 全飘红,项目如何启动我都不清楚,直接一脸懵逼。</p> <p><br></p> <p>花了一周的时间,idea 终于不红了,启动入口也找到了,背景音乐又缓缓响起,但是没过五分钟又焉了.....</p> <p><br></p> <p>我一阵点点点,从这个类跳到那个类,类中的方法又各种嵌套,每个类的名字我都念得出来,却是不知道它的作用,就这样迷失在 Spring 的类中。</p> <p><br></p> <p>不知所云,手足无措。</p> <p><br></p> <p>挣扎许久,终究化作一声叹息。</p> <p><br></p> <p>自我安慰道:算了,大牛让别人当吧,我当请教大牛的那个人,好好做绿叶该做的事情。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">卷土重来</strong></h1> <p><br></p> <p>就这样浑浑噩噩的过了两年,从一个菜鸟,变成了一个——老了两岁的菜鸟。</p> <p><br></p> <p>摆脱了实习生的身份,但实际能力可能也就比实习生 CRUD 写的快一些。</p> <p><br></p> <p>没办法,虽然期间我也看了很多博客,学习的热情是高涨的,但是对知识的理解基本上是浮于表面的。</p> <p><br></p> <p>平时开会啥都能搭上话,但一要我分析让我说个所以然,就慌了。</p> <p><br></p> <p>心里没底,因为很模糊,很多时候说的头头是道,让我写P都写不出来或者说不敢写,心里对源码是惧怕的,总觉得自己的代码这么low,配不上非 CURD 的功能。</p> <p><br></p> <p>直到有天,同事向我请教消息队列的实现原理,由于天天在他前面装B,我拉不下脸说不会,只能被动的去学习去解答他的问题。</p> <p><br></p> <p>你别说,这效果杠杠的,果然人都是逼出来的。</p> <p><br></p> <p>不过这也是因为我打开了正确学习源码方式。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">系统学习源码的方式</strong></h1> <p><br></p> <p>这内容我在 20 年分享过,这里再分享给星球的同学。</p> <p><br></p> <p>首先,我把 RocketMQ 官网、wiki 都读了一遍,了解具体涉及到的概念、名称、特性、架构。</p> <p><br></p> <p>对整体项目的主要角色以及之间的数据交互都了然于心,在脑海中形成了角色分布图和数据流转图。</p> <p><br></p> <p>当然也包括 RocketMQ 的功能使用,比如顺序消息发送的 api 要如何操作、事务消息怎么发的、如果消费消息等等。</p> <p><br></p> <p>在源码中提供了很多 demo,利用 demo 就可以非常快速地熟悉 api 的使用。</p> <p><br></p> <p></p> <p><br></p> <p>到这其实距离成功就差一半了,因为你已经打好了基础,且找到了突破口。</p> <p><br></p> <p>基础就是对这个项目的基本了解和功能熟悉,突破口就是这些 demo。</p> <p><br></p> <p>有了 demo 就有了 debug 的入口,你只要打好断点,debug 启动,一步一步跟下去。</p> <p><br></p> <p>这跟之前直接下源码看源码有区别吗?</p> <p><br></p> <p>当然有区别!</p> <p><br></p> <p>此时的你不像一只无头苍蝇到处乱啃,你是在知晓大体会涉及到的角色和数据流转之后读源码,这很不一样!</p> <p><br></p> <p>你会对一些方法调用有一种“认可感”,因为你知晓大致的流程,所以觉得本该如此,看完会有种,原来是这样实现的了然感。</p> <p><br></p> <p>读源码有时候会觉得代码很多,分支好多。</p> <p><br></p> <p>没事,代码在你本地,随意你修改,你可以先拷贝一份,然后把一些异常处理分支先删了。</p> <p><br></p> <p><strong style="color: rgb(60, 112, 198);">核心流程先理清楚!</strong></p> <p><br></p> <p>理清楚了一个流程之后开始画图,流程图、脑图都上。</p> <p><br></p> <p>然后反过来再把之前删减的异常代码补上,把异常部分也弄清楚,补充完整流程图、脑图等。</p> <p><br></p> <p>看看我之前分析 Kafka 的时候画的图,就类似这样的搞清楚一个流程:</p> <p><br></p> <p></p> <p><br></p> <p>然后这一模块就收工了!搞定!</p> <p><br></p> <p>接着各种其他流程模块发散出去,到最后大致的流程就都清晰了,源码也就读的差不多了。</p> <p><br></p> <p>读源码的时候肯定也会遇到一些不能理解的,先略过,主流程先搞懂。</p> <p><br></p> <p>搞懂整体核心流程之后可以抠一些细节了,比如我之前看 Dubbo 的时候就抠了一个细节:</p> <p><br></p> <p></p> <p><br></p> <p>就这个 if switch 能关系到底层 CPU 的分支预测!</p> <p><br></p> <p>再比如之前看 Kafka 的索引设计涉及到二分查找,但是源码中是改版的二分查找。把索引项分为热区和冷区,深究下去是为了避免缺页中断。</p> <p><br></p> <p></p> <p><br></p> <p>再比如 RocketMQ 里面看预热文件的时候涉及的这个方法。</p> <p><br></p> <p></p> <p><br></p> <p>这时候又涉及到 mlock、madvise 。</p> <p><br></p> <p></p> <p><br></p> <p>这些就是细节,而细节往往就是我们需要学习的地方,<strong style="color: rgb(60, 112, 198);">所以在理清整体流程之后不要错过细节</strong>。</p> <p><br></p> <p>往往你觉得很奇怪的地方可能就是一些“骚操作”,学的就是“骚操作”。</p> <p><br></p> <p>这是系统读源码的方法,是为了让自己得到晋升、学习优秀开源框架是如何设计而读的源码。</p> <p><br></p> <p>只要你攻克了一个,后续就能越读越多,越读越轻松。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">为了找问题读源码的方式</strong></h1> <p><br></p> <p>有了系统读源码的基础后,你就能尝试上手去解决各种问题了。</p> <p><br></p> <p>比如,有时候是项目出错,一般而言有日志,所以通过日志搜源码就行。</p> <p><br></p> <p>如果你本身对这个框架很熟悉那当然最好,如果不熟悉通过日志搜索结合上下文其实也能找到一些缘由。</p> <p><br></p> <p>不过有时候还是得整个链路分析下来才能排查问题,这个看功力了。</p> <p><br></p> <p>有时候是因为看到一些文章的说法冲突了,一篇说 A 另一篇说 B ,如果你找不到权威的信息你只能自己去看源码,通过关键字搜。</p> <p><br></p> <p>比如我这篇写的:</p> <p><br></p> <p></p> <p><br></p> <p>这就是源码之下无秘密。</p> <p><br></p> <p>这种目的性很强的读源码就得结合当时上下文和靠个人功力了,这也是想成为大牛必须要掌握的一个能力。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">最后</strong></h1> <p><br></p> <p>不知不觉说了这么多。</p> <p><br></p> <p>我只能告诉你不要被源码劝退,要<strong style="color: rgb(60, 112, 198);">自顶向下的看源码,不要一来就钻进细节,先从官网等渠道对开源框架有个全面的了解,然后看源码理清主流程</strong>。</p> <p><br></p> <p>再配合流程图、时序图、脑图等记录、归类。</p> <p><br></p> <p>然后再看细节,学学优秀开源框架的“骚操作”。</p> <p><br></p> <p>模仿它,学会它,从中能延展出很多额外的底层知识,比如上面提到的预防缺页中断,预热锁页,分支预测等等。</p> <p><br></p> <p>当然也可以先去网上搜一搜别人的源码分析文章,然后自己再上手,这样会比较舒服和顺利。</p> <p><br></p> <p>或者看一些相关的付费专栏,人家汇总整理知识,你花点小钱,节省你自己研究的时间,不亏。</p> <p><br></p> <p>还有,源码是块硬骨头这毋庸置疑,我只能告诉你看源码的准备工作和一些小心得。</p> <p><br></p> <p>最后,要抱着目的去学源码,比如你是想更进一步地去学习其中的原理,或想参考其实现解决工作上遇到的一些问题等等。</p> <p><br></p> <p>而不是为了学习源码而学习源码。</p> <p><br></p> <p>道阻且长,行则将至。</p> <p><br></p> <p>冲!</p> <p><br></p> <p>最后的最后,推荐下我在知识星球写的<strong style="color: rgb(60, 112, 198);"><a href="https://articles.zsxq.com/id_215wesherlix.html" target="_blank">消息队列-RocketMQ专栏</a></strong>,各位小伙伴可以看看,如果有啥问题或者疑问欢迎提出!</p> <p><br></p> <p>这个专栏的定位是原理入门,系统学习下来对付一般的面试消息队列原理问题应该是ok的。</p> <p><br></p> <p>我是yes,从一点点到亿点点,我们下篇见~</p> <p><br></p> <p><br></p> <p><br></p> </div> </body> </html>

下载 APP