jQuery源码分析之init方法

jQuery函数源码如下:

	jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	}
注意:按照这种写法最后返回的应该是init对象类型,为什么结果是jQuery类型呢,这里就用到了原型链的知识,init.prototype = jQuery.fn;所以把jQuery的原型设置为init的原型。
同时源码中使用对象字面量的形式对jQuery的原型赋值的,那么jQuery函数的constructor属性就会被覆盖,所以要手动修正。如下例:

因为constructor属性很容易被修改,constructor: jQuery所以会出现在源码中
    function A(){}
    //第二种方式直接把A的constructor属性覆盖掉了,所以这时候的new A().constructor就是function object(){[native code]}
    //因为jQuery源码中就是第二种方式,所以要对constructor进行重新声明!
    A.prototype={
      name:"xxx",
      sex:"female",
      constructor:A
    }
jQuery中init函数源码分析:

  init = jQuery.fn.init = function( selector, context ) {
		var match, elem;
		// HANDLE: $(""), $(null), $(undefined), $(false)
		//直接return this这个对象就是空的init对象!
		if ( !selector ) {
			return this;
		}
		// Handle HTML strings
		if ( typeof selector === "string" ) {
		//id选择器,class选择器等都是走else
			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 {
			//$("<li>hello").appendTo("ul")相当于只是添加空标签<li></li>和$("<li/>").appendTo("ul")一样!
			//var rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/
			match = rquickExpr.exec( selector );
			//对于$("#div1")和$("<div>hello")
			//matched=null//$(".box"),$("div"),$("#div1 div.box")
			//matched=["#div1,null,'div1'"]//对于$("#div1")
			//matched=["<li>hello",'<li>',null]//对于$("<li>hello")
			}
			//创建标签如:$("<li></li>")那么就会走这里的逻辑!或者$("<li>hello")也会走这里的逻辑!
			//id选择器也会走这里的逻辑,虽然matched[1]是false,但是context是不存在的,因为我们的id选择器一般不传入上下文!
			//如$("#div1"),因为id是唯一的!
			// Match html or make sure no context is specified for #id
			if ( match && (match[1] || !context) ) {
				// HANDLE: $(html) -> $(array)
				if ( match[1] ) {
		//创建标签怎么会有上下文呢?只能是$("<li>",document)第二个参数只能是document,如果是iframe,那么是contentWindow.document!
				//默认情况是document!
				//如果context直接是document那么直接返回document,如果context是$(document)那么就获取到原生的document!
					context = context instanceof jQuery ? context[0] : context;
					//$("<li>1</li><li>2</li>")如何把字符串转化为this={0:'li',1:'li',length:2}
					//因为后面的是DOM节点数组!于是通过parseHTML来完成!parseHTML第三个参数默认是false
		//但是parseHTML也可以创建script标签(注意写script标签的时候要转义,防止他和上面的script配对!)true表示可以添加script标签!
					// scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
		//怎么把后面的DOM数组转化为上面的JSON格式呢?用merge方法!merge方法也可以对JSON进行合并!下标必须是数字,length也是数字!
					//这里的this就是new init函数的一个空对象!
					jQuery.merge( this, jQuery.parseHTML(
						match[1],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );
					// HANDLE: $(html, props)
					//var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
					//$("<li>",{tile:"abcd",html:'abcn'})
					//这个正则是匹配单标签,如<li> 或者<li></li>也就是对$("<li></li><li></li>",{})不行!
					//isPlainObject用于判断是否为对象字面量!
					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {
							// Properties of context are called as methods if possible
							//因为上面的html方法已经存在,于是直接调用html,如直接调用this.html("abcn")
							//其它同名的在jQuery中源码方法如css也是直接调用!
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );
							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}
					return this;
               //因为页面中id是唯一的,所以不需要上下文!
			   matched=["#div1,null,'div1'"]//对于$("#div1")
				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[2] );
                     //也就是在黑莓4.6中页面中没有,但是克隆的时候还是能够找到,所以添加一个
					 //是否有parentNode,如果一个元素不存在那么父元素肯定不存在!
					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// Handle the case where IE and Opera return items
						// by name instead of ID\
						//在IE或者Opera浏览器中,返回的是用name查询的结果,而不是id!
						//那么调用:rootjQuery = jQuery( document );
						if ( elem.id !== match[2] ) {
							return rootjQuery.find( selector );
						}
                         //Jquery创建的时候是JSON类型不是数组,所以length要手动赋值!
						//可以参见上面有constructor,length属性等!
						// Otherwise, we inject the element directly into the jQuery object
						this.length = 1;
						this[0] = elem;
					}
					//id选择器的上下文肯定是document!
					this.context = document;
					//每一个jQuery对象的都有一个selector属性,表示$("#test")存储其中的"#test"字符串!
					this.selector = selector;
					return this;
				}
             //上面的id的情况和创建元素的情况已经处理完毕了!
			// HANDLE: $(expr, $(...))
			//如果没有传入上下文或者传入的上下文是jQuery对象!那么调用上下文的find方法继续缩小查找范围!
			//如$("li",$("ul"))
			} else if ( !context || context.jquery ) {
				return ( context || rootjQuery ).find( selector );
			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			//如果上下文不是jQuery对象,那么直接把上下文封装为jQuery对象继续调用find方法
			//this.constructor=jQuery
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		//调用方式:$(this),$(document)
		//直接把它封装为jQuery对象,这里是selector不是string的情况!
		} else if ( selector.nodeType ) {
		//保存到JSON的第0个元素上面!
			this.context = this[0] = selector;
			this.length = 1;
			return this;
		// HANDLE: $(function)
		// Shortcut for document ready
		//rootjQuery = jQuery( document );
		//文档加载两种方式:$(function(){})是简写的方式,最终调用的还是$(document).ready(function(){})
		//如果selecot是function,那么调用调用$(document).ready(function(){}),如果传入的是函数但是$(document).ready是undfined那么
		//直接把jQuery对象传入这个参数进行调用!
		} else if ( jQuery.isFunction( selector ) ) {
			return typeof rootjQuery.ready !== "undefined" ?
				rootjQuery.ready( selector ) 
				// Execute immediately if ready is not present
				selector( jQuery );
		}
       //如果调用方式是:$($("#div1"))那么要包装成为$("#div1")
	    //jQuery对象有selector属性!DOM对象有nodeType属性!
		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}
          //因为makeArray在内部调用的时候如果第二个参数传入的是JSON对象,那么最后返回的结果就是JSON!
		return jQuery.makeArray( selector, this );
	};
