JQuery设计模式之源码分析(二)

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对象

下一篇JQuery设计模式之源码分析(三)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值