jquery的总体架构分析及实现示例详解


Posted in Javascript onNovember 08, 2014

jQuery整体框架甚是复杂,也不易读懂,这几日一直在研究这个笨重而强大的框架。jQuery的总体架构可以分为:入口模块、底层模块和功能模块。这里,我们以jquery-1.7.1为例进行分析。

jquery的总体架构

16 (function( window, undefined ) {

         // 构造 jQuery 对象

  22     var jQuery = (function() {

  25         var jQuery = function( selector, context ) {

  27                 return new jQuery.fn.init( selector, context, rootjQuery );

  28             },

                 // 一堆局部变量声明

  97         jQuery.fn = jQuery.prototype = {

  98             constructor: jQuery,

  99             init: function( selector, context, rootjQuery ) { ... },

                 // 一堆原型属性和方法

 319         };

 322         jQuery.fn.init.prototype = jQuery.fn;

 324         jQuery.extend = jQuery.fn.extend = function() { ... };

 388         jQuery.extend({

                 // 一堆静态属性和方法

 892         });

 955         return jQuery;

 957     })();

          // 省略其他模块的代码 ...

9246     window.jQuery = window.$ = jQuery;

9266 })( window );

分析一下以上代码,我们发现jquery采取了匿名函数自执行的写法,这样做的好处就是可以有效的防止命名空间与变量污染的问题。缩写一下以上代码就是:

(function(window, undefined) {

    var jQuery = function() {}

    // ...

    window.jQuery = window.$ = jQuery;

})(window);

参数window

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

参数undefined

js在查找变量的时候,js引擎首先会在函数自身的作用域中查找这个变量,如果没有的话就继续往上找,找到了就返回该变量,找不到就返回undefined。undefined是window对象的一个属性,通过传入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构造函数,在955行(最上面的代码)返回并赋值给jQuery变量

jQuery.fn.init

jQuery.fn (上面97行)是构造函数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',this).addClass('bar');//限定查找范围,即上面的context

   });

jQuery.extend()和jQuery.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;

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

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

});

便为 jQuery 添加一个为add 的 “静态方法”,之后便可以在引入 jQuery 的地方,使用这个方法了,

$.add(3,4); //return 7
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;

            });

        }

    });

    // Use the newly created .check() method

    $( "input[type='checkbox']" ).check();

</script>

CSS选择器引擎 Sizzle

可以说,jQuery是为操作DOM而诞生的,jQuery之所以如此强大,得益于CSS选择器引擎 Sizzle,解析规则引用网上的一段实例:

selector:"div > p + div.aaron input[type="checkbox"]"

解析规则:
1 按照从右到左
2 取出最后一个token  比如[type="checkbox"]
                            {
                                matches : Array[3]
                                type    : "ATTR"
                                value   : "[type="
                                checkbox "]"
                            }
3 过滤类型 如果type是 > + ~ 空 四种关系选择器中的一种,则跳过,在继续过滤
4 直到匹配到为 ID,CLASS,TAG  中一种 , 因为这样才能通过浏览器的接口索取
5 此时seed种子合集中就有值了,这样把刷选的条件给缩的很小了
6 如果匹配的seed的合集有多个就需要进一步的过滤了,修正选择器 selector: "div > p + div.aaron [type="checkbox"]"
7 OK,跳到一下阶段的编译函数

deferred对象

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

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

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

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

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

$.ajax({
 url: "test.html",

 success: function(){


 alert("哈哈,成功了!");

 },

 error:function(){


 alert("出错啦!");

 }

 });

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

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是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;

    },

}

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 事件提供基础功能。

原型属性和方法

原型属性和方法源代码:

  97 jQuery.fn = jQuery.prototype = {

  98     constructor: jQuery,

  99     init: function( selector, context, rootjQuery ) {}

 210     selector: "",

 213     jquery: "1.7.1",

 216     length: 0,

 219     size: function() {},

 223     toArray: function() {},

 229     get: function( num ) {},

 241     pushStack: function( elems, name, selector ) {},

 270     each: function( callback, args ) {},

 274     ready: function( fn ) {}, //

 284     eq: function( i ) {},

 291     first: function() {},

 295     last: function() {},

 299     slice: function() {},

 304     map: function( callback ) {},

 310     end: function() {},

 316     push: push,

 317     sort: [].sort,

 318     splice: [].splice

 319 };

 

属性selector用于记录jQuery查找和过滤DOM元素时的选择器表达式。
属性.length表示当前jquery对象中元素的个数。
方法.size()返回当前jquery对象中元素的个数,功能上等同于属性length,但应该优先使用length,因为他没有函数调用开销。

