美文网首页
Redux源码里的闭包应用

Redux源码里的闭包应用

作者: 杨洋的围脖啊 | 来源:发表于2017-12-15 00:24 被阅读0次

    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是创建storecreateStore函数里的局部变量:

    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());
    

    如果编程语言本身支持函数(方法)嵌套,闭包是自然而然是一个需要考虑的问题。

    子函数引用了父函数的局部变量,当子函数独立之后,子函数引用的父函数的那些变量当然需要继续使用啊。这时候,独立的子函数加上子函数引用的父函数的变量便是一个闭包。

    相关文章

      网友评论

          本文标题:Redux源码里的闭包应用

          本文链接:https://www.haomeiwen.com/subject/dpfrwxtx.html