手写Redux(一):实现Redux

news2024/11/25 14:55:41

在React中,组件和组件之间通过props传递数据的规范,极大地增强了组件之间的耦合性,而context类似全局变量一样,里面的数据能被随意接触就能被随意修改,每个组件都能够改context里面的内容会导致程序的运行不可预料。

Redux是一个独立专门用于做状态管理的JS库,帮助开发出行为稳定可预测的、易于测试的应用程序,通过react-redux,可集中式管理React应用中多个组件共享的状态。

本系列的两篇文章带你手写一个Redux,以及结合React实现自己的react-redux。

1 如何修改状态?

说到如何管理React应用的状态数据,最直接的想法应该是设置一个全局的状态对象(state),各个组件可以读取、修改该对象中状态数据,下面我们通过一个具体的例子演示一下,使用create-react-app新建一个项目my-redux,修改public/index.html中的body

<body><div id='title'></div><div id='content'></div>
</body> 

清除src/index.js里面所有的代码,添加下面代码,改代码中包含了我们应用的状态,以及对状态的操作方法:

const appState = {title: {text: 'my-redux',color: 'red',},content: {text: '如何实现自己的redux?',color: 'blue'}
}

function renderApp (appState) {renderTitle(appState.title)renderContent(appState.content)
}
function renderTitle (title) {const titleDOM = document.getElementById('title')titleDOM.innerHTML = title.texttitleDOM.style.color = title.color
}
function renderContent (content) {const contentDOM = document.getElementById('content')contentDOM.innerHTML = content.textcontentDOM.style.color = content.color
}

renderApp(appState) 

浏览器打开http://localhost:3000,显示如下页面:

以上代码通过将页面属性与状态变量绑定,实现了状态的统一管理,但是这种方案存在一个重大的隐患,渲染数据的时候,使用的是一个共享状态appState,该变量没有任何封装保护,每个人都可以修改它:

// 这个方法中修改了 appState 的内容,比如:appState.title = null
// 出现问题的时候 debug 起来就非常困难
doSomthingMore()
// ...
renderApp(appState) 

renderApp(appState)之前执行了一大堆函数操作,你根本不知道它们会对appState做什么事情,renderApp(appState)的结果无法得到保障。一旦共享数据可以任意修改,所有对共享状态的操作都是不可预料的,出现问题的时候debug起来就非常困难,这就是老生常谈的尽量避免全局变量。

组件之间需要共享数据数据可能被任意修改导致不可预料的结果之间存在着矛盾。

解决方案: 提高数据修改的门槛,组件之间可以共享数据、修改数据。但是这个数据并不能直接改,只能执行某些允许的修改,而且你修改过程必须大张旗鼓,不能悄悄的改。

这里我们定义一个方法叫dispatch,通过这个方法专门负责数据的修改:

function dispatch (action) {switch (action.type) {case 'UPDATE_TITLE_TEXT':appState.title.text = action.textbreakcase 'UPDATE_TITLE_COLOR':appState.title.color = action.colorbreakdefault:break}
} 

所有对数据的操作必须通过dispatch函数,它接受一个参数action对象,里面必须包含一个type字段来声明你到底想干什么。dispatchswtich里面会识别这个type字段,能够识别出来的操作才会执行对appState的修改,这样就能管理所有对状态数据的操作。

比如上面的dispatch它只能识别两种操作,

1.UPDATE_TITLE_TEXT :用actiontext字段去更新appState.title.text
2.UPDATE_TITLE_COLOR:用actioncolor字段去更新appState.title.color

可以看到,action里面除了type是必须的以外,其他字段都是可以自定义的,任何的模块如果想要修改 appState.title.text,必须 大张旗鼓 地调用dispatch

dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色 

这样做有什么好处?

// 里面可能通过 dispatch 修改标题颜色
doSomthingMore()
// ...
renderApp(appState) 

我们不需要担心renderApp(appState)之前的那堆方法会对appState做什么奇怪的操作,因为我们规定不能直接修改appState,它们对appState的修改必须只能通过dispatch。而dispatch的实现只能修改 title.text 和 title.color,通过引入dispatch方法,组件间共享数据的方式发生了如下变化:

2 抽像 store 监听数据变化

