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

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

  分析别人的源代码,除了可以了解程序功能是如何实现之外,还可以学到一些比较先进的编程方式和思想,进而提高自己的水平。本着这一想法,我将对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 );
}
adpics.aspx?source=kbh1983&sourcesuninfo
目录
相关文章
|
5月前
|
JavaScript 前端开发 API
在Node.js上使用dojo库进行面向对象web应用开发
请注意,虽然这个例子在Node.js环境中使用了Dojo,但Dojo的许多功能(例如DOM操作和AJAX请求)在Node.js环境中可能无法正常工作。因此,如果你打算在Node.js环境中使用Dojo,你可能需要查找一些适用于服务器端JavaScript的替代方案。
60 0
|
XML JSON 前端开发
Bpmn.js 进阶指南之原理分析与模块改造(下)
Bpmn.js 进阶指南之原理分析与模块改造
1778 1
|
XML 缓存 JSON
Bpmn.js 进阶指南之原理分析与模块改造(上)
Bpmn.js 进阶指南之原理分析与模块改造
2338 1
|
前端开发 索引
【React工作记录七十九】React+hook+ts+ant design封装一个具有动态表格得页面
【React工作记录七十九】React+hook+ts+ant design封装一个具有动态表格得页面
142 0
|
前端开发
|
缓存 JavaScript 前端开发
手写简易版flexible.js以及源码分析
我们的移动端布局通常会有rem结合媒体查询的实现,但是,淘宝有这样的一个flexible.js框架,根据不同的width给网页中html根节点设置不同的font-size,大大提高了我们的开发效率,今天阿牛便带你手写一个简易版flexible.js并解读,了解他的大致原理。
458 0
手写简易版flexible.js以及源码分析
SAP UI5框架Component.js里extend函数的实现原理
SAP UI5框架Component.js里extend函数的实现原理
116 0
SAP UI5框架Component.js里extend函数的实现原理
SAP Fiori element框架template的框架加载逻辑
Created by Wang, Jerry, last modified on Dec 13, 2016
124 0
SAP Fiori element框架template的框架加载逻辑
SAP UI5框架 component.js的加载原理
Created by Wang, Jerry, last modified on Dec 16, 2014
SAP UI5框架 component.js的加载原理
|
移动开发 JavaScript 前端开发
Weex 框架中 JS Framework 的结构
Weex 具有移动端跨平台的特性,JS Framework 是其中比较关键的一层。首先来看一下 JS Framework 在 Weex 中的位置: ![Weex 整体结构](http://gtms02.alicdn.com/tps/i2/TB1ootBMpXXXXXrXXXXwi60UVXX-596-397.png) 从图中可以看出 Weex 整体的工作流程。首先开发者可以声明式的定义组件
10651 0