jQuery的$方法使用起来非常的多样式,接口实在太灵活了,有点违反设计模式的原则职责单一。但是用户却非常喜欢这种方式,因为不用记那么多名称,我只要记住一个$就可以实现许多功能,这个$简直就像个万能的魔术师,想要什么就变出来。其实当我们拆穿了这个魔术的表象,你会看到一个混乱的内部,jQuery内部做了太多的事了,而能够将这个混乱一一理清,并且毫无问题的运行起来,John Resig的能力不禁让人敬佩。

下面先来看看jQuery中的$方法(又叫jQuery)的几种用途或者调用方式:

1.$(document)  
2.$(‘<div>’)
3.$(‘div’)
4.$(‘#test’)
5.$(function(){})
6.$("input:radio", document.forms[0]);
7.$(‘input’, $(‘div’))
8.$()
9.$("<div>", {
         "class": "test",
         text: "Click me!",
         click: function(){ $(this).toggleClass("test"); }
      }).appendTo("body");
10$($(‘.test’))

当我们执行一个$方法时,例如:$('div'),在控制台console.log($('div'))看看出来的是什么东西:

  1. 0: div
  2. context: document
  3. length: 1
  4. prevObject: jQuery.fn.jQuery.init[1]
  5. selector: "div"
  6. __proto__: Object[0]
    1. add: function ( selector, context ) {
    2. addBack: function ( selector ) {
    3. addClass: function ( value ) {
    4. after: function () {
    5. ajaxComplete: function ( fn ){
    6. ajaxError: function ( fn ){
    7. ajaxSend: function ( fn ){
    8. ajaxStart: function ( fn ){
    9. ajaxStop: function ( fn ){
    10. ajaxSuccess: function ( fn ){
    11. andSelf: function ( selector ) {
    12. animate: function ( prop, speed, easing, callback ) {
    13. append: function () {

我们看到一个对象有0?, length, (这不是数组吗?),preObject, selector这些属性,而且这是个实例对象,其隐藏属性__proto__指向一个原型对象,那个原型对象有一堆方法,那堆方法正是jQuery手册的方法。

现在就让我们进入$的世界,看看它究竟是何方神圣

在jQuery源码中看到jQuery(即$的定义方式),返回的是一个jQuery.fn.init这个构造函数的实例,这个jQuery方法就相当于是个简单的工厂方法,rootjQuery也是一个jQuery方法返回的实例,只是其参数是document,这里作为jQuery的根对象

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

//...

rootjQuery = jQuery(document);

接下来往源码下面看:

jQuery.fn = jQuery.prototype = {
    jQuery: core_version,
    constructor: jQuery,
    // 构造函数
    init: function (selector, context, rootjQuery) {
        //…
    }
}

将jQuery的prototype对象的引用指向jQuery.fn,当两者其中一个发生改变,另一个也会随之改变。 jQuery.fn相当于是jQuery.prototype的简写。

然后init这个构造函数了,$的万能钥匙就在这个构造函数里:

// 构造函数
init: function (selector, context, rootjQuery) {
var match, elem;
// 处理 $(""), $(null), $(undefined), $(false)
// 返回的是jQuery()方法实例
if (!selector) {
return this;
} // 处理HTML字符串
if (typeof selector === 'string') {
if (selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' && selector.length >= 3) {
// 假设字符串以“<”开始且“>”结束
// 说明是HTML,略过正则检查
match = [null, selector, null];
} else {
match = rquickExpr.exec(selector);
} // 匹配HTML或者确保#id的上下文没被指定
if (match && (match[1] || !context)) {
// 处理 $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context; // 向后兼容
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
)); // 处理 $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
//
if (jQuery.isFunction(this[match])) {
this[match](context[match]);
} else {
this.attr(match, context[match]);
}
}
} return this; // 处理 $(#id)
} else {
elem = document.getElementById(match[2]); // 检查parentNode,因为Blackberry 4.6
// 返回的节点不在document中
if (elem && elem.parentNode) {
// 处理Opera返回的是name而不是id
if (elem.id !== match[2]) {
return rootjQuery.find(selector);
} // 给this实例对象添加类似数组的属性
this.length = 1;
this[0] = elem;
} // 再添加上下文,选择器属性,最后返回this,结束函数
this.context = document;
this.selector = selector;
return this;
} // 处理 $(expr, [$(...)])
} else if (!context || context.jQuery) {
// 返回jQuery.fn.find()获取的匹配元素,
// 该方法会使用jQuery.find方法(即Sizzle),
// 然后通过jQuery.fn.pushStack和merge方法附加元素集及合并
return (context || rootjQuery).find(selector); // 处理 $(expr, context)
// 即 $(context).find(expr)
} else {
return this.constructor(context).find(selector);
} // 处理$(DOMElement)
} else if (selector.nodeType) {
this.context = this[0] = selector;
this.length = 1;
return this; // 处理$(function)
// jQUery(document) ready的简写
} else if (jQuery.isFunction(selector)) {
// 调用jQuery.fn.ready方法
return rootjQuery.ready(selector);
} // 处理$($(...))
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
} // 返回伪数组对象
return jQuery.makeArray(selector, this);
}

  看完了init构造函数大概知道了$方法的多样式原来是这样来的,这么多条件判断。现在我们仅仅知道$()方法是怎么判断参数,却对生成的相应的元素集原理很模糊。我们还是以$('div')为例,当被实例化后会执行到这个分支的内容:

return (context || rootjQuery).find(selector);

这里调用了jQuery.fn.find方法,让我们来看看该部分的源码:

find: function (selector) {
var i, ret, self,
len = this.length; if (typeof selector !== 'string') {
self = this;
return this.pushStack(jQuery(selector).filter(function () {
for (i = 0; i < len; i++) {
if (jQuery.contains(self[i], this)) {
return true;
}
}
}));
} ret = [];
for (i = 0; i < len; i++) {
jQuery.find(selector, this[i], ret);
} // Needed because $( selector, context ) becomes $( context ).find( selector )
ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);
ret.selector = (this.selector ? this.selector + ' ' : '') + selector;
return ret;
}

  代码比较简单,没怎么注释,这里遍历jQuery元素集然后使用jQuery.find静态方法(同时也就是Sizzle方法),先暂时不讲解Sizzle选择器的工作原理,因为这不是一个篇幅可以讲完的事。Sizzle方法会将匹配到的元素数组返回给ret。此时ret已经有了我们希望操作的元素了,接下来要给实例对象添加数组特性和context, selector属性。jQuery.fn.pushStack方法会新实例化一个jQuery对象,并且合并元素到该新实例化对象中,再给新实例对象添加prevObject(保存着当前实例化对象的引用,非新实例化对象)和context属性

// 使用传入的元素生成一个新的jQuery元素,(
// 将元素数组合并到this对象中)
// 并将这个对象的prevObject设置成当
// 前这个实例对象(this).最后将这个新生成的jQuery对象返回
// 把当前的jQuery对象缓存起来,
// 以便以后使用end方法恢复这个jQuery对象
pushStack: function (elems) {
// 新建一个新的jQuery匹配元素集
// this.constructor === jQuery
// jQuery()返回的是this
// 通过将elems数组merge到this中,使this也具有类似数组的特性,
// 这就是使用选择器匹配到的元素被合并到this中的原因
var ret = jQuery.merge(this.constructor(), elems); // 把旧对象保存在prevObject属性上
ret.prevObject = this;
ret.context = this.context; // 返回新的元素集
return ret;
}

  jQuery.merge:

/**
* 合并两个数组(或类数组)
* 返回合并后的第一个内容
*/
merge: function (first, second) {
var l = second.length,
i = first.length,
j = 0; if (typeof l === 'number') {
for (; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
} first.length = i; return first;
}

  

至此我们的$方法已经获取了我们希望匹配的元素了,那么是怎么使用jQuery的其他方法的呢?

// 延迟实例化
/*
这里将init的构造函数原型指向jQuery.fn(即jQuery原型),
当我们给jQuery.fn扩展方法或属性的时候,实际上就是给init.prototype,
而jQuery()方法返回的是init构造函数的实例化对象,所以jQuery()就是其实例对象,
具有了其方法和属性。
*/
jQuery.fn.init.prototype = jQuery.fn;

  此时我们就可以使用jQuery.fn中的方法了,为了简便扩展方法,jQuery用了extend方法:

/*
用一个或多个其他对象来扩展一个对象,返回被扩展的对象
*/
// jQuery.extend(target, [object1], [objectN])
// jQuery.extend([deep], target, object1, [objectN])
// jQuery.fn.extend就是jQuery.fn.init.prototype.extend,
// 所以this就是init的实例化对象,即jQuery(..)
jQuery.extend = jQuery.fn.extend = function () {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false; // 处理 深拷贝的情况
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// 略过布尔值
i = 2;
} // target非对象或函数则强制转换为空对象
if (typeof target !== 'object' && !jQuery.isFunction(target)) {
target = {};
} // 当只有一个参数或者深度拷贝的两个参数时说明是扩展jQuery或者jQuery.fn
if (length === i) {
target = this;
--i;
} for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name];
copy = options[name]; // 避免循环递归, 不把自己的引用作为自己的一个成员
if (target === copy) {
continue;
} // 递归深度拷贝的对象或数组
if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
} // 递归调用
target[name] = jQuery.extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
} // 返回被修改的对象
return target;
}

  $.extend与$.fn.extend用法

1. $.extend({
min: function(a, b) { return a < b ? a : b; },
max: function(a, b) { return a > b ? a : b; }
});
$.min(2,3); // => 2
$.max(4,5); // => 5 2. $.fn.extend({
check: function() {
return this.each(function() { this.checked = true; });
},
uncheck: function() {
return this.each(function() { this.checked = false; });
}
});
$("input[type=checkbox]").check();
$("input[type=radio]").uncheck();

  终于到结尾了,如果有什么讲错的地方,希望大家提出来。

jQuery1.9.1--结构及$方法的工作原理源码分析的更多相关文章

  1. 【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  2. 【转】【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  3. jQuery原型属性constructor,selector,length,jquery和原型方法size,get,toArray源码分析

    首先看一下在jQuery1.7.1中定义的原型属性和方法有哪些? init方法作为实际的构造函数已经详细分析过了,需要了解可以参考http://www.cnblogs.com/yy-hh/p/4492 ...

  4. java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四

    前文地址 https://www.cnblogs.com/tera/p/13336627.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...

  5. Express工作原理和源码分析一:创建路由

    Express是一基于Node的一个框架,用来快速创建Web服务的一个工具,为什么要使用Express呢,因为创建Web服务如果从Node开始有很多繁琐的工作要做,而Express为你解放了很多工作, ...

  6. Phalcon的Mvc结构及启动流程(部分源码分析)

    Phalcon本身有支持创建多种形式的Web应用项目以应对不同场景,包括迷你应用.单模块标准应用.以及较复杂的多模块应用 创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalc ...

  7. Phalcon Framework的Mvc结构及启动流程(部分源码分析)

    创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalcon多模块应用 phalcon project eva --type modules入口文件为public/index.p ...

  8. jquery原型方法map的使用和源码分析

    原型方法map跟each类似调用的是同名静态方法,只不过返回来的数据必须经过另一个原型方法pushStack方法处理之后才返回,源码如下: map: function( callback ) { re ...

  9. Scrapy(爬虫框架)中,Spider类中parse()方法的工作机制

    parse(self,response):当请求url返回网页没有指定回调函数,默认的Request对象的回调函数,用来处理网页返回的response,和生成的Item或者Request对象 以下分析 ...

随机推荐

  1. 【C#】 装箱 (boxing) 和拆箱 (unboxing)

    目录: 1. 装箱和拆箱 2. 深入理解装箱和拆箱 3. int[] to object[],值类型数组到对象数组的转化 4. 使用泛型减少装箱和拆箱 1.  装箱和拆箱 装箱 就是把“值类型”转换成 ...

  2. PAT乙级真题1001. 害死人不偿命的(3n+1)猜想 (15)(解题)

    卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n+1)砍掉一半.这样一直反复砍下去,最后一定在某一步得到n=1.卡拉兹在1950年的世界数 ...

  3. 1084. Broken Keyboard (20)

    On a broken keyboard, some of the keys are worn out. So when you type some sentences, the characters ...

  4. SAP B1 ADDON 开发

    承接各类SAP B1 ADDON 开发. 有需要,请联系.

  5. App创意项目助跑计划

    APP创意项目助跑计划 该计划旨在帮助同学们将各种脑中稀奇古怪的想法借助互联网/移动互联网 相关的技术变成真实的项目. 谱写你的故事,从此刻开始! 我们帮助你提高编程(Java.C++.Objecti ...

  6. OpenStack:安装Glance

    >安装Glance1. 安装# apt-get install glance python-glanceclient删除sqlite文件rm -f /var/lib/glance/glance. ...

  7. ExtJs桌面组件(DeskTop)

    在desktop\js目录中包含了5个js文件,这5个js文件如下: 还有css样式表:desktop.css,图片素材 在这5个js文件中封装了用于模拟桌面的类,这些类如下: Ext.ux.Star ...

  8. WPF窗口长时间无人操作鼠标自动隐藏

    在软件开发中有时会有等待一段时间无人操作后隐藏鼠标,可能原因大致如下: 1.为了安全性,特别是那些需要用到用户名和密码登录服务端的程序,常常考虑长期无人操作,程序自动跳转到用户登录界面: 2.软件为了 ...

  9. 【转载】set_input_delay和set_output_delay的选项-max和-min的讨论

    转自:http://www.cnblogs.com/freshair_cnblog/archive/2012/09/12/2681060.html 一.存在背景分析 文档的说法是,set_input_ ...

  10. Lisp使用Lambda语法

    lamdba 其实就是一个匿名函数. 定义Lisp的lambda语法非常的简单,如下: (lambda ([parameter]) [experssion]) 调用lambda的语法有三种方法,如下: ...