摘要:到月底了,小明的爸爸的單位發(fā)了工資總計(jì)塊大洋,拿到工資之后第一件的事情就是上交,毫無(wú)疑問(wèn)的,除非小明爸爸不要命了。當(dāng)小明的爸爸收到這個(gè)通知之后,心的一塊大石頭也就放下來(lái)了。下面我們正式開(kāi)始我們的源碼閱讀之旅。
前言
用過(guò)react的小伙伴對(duì)redux其實(shí)并不陌生,基本大多數(shù)的React應(yīng)用用到它。一般大家用redux的時(shí)候基本都不會(huì)多帶帶去使用它,而是配合react-redux一起去使用。剛學(xué)習(xí)redux的時(shí)候很容易弄混淆redux和react-redux,以為他倆是同一個(gè)東西。其實(shí)不然,redux是javascript應(yīng)用程序的可預(yù)測(cè)狀態(tài)容器,而react-redux則是用來(lái)連接這個(gè)狀態(tài)容器與react組件。可能前端新人對(duì)這兩者還是覺(jué)得很抽象,打個(gè)比方說(shuō),在一個(gè)普通家庭中,媽媽在家里都是至高無(wú)上的地位,掌握家中經(jīng)濟(jì)大權(quán),家里的經(jīng)濟(jì)流水都要經(jīng)過(guò)你的媽媽,而你的爸爸則負(fù)責(zé)從外面賺錢然后交給你的媽媽。這里把你的媽媽類比成redux,而你的爸爸可以類比成react-redux,而外面的大千世界則是react組件。相信這樣的類比,大家對(duì)這react和react-redux的有了一個(gè)初步認(rèn)識(shí)。本篇文章介紹的主要內(nèi)容是對(duì)redux的源碼的分析,react-redux的源碼分析將會(huì)在我的下一篇文章中,敬請(qǐng)期待!各位小伙們?nèi)绻X(jué)得寫(xiě)的不錯(cuò)的話,麻煩多多點(diǎn)贊收藏關(guān)注哦!
redux的使用在講redux的源碼之前,我們先回顧一下redux是如何使用的,然后我們?cè)賹?duì)照著redux的使用去閱讀源碼,這樣大家的印象可能會(huì)更加深刻點(diǎn)。先貼上一段demo代碼:
const initialState={ cash:200, } const reducer=(state=initialState,action)=>{ const {type,payload} = action; switch(type){ case "INCREMENT": return Object.assign({},state,{ cash:state.cash+payload }); case "DECREMENT": return Object.assign({},state,{ cash:state.cash-payload }); default : return state; } } const reducers=Redux.combineReducers({treasury:reducer}); //創(chuàng)建小金庫(kù) const store=Redux.createStore(reducers); //當(dāng)小金庫(kù)的現(xiàn)金發(fā)生變化時(shí),打印當(dāng)前的金額 store.subscribe(()=>{ console.log(`余額:${store.getState().treasury.cash}`); }); //小明爸爸發(fā)了工資300塊上交 store.dispatch({ type:"INCREMENT", payload:300 }); //小明拿著水電費(fèi)單交100塊水電費(fèi) store.dispatch({ type:"DECREMENT", payload:100 });
上面這段代碼是一個(gè)非常典型的redux的使用,跟大家平時(shí)在項(xiàng)目里用的不太一樣,可能有些小伙伴們不能理解,其實(shí)react-redux只不過(guò)在這種使用方法上做了一層封裝。等當(dāng)我們弄清楚redux的使用,再去看react-redux源碼便會(huì)明白了我們?cè)陧?xiàng)目里為何是那種寫(xiě)法而不是這種寫(xiě)法。
說(shuō)到redux的使用,不免要說(shuō)一下action、reducer和store三者的關(guān)系。記得當(dāng)初第一次使用redux的時(shí)候,一直分不清這三者的關(guān)系,感覺(jué)這三個(gè)很抽象很玄學(xué),相信不少小伙伴們跟我一樣遇到過(guò)同樣的情況。其實(shí)并不難,我還是用文章開(kāi)頭打的比方還解釋這三者的關(guān)系。
現(xiàn)在保險(xiǎn)箱(store)里存放200塊大洋。到月底了,小明的爸爸的單位發(fā)了工資總計(jì)300塊大洋,拿到工資之后第一件的事情就是上交,毫無(wú)疑問(wèn)的,除非小明爸爸不要命了。小明的爸爸可以直接將這300塊大洋放到家里的保險(xiǎn)箱里面嗎?顯然是不可以的,所以小明的爸爸得向小明的爸爸提交申請(qǐng),而這個(gè)申請(qǐng)也就是我們所說(shuō)的action。這個(gè)申請(qǐng)(action)包括操作類型和對(duì)應(yīng)的東西,申請(qǐng)類型就是存錢(INCREMENT),對(duì)應(yīng)的東西就是300塊大洋(payload)。此時(shí)小明的媽媽拿到這個(gè)申請(qǐng)之后,將根據(jù)這個(gè)申請(qǐng)執(zhí)行對(duì)應(yīng)的操作,這里就是往保險(xiǎn)箱里的現(xiàn)金里放300塊大洋進(jìn)去,此時(shí)小明的媽媽干的事情就是reducer干的事情。當(dāng)300塊大洋放完之后,小明的媽媽就通知家里的所有人現(xiàn)在的小金庫(kù)的金額已經(jīng)發(fā)生了變化,現(xiàn)在的余額是500塊。當(dāng)小明的爸爸收到這個(gè)通知之后,心的一塊大石頭也就放下來(lái)了。過(guò)了一會(huì),小明回來(lái)了,并且拿著一張價(jià)值100塊的水電費(fèi)的催收單。于是,小明想小明媽媽申請(qǐng)交水電費(fèi),小明媽媽從保險(xiǎn)庫(kù)中取出來(lái)100塊給了小明,并通知了家里所有人小金庫(kù)的金額又發(fā)生了變化,現(xiàn)在余額400塊。
通過(guò)上面的例子,相信小伙們對(duì)三者的關(guān)系有了一個(gè)比較清晰的認(rèn)識(shí)。現(xiàn)在我們已經(jīng)理清楚了action、reducer和store三者的關(guān)系,并且也知道了redux是如何使用的了,現(xiàn)在將開(kāi)始我們得源碼閱讀之旅。
redux項(xiàng)目結(jié)構(gòu)本篇文章是基于redux的4.0.0版本做的源碼分析,小伙伴們?cè)趯?duì)照源碼的時(shí)候,千萬(wàn)別弄錯(cuò)了。整個(gè)redux項(xiàng)目的源碼的閱讀我們只需要關(guān)注src的目錄即可。
這里主要分為兩大塊,一塊為自定義的工具庫(kù),另一塊則是redux的邏輯代碼。先從哪塊開(kāi)始閱讀呢?我個(gè)人建議先閱讀自定義的工具庫(kù)這塊。主要有這么兩個(gè)原因:第一個(gè),這塊代碼比較簡(jiǎn)單,容易理解,大家更能進(jìn)入閱讀的狀態(tài);第二個(gè),redux邏輯代碼會(huì)用到這些自定義工具,先搞懂這些,對(duì)后續(xù)邏輯代碼的閱讀做了一個(gè)很好的鋪墊。下面我們正式開(kāi)始我們的源碼閱讀之旅。
utils actionTypes.jsconst ActionTypes = { INIT: "@@redux/INIT" + Math.random() .toString(36) .substring(7) .split("") .join("."), REPLACE: "@@redux/REPLACE" + Math.random() .toString(36) .substring(7) .split("") .join(".") } export default ActionTypes
這段代碼很好理解,就是對(duì)外暴露兩個(gè)action類型,沒(méi)什么難點(diǎn)。但是我這里想介紹的是Number.prototype.toString方法,估計(jì)應(yīng)該有不少小伙伴們不知道toString是可以傳參的,toString接收一個(gè)參數(shù)radix,代表數(shù)字的基數(shù),也就是我們所說(shuō)的2進(jìn)制、10進(jìn)制、16進(jìn)制等等。radix的取值范圍也很容易得出來(lái),最小進(jìn)制就是我們得二進(jìn)制,所以redix>=2。0-9(10個(gè)數(shù)字)+a-z(26個(gè)英文字母)總共36個(gè),所以redix<=36。總結(jié)一下2<=radix<=36,默認(rèn)是10。基于這個(gè)特性我們可以寫(xiě)一個(gè)獲取指定長(zhǎng)度的隨機(jī)字符串的長(zhǎng)度:
//獲取指定長(zhǎng)度的隨機(jī)字符串 function randomString(length){ let str=""; while(length>0){ const fragment= Math.random().toString(36).substring(2); if(length>fragment.length){ str+=fragment; length-=fragment.length; }else{ str+=fragment.substring(0,length); length=0; } } return str; }isPlainObject.js
export default function isPlainObject(obj) { if (typeof obj !== "object" || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
isPlainObject.js也很簡(jiǎn)單,僅僅只是向外暴露了一個(gè)用于判斷是否簡(jiǎn)單對(duì)象的函數(shù)。什么簡(jiǎn)單對(duì)象?應(yīng)該有一些小伙伴不理解,所謂的簡(jiǎn)單對(duì)象就是該對(duì)象的__proto__等于Object.prototype,用一句通俗易懂的話就是:
凡不是new Object()或者字面量的方式構(gòu)建出來(lái)的對(duì)象都不是簡(jiǎn)單對(duì)象
下面看一個(gè)例子:
class Fruit{ sayName(){ console.log(this.name) } } class Apple extends Fruit{ constructor(){ super(); this.name="蘋(píng)果" } } const apple = new Apple(); const fruit = new Fruit(); const cherry = new Object({ name:"櫻桃" }); const banana = { name:"香蕉" }; console.log(isPlainObject(apple));//false console.log(isPlainObject(fruit));//false console.log(isPlainObject(cherry));//true console.log(isPlainObject(banana));//true
這里可能會(huì)有人不理解isPlainObject(fruit)===false,如果對(duì)這個(gè)不能理解的話,自己后面要補(bǔ)習(xí)一下原型鏈的相關(guān)知識(shí),這里fruit.__proto__.__proto__才等價(jià)于Object.prototype。
warning.jsexport default function warning(message) { if (typeof console !== "undefined" && typeof console.error === "function") { console.error(message) } try { throw new Error(message) } catch (e) {} }
這個(gè)也很簡(jiǎn)單,僅僅是打印一下錯(cuò)誤信息。不過(guò)這里它的console居然加了一層判斷,我查閱了一下發(fā)現(xiàn)console其實(shí)是有兼容性問(wèn)題,ie8及其以下都是不支持console的。哎,不僅感嘆一句!
如果說(shuō)馬賽克阻礙了人類文明的進(jìn)程,那ie便是阻礙了前端技術(shù)的發(fā)展。邏輯代碼
到這里我已經(jīng)完成對(duì)utils下的js分析,很簡(jiǎn)單,并沒(méi)有大家想象的那么難。僅僅從這幾個(gè)簡(jiǎn)單的js中,就牽引出好幾個(gè)我們平時(shí)不太關(guān)注的知識(shí)點(diǎn)。如果我們不讀這些源碼,這些容易被忽視的知識(shí)點(diǎn)就很難被撿起來(lái),這也是為什么很多大佬建議閱讀源碼的原因。我個(gè)人認(rèn)為,閱讀源碼,理解原理是次要的。學(xué)習(xí)大佬的代碼風(fēng)格、一些解決思路以及對(duì)自己知識(shí)盲點(diǎn)的點(diǎn)亮更為重要。廢話不多說(shuō),開(kāi)始我們下一個(gè)部分的代碼閱讀,下面的部分就是整個(gè)redux的核心部分。
index.jsimport createStore from "./createStore" import combineReducers from "./combineReducers" import bindActionCreators from "./bindActionCreators" import applyMiddleware from "./applyMiddleware" import compose from "./compose" import warning from "./utils/warning" import __DO_NOT_USE__ActionTypes from "./utils/actionTypes" function isCrushed() {} if ( process.env.NODE_ENV !== "production" && typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed" ) { warning( "You are currently using minified code outside of NODE_ENV === "production". " + "This means that you are running a slower development build of Redux. " + "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " + "or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) " + "to ensure you have the correct code for your production build." ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }
index.js是整個(gè)redux的入口文件,尾部的export出來(lái)的方法是不是都很熟悉,每個(gè)方法對(duì)應(yīng)了一個(gè)js,這也是后面我們要分析的。這個(gè)有兩個(gè)點(diǎn)需要講一下:
第一個(gè),__DO_NOT_USE__ActionTypes。 這個(gè)很陌生,平時(shí)在項(xiàng)目里面我們是不太會(huì)用到的,redux的官方文檔也沒(méi)有提到這個(gè),如果你不看源碼你可能就不知道這個(gè)東西的存在。這個(gè)干嘛的呢?我們一點(diǎn)一點(diǎn)往上找,找到這么一行代碼:
import __DO_NOT_USE__ActionTypes from "./utils/actionTypes"
這個(gè)引入的js不就是我們之前分析的utils的其中一員嗎?里面定義了redux自帶的action的類型,從這個(gè)變量的命名來(lái)看,這是幫助開(kāi)發(fā)者檢查不要使用redux自帶的action的類型,以防出現(xiàn)錯(cuò)誤。
第二個(gè),函數(shù)isCrushed。 這里面定義了一個(gè)函數(shù)isCrushed,但是函數(shù)體里面并沒(méi)有東西。第一次看的時(shí)候很奇怪,為啥要這么干?相信有不少小伙伴們跟我有一樣的疑問(wèn),繼續(xù)往下看,緊跟著后面有一段代碼:
if ( process.env.NODE_ENV !== "production" && typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed" ) { warning( "You are currently using minified code outside of NODE_ENV === "production". " + "This means that you are running a slower development build of Redux. " + "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " + "or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) " + "to ensure you have the correct code for your production build." ) }
看到process.env.NODE_ENV,這里就要跟我們打包時(shí)用的環(huán)境變量聯(lián)系起來(lái)。當(dāng)process.env.NODE_ENV==="production"這句話直接不成立,所以warning也就不會(huì)執(zhí)行;當(dāng)process.env.NODE_ENV!=="production",比如是我們的開(kāi)發(fā)環(huán)境,我們不壓縮代碼的時(shí)候typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed"也不會(huì)成立;當(dāng)process.env.NODE_ENV!=="production",同樣是我們的開(kāi)發(fā)環(huán)境,我們進(jìn)行了代碼壓縮,此時(shí)isCrushed.name === "string" && isCrushed.name !== "isCrushed"就成立了,可能有人不理解isCrushed函數(shù)不是在的嗎?為啥這句話就不成立了呢?其實(shí)很好理解,了解過(guò)代碼壓縮的原理的人都知道,函數(shù)isCrushed的函數(shù)名將會(huì)被一個(gè)字母所替代,這里我們舉個(gè)例子,我將redux項(xiàng)目的在development環(huán)境下進(jìn)行了一次壓縮打包。代碼做了這么一層轉(zhuǎn)換:
未壓縮
function isCrushed() {} if ( process.env.NODE_ENV !== "production" && typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed" )
壓縮后
function d(){}"string"==typeof d.name&&"isCrushed"!==d.name
此時(shí)判斷條件就成立了,錯(cuò)誤信息就會(huì)打印出來(lái)。這個(gè)主要作用就是防止開(kāi)發(fā)者在開(kāi)發(fā)環(huán)境下對(duì)代碼進(jìn)行壓縮。開(kāi)發(fā)環(huán)境下壓縮代碼,不僅讓我們
createStore.js函數(shù)createStore接受三個(gè)參數(shù)(reducer、preloadedState、enhancer),reducer和enhancer我們用的比較多,preloadedState用的比較少。第一個(gè)reducer很好理解,這里就不過(guò)多解釋了,第二個(gè)preloadedState,它代表著初始狀態(tài),我們平時(shí)在項(xiàng)目里也很少用到它,主要說(shuō)一下enhancer,中文名叫增強(qiáng)器,顧名思義就是來(lái)增強(qiáng)redux的,它的類型的是Function,createStore.js里有這么一行代碼:
if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } return enhancer(createStore)(reducer, preloadedState) }
這行代碼展示了enhancer的調(diào)用過(guò)程,根據(jù)這個(gè)調(diào)用過(guò)程我們可以推導(dǎo)出enhancer的函數(shù)體的架子應(yīng)該是這樣子的:
function enhancer(createStore) { return (reducer,preloadedState) => { //邏輯代碼 ....... } }
常見(jiàn)的enhancer就是redux-thunk以及redux-saga,一般都會(huì)配合applyMiddleware一起使用,而applyMiddleware的作用就是將這些enhancer格式化成符合redux要求的enhancer。具體applyMiddleware實(shí)現(xiàn),下面我們將會(huì)講到。我們先看redux-thunk的使用的例子:
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import rootReducer from "./reducers/index"; const store = createStore( rootReducer, applyMiddleware(thunk) );
看完上面的代碼,可能會(huì)有人有這么一個(gè)疑問(wèn)“createStore函數(shù)第二個(gè)參數(shù)不是preloadedState嗎?這樣不會(huì)報(bào)錯(cuò)嗎?” 首先肯定不會(huì)報(bào)錯(cuò),畢竟官方給的例子,不然寫(xiě)個(gè)錯(cuò)誤的例子也太大跌眼鏡了吧!redux肯定是做了這么一層轉(zhuǎn)換,我在createStore.js找到了這么一行代碼:
if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState preloadedState = undefined }
當(dāng)?shù)诙€(gè)參數(shù)preloadedState的類型是Function的時(shí)候,并且第三個(gè)參數(shù)enhancer未定義的時(shí)候,此時(shí)preloadedState將會(huì)被賦值給enhancer,preloadedState會(huì)替代enhancer變成undefined的。有了這么一層轉(zhuǎn)換之后,我們就可以大膽地第二個(gè)參數(shù)傳enhancer了。
說(shuō)完createStore的參數(shù),下面我說(shuō)一下函數(shù)createStore執(zhí)行完之后返回的對(duì)象都有什么?在createStore.js最下面一行有這一行代碼:
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
他返回了有這么幾個(gè)方法,其中前三個(gè)最為常用,后面兩個(gè)在項(xiàng)目基本上不怎么用,接下來(lái)我們?nèi)ヒ灰黄饰觥?/p> 定義的一些變量
let currentState = preloadedState //從函數(shù)createStore第二個(gè)參數(shù)preloadedState獲得 let currentReducer = reducer //從函數(shù)createStore第一個(gè)參數(shù)reducer獲得 let currentListeners = [] //當(dāng)前訂閱者列表 let nextListeners = currentListeners //新的訂閱者列表 let isDispatching = false
其中變量isDispatching,作為鎖來(lái)用,我們r(jià)edux是一個(gè)統(tǒng)一管理狀態(tài)容器,它要保證數(shù)據(jù)的一致性,所以同一個(gè)時(shí)間里,只能做一次數(shù)據(jù)修改,如果兩個(gè)action同時(shí)觸發(fā)reducer對(duì)同一數(shù)據(jù)的修改,那么將會(huì)帶來(lái)巨大的災(zāi)難。所以變量isDispatching就是為了防止這一點(diǎn)而存在的。
dispatchfunction dispatch(action) { if (!isPlainObject(action)) { throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } if (typeof action.type === "undefined") { throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } if (isDispatching) { throw new Error("Reducers may not dispatch actions.") } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
函數(shù)dispatch在函數(shù)體一開(kāi)始就進(jìn)行了三次條件判斷,分別是以下三個(gè):
判斷action是否為簡(jiǎn)單對(duì)象
判斷action.type是否存在
判斷當(dāng)前是否有執(zhí)行其他的reducer操作
當(dāng)前三個(gè)預(yù)置條件判斷都成立時(shí),才會(huì)執(zhí)行后續(xù)操作,否則拋出異常。在執(zhí)行reducer的操作的時(shí)候用到了try-finally,可能大家平時(shí)try-catch用的比較多,這個(gè)用到的還是比較少。執(zhí)行前isDispatching設(shè)置為true,阻止后續(xù)的action進(jìn)來(lái)觸發(fā)reducer操作,得到的state值賦值給currentState,完成之后再finally里將isDispatching再改為false,允許后續(xù)的action進(jìn)來(lái)觸發(fā)reducer操作。接著一一通知訂閱者做數(shù)據(jù)更新,不傳入任何參數(shù)。最后返回當(dāng)前的action。
getStatefunction getState() { if (isDispatching) { throw new Error( "You may not call store.getState() while the reducer is executing. " + "The reducer has already received the state as an argument. " + "Pass it down from the top reducer instead of reading it from the store." ) } return currentState }
getState相比較dispatch要簡(jiǎn)單許多,返回currentState即可,而這個(gè)currentState在每次dispatch得時(shí)候都會(huì)得到響應(yīng)的更新。同樣是為了保證數(shù)據(jù)的一致性,當(dāng)在reducer操作的時(shí)候,是不可以讀取當(dāng)前的state值的。說(shuō)到這里,我想到之前一次的面試經(jīng)歷:
面試官:執(zhí)行createStore函數(shù)生成的store,可不可以直接修改它的state? 我:可以。(普羅大眾的第一反應(yīng)) 面試官:你知道redux怎么做到不能修改store的state嗎? 我:額......(處于懵逼狀態(tài)) 面試官:很簡(jiǎn)單啊!重寫(xiě)store的set方法啊!
那會(huì)沒(méi)看過(guò)redux的源碼,就被他忽悠了!讀完redux源碼之后,靠!這家伙就是個(gè)騙子!自己沒(méi)讀過(guò)源碼還跟我聊源碼,無(wú)語(yǔ)了!當(dāng)然,我自己也有原因,學(xué)藝不精,被忽悠了。我們這里看了源碼之后,getState函數(shù)返回state的時(shí)候,并沒(méi)有對(duì)currentState做一層拷貝再給我們,所以是可以直接修改的。只是這么修改的話,就不會(huì)通知訂閱者做數(shù)據(jù)更新。得出的結(jié)論是:
store通過(guò)getState得出的state是可以直接被更改的,但是redux不允許這么做,因?yàn)檫@樣不會(huì)通知訂閱者更新數(shù)據(jù)。subscribe
function subscribe(listener) { if (typeof listener !== "function") { throw new Error("Expected the listener to be a function.") } if (isDispatching) { throw new Error( "You may not call store.subscribe() while the reducer is executing. " + "If you would like to be notified after the store has been updated, subscribe from a " + "component and invoke store.getState() in the callback to access the latest state. " + "See https://redux.js.org/api-reference/store#subscribe(listener) for more details." ) } let isSubscribed = true //表示該訂閱者在訂閱狀態(tài)中,true-訂閱中,false-取消訂閱 ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( "You may not unsubscribe from a store listener while the reducer is executing. " + "See https://redux.js.org/api-reference/store#subscribe(listener) for more details." ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
在注冊(cè)訂閱者之前,做了兩個(gè)條件判斷:
判斷監(jiān)聽(tīng)者是否為函數(shù)
是否有reducer正在進(jìn)行數(shù)據(jù)修改(保證數(shù)據(jù)的一致性)
接下來(lái)執(zhí)行了函數(shù)ensureCanMutateNextListeners,下面我們看一下ensureCanMutateNextListeners函數(shù)的具體實(shí)現(xiàn)邏輯:
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
邏輯很簡(jiǎn)單,判斷nextListeners和currentListeners是否為同一個(gè)引用,還記得dispatch函數(shù)中有這么一句代碼以及定義變量時(shí)一行代碼嗎?
// Function dispatch const listeners = (currentListeners = nextListeners)
// 定義變量 let currentListeners = [] let nextListeners = currentListeners
這兩處將nextListeners和currentListeners引用了同一個(gè)數(shù)組,另外定義變量時(shí)也有這么一句話代碼。而ensureCanMutateNextListeners就是用來(lái)判斷這種情況的,當(dāng)nextListeners和currentListeners為同一個(gè)引用時(shí),則做一層淺拷貝,這里用的就是Array.prototype.slice方法,該方法會(huì)返回一個(gè)新的數(shù)組,這樣就可以達(dá)到淺拷貝的效果。
函數(shù)ensureCanMutateNextListeners作為處理之后,將新的訂閱者加入nextListeners中,并且返回取消訂閱的函數(shù)unsubscribe。函數(shù)unsubscribe執(zhí)行時(shí),也會(huì)執(zhí)行兩個(gè)條件判斷:
是否已經(jīng)取消訂閱(已取消的不必執(zhí)行)
是否有reducer正在進(jìn)行數(shù)據(jù)修改(保證數(shù)據(jù)的一致性)
通過(guò)條件判斷之后,講該訂閱者從nextListeners中刪除。看到這里可能有小伙伴們對(duì)currentListeners和nextListeners有這么一個(gè)疑問(wèn)?函數(shù)dispatch里面將二者合并成一個(gè)引用,為啥這里有啥給他倆分開(kāi)?直接用currentListeners不可以嗎?這里這樣做其實(shí)也是為了數(shù)據(jù)的一致性,因?yàn)橛羞@么一種的情況存在。當(dāng)redux在通知所有訂閱者的時(shí)候,此時(shí)又有一個(gè)新的訂閱者加進(jìn)來(lái)了。如果只用currentListeners的話,當(dāng)新的訂閱者插進(jìn)來(lái)的時(shí)候,就會(huì)打亂原有的順序,從而引發(fā)一些嚴(yán)重的問(wèn)題。
replaceReducerfunction replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function.") } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
這個(gè)函數(shù)是用來(lái)替換reducer的,平時(shí)項(xiàng)目里基本很難用到,replaceReducer函數(shù)執(zhí)行前會(huì)做一個(gè)條件判斷:
判斷所傳reducer是否為函數(shù)
通過(guò)條件判斷之后,將nextReducer賦值給currentReducer,以達(dá)到替換reducer效果,并觸發(fā)state更新操作。
observable/** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */
這里沒(méi)貼代碼,因?yàn)檫@塊代碼我們不需要掌握。這個(gè)observable函數(shù),并沒(méi)有調(diào)用,即便暴露出來(lái)我們也辦法使用。所以我們就跳過(guò)這塊,如果有興趣的話,可以去作者給的github的地址了解一下。
講完這幾個(gè)方法之后,還有一個(gè)小細(xì)節(jié)需要說(shuō)一下,createStore函數(shù)體里有這樣一行代碼。
dispatch({ type: ActionTypes.INIT })
為啥要有這么一行代碼?原因很簡(jiǎn)單,假設(shè)我們沒(méi)有這樣代碼,此時(shí)currentState就是undefined的,也就我說(shuō)我們沒(méi)有默認(rèn)值了,當(dāng)我們dispatch一個(gè)action的時(shí)候,就無(wú)法在currentState基礎(chǔ)上做更新。所以需要拿到所有reducer默認(rèn)的state,這樣后續(xù)的dispatch一個(gè)action的時(shí)候,才可以更新我們的state。
combineReducers.js這個(gè)js對(duì)應(yīng)著redux里的combineReducers方法,主要作用就是合并多個(gè)reducer。現(xiàn)在我們先給一個(gè)空的函數(shù),然后再一步步地根據(jù)還原源碼,這樣大家可能理解得更為透徹點(diǎn)。
//reducers Object類型 每個(gè)屬性對(duì)應(yīng)的值都要是function export default function combineReducers(reducers) { .... }第一步:淺拷貝reducers
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== "production") { if (typeof reducers[key] === "undefined") { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === "function") { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) }
這里定義了一個(gè)finalReducers和finalReducerKeys,分別用來(lái)拷貝reducers和其屬性。先用Object.keys方法拿到reducers所有的屬性,然后進(jìn)行for循環(huán),每一項(xiàng)可根據(jù)其屬性拿到對(duì)應(yīng)的reducer,并淺拷貝到finalReducers中,但是前提條件是每個(gè)reducer的類型必須是Function,不然會(huì)直接跳過(guò)不拷貝。
第二步:檢測(cè)finalReducers里的每個(gè)reducer是否都有默認(rèn)返回值function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === "undefined") { throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined. If you don"t want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } const type = "@@redux/PROBE_UNKNOWN_ACTION_" + Math.random() .toString(36) .substring(7) .split("") .join(".") if (typeof reducer(undefined, { type }) === "undefined") { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don"t try to handle ${ ActionTypes.INIT } or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined, but can be null.` ) } }) } export default function combineReducers(reducers) { //省略第一步的代碼 ...... let shapeAssertionError try { assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } }
assertReducerShape方法主要檢測(cè)兩點(diǎn):
不能占用
如果遇到未知的action的類型,不需要要用默認(rèn)返回值
如果傳入type為 @@redux/INIT<隨機(jī)值> 的action,返回undefined,說(shuō)明沒(méi)有對(duì)未
知的action的類型做響應(yīng),需要加默認(rèn)值。如果對(duì)應(yīng)type為 @@redux/INIT<隨機(jī)值> 的action返回不為undefined,但是卻對(duì)應(yīng)type為 @@redux/PROBE_UNKNOWN_ACTION_<隨機(jī)值> 返回為undefined,說(shuō)明占用了
export default function combineReducers(reducers) { //省略第一步和第二步的代碼 ...... let unexpectedKeyCache if (process.env.NODE_ENV !== "production") { unexpectedKeyCache = {} } return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } if (process.env.NODE_ENV !== "production") { const warningMessage = getUnexpectedStateShapeWarningMessage( state, finalReducers, action, unexpectedKeyCache ) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === "undefined") { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
首先對(duì)傳入的state用getUnexpectedStateShapeWarningMessage做了一個(gè)異常檢測(cè),找出state里面沒(méi)有對(duì)應(yīng)reducer的key,并提示開(kāi)發(fā)者做調(diào)整。接著我們跳到getUnexpectedStateShapeWarningMessage里,看其實(shí)現(xiàn)。
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) { const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? "preloadedState argument passed to createStore" : "previous state received by the reducer" if (reducerKeys.length === 0) { return ( "Store does not have a valid reducer. Make sure the argument passed " + "to combineReducers is an object whose values are reducers." ) } if (!isPlainObject(inputState)) { return ( `The ${argumentName} has unexpected type of "` + {}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join("", "")}"` ) } const unexpectedKeys = Object.keys(inputState).filter( key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] ) unexpectedKeys.forEach(key => { unexpectedKeyCache[key] = true }) if (action && action.type === ActionTypes.REPLACE) return if (unexpectedKeys.length > 0) { return ( `Unexpected ${unexpectedKeys.length > 1 ? "keys" : "key"} ` + `"${unexpectedKeys.join("", "")}" found in ${argumentName}. ` + `Expected to find one of the known reducer keys instead: ` + `"${reducerKeys.join("", "")}". Unexpected keys will be ignored.` ) } }
getUnexpectedStateShapeWarningMessage接收四個(gè)參數(shù) inputState(state)、reducers(finalReducers)、action(action)、unexpectedKeyCache(unexpectedKeyCache),這里要說(shuō)一下unexpectedKeyCache是上一次檢測(cè)inputState得到的其里面沒(méi)有對(duì)應(yīng)的reducer集合里的異常key的集合。整個(gè)邏輯如下:
前置條件判斷,保證reducers集合不為{}以及inputState為簡(jiǎn)單對(duì)象
找出inputState里有的key但是 reducers集合里沒(méi)有key
如果是替換reducer的action,跳過(guò)第四步,不打印異常信息
將所有異常的key打印出來(lái)
getUnexpectedStateShapeWarningMessage分析完之后,我們接著看后面的代碼。
let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === "undefined") { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state
首先定義了一個(gè)hasChanged變量用來(lái)表示state是否發(fā)生變化,遍歷reducers集合,將每個(gè)reducer對(duì)應(yīng)的原state傳入其中,得出其對(duì)應(yīng)的新的state。緊接著后面對(duì)新的state做了一層未定義的校驗(yàn),函數(shù)getUndefinedStateErrorMessage的代碼如下:
function getUndefinedStateErrorMessage(key, action) { const actionType = action && action.type const actionDescription = (actionType && `action "${String(actionType)}"`) || "an action" return ( `Given ${actionDescription}, reducer "${key}" returned undefined. ` + `To ignore an action, you must explicitly return the previous state. ` + `If you want this reducer to hold no value, you can return null instead of undefined.` ) }
邏輯很簡(jiǎn)單,僅僅做了一下錯(cuò)誤信息的拼接。未定義校驗(yàn)完了之后,會(huì)跟原state作對(duì)比,得出其是否發(fā)生變化。最后發(fā)生變化返回nextState,否則返回state。
compose.js這個(gè)函數(shù)主要作用就是將多個(gè)函數(shù)連接起來(lái),將一個(gè)函數(shù)的返回值作為另一個(gè)函數(shù)的傳參進(jìn)行計(jì)算,得出最終的返回值。以烹飪?yōu)槔康搅侠矶际菑淖畛醯氖巢慕?jīng)過(guò)一道又一道的工序處理才得到的。compose的用處就可以將這些烹飪工序連接到一起,你只需要提供食材,它會(huì)自動(dòng)幫你經(jīng)過(guò)一道又一道的工序處理,烹飪出這道料理。
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }
上面是es6的代碼,可能小伙伴們并不是很好理解,為了方便大家理解,我將其轉(zhuǎn)換成es5代碼去做講解。
function compose() { var _len = arguments.length; var funcs = []; for (var i = 0; i < _len; i++) { funcs[i] = arguments[i]; } if (funcs.length === 0) { return function (arg) { return arg; }; } if (funcs.length === 1) { return funcs[0]; } return funcs.reduce(function (a, b) { return function () { return a(b.apply(undefined, arguments)); }; }); }
梳理一下整個(gè)流程,大致分為這么幾步:
新建一個(gè)新數(shù)組funcs,將arguments里面的每一項(xiàng)一一拷貝到funcs中去
當(dāng)funcs的長(zhǎng)度為0時(shí),返回一個(gè)傳入什么就返回什么的函數(shù)
當(dāng)funcs的長(zhǎng)度為1時(shí),返回funcs第0項(xiàng)對(duì)應(yīng)的函數(shù)
當(dāng)funcs的長(zhǎng)度大于1時(shí),調(diào)用Array.prototype.reduce方法進(jìn)行整合
這里我們正好復(fù)習(xí)一下數(shù)組的reduce方法,函數(shù)reduce接受下面四個(gè)參數(shù)
total 初始值或者計(jì)算得出的返回值
current 當(dāng)前元素
index 當(dāng)前元素的下標(biāo)
array 當(dāng)前元素所在的數(shù)組
示例:
const array = [1,2,3,4,5,6,7,8,9,10]; const totalValue=array.reduce((total,current)=>{ return total+current }); //55
這里的compose有個(gè)特點(diǎn),他不是從左到右執(zhí)行的,而是從右到左執(zhí)行的,下面我們看個(gè)例子:
const value=compose(function(value){ return value+1; },function(value){ return value*2; },function(value){ return value-3; })(2); console.log(value);//(2-3)*2+1=-1
如果想要其從左向右執(zhí)行也很簡(jiǎn)單,做一下順序的顛倒即可。
===> 轉(zhuǎn)換前 return a(b.apply(undefined, arguments)); ===> 轉(zhuǎn)換后 return b(a.apply(undefined, arguments));applyMiddleware.js
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
前面我們講enhancer的時(shí)候,提到過(guò)這個(gè)applyMiddleware,現(xiàn)在我們將二者的格式對(duì)比看一下。
// enhancer function enhancer(createStore) { return (reducer,preloadedState) => { //邏輯代碼 ....... } } //applyMiddleware function //applyMiddleware(...middlewares) { return createStore => (...args) => { //邏輯代碼 ....... } }
通過(guò)二者的對(duì)比,我們發(fā)現(xiàn)函數(shù)applyMiddleware的返回就是一個(gè)enhancer,下面我們?cè)倏雌渚唧w實(shí)現(xiàn)邏輯:
通過(guò)createStore方法創(chuàng)建出一個(gè)store
定一個(gè)dispatch,如果在中間件構(gòu)造過(guò)程中調(diào)用,拋出錯(cuò)誤提示
定義middlewareAPI,有兩個(gè)方法,一個(gè)是getState,另一個(gè)是dispatch,將其作為中間件調(diào)用的store的橋接
middlewares調(diào)用Array.prototype.map進(jìn)行改造,存放在chain
用compose整合chain數(shù)組,并賦值給dispatch
將新的dispatch替換原先的store.dispatch
看完整個(gè)過(guò)程可能小伙伴們還是一頭霧水,玄學(xué)的很!不過(guò)沒(méi)關(guān)系,我們以redux-thunk為例,模擬一下整個(gè)過(guò)程中,先把redux-thunk的源碼貼出來(lái):
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === "function") { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
哈哈哈!看完redux-thunk的源碼之后是不是很奔潰,幾千star的項(xiàng)目居然就幾行代碼,頓時(shí)三觀就毀了有木有?其實(shí)源碼沒(méi)有大家想象的那么復(fù)雜,不要一聽(tīng)源碼就慌。穩(wěn)住!我們能贏!根據(jù)redux-thunk的源碼,我們拿到的thunk應(yīng)該是這樣子的:
const thunk = ({ dispatch, getState })=>{ return next => action => { if (typeof action === "function") { return action(dispatch, getState); } return next(action); }; }
我們經(jīng)過(guò)applyMiddleware處理一下,到第四步的時(shí)候,chain數(shù)組應(yīng)該是這樣子的:
const newDispatch; const middlewareAPI={ getState:store.getState, dispatch: (...args) => newDispatch(...args) } const { dispatch, getState } = middlewareAPI; const fun1 = (next)=>{ return action => { if (typeof action === "function") { return action(dispatch, getState); } return next(action); } } const chain = [fun1]
compose整合完chain數(shù)組之后得到的新的dispatch的應(yīng)該是這樣子:
const newDispatch; const middlewareAPI={ getState:store.getState, dispatch: (...args) => newDispatch(...args) } const { dispatch, getState } = middlewareAPI; const next = store.dispatch; newDispatch = action =>{ if (typeof action === "function") { return action(dispatch, getState); } return next(action); }
接下來(lái)我們可以結(jié)合redux-thunk的例子來(lái)模擬整個(gè)過(guò)程:
function makeASandwichWithSecretSauce(forPerson) { return function (dispatch) { return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize("The Sandwich Shop", forPerson, error)) ); }; } // store.dispatch就等價(jià)于newDispatch store.dispatch(makeASandwichWithSecretSauce("Me")) ====> 轉(zhuǎn)換 const forPerson = "Me"; const action = (dispatch)=>{ return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize("The Sandwich Shop", forPerson, error)) ); } newDispatch() ===> typeof action === "function" 成立時(shí) ((dispatch)=>{ return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize("The Sandwich Shop", forPerson, error)) ); })( (...args) => newDispatch(...args), getState) ====> 計(jì)算運(yùn)行結(jié)果 const forPerson = "Me"; const dispatch = (...args) => newDispatch(...args) ; fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize("The Sandwich Shop", forPerson, error)) ); // 其中: function fetchSecretSauce() { return fetch("https://www.google.com/search?q=secret+sauce"); } function makeASandwich(forPerson, secretSauce) { return { type: "MAKE_SANDWICH", forPerson, secretSauce }; } function apologize(fromPerson, toPerson, error) { return { type: "APOLOGIZE", fromPerson, toPerson, error }; } ====> 我們這里只計(jì)算Promise.resolve的結(jié)果,并且假設(shè)fetchSecretSauce返回值為"666",即sauce="666" const forPerson = "Me"; const dispatch = (...args) => newDispatch(...args) ; dispatch({ type: "MAKE_SANDWICH", "Me", "666" }) ====> 為了方便對(duì)比,我們?cè)俅无D(zhuǎn)換一下 const action = { type: "MAKE_SANDWICH", "Me", "666" }; const next = store.dispatch const newDispatch = action =>{ if (typeof action === "function") { return action(dispatch, getState); } return next(action); } newDispatch(action) ====> 最終結(jié)果 store.dispatch({ type: "MAKE_SANDWICH", "Me", "666" });
以上就是redux-thunk整個(gè)流程,第一次看肯能依舊會(huì)很懵,后面可以走一遍,推導(dǎo)一下加深自己的理解。
bindActionCreators.jsexport default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === "function") { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== "object" || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? "null" : typeof actionCreators }. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] if (typeof actionCreator === "function") { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
bindActionCreators針對(duì)于三種情況有三種返回值,下面我們根據(jù)每種情況的返回值去分析。(為了方便理解,我們選擇在無(wú)集成中間件的情況)
typeof actionCreators === "function"function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } const actionFun=bindActionCreator(actionCreators, dispatch) ===> 整合一下 const fun1 = actionCreators; const dispatch= stror.dispatch; const actionFun=function () { return dispatch(fun1.apply(this, arguments)) }
根據(jù)上面的推導(dǎo),當(dāng)變量actionCreators的類型為Function時(shí),actionCreators必須返回一個(gè)action。
typeof actionCreators !== "object" || actionCreators === nullthrow new Error( `bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? "null" : typeof actionCreators }. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` )
提示開(kāi)發(fā)者actionCreators類型錯(cuò)誤,應(yīng)該是一個(gè)非空對(duì)象或者是函數(shù)。
默認(rèn)const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] if (typeof actionCreator === "function") { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators
通過(guò)和第一種情況對(duì)比發(fā)現(xiàn),當(dāng)actionCreators的每一項(xiàng)都執(zhí)行一次第一種情況的操作。換句話說(shuō),默認(rèn)情況是第一種情況的集合。
以上是對(duì)bindActionCreators的剖析,可能小伙伴們對(duì)這個(gè)還是不夠理解,不過(guò)沒(méi)有關(guān)系,只要知道bindActionCreators干了啥就行。bindActionCreators是需要結(jié)合react-redux一起使用的,由于本篇文章沒(méi)有講解react-redux,所以這里我們不對(duì)bindActionCreators做更深入的講解。下篇文章講react-redux,會(huì)再次提到bindActionCreators。
結(jié)語(yǔ)到這里整個(gè)redux的源碼我們已經(jīng)剖析完了,整個(gè)redux代碼量不是很大,但是里面的東西還是很多的,邏輯相對(duì)來(lái)說(shuō)有點(diǎn)繞。不過(guò)沒(méi)關(guān)系,沒(méi)有什么是看了好幾次都看不懂的,如果有那就再多看幾次嘛!另外再多一嘴,如果想快讀提高自己的小伙伴們,我個(gè)人是強(qiáng)烈推薦看源碼的。正所謂“近朱者赤,近墨者黑”,多看看大神的代碼,對(duì)自己的代碼書(shū)寫(xiě)、代碼邏輯、知識(shí)點(diǎn)查缺補(bǔ)漏等等方面都是很大幫助的。就拿我自己來(lái)說(shuō),我每次閱讀完一篇源碼之后,都受益匪淺。可能第一次看源碼,有著諸多的不適應(yīng),畢竟萬(wàn)事開(kāi)頭難,如果強(qiáng)迫自己完成第一次的源碼閱讀,那往后的源碼閱讀將會(huì)越來(lái)越輕松,對(duì)自己的提升也就越來(lái)越快。各位騷年們,擼起袖子加油干吧!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97927.html
摘要:本文共字,閱讀大約需要分鐘概述在前文字符串類型內(nèi)部編碼剖析之中已經(jīng)剖析過(guò)最基本的類型的內(nèi)部是怎么編碼和存儲(chǔ)的,本文再來(lái)闡述中使用最為頻繁的數(shù)據(jù)類型哈希或稱散列,在內(nèi)部是怎么存的。 showImg(https://segmentfault.com/img/remote/1460000016158153); 本文共 1231字,閱讀大約需要 5分鐘 ! 概述 在前文《Redis字符串類型...
閱讀 654·2021-11-15 11:39
閱讀 2890·2021-10-08 10:04
閱讀 3252·2019-08-30 10:57
閱讀 3015·2019-08-26 13:25
閱讀 1896·2019-08-26 12:14
閱讀 2626·2019-08-23 15:27
閱讀 2988·2019-08-23 15:18
閱讀 1766·2019-08-23 14:26