浅析Memoization

Memoization,简单的说就是优化计算机的性能,缓存那些重复性的函数操作和计算,使得第一次之后的调用可以直接从缓存中得到结果,而无需重新计算和运行复杂、费时的函数。这个跟Lazy Definition的原理是比较相似的,Lazy Definition主要是对函数进行重复定义,避免浏览器检测等恶心的事情。在javascript里实现Memoization的技术,Keith Gaughan做了相关的叙述:Memoization in JavaScript。特别是它对Fibonacci的优化让我特别的玩味。

对于Fibonacci普通的实现方式是:

//这个性能不咋的,函数调用太频繁了
function Fib(n) {
    if (n < 2) { return n;}
    return Fib(n - 1) + Fib(n - 2);
}

Keith Gaughan对它的实现方式用Memoization进行了优化:

var IterMemoFib = function() {
    var cache = [1, 1];
    var fib = function(n) {
        if (n >= cache.length) {
            for (var i = cache.length; i <= n; i++) {
                //这句代码很耐人寻味,还有循环的方式
                cache[i] = cache[i - 2] + cache[i - 1];
            }
        }
        return cache[n];
    }
    return fib;
}();

上面关于Fibonacci的优化,就是使用了Memoization技巧,将上一步的加法操作缓存起来,用户循环中下一轮的递增。缓存计算结果是Memorize应用最多的需求,避免了对计算结果的重复性计算,但是如果计算结果是动态的呢,或者说你想获取动态修改过后的计算结果呢? 比如DOM的length属性,这个是易变的,这个时候就不能去获取缓存的结果了,需要重复计算。

(全文…)

并行加载、按顺序执行javascript文件

之前一直在想法设法的实现js脚本文件按顺序执行,而且并行下载的测试,但是都无终而返。最先设想的是通过轮询的方式来实现按顺序加载,可是这样就导致不能并行加载了。

今天又拾起这个一直未实现的冲动,首先既然要并行加载、按顺序执行,那么就必须得给每一个js文件开一个http请求,而且这个请求是无顺序的,即是加载的顺序是无顺序的,但是在执行的时候,就得要按照声明的顺序来执行,比如下面的代码示例:

jL.load({"label":"test","url":"test.js",isOrder:true});
jL.load({"label":"test2","url":"test2.js",isOrder:false});
jL.load({"label":"test3","url":"test3.js",isOrder:true,callback:function(){
  alert("test3.js is load");
}}).insert();

上面的代码中,load方法的参数中包含是否按顺序加载脚本文件,并且在脚本加载下来的时候包含callback函数回调。如果不是按顺序下载的,则直接使用DOM Element的方式插入script到document.body中;insert方法负责开始操作那些俺顺序执行的脚本文件的加载和插入工作;label参数对于需要按顺序执行的脚本文件必不可少,用于标识当前脚本,对无需按顺序执行的脚本可有可无。

(全文…)

说说分支技术的一种替代方案

大家应该都知道Javascript中分支技术的实现原理:是一种用来把浏览器间的差异封装到在运行期间进行设置的动态方法中的技术。没错,这个解释是出自《Javascript设计模式》。分支技术最大的特点是只在脚本加载是一次性的确定针对特定的浏览器的代码,这样一来,在初始化完成之后,每种浏览器都只会执行针对特定浏览器的代码,能够在运行时动态确定函数代码的能力。下面是分支技术的简单实现的一个例子:

