jQuery1.9.1--结构及$方法的工作原理源码分析
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'))看看出来的是什么东西:
- 0: div
- context: document
- length: 1
- prevObject: jQuery.fn.jQuery.init[1]
- selector: "div"
- __proto__: Object[0]
- add: function ( selector, context ) {
- addBack: function ( selector ) {
- addClass: function ( value ) {
- after: function () {
- ajaxComplete: function ( fn ){
- ajaxError: function ( fn ){
- ajaxSend: function ( fn ){
- ajaxStart: function ( fn ){
- ajaxStop: function ( fn ){
- ajaxSuccess: function ( fn ){
- andSelf: function ( selector ) {
- animate: function ( prop, speed, easing, callback ) {
- 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--结构及$方法的工作原理源码分析的更多相关文章
- 【Spring实战】Spring注解配置工作原理源码解析
一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...
- 【转】【Spring实战】Spring注解配置工作原理源码解析
一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...
- jQuery原型属性constructor,selector,length,jquery和原型方法size,get,toArray源码分析
首先看一下在jQuery1.7.1中定义的原型属性和方法有哪些? init方法作为实际的构造函数已经详细分析过了,需要了解可以参考http://www.cnblogs.com/yy-hh/p/4492 ...
- java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四
前文地址 https://www.cnblogs.com/tera/p/13336627.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
- Express工作原理和源码分析一:创建路由
Express是一基于Node的一个框架,用来快速创建Web服务的一个工具,为什么要使用Express呢,因为创建Web服务如果从Node开始有很多繁琐的工作要做,而Express为你解放了很多工作, ...
- Phalcon的Mvc结构及启动流程(部分源码分析)
Phalcon本身有支持创建多种形式的Web应用项目以应对不同场景,包括迷你应用.单模块标准应用.以及较复杂的多模块应用 创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalc ...
- Phalcon Framework的Mvc结构及启动流程(部分源码分析)
创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalcon多模块应用 phalcon project eva --type modules入口文件为public/index.p ...
- jquery原型方法map的使用和源码分析
原型方法map跟each类似调用的是同名静态方法,只不过返回来的数据必须经过另一个原型方法pushStack方法处理之后才返回,源码如下: map: function( callback ) { re ...
- Scrapy(爬虫框架)中,Spider类中parse()方法的工作机制
parse(self,response):当请求url返回网页没有指定回调函数,默认的Request对象的回调函数,用来处理网页返回的response,和生成的Item或者Request对象 以下分析 ...
随机推荐
- 实战MySQL集群,试用CentOS 6下的MariaDB-Galera集成版
说起mysql的集群估计很多人会首先想起mysql自带的replication或者mysql-mmm.mysql-mmm其实也是基于mysql自带的replication的,不过封装的更好用一些,但是 ...
- word中让首页和目录不显示页码的方法
在正文前一页,插入->分隔符->下一页,然后插入页码,取消与前一页页眉的链接,删除首页和目录的页码即可
- C,C++回文字符串判断(字符串指针的用法)
功能:输入一个字符串,判断是否为回文. 主要锻炼指针的用法. 1.C版 #include<stdio.h> int main() { ]; char a; ,flag=; while((a ...
- 调用微信退款接口时出现System.Security.Cryptography.CryptographicException: 出现了内部错误 解决办法
我总结了一下出现证书无法加载的原因有以下三个 1.证书密码不正确,微信证书密码就是商户号 解决办法:请检查证书密码是不是和商户号一致 2.IIS设置错误,未加载用户配置文件 解决办法:找到网站使用的应 ...
- centos6.4 安装 hive 0.12.0
环境:centos6.4 64bit, 前提:hadoop已经正常运行,可以使用hadoop dfsadmin -report查看 hive 解压 tar zcvf hive-0.12.0.ta ...
- 微信扫码支付asp.net(C#)实现步骤
支付提交页面: [HttpPost] public ActionResult index(decimal amount) { //生成订单10位序列号,此处用时间和随机数生成,商户根据自己调整,保证唯 ...
- INPC & RaizePropertyChanged in mvvmlight
INPC & RaizePropertyChanged in mvvmlight In WPF app, MvvM Framework, we binding the UIElement fr ...
- Python MYSQL - tiny ETL tool - 文件操作和数据库操作
import os import MySQLdb Con= MySQLdb.connect(host=',db='test') #链接数据库 cur=Con.cursor() os.chdir(&qu ...
- Eigen库实现简单的旋转、平移操作
本来课程要求用GUI界面来实现Eigen的旋转.平移操作的,但是接触GUI编程时间太短,虽然要求很简单,但是做了几天还是没有完成.就把命令行下面的简单的贴一下吧. main.cpp #include ...
- c语言编程之二叉排序树
二叉排序树,又称为二叉查找树.它是一颗空树,或者是具有下面的性质的二叉树: 1.若它的左子树不空,则左子树上所有节点的值均小于它的根结构的值: 2.若它的右子树不空,则右子树上所有节点的值均大于它的根 ...