现在,我们有了appStatedispatch,把它们集中到一个地方,给这个地方起个名字叫做store,然后构建一个函数createStore,用来专门生产这种statedispatch的集合:

function createStore (state, stateChanger) {const getState = () => stateconst dispatch = (action) => stateChanger(state, action)return { getState, dispatch }
} 

其中各个参数的含义与作用:

  • state: 表示应用程序状态数据;
  • stateChanger: 描述应用程序状态会根据action发生什么变化,相当于上文 dispatch 代码里面的内容;
  • getState: 用于获取state数据,把state对象返回;
  • dispatch: 用于修改数据,接收action,把stateaction一并传给stateChanger

通过createStore,我们可以这样渲染页面:

const appState = {title: {text: 'my-redux',color: 'red',},content: {text: '如何实现自己的redux?',color: 'blue'}
}

function stateChanger (state, action) {switch (action.type) {case 'UPDATE_TITLE_TEXT':state.title.text = action.textbreakcase 'UPDATE_TITLE_COLOR':state.title.color = action.colorbreakdefault:break}
}

// renderApp()、createStore()方法,此处省略,参照上文
// ...

// 创建store,包含获取状态的getState,和操作状态的dispatch
const store = createStore(appState, stateChanger)

renderApp(appState) // 首次渲染
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
renderApp(appState) // dispatch 修改完状态 appState 后,需要手动调用renderApp()再次触发渲染 

上面的代码有一个问题,通过dispatch修改数据的时候只是数据发生了变化,如果不手动调用renderApp(),页面是不会发生变化的,我们希望数据变化的时候程序能够自动触发重新渲染。

这个问题可以通过观察者模式“监听”数据变化,然后重新渲染页面:

function createStore (state, stateChanger) {const listeners = []// 调用subscribe,可以将监听器传入内部的listeners数组,监听数据变化const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {stateChanger(state, action)// 每当 dispatch 的时候,遍历并且执行监听器// 即当数据变化时候进行一些方法回调,比如说重现渲染listeners.forEach((listener) => listener())}return { getState, dispatch, subscribe }
} 

createStore里面定义了一个数组listeners和一个新的方法subscribe,可以通过 store.subscribe(listener)的方式给subscribe传入一个监听方法,这个函数会被push到listeners数组当中;修改dispatch,每次当它被调用的时候,除了会调用stateChanger进行数据的修改,还会遍历listeners数组里面的方法,然后一个个地去调用;监听回调方法可以通过subscribe注册进listeners数组。

完成以上修改后,在store中注册renderApp()回调方法,当数据变化时重新渲染,便会自动调用该方法:

// 其他方法及变量参考上文
// ...

const store = createStore(appState, stateChanger)
// 注册重新渲染回调方法,即当数据变化时候进行重现渲染
store.subscribe(() => renderApp(store.getState()))

renderApp(appState) // 首次渲染
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_CONTENT_COLOR', color: 'blue' }) // 修改标题颜色
// renderApp(appState) // 不再需要手动触发重新渲染了 

3 纯函数的概念

在开始下面的内容前,先看下函数式编程里面非常重要的概念 —— 纯函数(Pure Function)。 纯函数本身是一个函数,同时满足以下两点:

1.函数的返回结果只依赖于它的参数,相同的输入,总是会的到相同的输出;
2.执行过程中没有任何副作用。

下面说下这两点:

3.1 函数的返回结果只依赖于它的参数

let a = 1;
function xAdd(x) {return x + a;
};
xAdd(1); //2 

上面这个函数就不是一个纯函数,因为在程序执行的过程中,变量a发生改变,执行xAdd(1)时得到的输出不同。

function sum(x, y) {return x + y;
};
sum(1,2); //3 

这个例子中,符合相同的输入得到相同的输出这个概念,sum是一个纯函数。

3.2 执行过程中没有任何副作用

到底什么是副作用?这里的副作用指的是函数在执行过程中产生了 外部可观察变化,比如:

  • 发起HTTP请求
  • 操作DOM
  • 修改外部数据
let a = 1;
function func() {a = 'b';
};
func();
console.log(a); // b 

我们运行了func函数,外部的变量a的值发生了改变,这就是产生了所谓的副作用,所以func不是一个纯函数。

