jQuery源码分析(一)--- init extend merge 源码解析

jQuery整体架构

无new化架构(源码):
$ 就是jQuery的别称
$() 就是创建jQuery的实例对象

若使用new:

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

如果这样写会存在一个问题,在jQuery函数内部new一个新的jQuery,然后会一直这样new下去无限循环,最后陷入死循环。为了防止这种情况的出现,用init方法来进行工厂方法的处理,return new jQuery.fn.init( selector, context, rootjQuery ),这样就避免了死循环的问题,而且实现了一步到位的初始化。

  1. jq将代码封装在一个匿名函数里面,防止污染全局环境,然后创建一个对象jQuery,这个对象接受两个参数selector,context,其中selector就是我们通常传入的 #id,.class等字符串。
  2. init方法接收3个参数,在内部进行了各种判断,根据不同的场景,返回一个不同的实例对象,类似一个工厂方法。
  3. 将jQuery.fn.init的原型挂载到jquery的原型上。
  4. 将jQuery的constructor还原为jQuery
(function (root) {
	var jQuery = function(selector, context){
	        return new jQuery.prototype.init(selector, context)
	 }
	// selector 可以是对象,函数,字符串,context dom查询的时候 限定查询的范围
	// jQuery.fn相当于jQuery.prototype简写
	// 原型对象:
	jQuery.fn = jQuery.prototype = {	
		length: 0,
		selector: '',
		constructor: jQuery,
		init: function(selector, context) {
			context = context || document
			var match
			/* 
				selector 几种情况:
				1. 处理 ,$() $(undefined) $(null) $)(false),直接返回this
				2. 如果传入的是string,例如:$("<div>")、$("#id")、$(".class"),1). 查询dom, 2 )创建dom.
				3. 传入的是 selector如果是this,document,或window对象都会有nodetype
				4. 传入的是函数, 则在dom加载完后执行 
			*/
			//	1. 处理 
			if (!selector){
				return this
			}
			// 2. 如果传入的是string,
			if (typeof selector === 'string') {
				// 如果第一个字符是<, 最后一个字符是>,而且长度大雨3,则他就是html的字符串标签,则将它存到一个数组中
				if (selector.charAt(0) === '<' && selector.charAt(selector.length -1) === '>' && selector.length >= 3) {
					match = [selector]
				}
				if (match) { 
					// 有值的,创建dom,
					// merge传的参应该都是arr1, arr2,但是这里第一个是对象,这个对象中(上方)有个length属性,而这个length属性在下方merge方法中用到
					// jQuery.parseHTML(selector, context) 解析创建dom节点,此处拿到的就是一个dom节点(merge 和parseHTML方法 见下方)
					jQuery.merge(this, jQuery.parseHTML(selector, context))
				} else {
					// 查询dom
					elem = document.querySelectorAll(selector)	// 查出来是 类数组
					var elems = Array.prototype.slice.call(elem)	// 转化为数组
					this.length = elems.length		// this指向jquery对象(扩展,放到实例化对象上去)
					// 把document查到的elems扩展到jquery上
					var index = 0
					for (;index < elems.length; index++) {
						this[index] = elems[index] 
					}
					this.context = context
					this.selector = selector
				}
			} else if (selector.nodeType) {	
				// 3.  selector如果是this,document,或window对象都会有nodetype
				this.context = this[0] = selector
				this.length = 1
				return this
			} else if (jQuery.isFunction(selector)) {
				// 4. 如果是函数的话,则在dom加载完后执行	
			    //如果有ready方法先用ready否则用load。ready方法见下extend扩展
				// return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
			}
		},
		css: function() {...}
	}
	// 共享原型对象
	jQuery.fn.init.prototype = jQuery.fn
	root.$ = root.jQuery = jQuery
})(this)

jQuery原型上的init的构造函数跟 jq本身共享一个原型对象

