看完這篇,你也能夠實現一個 redux.javascript
本篇文章對應的代碼在: https://github.com/YvetteLau/... 建議先 clone
代碼,而後對照代碼閱讀本文。html
Redux
是什麼?Redux
是 JavaScript
狀態容器,提供可預測化的狀態管理。Redux
除了和 React
一塊兒用外,還支持其它界面庫。Redux
體小精悍,僅有 2KB
。這裏咱們須要明確一點:Redux
和 React
之間,沒有強綁定的關係。本文旨在理解和實現一個 Redux
,可是不會涉及 react-redux
(一次深刻理解一個知識點便可,react-redux
將出如今下一篇文章中)。前端
Redux
咱們先忘記 Redux
的概念,從一個例子入手,使用 create-react-app
建立一個項目: toredux
。java
代碼目錄: myredux/to-redux
中。react
將 public/index.html
中 body
修改成以下:git
<div id="app"> <div id="header"> 前端宇宙 </div> <div id="main"> <div id="content">你們好,我是前端宇宙做者劉小夕</div> <button class="change-theme" id="to-blue">Blue</button> <button class="change-theme" id="to-pink">Pink</button> </div> </div>
咱們要實現的功能如上圖所示,在點擊按鈕時,可以修改整個應用的字體的顏色。github
修改 src/index.js
以下(代碼: to-redux/src/index1.js
):redux
let state = { color: 'blue' } //渲染應用 function renderApp() { renderHeader(); renderContent(); } //渲染 title 部分 function renderHeader() { const header = document.getElementById('header'); header.style.color = state.color; } //渲染內容部分 function renderContent() { const content = document.getElementById('content'); content.style.color = state.color; } renderApp(); //點擊按鈕,更改字體顏色 document.getElementById('to-blue').onclick = function () { state.color = 'rgb(0, 51, 254)'; renderApp(); } document.getElementById('to-pink').onclick = function () { state.color = 'rgb(247, 109, 132)'; renderApp(); }
這個應用很是簡單,可是它有一個問題:state
是共享狀態,可是任何人均可以修改它,一旦咱們隨意修改了這個狀態,就能夠致使出錯,例如,在 renderHeader
裏面,設置 state = {}
, 容易形成難以預料的錯誤。數組
不過不少時候,咱們又的確須要共享狀態,所以咱們能夠考慮設置一些門檻,好比,咱們約定,不能直接修改全局狀態,必需要經過某個途經才能修改。爲此咱們定義一個 changeState
函數,全局狀態的修改均由它負責。app
//在 index.js 中繼續追加代碼 function changeState(action) { switch(action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } }
咱們約定只能經過 changeState
去修改狀態,它接受一個參數 action
,包含 type
字段的普通對象,type
字段用於識別你的操做類型(即如何修改狀態)。
咱們但願點擊按鈕,能夠修改整個應用的字體顏色。
//在 index.js 中繼續追加代碼 document.getElementById('to-blue').onclick = function() { let state = changeState({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); //狀態修改完以後,須要從新渲染頁面 renderApp(state); } document.getElementById('to-pink').onclick = function() { let state = changeState({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); renderApp(state); }
儘管如今咱們約定了如何修改狀態,可是 state
是一個全局變量,咱們很容易就能夠修改它,所以咱們能夠考慮將其變成局部變量,將其定義在一個函數內部(createStore
),可是在外部還須要使用 state
,所以咱們須要提供一個方法 getState()
,以便咱們在 createStore
獲取到 state
。
function createStore (state) { const getState = () => state; return { getState } }
如今,咱們能夠經過 store.getState()
方法去獲取狀態(這裏須要說明的是,state
一般是一個對象,所以這個對象在外部實際上是能夠被直接修改的,可是若是深拷貝 state
返回,那麼在外部就必定修改不了,鑑於 redux
源碼中就是直接返回了 state
,此處咱們也不進行深拷貝,畢竟耗費性能)。
僅僅獲取狀態是遠遠不夠的,咱們還須要有修改狀態的方法,如今狀態是私有變量,咱們必需要將修改狀態的方法也放到 createStore
中,並將其暴露給外部使用。
function createStore (state) { const getState = () => state; const changeState = () => { //...changeState 中的 code } return { getState, changeState } }
如今,index.js
中代碼變成下面這樣(to-redux/src/index2.js
):
function createStore() { let state = { color: 'blue' } const getState = () => state; function changeState(action) { switch (action.type) { case 'CHANGE_COLOR': state = { ...state, color: action.color } return state; default: return state; } } return { getState, changeState } } function renderApp(state) { renderHeader(state); renderContent(state); } function renderHeader(state) { const header = document.getElementById('header'); header.style.color = state.color; } function renderContent(state) { const content = document.getElementById('content'); content.style.color = state.color; } document.getElementById('to-blue').onclick = function () { store.changeState({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); renderApp(store.getState()); } document.getElementById('to-pink').onclick = function () { store.changeState({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); renderApp(store.getState()); } const store = createStore(); renderApp(store.getState());
儘管,咱們如今抽離了 createStore
方法,可是顯然這個方法一點都不通用,state
和 changeState
方法都定義在了 createStore
中。這種狀況下,其它應用沒法複用此模式。
changeState
的邏輯理應在外部定義,由於每一個應用修改狀態的邏輯定然是不一樣的。咱們將這部分邏輯剝離到外部,並將其重命名爲 reducer
(憋問爲何叫 reducer
,問就是爲了和 redux
保持一致)。reducer
是幹嗎的呢,說白了就是根據 action
的類型,計算出新狀態。由於它不是在 createStore
內部定義的,沒法直接訪問 state
,所以咱們須要將當前狀態做爲參數傳遞給它。以下:
function reducer(state, action) { switch(action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } }
function createStore(reducer) { let state = { color: 'blue' } const getState = () => state; //將此處的 changeState 改名爲 `dispatch` const dispatch = (action) => { //reducer 接收老狀態和action,返回一個新狀態 state = reducer(state, action); } return { getState, dispatch } }
不一樣應用的 state
定然是不一樣的,咱們將 state
的值定義在 createStore
內部必然是不合理的。
function createStore(reducer) { let state; const getState = () => state; const dispatch = (action) => { //reducer(state, action) 返回一個新狀態 state = reducer(state, action); } return { getState, dispatch } }
你們注意 reducer
的定義,在碰到不能識別的動做時,是直接返回舊狀態的,如今,咱們利用這一點來返回初始狀態。
要想 state
有初始狀態,其實很簡單,我們將初始的 state
的初始化值做爲 reducer
的參數的默認值,而後在 createStore
中派發一個 reducer
看不懂的動做就能夠了。這樣 getState
首次調用時,能夠獲取到狀態的默認值。
function createStore(reducer) { let state; const getState = () => state; //每當 `dispatch` 一個動做的時候,咱們須要調用 `reducer` 以返回一個新狀態 const dispatch = (action) => { //reducer(state, action) 返回一個新狀態 state = reducer(state, action); } //你要是有個 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是個狠人 dispatch({ type: `@@redux/__INIT__${Math.random()}` }); return { getState, dispatch } }
如今這個 createStore
已經能夠處處使用了, 可是你有沒有以爲每次 dispatch
後,都手動 renderApp()
顯得很蠢,當前應用中是調用兩次,若是須要修改1000次 state
呢,難道手動調用 1000次 renderApp()
?
能不能簡化一下呢?每次數據變化的時候,自動調用 renderApp()
。固然咱們不可能將 renderApp()
寫在 createStore()
的 dispatch
中,由於其它的應用中,函數名未必叫 renderApp()
,並且有可能不止要觸發 renderApp()
。這裏能夠引入 發佈訂閱模式
,當狀態變化時,通知全部的訂閱者。
function createStore(reducer) { let state; let listeners = []; const getState = () => state; //subscribe 每次調用,都會返回一個取消訂閱的方法 const subscribe = (ln) => { listeners.push(ln); //訂閱以後,也要容許取消訂閱。 //難道我訂了某本雜誌以後,就不容許我退訂嗎?可怕~ const unsubscribe = () => { listeners = listeners.filter(listener => ln !== listener); } return unsubscribe; }; const dispatch = (action) => { //reducer(state, action) 返回一個新狀態 state = reducer(state, action); listeners.forEach(ln => ln()); } //你要是有個 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是個狠人 dispatch({ type: `@@redux/__INIT__${Math.random()}` }); return { getState, dispatch, subscribe } }
至此,一個最爲簡單的 redux
已經建立好了,createStore
是 redux
的核心。咱們來使用這個精簡版的 redux
重寫咱們的代碼,index.js
文件內容更新以下(to-redux/src/index.js
):
function createStore() { //code(自行將上面createStore的代碼拷貝至此處) } const initialState = { color: 'blue' } function reducer(state = initialState, action) { switch (action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } } const store = createStore(reducer); function renderApp(state) { renderHeader(state); renderContent(state); } function renderHeader(state) { const header = document.getElementById('header'); header.style.color = state.color; } function renderContent(state) { const content = document.getElementById('content'); content.style.color = state.color; } document.getElementById('to-blue').onclick = function () { store.dispatch({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); } document.getElementById('to-pink').onclick = function () { store.dispatch({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); } renderApp(store.getState()); //每次state發生改變時,都從新渲染 store.subscribe(() => renderApp(store.getState()));
若是如今咱們如今但願在點擊完 Pink
以後,字體色不容許修改,那麼咱們還能夠取消訂閱:
const unsub = store.subscribe(() => renderApp(store.getState())); document.getElementById('to-pink').onclick = function () { //code... unsub(); //取消訂閱 }
順便說一句: reducer
是一個純函數(純函數的概念若是不瞭解的話,自行查閱資料),它接收先前的 state
和 action
,並返回新的 state
。不要問爲何 action
中必定要有 type
字段,這僅僅是一個約定而已(redux
就是這麼設計的)
遺留問題:爲何 reducer
必定要返回一個新的 state
,而不是直接修改 state
呢。歡迎在評論區留下你的答案。
前面咱們一步一步推演了 redux
的核心代碼,如今咱們來回顧一下 redux
的設計思想:
Redux
設計思想Redux
將整個應用狀態(state
)存儲到一個地方(一般咱們稱其爲 store
)dispatch
)一個 action
( action
是一個帶有 type
字段的對象)reducer
接收舊的 state
和 action
,並會返回一個新的 state
subscribe
設置訂閱,每次派發動做時,通知全部的訂閱者。我們如今已經有一個基礎版本的 redux
了,可是它還不能知足咱們的需求。咱們平時的業務開發不會像上面所寫的示例那樣簡單,那麼就會有一個問題: reducer
函數可能會很是長,由於 action
的類型會很是多。這樣確定是不利於代碼的編寫和閱讀的。
試想一下,你的業務中有一百種 action
須要處理,把這一百種狀況編寫在一個 reducer
中,不只寫得人噁心,後期維護代碼的同事更是想殺人。
所以,咱們最好單獨編寫 reducer
,而後對 reducer
進行合併。有請咱們的 combineReducers
(和 redux
庫的命名保持一致) 閃亮登場~
首先咱們須要明確一點:combineReducers
只是一個工具函數,正如咱們前面所說,它將多個 reducer
合併爲一個 reducer
。combineReducers
返回的是 reducer
,也就是說它是一個高階函數。
咱們仍是以一個示例來講明,儘管 redux
不是非得和 react
配合,不過鑑於其與 react
配合最爲適合,此處,以 react
代碼爲例:
這一次除了上面的展現之外,咱們新增了一個計數器功能( 使用 React
重構 ===> to-redux2
):
//如今咱們的 state 結構以下: let state = { theme: { color: 'blue' }, counter: { number: 0 } }
顯然,修改主題和計數器是能夠分割開得,由不一樣的 reducer
去處理是一個更好的選擇。
store/reducers/counter.js
負責處理計數器的state。
import { INCRENENT, DECREMENT } from '../action-types'; export default counter(state = {number: 0}, action) { switch (action.type) { case INCRENENT: return { ...state, number: state.number + action.number } case DECREMENT: return { ...state, number: state.number - action.number } default: return state; } }
store/reducers/theme.js
負責處理修改主題色的state。
import { CHANGE_COLOR } from '../action-types'; export default function theme(state = {color: 'blue'}, action) { switch (action.type) { case CHANGE_COLOR: return { ...state, color: action.color } default: return state; } }
每一個 reducer
只負責管理全局 state
中它負責的一部分。每一個 reducer
的 state
參數都不一樣,分別對應它管理的那部分 state
數據。
import counter from './counter'; import theme from './theme'; export default function appReducer(state={}, action) { return { theme: theme(state.theme, action), counter: counter(state.counter, action) } }
appReducer
便是合併以後的 reducer
,可是當 reducer
較多時,這樣寫也顯得繁瑣,所以咱們編寫一個工具函數來生成這樣的 appReducer
,咱們把這個工具函數命名爲 combineReducers
。
咱們來嘗試一下編寫這個工具函數 combineReducers
:
思路:
combineReducers
返回 reducer
combineReducers
的入參是多個 reducer
組成的對象reducer
只處理全局 state
中本身負責的部分//reducers 是一個對象,屬性值是每個拆分的 reducer export default function combineReducers(reducers) { return function combination(state={}, action) { //reducer 的返回值是新的 state let newState = {}; for(var key in reducers) { newState[key] = reducers[key](state[key], action); } return newState; } }
子 reducer
將負責返回 state
的默認值。好比本例中,createStore
中 dispatch({type:@@redux/__INIT__${Math.random()}
}),而傳遞給 createStore
的是 combineReducers(reducers)
返回的函數 combination
。
根據 state=reducer(state,action)
,newState.theme=theme(undefined, action)
, newState.counter=counter(undefined, action)
,counter
和 theme
兩個子 reducer
分別返回 newState.theme
和 newState.counter
的初始值。
利用此 combineReducers
能夠重寫 store/reducers/index.js
import counter from './counter'; import theme from './theme'; import { combineReducers } from '../redux'; //明顯簡潔了許多~ export default combineReducers({ counter, theme });
咱們寫的 combineReducers
雖然看起來已經可以知足咱們的需求,可是其有一個缺點,即每次都會返回一個新的 state
對象,這會致使在數據沒有變化時進行無心義的從新渲染。所以咱們能夠對數據進行判斷,在數據沒有變化時,返回本來的 state
便可。
combineReducers 進化版
//代碼中省略了一些判斷,默認傳遞的參數均是符合要求的,有興趣能夠查看源碼中對參數合法性的判斷及處理 export default function combineReducers(reducers) { return function combination(state={}, action) { let nextState = {}; let hasChanged = false; //狀態是否改變 for(let key in reducers) { const previousStateForKey = state[key]; const nextStateForKey = reducers[key](previousStateForKey, action); nextState[key] = nextStateForKey; //只有全部的 nextStateForKey 均與 previousStateForKey 相等時,hasChanged 的值纔是 false hasChanged = hasChanged || nextStateForKey !== previousStateForKey; } //state 沒有改變時,返回原對象 return hasChanged ? nextState : state; } }
官方文檔中,關於 applyMiddleware
的解釋很清楚,下面的內容也參考了官方文檔的內容:
考慮一個小小的問題,若是咱們但願每次狀態改變前可以在控制檯中打印出 state
,那麼咱們要怎麼作呢?
最簡單的便是:
//... <button onClick={() => { console.log(store.getState()); store.dispatch(actions.add(2)); }}>+</button> //...
固然,這種方式確定是不可取的,若是咱們代碼中派發100次,咱們不可能這樣寫一百次。既然是狀態改變時打印 state
,也是說是在 dispatch
以前打印 state
, 那麼咱們能夠重寫 store.dispatch
方法,在派發前打印 state
便可。
let store = createStore(reducer); const next = store.dispatch; //next 的命令是爲了和中間件的源碼一致 store.dispatch = action => { console.log(store.getState()); next(action); }
假設咱們不只僅須要打印 state
,還須要在派發異常出錯時,打印出錯誤信息。
const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致 store.dispatch = action => { try{ console.log(store.getState()); next(action); } catct(err) { console.error(err); } }
而若是咱們還有其餘的需求,那麼就須要不停的修改 store.dispatch
方法,最後致使這個這部分代碼難以維護。
所以咱們須要分離 loggerMiddleware
和 exceptionMiddleware
.
let store = createStore(reducer); const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致 const loggerMiddleware = action => { console.log(store.getState()); next(action); } const exceptionMiddleware = action => { try{ loggerMiddleware(action); }catch(err) { console.error(err); } } store.dispatch = exceptionMiddleware;
咱們知道,不少 middleware
都是第三方提供的,那麼 store
確定是須要做爲參數傳遞給 middleware
,進一步改寫:
const loggerMiddleware = store => action => { const next = store.dispatch; console.log(store.getState()); next(action); } const exceptionMiddleware = store => action => { try{ loggerMiddleware(store)(action); }catch(err) { console.error(err); } } //使用 store.dispatch = exceptionMiddleware(store)(action);
如今還有一個小小的問題,exceptionMiddleware
中的 loggerMiddleware
是寫死的,這確定是不合理的,咱們但願這是一個參數,這樣使用起來才靈活,沒道理只有 exceptionMiddleware
須要靈活,而無論 loggerMiddleware
,進一步改寫以下:
const loggerMiddleware = store => next => action => { console.log(store.getState()); return next(action); } const exceptionMiddleware = store => next => action => { try{ return next(action); }catch(err) { console.error(err); } } //使用 const next = store.dispatch; const logger = loggerMiddleware(store); store.dispatch = exceptionMiddleware(store)(logger(next));
如今,咱們已經有了通用 middleware
的編寫格式了。
middleware
接收了一個 next()
的 dispatch
函數,並返回一個 dispatch
函數,返回的函數會被做爲下一個 middleware
的 next()
可是有一個小小的問題,當中間件不少的時候,使用中間件的代碼會變得很繁瑣。爲此,redux
提供了一個 applyMiddleware
的工具函數。
上面咱們可以看出,其實咱們最終要改變的就是 dispatch
,所以咱們須要重寫 store
,返回修改了 dispatch
方法以後的 store
.
因此,咱們能夠明確如下幾點:
applyMiddleware
返回值是 store
applyMiddleware
確定要接受 middleware
做爲參數applyMiddleware
要接受 {dispatch, getState}
做爲入參,不過 redux
源碼中入參是 createStore
和 createStore
的入參,想一想也是,沒有必要在外部建立出 store
,畢竟在外部建立出的 store
除了做爲參數傳遞進函數,也沒有其它做用,不如把 createStore
和 createStore
須要使用的參數傳遞進來。//applyMiddleWare 返回 store. const applyMiddleware = middleware => createStore => (...args) => { let store = createStore(...args); let middle = loggerMiddleware(store); let dispatch = middle(store.dispatch); //新的dispatch方法 //返回一個新的store---重寫了dispatch方法 return { ...store, dispatch } }
以上是一個 middleware
的狀況,可是咱們知道,middleware
多是一個或者是多個,並且咱們主要是要解決多個 middleware
的問題,進一步改寫。
//applyMiddleware 返回 store. const applyMiddleware = (...middlewares) => createStore => (...args) => { let store = createStore(...args); let dispatch; const middlewareAPI = { getState: store.getstate, dispatch: (...args) => dispatch(...args) } //傳遞修改後的 dispatch let middles = middlewares.map(middleware => middleware(middlewareAPI)); //如今咱們有多個 middleware,須要屢次加強 dispatch dispatch = middles.reduceRight((prev, current) => current(prev), store.dispatch); return { ...store, dispatch } }
不知道你們是否是理解了上面的 middles.reduceRight
,下面爲你們細緻說明一下:
/*三個中間件*/ let logger1 = ({dispatch, getState}) => dispatch => action => { console.log('111'); dispatch(action); console.log('444'); } let logger2 = ({ dispatch, getState }) => dispatch => action => { console.log('222'); dispatch(action); console.log('555') } let logger3 = ({ dispatch, getState }) => dispatch => action => { console.log('333'); dispatch(action); console.log('666'); } let middle1 = logger1({ dispatch, getState }); let middle2 = logger2({ dispatch, getState }); let middle3 = logger3({ dispatch, getState }); //applyMiddleware(logger1,logger2,logger3)(createStore)(reducer) //若是直接替換 store.dispatch = middle1(middle2(middle3(store.dispatch)));
觀察上面的 middle1(middle2(middle3(store.dispatch)))
,若是咱們把 middle1
,middle2
,middle3
當作是數組的每一項,若是對數組的API比較熟悉的話,能夠想到 reduce
,若是你還不熟悉 reduce
,能夠查看MDN文檔。
//applyMiddleware(logger1,logger3,logger3)(createStore)(reducer) //reduceRight 從右到左執行 middles.reduceRight((prev, current) => current(prev), store.dispatch); //第一次 prev: store.dispatch current: middle3 //第二次 prev: middle3(store.dispatch) current: middle2 //第三次 prev: middle2(middle3(store.dispatch)) current: middle1 //結果 middle1(middle2(middle3(store.dispatch)))
閱讀過 redux
的源碼的同窗,可能知道源碼中是提供了一個 compose
函數,而 compose
函數中沒有使用 reduceRight
,而是使用的 reduce
,於是代碼稍微有點不一樣。可是分析過程仍是同樣的。
compose.js
export default function compose(...funcs) { //若是沒有中間件 if (funcs.length === 0) { return arg => arg } //中間件長度爲1 if (funcs.length === 1) { return funcs[0] } return funcs.reduce((prev, current) => (...args) => prev(current(...args))); }
關於 reduce
的寫法,建議像上面的 reduceRight
同樣,進行一次分析
使用 compose
工具函數重寫 applyMiddleware
。
const applyMiddleware = (...middlewares) => createStore => (...args) => { let store = createStore(...args); let dispatch; const middlewareAPI = { getState: store.getstate, dispatch: (...args) => dispatch(...args) } let middles = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...middles)(store.dispatch); return { ...store, dispatch } }
redux
還爲咱們提供了 bindActionCreators
工具函數,這個工具函數代碼很簡單,咱們不多直接在代碼中使用它,react-redux
中會使用到。此處,簡單說明一下:
//一般咱們會這樣編寫咱們的 actionCreator import { INCRENENT, DECREMENT } from '../action-types'; const counter = { add(number) { return { type: INCRENENT, number } }, minus(number) { return { type: DECREMENT, number } } } export default counter;
在派發的時候,咱們須要這樣寫:
import counter from 'xx/xx'; import store from 'xx/xx'; store.dispatch(counter.add());
固然,咱們也能夠像下面這樣編寫咱們的 actionCreator:
function add(number) { return { type: INCRENENT, number } }
派發時,須要這樣編寫:
store.dispatch(add(number));
以上代碼有一個共同點,就是都是 store.dispatch
派發一個動做。所以咱們能夠考慮編寫一個函數,將 store.dispatch
和 actionCreator
綁定起來。
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)); } function bindActionCreators(actionCreator, dispatch) { //actionCreators 能夠是一個普通函數或者是一個對象 if(typeof actionCreator === 'function') { //若是是函數,返回一個函數,調用時,dispatch 這個函數的返回值 bindActionCreator(actionCreator, dispatch); }else if(typeof actionCreator === 'object') { //若是是一個對象,那麼對象的每一項都要都要返回 bindActionCreator const boundActionCreators = {} for(let key in actionCreator) { boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch); } return boundActionCreators; } }
在使用時:
let counter = bindActionCreators(counter, store.dispatch); //派發時 counter.add(); counter.minus();
這裏看起來並無精簡太多,後面在分析 react-redux
時,會說明爲何須要這個工具函數。
至此,個人 redux
基本已經編寫完畢。與 redux
的源碼相比,還相差一些內容,例如 createStore
提供的 replaceReducer
方法,以及 createStore
的第二個參數和第三個參數沒有說起,稍微看一下代碼就能懂,此處再也不一一展開。