前言

俗话说得好,不打算做将军的小兵不是一个好士兵,同理 coder。对于更高层次的代码我有着自己的追求,其中最好奇的就属于jquery的源码是怎么样实现的,特别是$是怎样实现的。

这里代码review的是jquery-1.9.1.js,这个版本其实并不好有几个明显的错误在里面,逻辑也没有2.1.2的逻辑清晰(逻辑肯定有少,具体哪里说不上来,反正怪怪的有东西没写出来,可能通过find全部接管处理了),很多名称没有完全语义化。

比如这里rquickExpr = /^(?:(<[\w\W]+>)[^>]|#([\w-]))$/,这里明显漏写了空白符的匹配

最好改成这样rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]|#([\w-]))$/,

1. 先走一遍codereview

首先我们要确定一个思路,jquery不论怎么封装都逃不过需要在全局环境下注册$和jQuery,所以这其中必定有一个

window.jQuery = jQuery;window.

或者

window.jQuery = {{’&#95’}};jQuery;window. = {{'&#95'}};

那么检索一下,一共四个匹配项分别是以下位置

38:_jQuery = window.jQuery,
393:jQuery.extend( {
394:	noConflict: function( deep ) {
395:		if ( window.$ === jQuery ) {
396:			window.$ = _$;
397:		}
398:
399:		if ( deep && window.jQuery === jQuery ) {
400:			window.jQuery = _jQuery;
401:		}
402:
403:		return jQuery;
404:	},
9867:window.jQuery = window.$ = jQuery;

接着,我注意到了最后一个匹配项,这里将jquery变成了一个全局作用域,不过上面几个代码并没有什么用,_jquery仅仅只在上面第二段中使用了,显然是一个查重使用使用的方法,所以显然不能拿上面的来做二次搜索,那么该怎么办呢?

这里我想起了一件事情,在jquery纠错的时候常常出现一个jquery.fn.xxxx not a function,这给了我一个思路,有可能jquey继承了jquery.fn的原型,而其中很大可能至少有一个init方法或者constructor方法,或者其他类似的初始化方法,先做这些已经想到的方法,如果没有在添加,那么这样检索的方案锁定在了下面一段

jQuery.fn
jQuery.prototype
fn.prototype
fn.init.prototype
fn._construct
fn._construct.prototype
constructor

这样筛选一下内容得出一下一段

324:jQuery.fn.init.prototype = jQuery.fn;
61:jQuery = function( selector, context ) {
62:
63:		// The jQuery object is actually just the init constructor 'enhanced'
64:		return new jQuery.fn.init( selector, context, rootjQuery );
65:	},

下面一段是从121行开始到321行结束的

jQuery.fn = jQuery.prototype = {
 
	// The current version of jQuery being used
	jquery: core_version,
 
	constructor: jQuery,
	init: function( selector, context, rootjQuery ) {
		var match, elem;
 

这里我发现了重点了,找到了jquery的核心内容,确实和我所料的一样使用了init方法

那么这里结合前面的逻辑就通了,我们调用的$(xxxx)实质上调用的是jquery(“xxxxxx”),这里的context很少用是用来限定范围的,在调用这个方法时实质上是返回了一个全新的jquery.fn.init对象(这个对象实质上是返回的一个html dom元素或者function对象),让后这个对象的原型指向jquery.fn(实际上就是实例化的原型,然后通过指向原型来完成类似继承的效果)我们仔细看一下121行到321行的内容,注意下很快就能发现我们需要的内容。

1.1 init中的选择器

在131行中有这样一行代码

130:// HANDLE: $(""), $(null), $(undefined), $(false)
131:if ( !selector ) {
132:			return this;
133:		}

这行代码打了注释这是用来筛选空元素的,一旦筛选到直接返回jquery.fn

然后后面代码在135行开始到229行结束,这段代码非常长把思路讲解一下

jQuery.fn = jQuery.prototype = {
 
	// The current version of jQuery being used
	jquery: core_version,
 
	constructor: jQuery,
	init: function( selector, context, rootjQuery ) {
		var match, elem;
 
		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}
 
		// Handle HTML strings
		//html的选择器首先必须是一个string
		if ( typeof selector === "string" ) {
			//string说明必定是匹配html标签或者类似于#id形式的内容
 
			if ( selector.charAt( 0 ) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
				// 符合的则必定是一个html标签
				// Assume that strings that start and end with <> are HTML and skip the regex check
				// 那就不匹配正则了,肯定是一个标签,结果放入match=[null, selector, null]
				match = [ null, selector, null ];
 
			} else {
				// 匹配表达式为rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/
				// 这个表达式是用来干什么的呢?
				// 首先匹配头部为(<中间大小写字母1+次结尾为>)
				// 这里圆括号内内容不能反向引用,然后向后匹配任意多次的>
				// 前面半段(依照|分割)就是用来将html标签剥离出来
				// 而后面半段则直接将#id剥离出来
				// match=匹配结果,这样返回值将会是这样的一个形式:
				// [MatchString,Part1Match,Part2Match]
				// part1和part2有一个会是null
				match = rquickExpr.exec( selector );
			}
			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {
				// 通过if说明至少是一个html元素或者是一个#id的选择器
 
				// 这样结果中的match[1]代表html类的结果
				// 结果中的match[2]代表的是诸如#id的id选择器一类
				// HANDLE: $(html) -> $(array)
 
				if ( match[ 1 ] ) {
					// 有东西则说明必定是html(通过html检测或者直接定义的)
					context = context instanceof jQuery ? context[ 0 ] : context;
					// 那就简单了直接prase一下html然后然后按照context限定一下作用域,然后返回就好了
					// 这里的merge就是讲后面context合并到jquery
					// scripts is true for back-compat
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );
					// 这里添加prop和attr
					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {
 
							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );
 
							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}
 
					return this;
 
				// HANDLE: $(#id)
				} else {
					//说明这个必然是一个#id类的,之前匹配了的match[2中就是对应的id
					//直接document.getElementById( match[ 2 ] )就可以获得dom元素了
					elem = document.getElementById( match[ 2 ] );
 
					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// 随后检测一次是否有子节点,有子节点调用且match到的id不是输入的string则调用find在全局中查找
						// Handle the case where IE and Opera return items
						// by name instead of ID
						if ( elem.id !== match[ 2 ] ) {
							return rootjQuery.find( selector );
						}
 
						// Otherwise, we inject the element directly into the jQuery object
						this.length = 1;
						this[ 0 ] = elem;
					}
 
					this.context = document;
					this.selector = selector;
					//最后返回结果this
					return this;
				}
 
			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				// jquery或context.jquery中存在元素
				// 在jquery或者context.jquery中find对应的selector并返回
				return ( context || rootjQuery ).find( selector );
 
			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				// 则创建一个以context为对象的jquery
				//(就是为context添加jquery=>context.jquery)
 
				return this.constructor( context ).find( selector );
			}
 
		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			// 说明不是一个标准的选择器可能是比如dom集这样的东西
			// 所以先判断一下selector.nodeType看看是不是一个dom元素
			// 如果是的话就就把context=this[0]=selector
			this.context = this[ 0 ] = selector;
			this.length = 1;
			return this;
 
		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			// 判断一下是不是function,因为都不是的情况下可能是一个function对象然后等待这个function对象加载完毕然后返回他
			return rootjQuery.ready( selector );
		}
		// 那么在以上全部都判断完的情况下还有什么可能呢?
		// 这个jquery表达是可能是一个obj对象
		// 或者有可能是一个regex对象也可能是一个数组
		// 还有可能是数字之类的
		// 还有可能是undefined
		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}
 
		//然后就把这个东西解析一下变成一个数组。
		return jQuery.makeArray( selector, this );
	},

然后一个init选择器就弄好了,接着要干嘛呢?

2.通过extend添加各种方法

比如下面这样

jQuery.extend({
	min: function(a, b) { return a < b ? a : b; },
	max: function(a, b) { return a > b ? a : b; }
});
jQuery.min(2,3); //  2 
jQuery.max(4,5); //  5

2.总结一下

jquery的核心是什么? 首先有一个jQuery的function对象,通过添加原型方法,其获得了对应的jquery的基础方法,随后,他的原型方法被jquery.fn继承,然后通过window.$=window.jQuery=jQuery来全局生效。

在使用时,调用jquery(“xxx”)的时候实质是调用了jQuery.fn.init生成了一个全新的jquery对象(通过jquery内置的选择器实现方法来实现不同dom的选择),最后返回一个封装好相关jquery方法的jquery dom 对象。

从jquery的函数来看如果要选择一个比如这样的标签$(‘#id’)其性能几乎是等于document.getElementById(“id”)的而且它的方法更加丰富。

那么对于类似于 $('#id .class')这样的选择器,对于jquery是不怎么友好的,还如改成 $('#id').find('.class') 速度更快(前者遍历rootjQuery,后者遍历id所在elem集)

而相对于的 $( selector, context ) 这样的操作实质上将会转化成 $( context ).find( selector ) 这样的操作,如果这个context不是很大的通常是不会有性能问题的,具体的情况没做测试就不说辣,这次的jquery的代码code view就到这里了。

本文标题:icepro对于jquery($)实现的理解

永久链接:https://iceprosurface.com/2016/05/29/2016/2016-05-29-base-of-node-1/

作者授权:本文由 icepro 原创编译并授权刊载发布。

版权声明:本文使用「署名-非商业性使用-相同方式共享 4.0 国际」创作共享协议,转载或使用请遵守署名协议。

查看源码: