2.1问题探究分析
1.如何把创建的DOM节点包装成jQuery对象?
2.jQuery实例对象length属性的作用?
3.merge方法的应用场景有哪些?
4.$(document).ready() 与$(function(){})的关系?
2.2.1jQuery的选择器实现----创建DOM节点
结合第一节的代码实现$("<tagName>")的形式创建一个dom标签节点的功能。
在原先代码的基础上先定义一个parseHTML方法和merge方法(parseHTML方法用于将html标签转成游离动态的dom节点对象
而merge方法用于把数组成员合并在有length属性的对象上),扩展代码如下:
......
// 为当前的闭包中的jQuery实例扩展一些类型检测的方法(此时执行的是浅度拷贝的代码故深度拷贝的代码存在不会影响extend的执行)
jQuery.extend({
......
// 使用上下文的createElement的API将html标签转成游离动态的dom节点对象
parseHTML: function(data,context){
if(!data || typeof data !== "string"){// data 不为空或 data类型不是string
return null
}
// 使用正则过滤器提取html标签名(例'<a>' 提取为=> 'a')
var parse = /^<(\w+)\s*\/?>(?:<\/\1>|)$/.exec(data) //exec()找到了匹配的文本,则返回一个结果数组
return [context.createElement(parse[1])] // 返回一个存储dom节点对象的数组
},
// 把数组成员合并在有length属性的对象上
merge: function(first,second){// 合并数组
var l = second.length,
i = first.length,
j = 0
if(typeof l === "number"){ // 第二个参数是一个含有length属性的对象
for(;j < l;j++){ //dom节点存储到jQuery的实例对象
first[i++] = second[j] // JS标识符的知识点:在JS中对象的属性名可以为数字并且JS中的数组是可以动态添加长度的
}
} else {
while (second[j] !== undefined){
first[i++] = second[j++]
}
}
first.length = i
return first
},
......
})
......
结合上述代码实现创建dom节点的扩展代码,如下:
......
// jQuery的属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
// 扩展代码
length: 0,// 实现存储dom节点的数组对象平滑地添加到jQuery实例对象上
jQuery: "1.0.0",// 定义jQuery的版本
selector: "",
// 实现jQuery的选择器功能
init: function(selector,context){
init: function(){
context = context || document // context为空则上下文依旧为window的document对象
var match
if(!selector) { // 如果传入的参数为空
return this // 返回当前调用对象
}
// 如果传参是字符串
if(typeof selector === "string") {
// 判断是否满足html标签的结构要求
if(selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' && selector.length >= 3){
// 将该html标签存入一个数组里
match = [selector]
}
// match数组存在则判定为dom节点的创建操作
if(match){
// merge方法用于把数组成员合并在有length属性的对象上(而此时的参数一为this对象即Object类型,参数二为[动态dom节点]即数组类型)
jQuery.merge(this,jQuery.parseHTML(selector,context))
}
}
}
},
}
......
2.2.2jQuery的选择器实现----查询DOM节点
不论是创建dom还是查询dom我们都需要使用$()里面丢参数,所以我们只需要在创建dom代码的判断条件下继续判断是否为查询dom节点的功能,故上面代码进一步扩展如下:
......
// jQuery的属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
// 扩展代码
length: 0,// 实现存储dom节点的数组对象平滑地添加到jQuery实例对象上
jQuery: "1.0.0",// 定义jQuery的版本
selector: "",
// 实现jQuery的选择器功能
init: function(selector,context){
context = context || document // context为空则上下文依旧为window的document对象
var match
var elem,index = 0 // 进一步扩展判断是否为查询dom功能操作
if(!selector) { // 如果传入的参数为空
return this // 返回当前调用对象
}
// 如果传参是字符串
if(typeof selector === "string") {
// 判断是否满足html标签的结构要求
if(selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' && selector.length >= 3){
// 将该html标签存入一个数组里
match = [selector]
}
// match数组存在则判定为dom节点的创建操作
if(match){
// merge方法用于把数组成员合并在有length属性的对象上(而此时的参数一为this对象即Object类型,参数二为[动态dom节点]即数组类型)
jQuery.merge(this,jQuery.parseHTML(selector,context)) // 即将dom节点存储到实例对象上
} else {// 进一步扩展判断是否为查询dom功能操作
elem = document.querySelectorAll(selector) // 获取到的是一个类数组
var elems = Array.prototype.slice.call(elem) // 将类数组转数组(Array.prototype.slice.call能将有length属性的对象转换为数组)
this.length = elems.length
for(;index < elems.length;index++){ // 为this进行扩展(即将查询到的dom节点添加到jQuery实例对象上去)
// 将查询到的dom节点存储到jQuery实例对象上
this[index] = elems[index]
}
this.context = context
this.selector = selector
}
} else if (selector.modeType){// 传参如果是对象
this.context = this[0] = selector // 存储该对象到jQuery实例对象中
this.length = 1 // 并标记当前类数组的长度
return this // 返回当前jQuery实例对象
}
},
......
}
......
2.2.3jQuery的选择器实现----函数执行
如果$()中的传参是函数则需要先检测文档DOM是否已经加载完毕,故此需要继续对闭包里的jQuery扩展一些属性和方法,如下:
......
// 为当前的闭包中的jQuery实例扩展一些类型检测的方法(此时执行的是浅度拷贝的代码故深度拷贝的代码存在不会影响extend的执行)
jQuery.extend({
......
// 默认当前文档DOM没有被加载
isReady: false,
// 回调列表
readyList: [],
// ready当作是闭包中jQuery对象的一个事件函数
ready: function(){
jQuery.isReady = true
// 依次执行回调列表中的函数
jQuery.readyList.forEach(callback => {
callback.call(document)
});
// 此处如果有很多回调函数设置为null能达到小小的性能优化
jQuery.readyList = null
},
......
})
......
最后还需要添加原型方法ready进一步检测DOM是否加载完毕以便调用jQuery实例对象扩展的ready方法,如下:
......
// jQuery的属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
length: 0, // 实现存储dom节点的数组对象平滑地添加到jQuery实例对象上
jquery: "1.0.0",
selector: "",
init: function(selector,context){// context为指定查询的范围
context = context || document //设置context默认为document
var match,elem,index = 0
if(!selector){
return this
}
// 传参是字符串
if(typeof selector === "string"){
// 判断是创建dom节点操作还是查询dom节点操作
if(selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3){
match = [selector] // 将html标签字符串存在一个数组里
}
// match存在则是创建dom节点操作
if(match){
// merge方法用于合并数组(而此时的参数一为this对象即object类型,参数二为[动态dom节点]即数组类型)
jQuery.merge(this,jQuery.parseHTML(selector,context))
} else {
// 否则查询dom节点
elem = document.querySelectorAll(selector) // 获取到的是一个类数组
var elems = Array.prototype.slice.call(elem) // 将类数组转数组(Array.prototype.slice.call能将有length属性的对象转换为数组)
this.length = elems.length
for(;index < elems.length;index++){ // 为this进行扩展(即将查询到的dom节点添加到jQuery实例对象上去)
// 将查询到的dom节点存储到jQuery实例对象上
this[index] = elems[index]
}
this.context = context
this.selector = selector
}
} else if (selector.modeType){ // 传参是对象
this.context = this[0] = selector // 存储该对象到jQuery实例对象中
this.length = 1
return this // 返回jQuery的this
} else if (typeof selector === "function"){ // 传参是函数
// 此处调用的是原型上的ready(因为对象中的ready是无参数的故解释器会寻找原型上带参的ready进行调用)
rootjQuery.ready(selector)
}
},
ready: function(fn){
// 进一步检测DOM是否加载完毕,是则会访问jQuery对象的ready方法
document.addEventListener("DOMContentLoaded",jQuery.ready,false)
// 检测上面的DOMContentLoaded是否执行了
if(jQuery.isReady){// 默认false
fn.call(document) // 绑定执行上下文的对象为document
}else{// DOMContentLoaded没被触发
jQuery.readyList.push(fn) // 将处理函数放入回调列表
}
}
},
......
// 新建一个上下文为document的jQuery对象
var rootjQuery = jQuery(document)
2.3问题解答
问题1解答:context.createElement创建DOM节点存储在数组中,调用merge方法把数组中存储的DOM节点成员添加到jQuery实例对象上.
问题2解答:存储DOM节点的数组对象平滑地添加到jQuery实例对象上
问题3解答:1.合并数组2.把数组成员合并在有length属性的对象上
问题4解答:$(document).ready()是对document.DOMContentLoaded事件封装,$(function(){})每次调用$()传入的参数会收集在readyList数组中,当document.DOMContentLoaded事件触发时依次执行readyList中收集的处理函数
2.4完整代码
// $(function(){})每次调用$()传入的参数会收集在readyList数组中,当document.DOMContentLoaded事件触发依次执行readyList中收集的处理函数
(function(root){
// 正则判断是否为HTML标签
var rejectExp = /^<(\w+)\s*\/?>(?:<\/\1>|)$/ // ?:作用是非获取匹配,匹配冒号后的内容但不获取匹配结果,不进行存储供以后使用。而当“?”紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串,而默认的贪婪模式则尽可能多地匹配所搜索的字符串
var version = "1.0.1" // 定义jQuery版本
var jQuery = function(selector,context){
return new jQuery.prototype.init(selector,context) // 通过原型创建jQuery来实现无new构造,解决new的死循环构造
}
// jQuery的属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
length: 0, // 实现存储dom节点的数组对象平滑地添加到jQuery实例对象上
jquery: version,
selector: "",
init: function(selector,context){// context为指定查询的范围
context = context || document //设置context默认为document
var match,elem,index = 0
// $() $(undefined) $(null) $(false)的情况
if(!selector){
return this
}
// 传参是字符串
if(typeof selector === "string"){
// 判断是创建dom节点操作还是查询dom节点操作
if(selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3){
match = [selector] // 将html标签字符串存在一个数组里
}
// match存在则是创建dom节点操作
if(match){
// merge方法用于合并数组(而此时的参数一为this对象即object类型,参数二为[动态dom节点]即数组类型)
jQuery.merge(this,jQuery.parseHTML(selector,context))
} else {
// 否则查询dom节点
elem = document.querySelectorAll(selector) // 获取到的是一个类数组
var elems = Array.prototype.slice.call(elem) // 将类数组转数组(Array.prototype.slice.call能将有length属性的对象转换为数组)
this.length = elems.length
for(;index < elems.length;index++){ // 为this进行扩展(即将查询到的dom节点添加到jQuery实例对象上去)
this[index] = elems[index] // 将查询到的dom节点存储到jQuery实例对象上
}
this.context = context
this.selector = selector
}
} else if (selector.modeType){ // 传参是对象
this.context = this[0] = selector // 存储该对象到jQuery实例对象属性中并记录其作为上下文
this.length = 1
return this // 返回jQuery的实例对象
} else if (typeof selector === "function"){ // 传参是函数
// 此处调用的是原型上的ready(因为对象中的ready是无参数的故解释器会寻找原型上带参的ready进行调用)
rootjQuery.ready(selector) // 调用原型上的ready
}
},
ready: function(fn){
// 进一步检测DOM是否加载完毕,是则会访问jQuery对象的ready方法
document.addEventListener("DOMContentLoaded",jQuery.ready,false)
// 检测上面的DOMContentLoaded是否执行了
if(jQuery.isReady){// 默认false
fn.call(document) // 绑定执行上下文的对象为document
}else{// DOMContentLoaded没被触发
jQuery.readyList.push(fn) // 将处理函数放入回调列表
}
}
}
// extend扩展函数的实现(使得外部不管是$.fn.extend或者$.extend的形式调用最终都指向同一个匿名函数)
jQuery.fn.extend = jQuery.extend = function(){
// 判断参数来进行确定是为实例对象扩展还是自身属性的扩展
var target = arguments[0] || {} // 如果第一个参数对象为空则创建一个空对象赋值
var length = arguments.length
var i = 1
var deep = false // 作深一层的考量是否需要深度拷贝
var option,name,copy,src,copyIsArray,clone
if(typeof target === "boolean"){
deep = target
target = arguments[1]
i = 2
}
if(typeof target !== "object"){
target = {}
}
// 参数的个数只为一个则是对jQuery本身扩展
if(length === i){
target = this;
i--; // 把i变成0使得for循环能够进行浅拷贝的执行
}
// 浅拷贝: 遍历第二个及其后面的参数对象 深拷贝: 遍历第三个及其后面的参数对象
for(;i < length;i++){
if(arguments[i]){
if((option = arguments[i]) != null){
for(name in option){
// console.log(name)
// target[name] = option[name];
copy = option[name]
src = target[name] // 被扩展的对象
// 若当前模式为深拷贝,copy的值则必须为对象或数组(两者之一)
if(deep && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )){
if(copyIsArray){ // copy是一个数组
copyIsArray = false
clone = src && jQuery.isArray(src) ? src : []; // 如果当前的target不是数组则将clone设置为数组
} else { // 否则就是一个对象
clone = src && jQuery.isPlainObject(src) ? src : {}; // 如果当前的target不是对象则将clone设置为对象
}
target[name] = jQuery.extend(deep,clone,copy) // 递归完成深拷贝
}else if(copy !== undefined){
target[name] = copy
}
}
}
}
}
return target
}
//实现共享原型
jQuery.fn.init.prototype = jQuery.fn
// 为当前的闭包中的jQuery实例对象扩展一些类型检测的方法
jQuery.extend({
isPlainObject: function(obj){
return toString.call(obj) === "[object Object]"
},
isArray: function(obj){
return toString.call(obj) === "[object Array]"
},
isFunction: function(fn){
return toString.call(fn) === "[object Function]"
},
markArray: function(arr,results){// 将类数组转换成真正的数组
var ret = results || []
if(arr != null){
jQuery.merge(ret,typeof arr === "string" ? [arr] : arr)
}
return ret
},
// 把数组成员合并在有length属性的对象上
merge: function(first,second){// 合并数组
var l = second.length,
i = first.length,
j = 0
if(typeof l === "number"){ // second含有length
for(;j < l;j++){ //dom节点存储到jQuery的实例对象
first[i++] = second[j] // JS知识点:在JS中对象的属性名可以为数字并且JS中的数组是可以动态添加长度的
}
} else {
while (second[j] !== undefined){
first[i++] = second[j++]
}
}
first.length = i
return first // first指的是jQuery实例对象
},
parseHTML: function(data,context){
if(!data || typeof data !== "string"){// data 不为空或 data类型不是string
return null
}
// 使用正则过滤器提取html标签名(例'<a>' 提取为=> 'a')
var parse = rejectExp.exec(data) //exec()找到了匹配的文本,则返回一个结果数组
console.log("parse=>",parse)
return [context.createElement(parse[1])] //为上下文创建dom节点
},
isReady: false,// 当前DOM默认没有被加载
readyList: [],// 回调列表(也可以使用callback)
ready: function(){// 当作是一个事件函数
jQuery.isReady = true
jQuery.readyList.forEach(callback => {
callback.call(document) // callback执行的上下文对象设置为document
});
jQuery.readyList = null // 如果有很多回调函数设置为null能达到小小的性能优化
}
})
root.$ = root.jQuery = jQuery;
// 建立一个jQuery内部的jQuery对象,该jQuery对象指向的上下文为document
var rootjQuery = root.$(document)
})(this)// this指向当前的window对象