浅析jQuery源码特性的分析

jQuery的总体架构可以分为:入口模块、底层模块和功能模块。这里,我们以jquery-1.7为例进行分析。

一.jquery的总体架构

(function( window, undefined ) {
	// 构造 jQuery 对象
	var jQuery = (function() {
		var jQuery = function( selector, context ) {
			return new jQuery.fn.init( selector, context, rootjQuery );
		},
	// 一堆局部变量声明
	jQuery.fn = jQuery.prototype = {
		constructor: jQuery,
		init: function( selector, context, rootjQuery ) { ... },
		// 一堆原型属性和方法
	};
	jQuery.fn.init.prototype = jQuery.fn;
	jQuery.extend = jQuery.fn.extend = function() { ... };
	jQuery.extend({
		// 一堆静态属性和方法
		isArray:function(){..},
		each:function(){...}
	});
	jQuery.each({
...})
	return jQuery;

	// 省略其他模块的代码 ...
	window.jQuery = window.$ = jQuery;
 })( window );

参数window

匿名函数传了两个参数进来,一个是window,一个是undefined。我们知道,在js中变量是有作用域链的,这两个变量的传入就会变成匿名函数的局部变量,访问起来的时候速度会更快。通过传入window对象可以使window对象作为局部变量使用,那么,函数的参数也都变成了局部变量,当在jquery中访问window对象时,就不需要将作用域链退回到顶层作用域,从而可以更快的访问window对象。

参数undefined

js在查找变量的时候,js引擎首先会在函数自身的作用域中查找这个变量,如果没有的话就继续往上找,找到了就返回该变量,找不到就返回undefinedundefinedwindow对象的一个属性,通过传入undefined参数,但又不进行赋值,可以缩短查找undefined时的作用域链。在 自调用匿名函数 的作用域内,确保undefined是真的未定义。因为undefined能够被重写,赋予新的值。

 jquery.fn是啥?

 jQuery.fn = jQuery.prototype = {
              constructor: jQuery,
              init: function( selector, context, rootjQuery ) { ... },
                 // 一堆原型属性和方法
        };

通过分析以上代码,我们发现jQuery.fn即是jQuery.prototype,这样写的好处就是更加简短吧。之后,我们又看到jquery为了简洁,干脆使用一个$符号来代替jquery使用,因此,在我们使用jquery框架的使用经常都会用到$(), 

 

二.构造函数jQuery()

jQuery的对象并不是通过 new jQuery 创建的,而是通过 new jQuery.fn.init 创建的

var jQuery = function( selector, context ) {
 
       return new jQuery.fn.init( selector, context, rootjQuery );
 
}

这里定义了一个变量jQuery,他的值是jQuery构造函数,返回并赋值给jQuery变量

jQuery.fn.init

jQuery.fn是构造函数jQuery()的原型对象,jQuery.fn.init()是jQuery原型方法,也可以称作构造函数。负责解析参数selector和context的类型并执行相应的查找。

参数context:可以不传入,或者传入jQuery对象,DOM元素,普通js对象之一
参数rootjQuery:包含了document对象的jQuery对象,用于document.getElementById()查找失败等情况。

jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
jQuery(selector [,context])

默认情况下,对匹配元素的查找从根元素document 对象开始,即查找范围是整个文档树,不过也可以传入第二个参数context来限定它的查找范围。例如:

$('div.foo').click(function () {
            $('span').addClass('bar');//限定查找范围,即上面的context
   });

define

define函数,是AMD规范(即异步模块加载机制:描述了模块的定义,依赖关系,引用关系以及加载机制)里面的简有一个API;
定义一个模块,每个单独的js文件就是一个模块.

比如:

1.一种在myModule.js里面定义:
define(function(){

var myModule;


myModule.function1 = function(){

}

return myModule;

})

使用的是myModule这个模块
var myModule = require('myModule');
myModule.function1();

2.第二种

child.js里面
define({
  provinces: [
     {
      name: '上海',
      areas: ['浦东新区', '徐汇区']
    },
  {
      name: '江苏',
      cities: ['南京', '南通']
      //.....
  }    
    ];
});

那么如果某个模块需要这个数据,只需要: 
define(['china', function(china){
  //在这里使用中国省市数据
 });
 

$.extend()和$.fn.extend()

方法jQuery.extend(object)jQuery.fn.extend(object)用于合并两个或多个对象到第一个对象。相关源代码如下(部分):

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,//定义的一组局部变量
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

相关变量含义如下:

变量options:指向某个源对象
变量name:表示目标对象的某个属性名
变量src:表示目标对象的某个属性的原始值
变量copy:表示某个源对象的某个属性的值
变量copyIsArray:指示变量copy是否是数组
变量clone:表示深度复制时原始值的修正值
变量target:指向目标对象
变量i:表示源对象的起始下标
变量length:表示参数的个数,用于修正变量
变量deep:指示是否执行深度复制,默认为false

jQuery.extend(object); 为jQuery类添加添加类方法,可以理解为添加静态方法(扩展静态方法)。如:

$.extend({ 
  add:function(a,b){
        return a+b;
    } 
}); 


便为 jQuery 添加一个为add 的 “静态方法”,之后便可以在引入 jQuery 的地方,使用这个方法了, 就是将add方法合并到jquery的全局对象中。

1)jQuery.extend(object) 为扩展 jQuery 类本身,为类添加新的静态方法;

2)jQuery.fn.extend(object) 给 jQuery 对象添加实例方法,也就是通过这个 extend 添加的新方法,实例化的 jQuery 对象都能使用,因为它是挂载在 jQuery.fn 上的方法(上文有提到,jQuery.fn = jQuery.prototype )。 

它们的官方解释是:

1)jQuery.extend(): 把两个或者更多的对象合并到第一个当中,

