前端工程师手册

理解架构

Write less, do more.

代码结构

(function( global, factory ) {

    if ( typeof module === "object" && typeof module.exports === "object" ) {
        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
var jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    };
    jQuery.fn = jQuery.prototype = {};
    // 核心方法
    // 回调系统
    // 异步队列
    // 数据缓存
    // 队列操作
    // 选择器引
    // 属性操作
    // 节点遍历
    // 文档处理
    // 样式操作
    // 属性操作
    // 事件体系
    // AJAX交互
    // 动画引擎
    return jQuery;
}));

模块依赖

jQuery一共13个模块,从2.1版开始jQuery支持通过AMD模块划分,jQuery在最开始发布的1.0版本是很简单的,只有CSS选择符、事件处理和AJAX交互3大块。

jQuery也可理解分为五大块,选择器、DOM操作、事件、AJAX与动画,那么为什么有13个模块?因为jQuery的设计中最喜欢的做的一件事,就是抽出共同的特性使之“模块化”,当然也是更贴近S.O.L.I.D五大原则的“单一职责SRP”了,遵守单一职责的好处是可以让我们很容易地来维护这个对象,比如,当一个对象封装了很多职责的时候,一旦一个职责需要修改,势必会影响该对象的其它职责代码。通过解耦可以让每个职责更加有弹性地变化。

模块列表如图: jquery-modules

立即调用表达式

任何库与框架设计的第一个要点就是解决命名空间与变量污染的问题。jQuery就是利用了JavaScript函数作用域的特性,采用立即调用表达式包裹了自身的方法来解决这个问题。

比如:

(function(window, undefined) {
    var jQuery = function() {}
    // ...
    window.jQuery = window.$ = jQuery;
})(window);

从上面的代码可看出,自动初始化这个函数,让其只构建一次。这种写法的优势:

  • window和undefined都是为了减少变量查找所经过的scope作用域。当window通过传递给闭包内部之后,在闭包内部使用它的时候,可以把它当成一个局部变量,显然比原先在window scope下查找的时候要快一些。
  • undefined也是同样的道理,其实这个undefined并不是JavaScript数据类型的undefined,而是一个普普通通的变量名。只是因为没给它传递值,它的值就是undefined,undefined并不是JavaScript的保留字。

Q:为什么传递undefined?

Javascript 中的 undefined 并不是作为关键字,因此可以允许用户对其赋值。

大部分浏览器都是不能被修改的,但是IE8存在这个问题, 比如:

var undefined = 'xxx'
;(function(window) {
  alert(undefined);//IE8 'xxx'
})(window)

jQuery的类数组对象结构

很多人迷惑的jQuery为什么能像数组一样操作,通过对象get方法或者直接通过下标0索引就能转成DOM对象。

比如jQuery的入口都是统一的$, 通过传递参数的不同,实现了9种方法的重载:

jQuery([selector,[context]])
jQuery(element)
jQuery(elementArray)
jQuery(object)
jQuery(jQuery object)
jQuery(html,[ownerDocument])
jQuery(html,[attributes])
jQuery()
jQuery(callback)

9种用法整体来说可以分三大块:选择器、dom的处理、dom加载。运用了设计模式里面的工厂模式

所以为了更方便这些操作,让节点与实例对象通过一个桥梁给关联起来,jQuery内部就采用了一种叫“类数组对象”的方式作为存储结构,所以我们即可以像对象一样处理jQuery操作,也能像数组一样可以使用push、pop、shift、unshift、sort、each、map等类数组的方法操作jQuery对象了。

抽象的表示创建可用数组下标操作的对象:

var aQuery = function(selector) {
    //强制为对象
    if (!(this instanceof aQuery)) {
        return new aQuery(selector);
    }
    var elem = document.getElementById(/[^#].*/.exec(selector)[0]);
    this.length = 1;
    this[0] = elem;
    this.context = document;
    this.selector = selector;
    this.get = function(num) {
        return this[num];
    }
    return this;
}

jQuery的无new构建原理

函数aQuery()内部首先保证了必须是通过new操作符构建。这样就能保证当前构建的是一个带有this的实例对象,既然是对象我们可以把所有的属性与方法作为对象的key与value的方式给映射到this上,所以如上结构就可以模拟出jQuery的这样的操作了,即可通过索引取值,也可以链式方法取值,但是这样的结构是有很大的缺陷的,每次调用ajQuery方法等于是创建了一个新的实例,那么类似get方法就要在每一个实例上重新创建一遍,性能就大打折扣,所以jQuery在结构上的优化不仅仅只是我们看到的,除了实现类数组结构、方法的原型共享,而且还实现方法的静态与实例的共存,这是我们之后将会重点分析的。

jQuery中ready与load事件

jQuery中存在三种文档加载的方法:

$(document).ready(function() {
    // ...代码...
})
//document ready 简写
$(function() {
    // ...代码...
})
$(document).load(function() {
    // ...代码...
})

DOM文档加载的步骤

  • 解析HTML结构。
  • 加载外部脚本和样式表文件。
  • 解析并执行脚本代码。
  • 构造HTML DOM模型。//ready
  • 加载图片等外部文件。
  • 页面加载完毕。//load

jQuery多库共存处理

多库共存换句话说可以叫无冲突处理。jQuery给出了解决方案: noConflict函数。使用demo:

jQuery.noConflict();
// 使用 jQuery
jQuery("aaron").show();
// 使用其他库的 $()
$("aaron").style.display = ‘block’;

这个函数必须在你导入jQuery文件之后,并且在导入另一个导致冲突的库之前使用。当然也应当在其他冲突的库被使用之前,除非jQuery是最后一个导入的。

参考资料