总结:

一,首先判断传入的第一个参数selector是否为string类型,如果不是string类型那么走下面的“二,三,四”,否则就是走"1,2,3"

  1,判断是否是创建HTML标签或者id选择器,如果是创建HTML标签那么调用parseHTML方法得到DOM数组,并且把这个数组封装到JSON对象this上,同时,创建的时候第二个参数可以是一个对象,这时候如果对象上有jQuery的同名方法那么直接调用!(merge第一个参数为JSON情况),如果是id选择器那么调用getElementById获取对象,然后封装到this上面!

 2,判断第二个参数context如果不存在或者存在同时是对象,那么把这个对象封装成为jQuery对象,同时调用该对象的find方法!(对于class等选择器都是最终调用find方法)

二,如果selector是DOM,那么直接把DOM封装到this[0]上面,并返回这个jQuery对象!(源码中的this一直就是jQuery对象,因为这是构造函数内部this)
三,如果selector是函数,那么直接调用$(document).ready()并且把函数作为ready函数的参数执行!

四,如果传入的selector是jQuery对象,那么把参数的jQuery对象的所有信息封装到新返回的对象中!

建议你首先阅读正则表达式的内容:

案例1:

$("<div></div><div></div>",{html:'abcn'}).appendTo($("body"));
				
这种情况,虽然也是创建标签,而且第二个参数isPlainObject返回true,但是var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);正则表达式无法通过,最终jQuery什么也不做!
案例2:

