javascript里一个相对难懂且面试官极爱问到的概念就是闭包。
我在早先的面试也遇到过考闭包,只记得我解释的也并不清楚,面试官不甚满意。
今天在看redux的源码,又看到redux用到了闭包。
这个用到闭包的方法就是createStore
。贴出它的代码:
function createStore(reducer, initialState, enhancer) {
if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
enhancer = initialState;
initialState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, initialState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer;
var currentState = initialState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all states changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!(0, _isPlainObject2["default"])(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;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT });
return {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
};
}
当我们调用这个函数:
var store = createStore(reducer);
我们先看这个函数返回的是什么。
return {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
};
它返回了一个对象,该对象有四个属性,四个属性分别存放了一个函数。也就是说,变量store
是一个有着4个函数的对象。
想到了什么?store是不是很像C#等高级语言的对象——有着4个实例方法的实例对象?等等,但是这个对象似乎没有成员变量,那么它就是一个无状态的对象啊!
但是我们挑出四个方法的一个方法(函数)来看看:
function getState() {
return currentState;
}
我们调用这个方法:
var store = createStore(reducer);
store.getState();
它却返回了是一个表示状态的变量啊。但是我们store
并没有任何“成员变量”。翻看代码发现currentState
是创建store
的createStore
函数里的局部变量:
var currentState = initialState;
对于大多数语言特性来说,局部变量在方法结束调用之后就会销毁。但是这里的局部变量并不会销毁,它继续驻存在内存中,所以store
对象里的函数可以引用它。
store
对象就是一个闭包,它不仅包含了4个函数,还包含了运行这4个函数所需要的所有变量:
var currentReducer = reducer;
var currentState = initialState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
虽然以上5个变量均不是store
的内部变量,而是生成store
变量的父函数createStore
的局部变量,但它们在createStore
结束之后依然在留存在内存中,且可供store
访问。这使得你在调用store
的时候,无需为它提供任何参数变量,store
自成一体,独立运行,顾名思义,所以被称为闭包(closure)。store
说我闭着我的大门,也能自己运行。
最后一个极度简化源码便于理解的例子:
function createStore(state) {
var currentState = state;
function getState(){
return currentState;
}
return {getState : getState}
}
var v = createStore("hello,world");
alert(v.getState());
如果编程语言本身支持函数(方法)嵌套,闭包是自然而然是一个需要考虑的问题。
子函数引用了父函数的局部变量,当子函数独立之后,子函数引用的父函数的那些变量当然需要继续使用啊。这时候,独立的子函数加上子函数引用的父函数的变量便是一个闭包。