memoize.js revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Provides a system for memoizing computations applied to
7 * DOM nodes within the same call stack.
8 *
9 * To make a function memoizable - suppose you have a function
10 * isAccessible that takes a node and returns a boolean:
11 *
12 * function isAccessible(node) {
13 *   return expensiveComputation(node);
14 * }
15 *
16 * Make it memoizable like this:
17 *
18 * function isAccessible(node) {
19 *   return cvox.Memoize.memoize(computeIsAccessible_, 'isAccessible', node);
20 * }
21 *
22 * function computeIsAccessible_(node) {
23 *   return expensiveComputation(node);
24 * }
25 *
26 * To take advantage of memoization, you need to wrap a sequence of
27 * computations in a call to memoize.scope() - memoization is only
28 * enabled while in that scope, and all cached data is thrown away at
29 * the end. You should use this only when you're sure the computation
30 * being memoized will not change within the scope.
31 *
32 * cvox.Memoize.scope(function() {
33 *   console.log(isAccessible(document.body));
34 * });
35 *
36 */
37
38
39goog.provide('cvox.Memoize');
40
41
42/**
43 * Create the namespace.
44 * @constructor
45 */
46cvox.Memoize = function() {
47};
48
49/**
50 * The cache: a map from string function name to a WeakMap from DOM node
51 * to function result. This variable is null when we're out of scope, and it's
52 * a map from string to WeakMap to result when we're in scope.
53 *
54 * @type {?Object.<string, WeakMap.<Node, *> >}
55 * @private
56 */
57cvox.Memoize.nodeMap_ = null;
58
59/**
60 * Keeps track of how many nested times scope() has been called.
61 * @type {number}
62 * @private
63 */
64cvox.Memoize.scopeCount_ = 0;
65
66
67/**
68 * Enables memoization within the scope of the given function. You should
69 * ensure that the DOM is not modified within this scope.
70 *
71 * It's safe to nest calls to scope. The nested calls have
72 * no effect, only the outermost one.
73 *
74 * @param {Function} functionScope The function to call with memoization
75 *     enabled.
76 * @return {*} The value returned by |functionScope|.
77 */
78cvox.Memoize.scope = function(functionScope) {
79  var result;
80  try {
81    cvox.Memoize.scopeCount_++;
82    if (cvox.Memoize.scopeCount_ == 1) {
83      cvox.Memoize.nodeMap_ = {};
84    }
85    result = functionScope();
86  } finally {
87    cvox.Memoize.scopeCount_--;
88    if (cvox.Memoize.scopeCount_ == 0) {
89      cvox.Memoize.nodeMap_ = null;
90    }
91  }
92  return result;
93};
94
95/**
96 * Memoizes the result of a function call, so if you call this again
97 * with the same exact parameters and memoization is currently enabled
98 * (via a call to scope()), the second time the cached result
99 * will just be returned directly.
100 *
101 * @param {Function} functionClosure The function to call and cache the
102 *     result of.
103 * @param {string} functionName The name of the function you're calling.
104 *     This string is used to store and retrieve the cached result, so
105 *     it should be unique. If the function to be memoized takes simple
106 *     arguments in addition to a DOM node, you can incorporate those
107 *     arguments into the function name.
108 * @param {Node} node The DOM node that should be passed as the argument
109 *     to the function.
110 * @return {*} The return value of |functionClosure|.
111 */
112cvox.Memoize.memoize = function(functionClosure, functionName, node) {
113  if (cvox.Memoize.nodeMap_ &&
114      cvox.Memoize.nodeMap_[functionName] === undefined) {
115    cvox.Memoize.nodeMap_[functionName] = new WeakMap();
116  }
117
118  // If we're not in scope, just call the function directly.
119  if (!cvox.Memoize.nodeMap_) {
120    return functionClosure(node);
121  }
122
123  var result = cvox.Memoize.nodeMap_[functionName].get(node);
124  if (result === undefined) {
125    result = functionClosure(node);
126    if (result === undefined) {
127      throw 'A memoized function cannot return undefined.';
128    }
129    cvox.Memoize.nodeMap_[functionName].set(node, result);
130  }
131
132  return result;
133};
134