QUnit系列 -- 5.QUnit源码分析之<大致结构>

  分析别人的源代码,除了可以了解程序功能是如何实现之外,还可以学到一些比较先进的编程方式和思想,进而提高自己的水平。本着这一想法,我将对QUnit的源代码加以解读,也希望对大家js水平的提高有个帮助作用。

  好的js框架在语言上总是很干练的,里面也使用了很多比较先进的编程技巧,这就要求读者必须要有比较扎实的js基础知识。在这里我重点推荐汤姆大叔的译作《深入理解JavaScript系列》。文章很多共有50多篇,前20多篇对js基础知识作了很深入的讲解(市面上没看到比他更深入的书籍和博文,也可能是我看的资料少的缘故),后20多篇讲的是js的设计模式,我重点推荐前20多篇。相信通过对该系列重复的阅读(一次读不懂不怕,多读几次总会有感觉的),一定会让你的js水平有个质的提升。

  现在我们言归正传,开始解读QUnit源代码。我们首先来看QUnit源码的大致结构:

(function( window ) {
    var QUnit,
        config,
        ...;

    function Test( settings ) {
    }
    Test.count = 0;
    Test.prototype = {
    };

    QUnit = {
        module: function( name, testEnvironment ) {
        },
        asyncTest: function( testName, expected, callback ) {
        },
        test: function( testName, expected, callback, async ) {
        },
        expect: function( asserts ) {
        },
        start: function( count ) {
        },
        stop: function( count ) {
        }
    };

    QUnit.assert = {
        ok: function( result, msg ) {
        },
        equal: function( actual, expected, message ) {
        },
        notEqual: function( actual, expected, message ) {
        },
        deepEqual: function( actual, expected, message ) {
        },
        notDeepEqual: function( actual, expected, message ) {
        },
        strictEqual: function( actual, expected, message ) {
        },
        notStrictEqual: function( actual, expected, message ) {
        },
        "throws": function( block, expected, message ) {
        }
    };

    extend( QUnit, QUnit.assert );

    ...

    if ( typeof exports === "undefined" ) {
        extend( window, QUnit );

        window.QUnit = QUnit;
    }

    ...

    QUnit.load = function() {
    };

    addEvent( window, "load", QUnit.load );

    function addEvent( elem, type, fn ) {
        if ( elem.addEventListener ) {
            elem.addEventListener( type, fn, false );
        } else if ( elem.attachEvent ) {
            elem.attachEvent( "on" + type, fn );
        } else {
            fn();
        }
    }

    function extend( a, b ) {
        for ( var prop in b ) {
            if ( b[ prop ] === undefined ) {
                delete a[ prop ];

                // 因为设置window.constructor避免IE8发生 "Member not found" 的错误
            } else if ( prop !== "constructor" || a !== window ) {
                a[ prop ] = b[ prop ];
            }
        }

        return a;
    }

    ...

    function id( name ) {
        return !!( typeof document !== "undefined" && document && document.getElementById ) &&
            document.getElementById( name );
    }

    ...

// 获取全局对象
}( (function() {return this;}.call()) ));

  

  框架整体式一个即时匿名函数,也叫立即执行匿名函数。

(function(){
...
}(args))

他的特点是代码在解析之后会自动执行,本身又是一个闭包环境,内部变量不会对全局变量造成污染。这种方式是大多数第三方类库使用的开发方式,例如jquery,值得大家在自己的项目中实践。此外我们还注意到即使匿名函数传递的参数:

(function() {return this;}.call()) 

call方法执行时候的上下文是null,this会返回global,也就是返回window对象。具体的原因可以通过阅读博文《深入理解JavaScript系列(13):This? Yes,this!》找到答案。

  

  代码之后定义了一些内部使用的变量。接下来定义的是Test对象,会在QUnit.test()中使用,稍后的文章我会加以介绍。

  下来的内容就是重头戏了,他定义了QUnit常用的api方法和相关的断言方法。对于断言方法你也许会感到奇怪,因为他是定义在QUnit.assert中的。而我们在单元测试中使用的时候,前面并没有添加QUnit前缀,这是怎么回事呢。原因是源码中使用了扩展方法,把QUnit.assert中的方法扩展到了QUnit中。

extend( QUnit, QUnit.assert );

  扩展方法位于稍后的位置,我们来看他是如何实现的。

function extend( a, b ) {
  for ( var prop in b ) {
    if ( b[ prop ] === undefined ) {
      delete a[ prop ];
    // 因为设置window.constructor避免IE8发生 "Member not found" 的错误
    } else if ( prop !== "constructor" || a !== window ) {
      a[ prop ] = b[ prop ];
    }
  }

  return a;
}

  可以说这个方法实现的中规中矩,这是一种很通用的实现扩展或者继承的实现方式。就是简单的把b中存在的属性复制给了a,函数最后然后返回a对象。extend( QUnit, QUnit.assert ) 实现的功能,相信大家一定已经清楚了。源码中很多实现扩展的地方都使用了这个方法。

  

  接下来QUnit使用下面的语句把本身暴露给了window对象,这样我们才能在单元测试中访问到api相关的方法。例如在单元测试中,我们可以直接使用module和test方法,前面不用添加QUnit前缀,就是下面的代码起的作用。QUnit把这些属性复制给了window。你直接使用的module和test其实就是window.module 和 window.test。

if ( typeof exports === "undefined" ) {
  extend( window, QUnit );

  window.QUnit = QUnit;
}

  

  源码中定义了事件注册的方法:addEvent()。代码中使用addEvent( window, "load", QUnit.load )实现对window load事件的绑定,当页面加载完毕之后执行QUnit对象的加载操作。

QUnit.load = function() {
};

addEvent( window, "load", QUnit.load );

 

  最后,我们通过对id方法的讲解介绍点js的一些小技巧。这里有两个值得学习的技巧,第一个是!!(双感叹号)的语法,他相当于一个三元运算符,返回的结果是Boolean值,可以参照我的博文:《js双感叹号判断》。第二个是多 && 判断,他会判断多个判断条件是否都成立,在此前提下返回最后一个判断对象,像下面的代码会返回document.getElementById。

 function id( name ) {
   return !!( typeof document !== "undefined" && document && document.getElementById ) &&
            document.getElementById( name );
}

你可能感兴趣的:(源码分析)