首先看看创建XHR最简单的方法:
function createXHR() { // Factory method. var methods = [ function() { return new XMLHttpRequest(); }, function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, function() { return new ActiveXObject('Microsoft.XMLHTTP'); } ]; var xhr; for (var i = 0, len = methods.length; i < len; i++) { try { xhr = methods[i](); } catch(e) { continue; } // If we reach this point, method[i] worked. return xhr; } throw new Error('Could not create an XHR object.'); }
我们调用createXHR函数来发起一个ajax请求,该函数会在内部遍历查找当前浏览器可用的xhr类型,然后返回。但显然每次都要循环一遍是没有必要的,能否让函数记住上一次返回的结果呢。
在Pro Javascript design patterns一书里,可以看到这样的解决方法:
function createXHR2() { var methods = [ function() { return new XMLHttpRequest(); }, function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, function() { return new ActiveXObject('Microsoft.XMLHTTP'); } ]; var xhr for (var i = 0, len = methods.length; i < len; i++) { try { xhr = methods[i](); } catch(e) { continue; } createXHR2 = function() {// cache it return xhr; } return methods[i]; } throw new Error('Could not create an XHR object.'); }
可以看到14行的作用是,在函数第一次执行之后,把函数自身给替换了,当函数第二次执行时,直接就是return xhr;,这种做法使得函数具有了记忆功能。
很巧妙的做法,但这样做的代价是,让一个函数多实现了一个它本不该有的功能,即cache功能,对于函数功能单一性的原则说,这种做法算不上完美。
可能经常会碰到这种需要记忆的函数,这种机制需要被抽象出来:
(以下代码来源于 Memoization in JavaScript,略有改动)
function asArray(quasiArray, start) { var r = []; for (var i = (start || 0); i < quasiArray.length; i++) r.push(quasiArray[i]); return r; } Function.prototype.memoize = function(scope) { var cache = {}; var self = this; scope = scope !== undefined ? scope : null; var memoizedFn = function() { var key = asArray(arguments).toString(); if (!(key in cache)) { cache[key] = self.apply(scope, arguments); } return cache[key]; }; memoizedFn.unmemoize = function() { return self; }; return memoizedFn; };
注:arguments是类似数组的东东,有部分数组特性,比如arguments[0],但没有toString()之类数组内置方法,所以需要用asArray将其转换为数组。
var memoizedCreateXHR = createXHR.memoize(); console.info(memoizedCreateXHR());
关于memoization的更多应用,可以参考
speed-up-your-javascript-part-2/