第二章中 作者给了几个简单的断言例子,思路与方向是极不错的,创造JQ的大神,思想高度绝对无法让我质疑的,但是代码的功底细节,实在是让人不敢恭维。

第一例:


function assert(value, desc) {
    var li = document.createElement('li');
    li.className = value ? 'pass' : 'fail';
    li.appendChild(document.createTextNode(desc));
    //为何不直接使用textContent进行文本赋值呢?相比下,性能会更好!
    document.getElementById('result').appendChild(li);
    //每次执行断言 都要重新动态查找一次result节点?
}

assert(true, '这是真币!');
assert(false, '这是假币@');

上述书中案例,我从鸡蛋里挑骨头,选了两处不妥之处,一个是反复查找节点无缓存,另一个是文本节点创造的低效率。

改造代码:

var assert = (function () {
    //通过闭包 缓存断言的根ul节点
    var results = document.getElementById('result');
    return function (value, desc) {
        var li = document.createElement('li');
        li.className = value ? 'pass' : 'fail';
        //使用textContent属性插入文本节点 提高效率
        li.textContent = desc;
        results.appendChild(li);
    };
})();

上面的代码改善了书里的小遗漏,仍然不够完美,因为初始的惰性加载,会有额外的性能损耗,下面再提供两种极改善方案。

function getAssert() {
    //取消惰性加载
    var results = document.getElementById('result');
    return function (value, desc) {
        var li = document.createElement('li');
        li.className = value ? 'pass' : 'fail';
        li.textContent = desc;
        results.appendChild(li);
    };
};
//需要初始拿到返回方法
var assert = getAssert();

assert(true, '这是真币!');
assert(false, '这是假币@');

上面这一则,取消了惰性加载,但是需要手动获取返回的方法。

下面使用重载:

var assert = function(value,desc)  {
    //保留作用域 缓存私有变量results
    var results = document.getElementById('result');
    //重赋值
    assert =  function (value, desc) {
        var li = document.createElement('li');
        li.className = value ? 'pass' : 'fail';
        li.textContent = desc;
        results.appendChild(li);
    };
    //第一次调用 手动调用
    assert(value,desc);
};


assert(true, '这是真币!');
assert(false, '这是假币@');

世界清静了,代码终于看似完美了。但实际的需求里,可能我们要将方法封闭起来,让同事或者用户使用,那么results这个id,就有了相当大的局限性了,fail与pass的类名也不够灵活。这个场景下,我们更应该使用再往上一个的方式,可以给与我们更大的diy空间。

第二例

(function () {
    
    var results;
    
    this.assert = function (value, desc) {
        var li = document.createElement('li');
        li.className = value ? 'pass' : 'fail';
        results.appendChild(li);
        if (value) {
            li.parentNode.parentNode.className = 'fail';
        }
        return li;
    };
    
    this.test = function (name, fn) {
        results = document.getElementById('results');
        results = assert(true, name).appendChild(
            document.createElement('ul');
    )
        fn();
    };
    
});

这段代码是用来做将断言测试分组的,代码多了些,问题自然也更多了些。

首先作者使用了自执行方法封闭了作用域,使用this来指向全局对象,进而产生全局可访问的属性。

但这段代码是有着执行缺陷的,assert方法可以在test方法外调用,那么此时results是根级ul,还是分组Ul呢?而且动态查找节点的问题依旧没有改动。

代码改善:

var global = (function () {
    //严格模式下全局的this 无法访问 在此做一个防御措施
    return this ? this : window;
})();

(function (global) {
        //缓存根root节点
    var rootResults = document.querySelector('.test-root'),
        results;
    // 将assert私有化 外部不得访问
    function assert(value, desc) {
        var domLi = document.createElement('li');
        domLi.className = value ? 'pass' : 'fail';
        domLi.textContent = desc;
        results.appendChild(domLi);
        if (!value) {
            domLi.parentNode.parentNode.className = 'fail';
        }
        return domLi;
    };
    
    global.test = function (name, fn) {
        results = rootResults;
        results = assert(true, name).appendChild(
            document.createElement('ul')
        );
        //回调函数可以从参数里调用assert
        fn(assert);
    }
    
})(global);

重复了缓存DOM节点的操作,为this的指向做出回退机制,私有化assert方法,将assert方法入参到test方法的回调方法中,算是勉强完美了。

没想到,久负盛名,豆瓣评分8+的大作,JQ作者的光环,代码风格居然是如此的不谨慎。暂待我往下阅读,期望能够有打脸回馈。


Ives
29 声望1 粉丝

东北大汉一枚,粗通一些切图功夫。