extend: 扩展

	/*
		 extend 核心功能函数
		 外部使用:对对象进行扩展,      
		 以默认配置为优先,用户配置为覆盖 
		 所以$.fn.extend与$.extend拿到的都是同一个匿名函数的引用
		 给任意对象扩展,两个及两个以上
		 给jq本身扩展,一个参数
		 给jq的实例对象扩展,一个参数 
	 */
	jQuery.fn.extend = jQuery.extend = function () {
		/* 
			思路:
			传来的第一个参数必须是对象!!!
		    1. extend 给任意对象扩展的参数,
		    	$.extend(true, {}, {name: 'max', age: '30'})	深浅拷贝,拷贝结果值,拷贝对象
		    2. 自定义扩展jq本身的一些实例和方法,即插件扩展的接口
			 jQuery.extend({
				work: function() {
					console.log('ddd')
				}
			})
			jQuery.work() 
		   3. 给实例对象进行扩展
			$.fn.extend({sex: '男'})
			$().sex 
		 */
		var target = arguments[0] || {};
		var length = arguments.length;
		var i = 1;
		var option, name, copy, src, copyIsArray, clone;
		var deep = false; 						// 是否深拷贝
		
		// 第一个值必须是对象,所以先将决定深浅拷贝的布尔值拿出
		if (typeof target === 'boolean') {
			deep = target
			target = arguments[1]
			i = 2
		}
		// 取到的target不一定是对象
		if (typeof target !== 'object') {
			target = {}
		}
		// 参数的个数
		if (length === i) {
			// 给jq本身扩展,一个参数  调用时this 指向jq
			// 给jq的实例对象扩展,一个参数 	调用时this指向jq实例对象
			target = this
			// 如果长度只有1,那么i--,下面使用argument[0]就可以获取到下方第一个对象
			i--
		}
		// 给任意对象扩展,两个及两个以上,ps:第一个对象不需要循环,等着被扩展就可以了
		// 浅拷贝:只会发生替换的关系,深拷贝,会进行合并
		for (;i < length; i++) {
			if ((option = arguments[i]) != null) {
				for (name in option) {
					copy = option[name] 
					src = target[name]
					/* 
						如果是1 深拷贝,且该数据类型必须是2 数组或者3 对象 
					   var ret = {name: 'max', list:{age: '30'}}
						var res = {list:{sex: '女'}}
						copyIsArray 是否为数组 区分copy的数据类型,与下面的操作有很大的关系
						isPlanObject 是否为对象
					*/
					if (deep && (jQuery.isPlanObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
						if (copyIsArray) {
							copyIsArray = false 	// 重置下
							// 如果src是数组则 直接赋值,若不是则创建一个空数组!!!
							clone = src && jQuery.isArray(src) ? src : [] 
						} else {
							clone = src && jQuery.isPlanObject(src) ? src :{}
						}
						// 再做一次浅拷贝
						target[name] = jQuery.extend(deep, clone, copy)
					} else if (copy != undefined){
						// 浅拷贝
						target[name] = copy
					}
				}
			}
		}
		return target
	}

使用extend 给jq扩展属性和方法:

	/* 	1. 正则包含在//中
		2. ^< 表示以 < 开头
		3. \w 表示匹配包括下划线的任意单词字符,等价于 [A-Za-z0-9_]
		4. \s* 匹配任意空白字符,包括空格,制表符,换行符等
		5. \/?>  \转义, /?表示匹配 / 零次或一次,后面接>
		6. (?:<\/\1>|) 接 <\/\1> 或者不接任何内容,标签结束,
		7. <\/\1> 其中‘\/’表示匹配‘/’符号,‘\1’指前面的‘\w+’
		8. 结束
	 */
	var rejectExp = /^<(\w+)\s*\/?>(?:<\/\1>|)$/		// /^<$/
	jQuery.extend({
		isPlanObject: function(obj) {
			return toString.call(obj) === '[Object Object]'
		},
		isArray: function (obj) {
			return toString.call(obj) === '[Object Array]'
		},
		isFunction: function (obj) {
			return toString.call(obj) === '[Object Function]'
		},
		ready: function(fn) {
		    /* bindReady();
		    if ( jQuery.isReady )
		       //如果已经加载完毕,则马上执行
		      fn.call( document, jQuery );
		    else
		      // 否则将函数添加到等待列表redylist
		      jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
		    return this */;
		},
		// $.merge( [0,1,2], [2,3,4] )			---> [0,1,2,2,3,4]
		// $.merge( {name:'andrew',age:'23'}, [2,3,4] )  ---> {name: "Andrew", age: "23", NaN: 3, length: NaN}
		merge: function (first, second) {
			var l = second.length,	// second  dom节点
				i = first.length,
				j = 0;
				if (typeof l === 'number') {
					for (; j < l; j++) {
					// first[i++] first中添加数据,若first指的是jquery的实例对象,即jquery对象中会存储我们查询出来的dom节点
						first[i++] = second[j]
					}
				} else {
					while (second[j] !== undefined) {
						first[i++] = second[j++]
					}
				}
				first.length = i
				return first
		},
		// 解析创建dom节点
		parseHTML: function (data, context) {
			if (!data || typeof data !== 'string') {
				return null
			}
			// 过滤器,data:<a> => a。用正则来提取selector 里面的标签名
			  /* 
			  如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,
			  第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),
			  第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),
			  以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。
			  input 属性则存放的是被检索的字符串 string。
			  可以看得出,在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的 
			  */
			var parse = rejectExp.exec(data);
			console.log(parse)	// ["<a>", "a", index: 0, input: '<a>']
			return [context.createElement(parse[1])]
		}
	})

DOM对象 与 jQuery对象

DOM对象:
由宿主环境提供的对象,在文档对象模型中,每个部分都是节点,如 所有html元素是元素节点,而element对象表示html元素。HTML DOM elment对象可以拥有类型为元素节点、文本节点、注释节点的子节点。
jQuery对象:
jQuery构造函数创建出来的对象。通过jQuery选择器可以获取html的元素,并且以一种类数组的形式存储在jq对象中。

// 传入对象
$(this) $(document) 把传入的对象包装成jq对象

// 传入函数
$(function(){}) 这个是在页面dom文档加载完成后加载执行,相当于 在dom加载完毕后执行$(document).ready()

// 传入字符串
$('.box') 查询dom节点包装成jq对象

// 传入html字符串
$('<div'>) 创建dom节点包装成jq对象

// 空
$() 创建jq对象

ps:一些问题总结:

如何把创建的dom节点包装成jq对象?

context.createElement创建dom节点存储在数组中,调用merge方法把数组中存储的dom节点的成员添加到jq实例对象上。

jq实例对象length属性的作用?

存储dom节点的数组对象平滑地添加到jq实例对象上

merge方法的应用场景:

合并数组 & 把数组成员合并在有length属性的对象上

$(document).ready() 与 $(function(){})的关系?

$(document).ready() 是对 document.DOMContentLoaded事件封装,而(function(){})每次调用$()传入的参数会收集在readyList数组中,当document.DOMContentLoaded事件触发依次执行readyList中收集的处理函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值