一文读懂vuex4源码,原来provide/inject就是妙用了原型链?

1. 前言

你好,我是若川,欢迎加我微信ruochuan12,加群长期交流学习。

这是学习源码整体架构系列 之 vuex4 源码(第十篇)。学习源码整体架构系列文章(有哪些必看的JS库):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux、vue-devtools 直接打开文件功能揭秘。

本文仓库地址[1]git clone https://github.com/lxchuan12/vuex4-analysis.git,本文最佳阅读方式,克隆仓库自己动手调试,容易吸收消化。

要是有人说到怎么读源码,正在读文章的你能推荐我的源码系列文章,那真是无以为报啊

我的文章,尽量写得让想看源码又不知道怎么看的读者能看懂。我都是推荐使用搭建环境断点调试源码学习哪里不会点哪里边调试边看,而不是硬看。正所谓:授人与鱼不如授人予渔

阅读本文后你将学到:

  • git subtree 管理子仓库

    如何学习Vuex 4源码、理解Vuex原理

    Vuex 4Vuex 3 的异同

    Vuex 4 composition API 如何使用

    Vue.provide / Vue.inject API 使用和原理

    如何写一个 Vue3 插件

    等等

如果对于谷歌浏览器调试还不是很熟悉的读者,可以看这篇文章chrome devtools source面板,写的很详细。顺带提一下,我打开的设置,source面板中支持展开搜索代码块(默认不支持),一图胜千言。

谷歌浏览器是我们前端常用的工具,所以建议大家深入学习,毕竟工欲善其事,必先利其器

之前写过Vuex 3的源码文章学习 vuex 源码整体架构,打造属于自己的状态管理库[2],仓库有很详细的注释和看源码方法,所以本文不会过多赘述与Vuex 3源码相同的地方。

1.1 本文阅读最佳方式

把我的vuex4源码仓库 git clone https://github.com/lxchuan12/vuex4-analysis.git克隆下来,顺便star一下我的vuex4源码学习仓库[3]^_^。跟着文章节奏调试和示例代码调试,用chrome动手调试印象更加深刻。文章长段代码不用细看,可以调试时再细看。看这类源码文章百遍,可能不如自己多调试几遍,大胆猜测,小心求证。也欢迎加我微信交流ruochuan12

2. Vuex 原理简述

结论先行Vuex原理可以拆解为三个关键点。第一点、其实就是每个组件实例里都注入了Store实例。第二点、Store实例中的各种方法都是为Store中的属性服务的。第三点、Store中的属性变更触发视图更新。

本文主要讲解第一点。第二点在我的上一篇文章学习 vuex 源码整体架构,打造属于自己的状态管理库详细讲了,本文就不赘述了。第三点两篇文章都没有详细讲述。

以下是一段简短的代码说明Vuex原理的。

// 简版
class Store{constructor(){this._state = 'Store 实例';}dispatch(val){this.__state = val;}commit(){}// 省略
}const store = new Store();
var rootInstance = {parent: null,provides: {store: store,},
};
var parentInstance = {parent: rootInstance,provides: {store: store,}
};
var childInstance1 = {parent: parentInstance,provides: {store: store,}
};
var childInstance2 = {parent: parentInstance,provides: {store: store,}
};store.dispatch('我被修改了');
// store Store {_state: "我被修改了"}// rootInstance、parentInstance、childInstance1、childInstance2 这些对象中的provides.store都改了。
// 因为共享着同一个store对象。
provide,inject示例图

看了上面的官方文档中的图,大概知道是用provide父级组件中提供Store实例,用inject来获取到Store实例。

那么接下来,带着问题:

1、为什么修改了实例store里的属性,变更后会触发视图更新。
2、Vuex4作为Vue的插件如何实现和Vue结合的。
3、provideinject的如何实现的,每个组件如何获取到组件实例中的Store的。
4、为什么每个组件对象里都有Store实例对象了(渲染组件对象过程)。
5、为什么在组件中写的provide提供的数据,能被子级组件获取到。

3. Vuex 4 重大改变

