惰性载入技巧

当我们的程序中有很多 if 语句的时候,某些情况下会造成性能的浪费。因此引入了惰性载入的概念,它指函数执行的分支仅会发生一次。比如:

因为不同厂商的浏览器相互之间存在一些行为上的差异,很多 js 代码包含了大量的if语句,将执行引导到正确的分支代码中去,比如下面的例子。

function createXHR() {
    if (typeof XMLHttpRequest != 'undefined') {
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != 'undefined') {
        if (typeof arguments.callee.activeXString != 'string') {
            var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
            var i, len;
            for (i = 0, len = versions.length; i < len; i++) {
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                } catch (e) {
                    // skip
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error('No XHR object available.');
    }
}

我们可以发现,在浏览器每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,但是很明显当第一次检查之后,我们就应该知道浏览器是否支持我们所需要的能力,因此除第一次之外的检查都是多余的。即使只有一个if语句也肯定要比没有if语句慢,所以if语句不必每次都执行,那么代码可以运行的更快一些,惰性载入就是用来解决这种问题的技巧。

实现原理

函数重写:由于一个函数可以返回另一个函数,因此可以在函数内部用新的函数来覆盖旧的函数。

function sayHi() {
    console.info('Hi');
    sayHi = function() {
        console.info('Hello');
    }
}

我们第一次调用sayHi()函数时,控制台会打印出Hi,全局变量sayHi被重新定义,被赋予了新的函数,从第二次开始之后的调用都会打印出Hello。惰性载入函数的本质就是函数重写,惰性载入的意思就是函数执行的分支只会发生一次。

两种实现方式

(1)在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。

function createXHR() {
    if (typeof XMLHttpRequest != 'undefined') {
        createXHR = function() {
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject != 'undefined') {
        createXHR = function() {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
                var i, len;
                for (i = 0, len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        createXHR = function() {
            throw new Error('No XHR object available.');
        }
    }
    return createXHR()
}

在这个惰性载入的 createXHR() 中,if 语句的每一个分支都会为 createXHR 变量入职,有效覆盖了原有的函数。最后一步便是调用新赋的函数。下一次调用 createXHR 的时候,就会直接调用被分配的函数,这样就不用再次执行 if 语句了。

(2)在声明函数时就指定适当的函数。这样第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。

var createXHR = (function () {
    if (typeof XMLHttpRequest != 'undefined') {
        return function() {
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject != 'undefined') {
        return function() {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
                var i, len;
                for (i = 0, len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function() {
            throw new Error('No XHR object available.');
        }
    }
})()

这个例子的技巧在于创建了一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。