function func2() {let a = 1;a = 'a';return a
};
func(); // a 

函数fun2不会对产生外部可观察变化,也就不会产生副作用,它就是一个纯函数。

3.3 纯函数的好处

1.更容易进行测试,结果只依赖输入,测试时可以确保输出稳定;
2.更容易维护和重构,我们可以写出质量更高的代码;
3.更容易调用,我们不用担心函数会有什么副作用;
4.结果可以缓存,因为相同的输入总是会得到相同的输出。

3.4 纯函数运用的经典案例

1.数组的很多基本方法都是纯函数,例如map,forEach,filter,reduce等等;
2.redux中三大原则之一使用纯函数来执行修改,其中就运用了reducer来描述 action 如何改变 state tree;
3.Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库,纯函数代表。

4 共享结构对象解决性能问题

4.1 整体刷新的性能问题

第二节中实现的createStore仍然有比较严重的性能问题,我们通过subscribe注册了重新渲染的回调函数,

store.subscribe(() => renderApp(store.getState())) // 监听数据变化 

可通过dispatch修改状态中的Title文本数据,从而触发渲染,

store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本 

但是,renderApp的实现,同时渲染了Title组件和Content组件,Content组件的状态数据没有发生变化,但还是触发了一次渲染:

function renderApp (appState) {renderTitle(appState.title)renderContent(appState.content)
} 

这里提出的解决方案是,在每个渲染函数执行渲染操作之前先做个判断,判断传入的 新数据旧数据 是不是相同,相同的话就不渲染了。 修改renderApp:

function renderApp(newAppState, oldAppState = {}) { // 防止 oldAppState 没有传入,所以加了默认参数 oldAppState = {}if (newAppState === oldAppState) return // 数据没有变化就不渲染了console.log('render app...')renderTitle(newAppState.title, oldAppState.title)renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle = {}) {if (newTitle === oldTitle) return // 数据没有变化就不渲染了console.log('render title...')const titleDOM = document.getElementById('title')titleDOM.innerHTML = newTitle.texttitleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent = {}) {if (newContent === oldContent) return // 数据没有变化就不渲染了console.log('render content...')const contentDOM = document.getElementById('content')contentDOM.innerHTML = newContent.textcontentDOM.style.color = newContent.color
} 

使用新的renderApp()

const store = createStore(appState, stateChanger)
let oldState = store.getState() // 缓存旧的 state
store.subscribe(() => {const newState = store.getState() // 数据可能变化,获取新的 staterenderApp(newState, oldState) // 把新旧的 state 传进去渲染oldState = newState // 渲染完以后,新的 newState 变成了旧的 oldState,等待下一次数据变化重新渲染
}) 

刷新页面,查看日志输出,发现以下两个dispatch并没有触发页面重新渲染:

store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色 

原因是这样的,if条件中判断newAppState === oldAppState比较的是对象引用,而用于比较的对象的引用始终没有发生变化(类似的问题还有深拷贝和浅拷贝的比较)。分析renderApp代码发现:

  • renderApp比较的是:newAppState === oldAppState
  • renderTitle比较的是:newAppState.title === oldAppState.title
  • renderContent比较的是:newAppState.content === oldAppState.content

分析stateChanger代码发现,仅仅修改了以下两处:

1.state.title.text = action.text
2.state.title.color = action.color

因此,上述三处if判断恒等于true,直接return,没有进行后面的渲染。

4.2 共享结构的对象

通过上一节的分析,我们发现重新渲染回调没有生效的原因是,我们 直接修改了状态对象中的属性,未修改对象自身, 导致新老状态对象比较时,对象引用是一样的,是同一个对象renderXXX函数认为没有发生变化,不需要刷新。

解决方法是,禁止直接修改原来的对象,一旦你要修改某些东西,你就得把修改路径上的所有对象复制一遍,生成一个新的对象,例如,我们不写下面的修改代码:

appState.title.text = 'spring-boot' 

而是新建一个appState,新建appState.title,新建appState.title.text