在看源码之前,先来看下Vuex 4发布的release和官方文档迁移提到的重大改变,Vuex 4 release[4]

从 3.x 迁移到 4.0[5]

Vuex 4的重点是兼容性。Vuex 4支持使用Vue 3开发,并且直接提供了和Vuex 3完全相同的API,因此用户可以在Vue 3项目中复用现有的Vuex代码。

相比Vuex 3版本。主要有如下重大改变(其他的在上方链接中):

3.1 安装过程

Vuex 3Vue.use(Vuex)

Vuex 4则是app.use(store)

import { createStore } from 'vuex'export const store = createStore({state() {return {count: 1}}
})
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'const app = createApp(App)app.use(store)app.mount('#app')

3.2 核心模块导出了 createLogger 函数

import { createLogger } from 'vuex'

接下来我们从源码的角度来看这些重大改变

4. 从源码角度看 Vuex 4 重大变化

4.1 chrome 调试 Vuex 4 源码准备工作

git subtree add --prefix=vuex https://github.com/vuejs/vuex.git 4.0

这种方式保留了vuex4仓库的git记录信息。更多git subtree使用方式可以查看这篇文章用 Git Subtree 在多个 Git 项目间双向同步子项目,附简明使用手册[6]

作为读者朋友的你,只需克隆我的`Vuex 4`源码仓库[7] https://github.com/lxchuan12/vuex4-analysis.git 即可,也欢迎star一下。

vuex/examples/webpack.config.js,加个devtool: 'source-map',这样就能开启sourcemap调试源码了。

我们使用项目中的购物车的例子调试,贯穿全文。

git clone https://github.com/lxchuan12/vuex4-analysis.git
cd vuex
npm i
npm run dev
# 打开 http://localhost:8080/
# 选择 composition  购物车的例子 shopping-cart
# 打开 http://localhost:8080/composition/shopping-cart/
# 按 F12 打开调试工具,source面板 => page => webpack:// => .

据说一图胜千言,这时简单截个调试的图。

vuex debugger

找到 createStore函数打上断点。

// webpack:///./examples/composition/shopping-cart/store/index.js
import { createStore, createLogger } from 'vuex'
import cart from './modules/cart'
import products from './modules/products'const debug = process.env.NODE_ENV !== 'production'export default createStore({modules: {cart,products},strict: debug,plugins: debug ? [createLogger()] : []
})

找到app.js入口,在app.use(store)app.mount('#app')等打上断点。

// webpack:///./examples/composition/shopping-cart/app.js
import { createApp } from 'vue'
import App from './components/App.vue'
import store from './store'
import { currency } from './currency'const app = createApp(App)app.use(store)app.mount('#app')

接下来,我们从createApp({})app.use(Store)两个方面发散开来讲解。

4.2 Vuex.createStore 函数

相比 Vuex 3 中,new Vuex.Store,其实是一样的。只不过为了和Vue 3 统一,Vuex 4 额外多了一个 createStore 函数。