2)jQuery.fn.extend():把对象挂载到 jQuery 的 prototype 属性,来扩展一个新的 jQuery 实例方法。

也就是说,使用 jQuery.extend() 拓展的静态方法,我们可以直接使用 $.xxx 进行调用(xxx是拓展的方法名),

而使用 jQuery.fn.extend() 拓展的实例方法,需要使用 $().xxx 调用。

需要注意的是这一句 jQuery.extend = jQuery.fn.extend = function() {} ,也就是 jQuery.extend 的实现和 jQuery.fn.extend 的实现共用了同一个方法,但是为什么能够实现不同的功能了,这就要归功于 Javascript 强大(怪异?)的 this 了。

1)在 jQuery.extend() 中,this 的指向是 jQuery 对象(或者说是 jQuery 类),所以这里扩展在 jQuery 上;

2)在 jQuery.fn.extend() 中,this 的指向是 fn 对象,前面有提到 jQuery.fn = jQuery.prototype ,也就是这里增加的是原型方法,也就是对象方法。

jQuery.fn.extend(object),查看一段官网的代码演示如下:

<label><input type="checkbox" name="foo"> Foo</label>
<label><input type="checkbox" name="bar"> Bar</label>
 
<script>
    jQuery.fn.extend({
        check: function() {
            return this.each(function() {
                this.checked = true;
            });
        },
        uncheck: function() {
            return this.each(function() {
                this.checked = false;
            });
        }
    });
或是
 $.fn.extend({
       check: function() {
            return this.each(function() {
                this.checked = true;
            });
        },
        uncheck: function() {
            return this.each(function() {
                this.checked = false;
            });
        }
 });

    // 使用
    $( "input[type='checkbox']" ).check();
</script>

三.Sizzle选择器引擎介绍

可以说,jQuery是为操作DOM而诞生的,jQuery之所以如此强大,得益于CSS选择器引擎 Sizzle

什么是Sizzle引擎?
    我们经常使用JQuery的选择器查询元素,查询的选择器有简单也有复杂:
    简单点:“div”、“.navi”、“div.navi”。

    复杂点:"div input[type='checkbox']"、"div.navi + .section p"。

Query实现查询时也是优先使用DOM标准查询函数,例如:

    document.getElementById()

    document.getElementsByTagName()

    document.getElementsByClassName()

    document.getElementsByName()

    高级浏览器还实现了:

    querySelector()

    querySelectorAll()

    由于浏览器版本差异导致的兼容问题,上面的函数并不是所有浏览器都支持。但JQuery得解决这些问题,所以就引入了Sizzle引擎。JQuery在筛选元素时优先使用浏览器自带的高级查询函数,因为查询效率高。其次才选择使用Sizzle引擎筛选元素

Sizzle原理
    1、浏览器原生支持的方法,效率肯定比Sizzle自己js写的方法要高,优先使用也能保证Sizzle更高的工作效率,在不支持querySelectorAll方法的情况下,Sizzle也是优先判断是不是可以直接使用getElementById、getElementsByTag、getElementsByClassName等方法解决问题。
    2、相对复杂的情况,Sizzle总是选择先尽可能利用原生方法来查询选择来缩小待选范围,然后才会利用前面介绍的“编译原理”来对待选范围的元素逐个匹配筛选。进入到“编译”这个环节的工作流程有些复杂,效率相比前面的方法肯定会稍低一些,但Sizzle在努力尽量少用这些方法,同时也努力让给这些方法处理的结果集尽量小和简单,以便获得更高的效率。
    3、即便进入到这个“编译”的流程,Sizzle还做了我们前面为了优先解释清楚流程而暂时忽略、没有介绍的缓存机制。Sizzle.compile是“编译”入口,也就是它会调用第三个核心方法superMatcher,compile方法将根据selector生成的匹配函数缓存起来了。还不止如此,tokenize方法,它其实也将根据selector做的分词结果缓存起来了。也就是说,当我们执行过一次Sizzle (selector)方法以后,下次再直接调用Sizzle (selector)方法,它内部最耗性能的“编译”过程不会再耗太多性能了,直接取之前缓存的方法就可以了。我在想所谓“编译”的最大好处之一可能也就是便于缓存,所谓“编译”在这里可能也就可以理解成是生成预处理的函数存储起来备用。