.size()源码如下:

size():function(){

    return this.length;

}

方法.toArray()将当前jQuery对象转换为真正的数组,转换后的数组包含了所有元素,其源码如下:

toArray: function() {

        return slice.call( this );

    },

方法.get(index)返回当前jQuery对象中指定位置的元素,或包含了全部元素的数组。其源
码如下:

    get: function( num ) {

        return num == null ?
            // Return a 'clean' array

            this.toArray() :
            // Return just the object

            ( num < 0 ? this[ this.length + num ] : this[ num ] );

    },

如果没有传入参数,则调用.toArray()返回了包含有锁元素的数组;如果指定了参数index,则返回一个单独的元素,index从0开始计数,并且支持负数。

首先会判断num是否小于0,如果小于0,则用length+num重新计算下标,然后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果大于等于0,直接返回指定位置的元素。

eg()和get()使用详解:jquery常用方法及使用示例汇总

方法.each()用于遍历当前jQuery对象,并在每个元素上执行回调函数。方法.each()内部通过简单的调用静态方法jQuery.each()实现:

each: function( callback, args ) {

        return jQuery.each( this, callback, args );

    },

回调函数是在当前元素为上下文的语境中触发的,即关键字this总是指向当前元素,在回调函数中return false 可以终止遍历。

方法.map()遍历当前jQuery对象,在每个元素上执行回调函数,并将回调函数的返回值放入一个新jQuery对象中。该方法常用于获取或设置DOM元素集合的值。

map: function( callback ) {

        return this.pushStack( jQuery.map(this, function( elem, i ) {

            return callback.call( elem, i, elem );

        }));

    },

原型方法.pushStack()创建一个新的空jQuery对象,然后把DOM元素集合放入这个jQuery对象中,并保留对当前jQuery对象的引用。

原型方法.pushStack()是核心方法之一,它为以下方法提供支持:

jQuery对象遍历:.eq()、.first()、.last()、.slice()、.map()。

DOM查找、过滤:.find()、.not()、.filter()、.closest()、.add()、.andSelf()。

DOM遍历:.parent()、.parents()、.parentsUntil()、.next()、.prev()、.nextAll()、.prevAll()、.nextUnit()、.prevUnit()、.siblings()、.children()、.contents()。

DOM插入:jQuery.before()、jQuery.after()、jQuery.replaceWith()、.append()、.prepent()、.before()、.after()、.replaceWith()。
定义方法.push( elems, name, selector ),它接受3个参数:

参数elems:将放入新jQuery对象的元素数组(或类数组对象)。

参数name:产生元素数组elems的jQuery方法名。

参数selector:传给jQuery方法的参数,用于修正原型属性.selector。
方法.end()结束当前链条中最近的筛选操作,并将匹配元素还原为之前的状态

end: function() {

        return this.prevObject || this.constructor(null);

    },

返回前一个jQuery对象,如果属性prevObect不存在,则构建一个空的jQuery对象返回。方法.pushStack()用于入栈,方法.end()用于出栈

静态属性和方法

相关源码如下:

388 jQuery.extend({

 389     noConflict: function( deep ) {},

 402     isReady: false,

 406     readyWait: 1,

 409     holdReady: function( hold ) {},

 418     ready: function( wait ) {},

 444     bindReady: function() {},

 492     isFunction: function( obj ) {},

 496     isArray: Array.isArray || function( obj ) {},

 501     isWindow: function( obj ) {},

 505     isNumeric: function( obj ) {},

 509     type: function( obj ) {},

 515     isPlainObject: function( obj ) {},

 544     isEmptyObject: function( obj ) {},

 551     error: function( msg ) {},

 555     parseJSON: function( data ) {},

 581     parseXML: function( data ) {},

 601     noop: function() {},

 606     globalEval: function( data ) {},

 619     camelCase: function( string ) {},

 623     nodeName: function( elem, name ) {},

 628     each: function( object, callback, args ) {},

 669     trim: trim ? function( text ) {} : function( text ) {},

 684     makeArray: function( array, results ) {},

 702     inArray: function( elem, array, i ) {},

 724     merge: function( first, second ) {},

 744     grep: function( elems, callback, inv ) {},

 761     map: function( elems, callback, arg ) {},

 794     guid: 1,

 798     proxy: function( fn, context ) {},

 825     access: function( elems, key, value, exec, fn, pass ) {},

 852     now: function() {},

 858     uaMatch: function( ua ) {},

 870     sub: function() {},

 891     browser: {}

 892 });

 