//三个对象都声明相同的createXhr方法以示统一
var SimpleXhrFactory=(function(){
   var standard={
       createXhr:function(){
            return new XMLHttpRequest();
       }
   };
   var activeXNew={
       createXhr:function(){
            return new ActiveXObject("Msxml2.XMLHTTP");
       }
   };
   var activeXOld={
      createXhr:function(){
           return new ActiveXObject("Microsoft.XMLHTTP");
      }
   };

   var testObj;
   try{
       testObj=standard.createXhr();
       return standard;
   }catch(e){
        try{
              testObj=activeXNew.createXhr();
              return activeXNew;
        }catch(e){
              testObj=activeXOld.createXhr();
              return activeXNew;
        }
   }
}();

上面的例子就是分支技术的一个应用。我不知道大家对这段代码是否有什么意见或者异议,反正我是觉得冗长了,无端端的声明了三个对象不说,最后还是需要try…catch一套。下面是我所想到的一种解决方案来代替这个关于声明XMLHttpRequest实例对象的例子:

//在运行时就确定了特定的浏览器下将要执行哪个分支的函数
var XHR = {}
XHR.create = (function(){
    if (window.XMLHttpRequest) {
        return function(){
            return new XMLHttpRequest();
        }
    }
    else {
        try {
            //为了判断Msxml2和Microsoft版本的ActiveXObject,不得不实例化一个,不过也就是一次性过了而已
            return new ActiveXObject("Msxml2.XMLHTTP") && function(){
                return new ActiveXObject("Msxml2.XMLHTTP");
            }
        } catch (e) {
            return function(){
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
    }
})();

上面的代码简洁明了,有一个不好的缺点就是需要在IE6下需要实例化多一个ActiveXObject(“Msxml2.XMLHTTP”)作为判断条件,IE7以上以及FF等其他浏览器则不会有这个实例化的过程,不过还是那句话,也就是在初始化的时候有这个过程而已,之后浏览器会根据特定的函数来执行。测试页面

(全文…)

Javascript的Lazy Definition Pattern

Lazy Definition Pattern是这样的一个模式:根据浏览器之间的解析javascript的差异性,使得创建封装的对象或者是函数的时候都需要使用浏览器的嗅探技术来做判断,而且对象或者方法每调用一次就需要去嗅探,这是一个非常不好的额外功。而解决这个瓶颈的方法之一就是Lazy Definition了。它会在浏览器第一次执行该对象或者函数的时候就记住这次的操作,以至于下面的重复调用不会再去执行浏览器的嗅探。让我们先从一个简单的addListener封装函数来一步步分析。

我们通常封装浏览器的添加事件函数的方式是使用下面的方式:

var addListener=function(el, type, handle) {
   if (el.addEventListener) {
      el.addEventListener(type, handle, false);
   }else if (el.attachEvent) {
      el.attachEvent('on' + type, handle);
   } else {
      el['on' + type] = handle;
   }
}

上面是一种使用很普遍的封装方式,它的问题之一就是口头说的,每次addListener函数调用的时候,都需要去执行if判断,以至于使用正确的方式,讨厌的if的执行次数跟调用次数相同了,不爽!

为了解决上面讨厌的if的反复执行,我们使用Lazy Definition来改造addListener方法:

var addListener = function(el, type, handle){
    if (el.addEventListener) {
        //重新定义addListener的方法体,使得下次调用addListener方法的时候就直接调用这个方法体,下同
        addListener = function(el, type, handle){
            el.addEventListener(type, handle, false);
        }
    } else
        if (el.attachEvent) {
            addListener = function(el, type, handle){
                el.attachEvent("on" + type, function(e){
                    handle.call(el, e);
                });
            }
        } else {
            addListener = function(el, type, handle){
                el["on" + type] = handle;
            }
        }
    //第一次执行addListener方法
    addListener(el, type, handle);
}

上面的addListener定义方式是YAHOO提出的一种Lazy Definition方式,它还有一种变体,我把它改成如下的方式(有点类似于分支技术):

var addListener = function(el, type, handle){
	//在这里也是重新定义addListener方法
    addListener = (function(){
        if (el.addEventListener) {
            return function(el, type, handle){
                el.addEventListener(type, handle, false);
            };
        } else if (el.attachEvent) {
                return function(el, type, handle){
                    el.attachEvent("on" + type, function(e){
                        handle.call(el, e);
                    });
                };
            } else {
                return function(el, type, handle){
                    el["on" + type] = handle;
                };
            }
    })();
    addListener(el, type, handle);
}

现在Lazy Definition的原理已经很清晰了:就是在函数内部重新自定义自己的方法体来实现。【测试页面】。下面再用一个简单的函数来表述:

var foo=function(){
    var t=new Date();
    foo=function(){
         return t;
    }
    return foo(); //用于第一次调用的时候执行
}

更多资料可以阅读:《Lazy Function Definition Pattern