如何打造高效的选择器?

毋庸置疑 id是最快的, 因为节点较少 。

通过对sizzle分析得知都选择器是从右向左匹配, ("#demo li:nth-child(1)") 这句将先匹配所有 li元素,在匹配#demo("#demo").find("li:nth-child(1)") 而这里则先匹配#demo,再从中找匹配li,匹配范围缩短,效率明显提升

四.deferred对象--延迟对象

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。

回顾一下jQueryajax操作的传统写法:

$.ajax({
   url: "test.html",
   success: function(){
     alert("哈哈,成功了!");
   },
   error:function(){
     alert("出错啦!");
   }
 
 });

在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,是通过调用jQuery.Deferred()方法创建一个可链式调用的工具对象

现在,新的写法是这样的:

$.ajax("test.html")
 
  .done(function(){ alert("哈哈,成功了!"); })
 
  .fail(function(){ alert("出错啦!"); });

为多个操作指定回调函数
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的方法$.when():

$.when($.ajax("test1.html"), $.ajax("test2.html"))
 
 .done(function(){ alert("哈哈,成功了!"); })
 
 .fail(function(){ alert("出错啦!"); });

这段代码的意思是,先执行两个操作$.ajax("test1.html")$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。 

jQuery.Deferred( func ) 的实现原理
内部维护了三个回调函数列表:成功回调函数列表、失败回调函数列表、消息回调函数列表,其他方法则围绕这三个列表进行操作和检测。
jQuery.Deferred( func ) 的源码结构:

 jQuery.extend({
 
    Deferred: function( func ) {
            // 成功回调函数列表
        var doneList = jQuery.Callbacks( "once memory" ),
            // 失败回调函数列表
            failList = jQuery.Callbacks( "once memory" ),
            // 消息回调函数列表
            progressList = jQuery.Callbacks( "memory" ),
            // 初始状态
            state = "pending",
            // 异步队列的只读副本
            promise = {
                // done, fail, progress
                // state, isResolved, isRejected
                // then, always
                // pipe
                // promise           
            },
            // 异步队列
            deferred = promise.promise({}),
            key;
        // 添加触发成功、失败、消息回调函列表的方法
        for ( key in lists ) {
            deferred[ key ] = lists[ key ].fire;
            deferred[ key + "With" ] = lists[ key ].fireWith;
        }
        // 添加设置状态的回调函数
        deferred.done( function() {
            state = "resolved";
        }, failList.disable, progressList.lock )
        .fail( function() {
            state = "rejected";
        }, doneList.disable, progressList.lock );
        // 如果传入函数参数 func,则执行。
        if ( func ) {
            func.call( deferred, deferred );
        }
 
        // 返回异步队列 deferred
        return deferred;
    },
    when: function( firstParam ) {
       // ...
        return promise;
    }
}
         
 
jQuery.when( deferreds )
 
提供了基于一个或多个对象的状态来执行回调函数的功能,通常是基于具有异步事件的异步队列。
jQuery.when( deferreds ) 的用法
 
如果传入多个异步队列对象,方法 jQuery.when() 返回一个新的主异步队列对象的只读副本,只读副本将跟踪所传入的异步队列的最终状态。
 
一旦所有异步队列都变为成功状态,“主“异步队列的成功回调函数被调用;
 
如果其中一个异步队列变为失败状态,主异步队列的失败回调函数被调用。
 
 
/*
请求 '/when.do?method=when1' 返回 {"when":1}
请求 '/when.do?method=when2' 返回 {"when":2}
请求 '/when.do?method=when3' 返回 {"when":3}
*/
var whenDone = function(){ console.log( 'done', arguments ); },
    whenFail = function(){ console.log( 'fail', arguments ); };
$.when(
    $.ajax( '/when.do?method=when1', { dataType: "json" } ),
    $.ajax( '/when.do?method=when2', { dataType: "json" } ),
    $.ajax( '/when.do?method=when3', { dataType: "json" } )
).done( whenDone ).fail( whenFail );

异步队列 Deferred

解耦异步任务和回调函数

为 ajax 模块、队列模块、ready 事件提供基础功能。

补充:

Promises是什么

由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:

1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。

Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。比如,假定ajax操作返回一个Promise对象

查看: jQuery之Deferred对象详解

参考:

浅析jQuery整体框架与实现

【深入浅出jQuery】源码浅析--整体架构

Sizzle选择器引擎介绍

JQuery Sizzle引擎源代码分析

jQuery 2.0.3 源码分析Sizzle引擎 - 高效查询

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值