未完待续、、、今天就先到这里了,下次补齐。别急哈小伙伴们

Javascript 相关文章推荐
JS setCapture 区域外事件捕捉
Mar 18 Javascript
js实现日期级联效果
Jan 23 Javascript
JavaScript图片轮播代码分享
Jul 31 Javascript
ASP.NET jquery ajax传递参数的实例
Nov 02 Javascript
使用jQuery的load方法设计动态加载及解决被加载页面js失效问题
Mar 01 Javascript
详解如何在vue中使用sass
Jun 21 Javascript
在vue项目中安装使用Mint-UI的方法
Dec 27 Javascript
浅谈Vue2.0父子组件间事件派发机制
Jan 08 Javascript
js中怎么判断两个字符串相等的实例
Jan 17 Javascript
react高阶组件添加和删除props
Apr 26 Javascript
VUE项目初建和常见问题总结
Sep 12 Javascript
微信小程序scroll-view点击项自动居中效果的实现
Mar 25 Javascript
jquery常用方法及使用示例汇总
Nov 08 #Javascript
JQuery遍历json数组的3种方法
Nov 08 #Javascript
JQuery中使用.each()遍历元素学习笔记
Nov 08 #Javascript
jQuery遍历之next()、nextAll()方法使用实例
Nov 08 #Javascript
jQuery遍历对象、数组、集合实例
Nov 08 #Javascript
JavaScript中具名函数的多种调用方式总结
Nov 08 #Javascript
JavaScript中使用typeof运算符需要注意的几个坑
Nov 08 #Javascript
You might like
全国FM电台频率大全 - 20 广西省
2020/03/11 无线电
《APMServ 5.1.2》使用图解
2006/10/23 PHP
PHP中strlen()和mb_strlen()的区别浅析
2014/06/19 PHP
PHP正则表达式替换站点关键字链接后空白的解决方法
2014/09/16 PHP
php魔术方法功能与用法实例分析
2016/10/19 PHP
使用laravel和ajax实现整个页面无刷新的操作方法
2019/10/03 PHP
php pdo连接数据库操作示例
2019/11/18 PHP
根据鼠标的位置动态的控制层的位置
2009/11/24 Javascript
jQuery对象数据缓存Cache原理及jQuery.data方法区别介绍
2013/04/07 Javascript
JS实现单行文字不间断向上滚动的方法
2015/01/29 Javascript
JavaScript实现信用卡校验方法
2015/04/07 Javascript
使用js复制链接中的部分文字的方法
2015/07/30 Javascript
javascript实现五星评价代码(源码下载)
2015/08/11 Javascript
bootstrap表格分页实例讲解
2016/12/30 Javascript
详解使用vue脚手架工具搭建vue-webpack项目
2017/05/10 Javascript
利用JS对iframe父子(内外)页面进行操作的方法教程
2017/06/15 Javascript
vue实现导航栏效果(选中状态刷新不消失)
2017/12/13 Javascript
vue 设置proxyTable参数进行代理跨域
2018/04/09 Javascript
vue中使用element-ui进行表单验证的实例代码
2018/06/22 Javascript
示例vue 的keep-alive缓存功能的实现
2018/12/13 Javascript
Vue单文件组件开发实现过程详解
2020/07/30 Javascript
通过实例解析json与jsonp原理及使用方法
2020/09/27 Javascript
django1.8使用表单上传文件的实现方法
2016/11/04 Python
Python使用win32com模块实现数据库表结构自动生成word表格的方法
2018/07/17 Python
Python基于WordCloud制作词云图
2019/11/29 Python
Python自动登录QQ的实现示例
2020/08/28 Python
在印度上传处方,在线订购药品:Medlife
2019/03/28 全球购物
澳大利亚设计师服装在线:MISHA
2019/10/07 全球购物
Tessabit美国:集世界奢侈品和设计师品牌的意大利精品买手店
2020/06/29 全球购物
什么是Connection-oriented Protocol/Connectionless Protocol面向连接的协议/无连接协议
2012/09/06 面试题
奥利奥广告词
2014/03/20 职场文书
绿色环保演讲稿
2014/05/10 职场文书
火烧圆明园观后感
2015/06/03 职场文书
2015年思想品德教学工作总结
2015/07/22 职场文书
2016年小学生新年寄语
2015/08/18 职场文书
Python 绘制多因子柱状图
2022/05/11 Python