Home » Code » 菜鸟学习jQuery源码Part2——基本原型属性方法

菜鸟学习jQuery源码Part2——基本原型属性方法

定义了jQuery之后,就是一堆匹配数字、标签,分割字符串之类的正则。对于正则不熟,没什么能说的。

从96行开始,开始定义对象原型jQuery.prototype也是jQuery.fn。添加了以下属性方法:

  • jquery:当前jQuery的版本号
  • constructor:修正jQuery对象的constructor指向,指向jQuery()函数
  • init():jQuery对象的构造函数
  • selector:选择器
  • length:长度,即选中元素个数
  • toArray():将NodeList、DOMCollection等转化为真正数组
  • get(num):从jQuery对象中获取具体的DOM对象
  • pushStack(elems):将一组元素入栈
  • each(callback, args):遍历
  • ready(fn):DOMCompleted完成执行
  • slice():从jQuery对象栈中按位置筛选
  • first():取第一个
  • last():取最后一个
  • eq(i):取第i个
  • map():遍历回调
  • end():返回“上一层”
  • push():内部使用函数,纯数组的push
  • sort():内部使用函数,纯数组的sort
  • splice():内部使用函数,纯数组的splice

这里面有一些平时不太常用,有一些内部使用的。重点之一的init()这个对象的构造函数,所有的jQuery对象都是由它产生的,因此要针对多种情况进行讨论。以下是个人注释:

function( selector, context, rootjQuery ) {
	var match, elem;

	// HANDLE: $(""), $(null), $(undefined), $(false)
	//处理各种“空”的情况
	if ( !selector ) {
		return this;
	}

	// Handle HTML strings
	//处理字符串
	if ( typeof selector === "string" ) {
		if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
			// Assume that strings that start and end with <> are HTML and skip the regex check
			//参数以'<'开头,'>'结尾,且中间还有其他字符的跳过正则检测
			match = [ null, selector, null ];

		} else {
			//rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,匹配<div>,<div>abc,#id这几种
			match = rquickExpr.exec( selector );
			/*exec(),如有匹配到,返回数组,第0个是与表达式匹配的文本,第1个是与第一个子表达式匹配的文本,依此类推
			*如是<div>,match = ["<div>", "<div>", undefined]
			*如是<div>abc,match = ["<div>abc", "<div>", undefined]
			*如是#id,match = ["#id", undefined, "id"]
			*/

		}

		// Match html or make sure no context is specified for #id
		if ( match && (match[1] || !context) ) {//创建元素或#id选择器能进入

			// HANDLE: $(html) -> $(array)
			if ( match[1] ) {//创建元素
				context = context instanceof jQuery ? context[0] : context;//创建元素一般是当前文档,在操作iframe时或许有用

				// scripts is true for back-compat
				//jQuery.parseHTML(),将字符串<li>1</li><li>2</li>等转化为DOM对象数组
				//jQuery.merge(),将数组合并到jQuery对象上,jQuery对象是诸如{0:'a', length: 1}这样,注,一定要有length,否则合并不了
				jQuery.merge( this, jQuery.parseHTML(
					match[1],
					context && context.nodeType ? context.ownerDocument || context : document,
					true//参数三为true,script标签代码也能创建
				) );

				// HANDLE: $(html, props),创建元素带属性,如$('li', {class: 'item', title: '这是title'})
				//rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,匹配单标签,如<li>,<li>aa</li>,多个时匹配不了
				if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
					for ( match in context ) {
						// Properties of context are called as methods if possible,
						//属性名刚好是函数是运行之,如{css: {backgroundColor: 'red'}},会调用css({backgroundColor: 'red'})
						if ( jQuery.isFunction( this[ match ] ) ) {
							this[ match ]( context[ match ] );

						// ...and otherwise set as attributes,其他情况设置为正常属性
						} else {
							this.attr( match, context[ match ] );
						}
					}
				}

				return this;

			// HANDLE: $(#id),#id选择器
			} else {
				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 ) {//解决黑莓4.6系统下的一个问题
					// Inject the element directly into the jQuery object
					this.length = 1;
					this[0] = elem;
				}

				this.context = document;
				this.selector = selector;
				return this;
			}

		// HANDLE: $(expr, $(...)),没有上下文环境,或者上下文环境为jQuery对象,如$('div')、$('.class', $obj)
		} else if ( !context || context.jquery ) {
			return ( context || rootjQuery ).find( selector );

		// HANDLE: $(expr, context)
		// (which is just equivalent to: $(context).find(expr)
		//有上下文,但不是jQuery对象,如$('.item', '#container')
		} else {
			return this.constructor( context ).find( selector );
		}

	// HANDLE: $(DOMElement)
	} else if ( selector.nodeType ) {//DOM对象
		this.context = this[0] = selector;
		this.length = 1;
		return this;

	// HANDLE: $(function)
	// Shortcut for document ready
	} else if ( jQuery.isFunction( selector ) ) {//加载完毕执行的简便形式
		return rootjQuery.ready( selector );
	}

	if ( selector.selector !== undefined ) {//选择器selector就是jQuery对象,如$($('#id'))
		this.selector = selector.selector;
		this.context = selector.context;
	}
	//将selector对象(jQuery对象)这个类数组转换为真正数组,并合并到当前jQuery对象上,这是内部用法!
	return jQuery.makeArray( selector, this );
},