$("<li>hello").appendTo($("body"))
这种情况也是创建标签,但是经过match = rquickExpr.exec( selector );最后的结果仅仅获取到的match[1]是一个空的li标签,而不是<li>Hello</li>所以添加到body中的是一个空的li标签!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jQuery 是一个非常流行的 JavaScript 库,它简化了 JavaScript 代码的编写和操作 DOM 的过程。以下是 jQuery 码的逐行详解: ```javascript (function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // 如果是 CommonJS 规范,则添加到模块中 // 在 Node.js 环境中,module 对象代表当前模块,exports 对象是 module 的属性,用于指定模块对外输出的接口。 // 通过 exports 对象把模块导出,其他文件就可以通过 require() 函数来引用该模块。 module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { // 如果是浏览器环境,则将 jQuery 添加到全局对象 window 中 factory( global ); } // 在这里传入了两个参数,global 和 factory,分别表示全局对象和一个工厂函数 }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { "use strict"; var deletedIds = []; var document = window.document; var slice = deletedIds.slice; var concat = deletedIds.concat; var push = deletedIds.push; var indexOf = deletedIds.indexOf; // ... ``` 这段代码定义了一个立即执行函数,其目的是为了防止变量污染全局作用域。如果是在 Node.js 环境中,那么需要将 jQuery 添加到模块中,否则将 jQuery 添加到全局对象 window 中。然后定义了一些变量,包括 deletedIds、document、slice、concat、push 和 indexOf 等。这些变量都是为了后面的代码服务。 ```javascript var version = "3.6.0", // 定义了一个空函数 jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }, // ... ``` 这里定义了 jQuery 的版本号和 jQuery 函数。jQuery 函数是一个工厂函数,用于创建 jQuery 对象,其实现是通过调用 jQuery.fn.init 函数实现的。 ```javascript jQuery.fn = jQuery.prototype = { // jQuery 对象的方法和属性 }; jQuery.fn.init.prototype = jQuery.fn; ``` 这里定义了 jQuery 对象的原型,包括 jQuery.fn 和 jQuery.prototype。jQuery.fn 和 jQuery.prototype 是等价的,都是为了扩展 jQuery 对象的方法和属性。同时还将 jQuery.fn.init 的原型指向了 jQuery.fn,这样可以让 jQuery.fn.init 实例化出来的对象拥有 jQuery.fn 的方法和属性。 ```javascript jQuery.extend = jQuery.fn.extend = function() { // ... }; jQuery.extend({ // 一些静态方法和属性 }); jQuery.fn.extend({ // 一些实例方法和属性 }); ``` 这里定义了 jQuery 的 extend 方法,用于实现对象的合并。同时还定义了一些静态方法和属性,以及一些实例方法和属性。 ```javascript jQuery.expr = { // ... }; jQuery.expr[ ":" ] = { // ... }; ``` 这里定义了 jQuery 的表达式引擎,用于实现选择器的解析。其中,jQuery.expr 是一个对象,存储了所有的选择器类型。jQuery.expr[ ":" ] 表示的是伪类选择器。 ```javascript jQuery.ajax = function( url, options ) { // ... }; jQuery.fn.load = function( url, params, callback ) { // ... }; ``` 这里定义了 jQuery 的 ajax 和 load 方法,用于实现异步请求和载入 HTML 片段。 ```javascript var // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; jQuery.noConflict = function( deep ) { // ... }; if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } ``` 这里定义了 jQuery 的 noConflict 方法,用于解决命名冲突问题。同时还保存了 window.jQuery 和 window.$ 的引用,以便在需要时恢复原来的状态。最后,将 jQuery 和 $ 添加到全局对象 window 中。 以上是 jQuery 码的部分逐行详解,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值