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 ),这样就避免了死循环的问题,而且实现了一步到位的初始化。
- jq将代码封装在一个匿名函数里面,防止污染全局环境,然后创建一个对象jQuery,这个对象接受两个参数selector,context,其中selector就是我们通常传入的 #id,.class等字符串。
- init方法接收3个参数,在内部进行了各种判断,根据不同的场景,返回一个不同的实例对象,类似一个工厂方法。
- 将jQuery.fn.init的原型挂载到jquery的原型上。
- 将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中收集的处理函数