可以看到,构造函数针对不同情况做了处理,一些复杂的情况都是通过调用find()方法实现,而这个则需要使用Sizzle,后面再说。这里面用到了一些静态方法,如jQuery.parseHTML()、jQuery.merge()、jQuery.isPlainObject()、jQuery.isFunction()、jQuery.makeArray(),它们是在后边定义的,暂时不说。为了更清楚了解这个入口所作的分类处理,画图如下:

jQuery入口分类讨论

jQuery入口分类讨论

下面再来看其他一些原型方法的实现。

toArray()

这个其实就是调用数组的slice方法。但要注意,slice方法并不是能将任意的对象转换为数组的,它要求对象的结构必须是{0: “elem 1”, 1: “elem 2”, “length”: 2}这样子索引为数字并且具体length属性:

var json = {0: 'a', 1: 'b', 'selector': '#id', 'length': 2};
function Person() {
  this[0] = 'index 0 elem';
  this.name = '小明';
  this.say = function() {
    console.log('my name is ' + this.name);
  },
  this.length = 3;
}
var obj = new Person();
var r1 = [].slice.call(json);
var r2 = [].slice.call(obj);
console.log(r1);//["a", "b"]
console.log(r2);//["index 0 elem"]

如果不是数字索引,返回的是[];如果没有length属性,返回的也是[];如果length值比带索引的元素的个数小,返回前length个;如果大,返回所有带索引的元素。当然,jQuery当中值是相等的。jQuery这样子设计对象的结构,不知道是不是就是因为这个函数。

get(num)

这里如果不传参数,直接调用toArray()返回所有。如果传了参数,还支持负数从后面往回算,返回this[this.length + num],为正就是this[num]了。

pushStack(elems)

这个外部很少使用,但在jQuery内部就是非常重要的一个方法了。它的作用是将新的元素“入栈”,放置到当前元素的“顶端”,是一个“破坏性操作”,返回的jQuery对象指向新elems而不再是当前元素。

pushStack: function( elems ) {

	// Build a new jQuery matched element set,创建一个新的jQuery对象
	var ret = jQuery.merge( this.constructor(), elems );

	// Add the old object onto the stack (as a reference)
	ret.prevObject = this;//添加旧对象到新对象的prevObject属性上
	ret.context = this.context;

	// Return the newly-formed element set
	return ret;
},

比如以下操作:

<div id="div">this is div</div>
<span id="span">this is span</span>

<script type="text/javascript">
var r = $('#div').pushStack($('#span'));
console.log(r);

jQuery_pushStack
将#span入栈后,结果就是选择的#span了。要回到“上一层”也就是之前的#div,调用end()方法。

each()

这个就是调用jQuery.each(),在jQuery中,很多原型上供实例使用的方法跟工具方法也就是jQuery的静态方法其实是一样的。后边再说。

slice()

从当前jQuery对象截取相应的DOM对象,入栈之!也是一个破坏性操作。

slice: function() {
	return this.pushStack( core_slice.apply( this, arguments ) );
},

eq()

一样的原理,取得某个元素入栈之!first()、last()直接调用eq()。不得不说这个pushStack()操作设计得很精妙。

eq: function( i ) {
	var len = this.length,
		j = +i + ( i < 0 ? len : 0 );
	return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},

map()

调用工具方法jQuery.map(),再将处理完后的结果入栈!

map: function( callback ) {
	return this.pushStack( jQuery.map(this, function( elem, i ) {
		return callback.call( elem, i, elem );
	}));
},

end()

返回“上一级”,就是直接返回它的prevObject属性或者新创建一个空jQuery对象。jQuery的栈式结构一大亮点啊!

end: function() {
	return this.prevObject || this.constructor(null);
},

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.