export function createStore (options) {return new Store(options)
}
class Store{constructor (options = {}){// 省略若干代码...this._modules = new ModuleCollection(options)const state = this._modules.root.stateresetStoreState(this, state)// 省略若干代码...}
}
function resetStoreState (store, state, hot) {// 省略若干代码...store._state = reactive({data: state})// 省略若干代码...
}

监测数据

Vuex 3不同的是,监听数据不再是用new Vue(),而是Vue 3提供的reactive方法。

Vue.reactive 函数方法,本文就不展开讲解了。因为展开来讲,又可以写篇新的文章了。只需要知道主要功能是监测数据改变,变更视图即可。

这也就算解答了开头提出的第一个问题。

跟着断点我们继续看app.use()方法,Vue提供的插件机制。

4.3 app.use() 方法

use做的事情说起来也算简单,把传递过来的插件添加插件集合中,到防止重复。

执行插件,如果是对象,install是函数,则把参数app和其他参数传递给install函数执行。如果是函数直接执行。

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function createAppAPI(render, hydrate) {return function createApp(rootComponent, rootProps = null) {// 代码有删减const installedPlugins = new Set();const app = (context.app = {use(plugin, ...options) {// 已经有插件,并且 不是生产环境,报警告。if (installedPlugins.has(plugin)) {(process.env.NODE_ENV !== 'production') && warn(`Plugin has already been applied to target app.`);}// 插件的install 是函数,则添加插件,并执行 install 函数else if (plugin && isFunction(plugin.install)) {installedPlugins.add(plugin);// 断点plugin.install(app, ...options);}// 插件本身 是函数,则添加插件,并执行 插件本身函数else if (isFunction(plugin)) {installedPlugins.add(plugin);plugin(app, ...options);}// 如果都不是报警告else if ((process.env.NODE_ENV !== 'production')) {warn(`A plugin must either be a function or an object with an "install" ` +`function.`);}// 支持链式调用return app;},provide(){ // 省略... 后文再讲}});}
}

上面代码中,断点这行plugin.install(app, ...options);

跟着断点走到下一步,install函数。

4.4 install 函数

export class Store{// 省略若干代码...install (app, injectKey) {// 为 composition API 中使用//  可以传入 injectKey  如果没传取默认的 storeKey 也就是 storeapp.provide(injectKey || storeKey, this)// 为 option API 中使用app.config.globalProperties.$store = this}// 省略若干代码...
}

Vuex4中的install函数相对比Vuex3中简单了许多。第一句是给Composition API提供的。注入到根实例对象中。第二句则是为option API提供的。

接着断点这两句,按F11来看app.provide实现。

4.4.1 app.provide

简单来说就是给contextprovides属性中加了store = Store实例对象

provide(key, value) {// 如果已经有值了警告if ((process.env.NODE_ENV !== 'production') && key in context.provides) {warn(`App already provides property with key "${String(key)}". ` +`It will be overwritten with the new value.`);}// TypeScript doesn't allow symbols as index type// https://github.com/Microsoft/TypeScript/issues/24587context.provides[key] = value;return app;
}

接着从上方代码中搜索context,可以发现这一句代码:

const context = createAppContext();

接着我们来看函数 createAppContextcontext 为上下文

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function createAppContext() {return {app: null,config: {isNativeTag: NO,performance: false,globalProperties: {},optionMergeStrategies: {},isCustomElement: NO,errorHandler: undefined,warnHandler: undefined},mixins: [],components: {},directives: {},provides: Object.create(null)};
}

Vue3 文档应用配置(app.config)[8]

4.4.2 app.config.globalProperties

app.config.globalProperties 官方文档[9]

用法:

app.config.globalProperties.$store = {}app.component('child-component', {mounted() {console.log(this.$store) // '{}'}
})

也就能解释为什么每个组件都可以使用 this.$store.xxx 访问 vuex中的方法和属性了。

也就是说在appContext.provides中注入了一个Store实例对象。这时也就是相当于根组件实例和config全局配置globalProperties中有了Store实例对象

至此我们就看完,createStore(store)app.use(store)两个API

app.provide 其实是用于composition API使用的。

但这只是文档中这样说的,为什么就每个组件实例都能访问的呢,我们继续深入探究下原理。

接下来,我们看下源码具体实现,为什么每个组件实例中都能获取到的。

这之前先来看下组合式API中,我们如何使用Vuex4,这是线索。

4.5 composition API 中如何使用Vuex 4

接着我们找到如下文件,useStore是我们断点的对象。

// webpack:///./examples/composition/shopping-cart/components/ShoppingCart.vue
import { computed } from 'vue'
import { useStore } from 'vuex'
import { currency } from '../currency'export default {setup () {const store = useStore()// 我加的这行代码window.ShoppingCartStore = store;// 省略了若干代码}
}

接着断点按F11,单步调试,会发现最终是使用了Vue.inject方法。

4.5.1 Vuex.useStore 源码实现

// vuex/src/injectKey.js
import { inject } from 'vue'export const storeKey = 'store'export function useStore (key = null) {return inject(key !== null ? key : storeKey)
}

4.5.2 Vue.inject 源码实现

接着看inject函数,看着代码很多,其实原理很简单,就是要找到我们用provide提供的值。

如果没有父级,也就是根实例,就取实例对象中的vnode.appContext.provides。否则就取父级中的instance.parent.provides的值。

Vuex4源码里则是:Store实例对象。

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function inject(key, defaultValue, treatDefaultAsFactory = false) {// fallback to `currentRenderingInstance` so that this can be called in// a functional component// 如果是被一个函数式组件调用则取 currentRenderingInstanceconst instance = currentInstance || currentRenderingInstance;if (instance) {// #2400// to support `app.use` plugins,// fallback to appContext's `provides` if the intance is at rootconst provides = instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides: instance.parent.provides;if (provides && key in provides) {// TS doesn't allow symbol as index typereturn provides[key];}// 如果参数大于1个 第二个则是默认值 ,第三个参数是 true,并且第二个值是函数则执行函数。else if (arguments.length > 1) {return treatDefaultAsFactory && isFunction(defaultValue)? defaultValue(): defaultValue;}// 警告没找到else if ((process.env.NODE_ENV !== 'production')) {warn(`injection "${String(key)}" not found.`);}}// 如果没有当前实例则说明则报警告。// 也就是是说inject必须在setup中调用或者在函数式组件中使用else if ((process.env.NODE_ENV !== 'production')) {warn(`inject() can only be used inside setup() or functional components.`);}
}

接着我们继续来看inject的相对应的provide

4.5.3  Vue.provide 源码实现

provide函数作用其实也算简单,1、也就是给当前组件实例上的provides对象属性,添加键值对key/value

2、还有一个作用是当当前组件和父级组件的provides相同时,在当前组件实例中的provides对象和父级,则建立链接,也就是原型[[prototype]],(__proto__)。

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function provide(key, value) {if (!currentInstance) {if ((process.env.NODE_ENV !== 'production')) {warn(`provide() can only be used inside setup().`);}}else {let provides = currentInstance.provides;// by default an instance inherits its parent's provides object// but when it needs to provide values of its own, it creates its// own provides object using parent provides object as prototype.// this way in `inject` we can simply look up injections from direct// parent and let the prototype chain do the work.const parentProvides = currentInstance.parent && currentInstance.parent.provides;if (parentProvides === provides) {provides = currentInstance.provides = Object.create(parentProvides);}// TS doesn't allow symbol as index typeprovides[key] = value;}
}

provide函数中的这段,可能不是那么好理解。

if (parentProvides === provides) {provides = currentInstance.provides = Object.create(parentProvides);
}

我们来举个例子消化一下。

var currentInstance = { provides: { store: { __state: 'Store实例' }  } };
var provides = currentInstance.provides;
// 这句是我手动加的,在后文中则是创建实例时就是写的同一个对象,当然就会相等了。
var parentProvides = provides;
if(parentProvides === provides){provides =  currentInstance.provides = Object.create(parentProvides);
}

经过一次执行这个后,currentInstance 就变成了这样。

{provides: {// 可以容纳其他属性,比如用户自己写的__proto__ : { store: { __state: 'Store实例' }  }}
}

执行第二次时,currentInstance 则是:

{provides: {// 可以容纳其他属性,比如用户自己写的__proto__: {// 可以容纳其他属性,比如用户自己写的__proto__ : { store: { __state: 'Store实例' }  }}}
}

以此类推,多执行provide几次,原型链就越长。

上文injectprovide函数中都有个变量currentInstance当前实例,那么当前实例对象是怎么来的呢。

为什么每个组件就能访问到,依赖注入的思想。有一个讨巧的方法,就是在文件runtime-core.esm-bundler.js中搜索provides,则能搜索到createComponentInstance函数

接下来我们createComponentInstance函数如何创建组件实例。

4.6 createComponentInstance 创建组件实例

可以禁用其他断点,单独断点这里, 比如:const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;来看具体实现。

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
const emptyAppContext = createAppContext();
let uid$1 = 0;
function createComponentInstance(vnode, parent, suspense) {const type = vnode.type;const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;const instance = {uid: uid$1++,vnode,type,parent,appContext,root: null,next: null,subTree: null,// ...provides: parent ? parent.provides : Object.create(appContext.provides),// ...}instance.root = parent ? parent.root : instance;// ...return instance;
}

断点时会发现,根组件实例时vnode已经生成,至于是什么时候生成的,我整理了下简化版。

// 把上文中的 appContext 赋值给了 `appContext`
mount(rootContainer, isHydrate) {if (!isMounted) {const vnode = createVNode(rootComponent, rootProps);// store app context on the root VNode.// this will be set on the root instance on initial mount.vnode.appContext = context;}
},

其中 Object.create 其实就是建立原型关系。这时放一张图,一图胜千言。

直观的图

出自黄轶老师拉勾专栏,本想自己画一张图,但觉得这张挺好的。

4.6.1 组件实例生成了,那怎么把它们结合呢

这时,也有一个讨巧的方法,在runtime-core.esm-bundler.js文件中,搜索 provide(可以搜到如下代码:

这段代码其实看起来很复杂的样子,实际上主要就是把用户在组件中写的provides对象或者函数返回值遍历, 生成类似这样的实例对象:

// 当前组件实例
{parent: '父级的实例',provides: {// 可以容纳其他属性,比如用户自己写的__proto__: {// 可以容纳其他属性,比如用户自己写的__proto__ : { store: { __state: 'Store实例' }  }}}
}
// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {// ...if (provideOptions) {deferredProvide.push(provideOptions);}if (!asMixin && deferredProvide.length) {deferredProvide.forEach(provideOptions => {// 组件中写 provides 可以是对象或者是函数const provides = isFunction(provideOptions)? provideOptions.call(publicThis): provideOptions;Reflect.ownKeys(provides).forEach(key => {provide(key, provides[key]);});});}// ...
}

这样一来就从上到下app.provide提供的对象,被注入到每一个组件实例中了。同时组件本身提供的provides也被注入到实例中了。

接着我们跟着项目来验证下,上文中的表述。翻看Vue3文档可以发现有一个API可以获取当前组件实例。

4.7 getCurrentInstance 获取当前实例对象

getCurrentInstance 支持访问内部组件实例,用于高阶用法或库的开发。

import { getCurrentInstance } from 'vue'const MyComponent = {setup() {const internalInstance = getCurrentInstance()internalInstance.appContext.config.globalProperties // 访问 globalProperties}
}

知道这个API后,我们可以在购物车例子的代码中添加一些代码。便于我们理解。

// vuex/examples/composition/shopping-cart/components/App.vue
import { getCurrentInstance, provide } from 'vue'
import { useStore } from 'vuex';
setup () {const store = useStore()provide('ruochuan12', '微信搜索「若川视野」关注我,专注前端技术分享。')window.AppStore = store;window.AppCurrentInstance = getCurrentInstance();
},
// vuex/examples/composition/shopping-cart/components/ProductList.vue
setup(){const store = useStore()// 若川加入的调试代码--startwindow.ProductListStore = store;window.ProductListCurrentInstance = getCurrentInstance();provide('weixin-2', 'ruochuan12');provide('weixin-3', 'ruochuan12');provide('weixin-4', 'ruochuan12');const mp = inject('ruochuan12');console.log(mp, '介绍-ProductList'); // 微信搜索「若川视野」关注我,专注前端技术分享。// 若川加入的调试代码---end
}
// vuex/examples/composition/shopping-cart/components/ShoppingCart.vue
setup () {const store = useStore()// 若川加入的调试代码--startwindow.ShoppingCartStore = store;window.ShoppingCartCurrentInstance = getCurrentInstance();provide('weixin', 'ruochuan12');provide('weixin1', 'ruochuan12');provide('weixin2', 'ruochuan12');const mp = inject('ruochuan12');console.log(mp, '介绍-ShoppingList'); // 微信搜索「若川视野」关注我,专注前端技术分享。// 若川加入的调试代码--start
}

在控制台输出这些值

AppCurrentInstance
AppCurrentInstance.provides
ShoppingCartCurrentInstance.parent === AppCurrentInstance // true
ShoppingCartCurrentInstance.provides
ShoppingCartStore === AppStore // true
ProductListStore === AppStore // true
AppStore // store实例对象
控制台输出的结果

看控制台截图输出的例子,其实跟上文写的类似。这时如果写了顺手自己注入了一个provide('store': '空字符串'),那么顺着原型链,肯定是先找到用户写的store,这时Vuex无法正常使用,就报错了。

当然vuex4提供了注入的key可以不是store的写法,这时就不和用户的冲突了。

export class Store{// 省略若干代码...install (app, injectKey) {// 为 composition API 中使用//  可以传入 injectKey  如果没传取默认的 storeKey 也就是 storeapp.provide(injectKey || storeKey, this)// 为 option API 中使用app.config.globalProperties.$store = this}// 省略若干代码...
}
export function useStore (key = null) {return inject(key !== null ? key : storeKey)
}

5. 解答下开头提出的5个问题

统一解答下开头提出的5个问题:

1、为什么修改了实例store里的属性,变更后会触发视图更新。

答:使用Vue 中的 reactive 方法监测数据变化的。

class Store{constructor (options = {}){// 省略若干代码...this._modules = new ModuleCollection(options)const state = this._modules.root.stateresetStoreState(this, state)// 省略若干代码...}
}
function resetStoreState (store, state, hot) {// 省略若干代码...store._state = reactive({data: state})// 省略若干代码...
}

2、Vuex4作为Vue的插件如何实现和Vue结合的。

答:app.use(store) 时会执行Store中的install方法,一句是为 composition API 中使用,提供Store实例对象到根实例中。一句则是注入到根实例的全局属性中,为 option API 中使用。它们都会在组件生成时,注入到每个组件实例中。

export class Store{// 省略若干代码...install (app, injectKey) {// 为 composition API 中使用//  可以传入 injectKey  如果没传取默认的 storeKey 也就是 storeapp.provide(injectKey || storeKey, this)// 为 option API 中使用app.config.globalProperties.$store = this}// 省略若干代码...
}

3、provideinject的如何实现的,每个组件如何获取到组件实例中的Store的。

5、为什么在组件中写的provide提供的数据,能被子级组件获取到。

答:provide函数建立原型链区分出组件实例用户自己写的属性和系统注入的属性。inject函数则是通过原型链找父级实例中的provides对象中的属性。

// 有删减
function provide(){let provides = currentInstance.provides;const parentProvides = currentInstance.parent && currentInstance.parent.provides;if (parentProvides === provides) {provides = currentInstance.provides = Object.create(parentProvides);}provides[key] = value;
}
// 有删减
function inject(){const provides = instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides: instance.parent.provides;if (provides && key in provides) {return provides[key];}
}

也就是类似这样的实例:

// 当前组件实例
{parent: '父级的实例',provides: {// 可以容纳其他属性,比如用户自己写的__proto__: {// 可以容纳其他属性,比如用户自己写的__proto__ : { store: { __state: 'Store实例' }  }}}
}

4、为什么每个组件对象里都有Store实例对象了(渲染组件对象过程)。

答:渲染生成组件实例时,调用createComponentInstance,注入到组件实例的provides中。

function createComponentInstance(vnode, parent, suspense) {const type = vnode.type;const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;const instance = {parent,appContext,// ...provides: parent ? parent.provides : Object.create(appContext.provides),// ...}// ...return instance;
}
  1. 你怎么知道那么多的

答:因为社区有人写了`Vue4`源码文章[10]

6. 总结

本文主要讲述了Vuex4Store实例注入到各个组件中的原理,展开讲述了Vuex4相对与Vuex3安装方式的改变Vuex.createStoreapp.use(store) ,深入源码分析Vue.injectVue.provide实现原理。

Vuex4 除了安装方式和监测数据变化方式使用了Vue.reactive,其他基本和Vuex3.x版本没什么区别。

最后回顾下文章开头的图,可以说就是原型链的妙用。

provide,inject示例图

是不是觉得豁然开朗。

Vuex其实也是Vue的一个插件,知晓了Vuex原理,对于自己给Vue写插件也是会游刃有余。

参考资料

[1]

本文仓库地址: https://github.com/lxchuan12/vuex4-analysis.git

[2]

更多参考链接,可以点击阅读原文查看



最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你进群。


················· 若川出品 ·················

今日话题

10篇源码系列文章小成就达成,从19年7月开始写,19年写了6篇,20年写了2篇,今年写了2篇。算是一个完结吧。短时间内应该暂时不更新这个系列了。主要是投入的时间和精力比较多,看的人很少,得到的反馈也比较少。之后先写其他文章吧。欢迎持续关注我(若川)。欢迎在下方留言~  欢迎分享、收藏、点赞、在看我的公众号文章~

一个愿景是帮助5年内前端人走向前列的公众号

可加我个人微信 ruochuan12,长期交流学习

推荐阅读

我在阿里招前端,我该怎么帮你?(现在还能加我进模拟面试群)

若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

点击方卡片关注我、加个星标
学习源码整体架构系列、年度总结、JS基础系列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275848.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring4.3x教程之一IOCDI

SpringIOC也称为DI,对属性内容的注入可以通过属性的setXXX方法进行也可以通过构造方法进行,当然还可以使用工厂模式进行属性内容的注入。 什么是DI?什么是IOC? DI:Dependency Injection依赖注入 其实一个类中的属性就是…

战神4 幕后花絮 概念艺术_幕后花絮:品牌更新的背后

战神4 幕后花絮 概念艺术Under the Hood gives you an inside look at different parts of Waze — straight from the people working on them every day.在引擎盖下,您可以深入了解Waze的不同部分-直接来自每天进行工作的人员。 Traffic is the worst. It makes …

C#日期控件(js版)

js 脚本代码: <script type"text/javascript"> //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // 这是一个日历 Javascript 页…

python第三周测试_python第三周小测

1.读取一个文件&#xff0c;显示除了井号(#)开头的行意外的所有行# -*- coding: utf-8 -*-"""Created on Tue May 28 09:37:08 2019author: Omega_Sendoh"""#打开文件f open("install-sh","r")#读取文件的所有行&#xff0…

「Vueconf」探索 Vue3 中 的 JSX

大家好&#xff0c;我是若川。今天再分享 Vueconf 的一篇文章。另外 Vueconf 主办方提供的录播链接是&#xff1a; https://www.bilibili.com/read/mobile?id11408693&#xff0c;感兴趣可以复制观看。点击下方卡片关注我、加个星标。学习源码整体架构系列、年度总结、JS基础…

安卓加载asset中的json文件_Android解析Asset目录下的json文件

在app module中的src/main/assets目录下我们准备了两个json文件&#xff1a;destination.json如下&#xff1a;{"main/tabs/sofa": {"isFragment": true,"asStarter": false,"needLogin": false,"pageUrl": "main/tabs…

一文搞懂 Promise、Genarator、 Async 三者的区别和联系

非985/211大学毕业&#xff0c;软件工程专业&#xff0c;前端&#xff0c;坐标&#xff1a;北京工作三年多&#xff0c;第一家人数 30 多人的创业公司&#xff0c;1 年多。第二家属于前端技术不错的公司&#xff0c;2 年多。01我是一个喜欢在技术领域“折腾”的人&#xff0c;技…

dynamic 仪表板_仪表板完成百万美元交易

dynamic 仪表板问题 (The Problem) Anybody dealing with tech products and data-focused services runs into the same fundamental problem: what you do is technical but non-technical people control the budget. In other words:任何处理高科技产品和以数据为中心的服务…

checkStyle -- 代码风格一致

download page: http://sourceforge.net/project/showfiles.php?group_id80344&package_id107587 转载于:https://www.cnblogs.com/xuqiang/archive/2010/10/26/1953431.html

在线VS Code阅读源码神器 github1s

大家好&#xff0c;我是若川。github1s大部分人知道了&#xff0c;但还是有一部分不知道。我在掘金发过沸点和知乎发过想法还是有挺多人不知道&#xff0c;所以再发公众号推荐下。点击下方卡片关注我、加个星标。学习源码整体架构系列、年度总结、JS基础系列近日&#xff0c;一…

lenze变频器怎么更改地址_英威腾变频器GD300维修

英威腾变频器GD300维修英威腾变频器GD300维修41. 问题&#xff1a;变频器跟PLC采用485通讯不上答&#xff1a;1.检查变频器的通讯地址是否正确&#xff0c;如果采用通讯启动&#xff0c;检查P0.01是否为1&#xff0c;如果通过通讯设定频率&#xff0c;检查P0.068&#xff0c;P0…

代码设计的基础原则_设计原则:良好设计的基础

代码设计的基础原则As designers, it’s our goal to pass information in the most pleasing way possible. Starting out, there’s a wealth of literature to read and videos to watch that can get quite overwhelming to take in at a glance. People take different ro…

java金额类型_Java中存储金额用什么数据类型?

很早之前, 记得一次面试, 面试官问存储金钱用什么数据类型? 当时只知道8种数据类型(boolean, byte, short, int, long, float, double, char)的我, 回答了double, 因为我觉得double是双精度类型, 最适合, 但是面试官告诉我应该用BigDecimal! 最近在做支付的项目, 才对这种数据…

React Hooks 不知道怎么学?看这篇

大家好&#xff0c;我是若川。最近跟朋友聊技术&#xff0c;发现越来越多的大厂&#xff0c;都优先考虑用 React 做项目&#xff0c;在面试中也经常会考察对 React Hooks 的理解。其实&#xff0c;我一直觉得&#xff0c;React 才是前端的正确打开方式。当然&#xff0c;并不是…

ui原型设计工具_UI设计师的工具包,用于专业模型,原型和产品插图

ui原型设计工具This is a followup to my previous article 这是我上一篇文章的后续 visual tools for UX Designers视觉工具Tools don’t make designs better– you do! It doesn’t matter if you paid a lot of money for the latest software, or if you simply have a p…

前端 Offer 提速:如何写出有亮点的简历

大家好&#xff0c;我是若川。今天推荐一篇8年工作经验字节大佬的文章&#xff0c;如何写出有亮点的简历。可以收藏常看。点击下方卡片关注我、加个星标。学习源码整体架构系列、年度总结、JS基础系列先来个灵魂拷问&#xff1a;「你与他人相比&#xff0c;有什么能形成明显区分…

2008中的membership profile操作(转)

<profile > <properties> <add name"jimmy" /> </< span>properties> </< span>profile> 然后就那么简单,后台就能通过Profile拿到: Profile.jimmy "Pumpkin Ravioli"; 然后~通过这种方式就跟Session一样&a…

css网格_一个CSS网格可以全部统治

css网格The case for using one CSS grid for your entire website在整个网站上使用一个CSS网格的情况 CSS网格与Flexbox (CSS Grid vs Flexbox) In the dark ages, we used table, a few years ago we used float and before today most of us used flex . Of course, these …

java 高级泛型_Java 泛型高级

1、限制泛型可用类型在定义泛型类别时&#xff0c;预设可以使用任何的类型来实例化泛型中的类型&#xff0c;但是如果想要限制使用泛型的类别时&#xff0c;只能用某个特定类型或者其子类型才能实例化该类型时&#xff0c;使用extends关键字指定这个类型必须是继承某个类&#…

2021 年最值得使用的 Node.js 框架

大家好&#xff0c;我是若川。今天推荐一篇译文&#xff1a;2021年最值得使用nodejs框架&#xff0c;值得收藏&#xff0c;很快能看完。点击下方卡片关注我、加个星标。学习源码整体架构系列、年度总结、JS基础系列Node.js 是最敏捷的服务端 web 应用平台&#xff0c;因为它为应…