let newAppState = { // 新建一个 newAppState...appState, // 复制 appState 里面的内容title: { // 用一个新的对象覆盖原来的 title 属性...appState.title, // 复制原来 title 对象里面的内容text: 'spring-boot' // 覆盖 text 属性}
}
return newAppState 
  • appStatenewAppState引用不一样,需要刷新;
  • appState.conetntnewAppState.conetnt引用一样,不需要刷新;
  • appState.titlenewAppState.title引用不一样,需要刷新。

按照上面的思路,改造stateChanger

function stateChanger (state, action) {switch (action.type) {case 'UPDATE_TITLE_TEXT':return { // 构建新的对象并且返回...state,title: {...state.title,text: action.text}}case 'UPDATE_TITLE_COLOR':return { // 构建新的对象并且返回...state,title: {...state.title,color: action.color}}default:return state // 没有修改,返回原来的对象}
} 

因为stateChanger不会修改原来对象了,而是返回对象,所以我们需要修改一下createStorestate = stateChanger(state, action)覆盖原来的state

function createStore (state, stateChanger) {const listeners = []// 调用subscribe,可以将监听器传入内部的listeners数组,监听数据变化const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = stateChanger(state, action)// 覆盖原对象// 每当 dispatch 的时候,遍历并且执行监听器// 即当数据变化时候进行一些方法回调,比如说重现渲染listeners.forEach((listener) => listener())}return { getState, dispatch, subscribe }
} 

刷新页面,查看日志,我们成功地把不必要的页面渲染优化掉了:

5 reducer的概念

经过了前面几节的内容,我们有了很通用的createStorestateChanger,可以优化一下,将appStatestateChanger合并到一起,删除appState对象:

function stateChanger (state, action) {if (!state) {return {title: {text: 'my-redux',color: 'red',},content: {text: '如何实现自己的redux?',color: 'blue'}}}switch (action.type) {case 'UPDATE_TITLE_TEXT':return {...state,title: {...state.title,text: action.text}}case 'UPDATE_TITLE_COLOR':return {...state,title: {...state.title,color: action.color}}default:return state}
} 

stateChanger现在既充当了获取初始化数据的功能,也充当了生成更新数据的功能,如果有传入state 就生成更新数据,否则就是初始化数据。

更新createStore方法:

function createStore (stateChanger) {let state = nullconst listeners = []// 调用subscribe,可以将监听器传入内部的listeners数组,监听数据变化const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = stateChanger(state, action)// 覆盖原对象// 每当 dispatch 的时候,遍历并且执行监听器// 即当数据变化时候进行一些方法回调,比如说重现渲染listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe }
} 

createStore内部的state不再通过参数传入,而是一个局部变量let state = nullcreateStore的最后会手动调用一次dispatch({})dispatch内部会调用stateChanger,这时候的state是 null,所以这次的dispatch其实就是初始化数据了。createStore内部第一次的dispatch导致state初始化完成,后续外部的dispatch就是修改数据的行为了。修改store的获取方式:

const store = createStore(stateChanger) 

我们给stateChanger这个方法起一个更加形象的名字:reducer,为什么叫reducer呢?因为这个函数实现了以下功能:

state + action = newState

是不是和MapReduce里的reduce是一个意思哈?而且这个reducer还是个纯函数。

修改下参数名:

function createStore (reducer) {let state = nullconst listeners = []// 调用subscribe,可以将监听器传入内部的listeners数组,监听数据变化const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)// 覆盖原对象// 每当 dispatch 的时候,遍历并且执行监听器// 即当数据变化时候进行一些方法回调,比如说重现渲染listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe }
} 

至此,我们给reducer下个定义,并明确其作用:

定义: createStore接受一个叫reducer的函数作为参数,这个函数规定是一个纯函数,它接受两个参数,一个是state,一个是action

作用: 如果没有传入state或者state是 null,那么它就会返回一个初始化的数据。如果有传入 state的话,就会根据action来修改数据,但其实它并没有修改原始state对象,而是要通过上节所说的把修改路径的对象都复制一遍,然后产生一个新的对象返回。如果它不能识别你的action,它就不会产生新的数据,而是把state原封不动地返回(default分支)。 reducer是不允许有副作用的。你不能在里面操作 DOM,也不能发 Ajax 请求,更不能直接修改state,它要做的仅仅是初始化和计算新的state。 现在我们可以用这个createStore来构建不同的store了,只要给它传入符合上述的定义的reducer即可:

function themeReducer (state, action) {if (!state) return {themeName: 'Red Theme',themeColor: 'red'}switch (action.type) {case 'UPATE_THEME_NAME':return { ...state, themeName: action.themeName }case 'UPATE_THEME_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
}
const store = createStore(themeReducer) 

6 总结

在上面的几节内容中,

1.我们从一个简单的例子开始,提出一个可以被不同模块任意修改共享的数据状态,其操作都是不可预料的,出现问题的时候debug起来就非常困难,需要避免全局变量;
2.于是我们提高了修改数据的门槛:必须通过dispatch执行某些允许的修改操作;
3.我们抽取了一个createStore方法,其返回的store对象中包含了获取状态的方法getState和修改状态的方法dispatch;然后,通过观察者模式设置监听回调函数,在状态翻身变化时指定触发页面渲染;
4.针对渲染过程中,未发生状态的组件无效渲染的性能问题,通过引入“共享结构的对象”的概念,优化了其中无效的渲染;
5.引入rudex中的reducer,作为纯函数,负责初始化state、根据stateaction计算具有共享结构的新state对象。

6.1 完整代码

public/index.html

<!DOCTYPE html>
<html lang="en"><head><title>React App</title></head><body><div id='title'></div><div id='content'></div></body>
</html> 

src/index.js

const appState = {title: {text: 'my-redux',color: 'red',},content: {text: '如何实现自己的redux?',color: 'blue'}
}

function stateChanger (state, action) {if (!state) {return {title: {text: 'my-redux',color: 'red',},content: {text: '如何实现自己的redux?',color: 'blue'}}}switch (action.type) {case 'UPDATE_TITLE_TEXT':return {...state,title: {...state.title,text: action.text}}case 'UPDATE_TITLE_COLOR':return {...state,title: {...state.title,color: action.color}}default:return state}
}

function renderApp(newAppState, oldAppState = {}) { // 防止 oldAppState 没有传入,所以加了默认参数 oldAppState = {}if (newAppState === oldAppState) return // 数据没有变化就不渲染了console.log('render app...')renderTitle(newAppState.title, oldAppState.title)renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle = {}) {if (newTitle === oldTitle) return // 数据没有变化就不渲染了console.log('render title...')const titleDOM = document.getElementById('title')titleDOM.innerHTML = newTitle.texttitleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent = {}) {if (newContent === oldContent) return // 数据没有变化就不渲染了console.log('render content...')const contentDOM = document.getElementById('content')contentDOM.innerHTML = newContent.textcontentDOM.style.color = newContent.color
}

function createStore (reducer) {let state = nullconst listeners = []// 调用subscribe,可以将监听器传入内部的listeners数组,监听数据变化const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)// 覆盖原对象// 每当 dispatch 的时候,遍历并且执行监听器// 即当数据变化时候进行一些方法回调,比如说重现渲染listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe }
}

const store = createStore( stateChanger)
let oldState = store.getState() // 缓存旧的 state
store.subscribe(() => {const newState = store.getState() // 数据可能变化,获取新的 staterenderApp(newState, oldState) // 把新旧的 state 传进去渲染oldState = newState // 渲染完以后,新的 newState 变成了旧的 oldState,等待下一次数据变化重新渲染
})

renderApp(appState) // 首次渲染
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'spring-boot' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
// renderApp(appState) // dispatch 不需要再次出触发渲染 

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

复习计算机网络——第二章记录(2)

理解一些基本概念&#xff1a; 1、数据&#xff08;data&#xff09;是运送信息的实体。 2、信号&#xff08;signal&#xff09;是数据的电气的或电磁的表现。 3、数据或信号可以是模拟的&#xff0c;也可以是数字的。 4、所谓“模拟的”就是连续变化的。 5、所谓“数字的…

后端微服务项目中出现的问题整理2022年11月

后端微服务项目中出现的问题整理2022年11月后端微服务项目中出现的问题整理2022年11月1.SpringBoot-Mail-Service&#xff08;Spring邮箱服务&#xff09;报错截图解决办法方法一&#xff1a;使用Resource注解方法二&#xff1a;添加(requiredfalse)Resource和Autowired区别2.反…

基于plc的自动洗碗机的设计(西门子)

目 录 摘 要 I Abstract II 1绪论 1 1.1全自动洗碗机的发展 1 1.2全自动洗碗机概述 2 1.2.1 全自动洗碗机的分类 2 1.2.2 全自动洗碗机的基本结构 3 1.2.3 全自动洗碗机的工作原理 4 1.3研究主要内容 4 2 全自动洗碗机机械设计 6 2.1 整体方案设计 6 2.2 各重要部件设计 6 2.2.…

TF-IDF

2.TF - IDF&#xff1a;作用&#xff1a;提取出来一句话中词的重要性&#xff0c;分成两个部分&#xff1a; tf: 词频(某一类中词条出现的次数 / 该类中所有词条数目) idf: 逆文档频率&#xff08;作用&#xff1a;去掉逗号&#xff0c;的等&#xff09;公式&#xff1a;idf l…

【第一阶段:java基础】第8章:面向对象编程高级-2(P394-P423)final、抽象类、接口、内部类

本系列博客是韩顺平老师java基础课的课程笔记&#xff0c;吐血推荐的一套全网最细java教程&#xff0c;获益匪浅 韩顺平java课程 目录&#xff08;P394-P423&#xff09; 1 final 2 抽象类 3 接口 4 内部类 1️⃣ 局部内部类 2️⃣&#x1f353;匿名内部类 3️⃣成员内部类 4️…

C++,顺序结构,选择结构,循环结构

目录 1.选择结构 1.1if语句 1.1.1单行if语句 1.1.2多行格式的if语句 1.1.3多条件if语句 1.1.4嵌套if语句 1.2三目运算符 1.3switch语句 2.循环结构 2.1while循环 2.2do..while语句 2.3for循环语句 2.4嵌套循环 3.跳转语句 3.1.1break 3.1.2continue 3.1.3goto语句 …

UDP-GlcNAc,UDPAG,尿苷二磷酸-N-乙酰基葡萄糖胺,UDP-N-乙酰葡糖胺

产品名称&#xff1a;UDP-GlcNAc 尿苷二磷酸-N-乙酰基葡萄糖胺&#xff0c;UDPAG&#xff0c;UDP-N-乙酰葡糖胺 别 名&#xff1a;5-二磷酸尿嘧啶核苷-N-乙酰半乳糖胺二钠盐;5′-二磷酸尿嘧啶核苷-N-乙酰半乳糖胺二钠盐 英文名&#xff1a;uridine 5-diphospho-N-acetyl-*g…

在家做副业可以做什么,做自媒体应该选择什么平台

今天分享五个&#xff0c;即使你在家里没有收入&#xff0c;自己就可以做的自媒体副业&#xff0c;给自己带来一些收益。 ​ 第一个自媒体副业是公众号&#xff0c;很多人多人都知道。很多人也喜欢看别人写的公众号。 其实微信官方账号注册很简单&#xff0c;不需要花钱有个邮…

Huggingface Transformers各类库介绍(Tokenizer、Pipeline)

目录前言0、transformers的安装以及介绍0-1、 介绍0-2、安装一、分词——transformers.AutoTokenizer1-0、相关参数介绍&#xff08;常用参数介绍&#xff09;1-1、加载、保存1-2、使用以及原理二、黑盒子——transformers.pipeline2-0、pipeline怎么理解&#xff1f;2-1、目前…

SpringCloud:Gateway之限流、熔断

目录 一、服务雪崩简介及压测实践演示 ​编辑 二、sentinel简单模式之流控QPS案例 什么是Sentinel ​ 安装Sentinel控制台 三、sentinel流控简单模式之并发线程数案例 四、sentinel流控之关联模式&链路模式 关联模式 链路模式 五、sentinel降级之平均响应时间&…

Mybatis入门

详细的Mybatis介绍/使用&#xff0c;可以访问&#xff1a; 地址&#xff1a;mybatis – MyBatis 3 | 简介 目录 一、Mybatis介绍 二、依赖 三、什么是rowmapper 四、Mybatis demo 演示 4.1 背景 4.2 JDBC执行sql的流程 &Mybatis 关键要素 4.3 项目的整体结构&…

kubernetes 实战学习

文章目录kubernetes 实战学习1. 资源创建方式2. Namespace3. Pod3.1 使用命令行创建一个 Pod3.2 使用 YAML 文件创建一个 pod3.3 查看 pod 日志3.4 查看 pod IP3.5 进入 pod 查看信息3.6 一个 pod 里同时运行多个容器3.7 pod 和宿主机之间文件拷贝4. Deployment4.1 deployment …

功能测试(一)—— web项目环境与测试流程、业务流程测试

目录 目标 一、web项目环境说明 【了解】 ​编辑 1.1 环境的定义 1.2 环境(服务器)的组成 1.3 面试题&#xff1a;你们公司有几套环境&#xff1f; 二、熟悉商城项目 【了解】 2.1 商城项目的核心业务 2.2 商城项目的核心功能模块 三、测试流程的应用 【重点】 3.1 需…

Java 基础数据类型占用内存空间和字符串编码简介(二)

Java 基础数据类型占用内存空间简介一 计算机简介1.基本概念2.CPU 三级缓存3.本机参数查看二 数据占用内存情况1.多线程Demo2.结果解析1.直接计算2.volatile 计算3.缓存行填充一 计算机简介 结合多线程计算机的硬件&#xff0c;从侧面理解数据存储如何影响我们的程序 1.基本概…

数据结构学习笔记(Ⅶ):查找

目录 1 查找 1.1 定义 1.2 查找操作 1.3 算法评价指标 2 查找算法 2.1 顺序查找 1.算法思想 2.实现 3.查找效率 4.算法优化 2.2 折半查找 1.算法思想 2.算法实现 3.查找判定树 4.折半查找效率 2.3 分块查找 1.算法思想 2.查找效率分析 3 B树 3.1 B树概念 3…

Java#30(扩展知识:可变参数与Collections)

目录 一.可变参数 二.Collections 1.Collections常用的API 一.可变参数 本质: 可变参数本质上是一个数组 作用: 在形参中接收多个数据 格式: 数据类型...参数名称 例如: int...a 代码示范: package Demo;public class Test {public static void main(String[] args) {int s…

单目标优化:蜣螂优化算法(Dung beetle optimizer,DBO)

蜣螂优化算法&#xff08;Dung beetle optimizer&#xff0c;DBO&#xff09;由Jiankai Xue和Bo Shen于2022年提出&#xff0c;该算法主要受蜣螂的滚球、跳舞、觅食、偷窃和繁殖行为的启发所得。 一、蜣螂优化算法 1.1蜣螂滚球 &#xff08;1&#xff09;当蜣螂前行无障碍时…

redis基础3——配置文件核心参数实测+RDB持久化、AOF持久化核心参数详解

文章目录一、redis.conf配置文件详解1.1 查看配置文件位置1.2 启动需指定配置文件1.3 单位大小写不敏感1.4 引用其他配置文件&#xff08;包含&#xff09;1.4.1 测试参数存放前后位置1.4.1.1 反例1.4.1.2 正例1.4.2 测试引用参数有效性1.5 网络相关1.5.1 绑定IP1.5.2 保护模式…

全球领先飞瞳引擎™云服务全球两千+企业用户,集装箱识别集装箱箱况残损检测,正常箱号识别率99.98%以上,箱信息识别及铅封识别免费

全球领先飞瞳引擎™AI集装箱识别检测云服务全球两千企业用户&#xff0c;集装箱识别集装箱箱况残损检测&#xff0c;正常箱号识别率99.98%以上&#xff0c;箱信息识别及铅封识别免费。CIMCAI中集飞瞳是全球应用落地最广&#xff0c;规模最大&#xff0c;最先进的的港航人工智能…

链游新发展方向:告别高强度打金,回归游戏本质

2022 年伊始&#xff0c;加密领域最瞩目的项目要属区块链游戏了。随着 Axie Infinity、Genopets 和 Perion 等公司大获成功&#xff0c;区块链游戏生态系统逐渐进入主流圈。值得一提的是&#xff0c;游戏和 NFT 这两个备受关注的类别在 Web 3 世界中互相成就&#xff0c;创造出…