上一篇已经搭建了一个非常简陋的jQuery框架雏形,如没有阅读搭建过程,请先阅读《jQuery内核详解与实践读书笔记1:原型技术分解1》初始搭建过程。接下来,完成书中介绍的剩下三个步骤:
7. 延续--功能扩展
jQuery框架是通过extend()函数来扩展功能的,extend()函数的功能实现起来也很简单,它只是吧指定对象的方法复制给jQuery对象或jQuery.prototype对象,如下示例代码就为jQuery类和原型定义了一个扩展功能的函数extend()。
1 var $ = jQuery = function(selector, context) { //定义类 2 return new jQuery.fn.init(selector, context); //返回选择器的实例 3 }; 4 jQuery.fn = jQuery.prototype = { //jQuery类的原型对象 5 init : function(selector, context) { //定义选择器构造器 6 selector = selector || document; //设置默认值为document 7 context = context || document; //设置默认值为document 8 if(selector.nodeType) { //如果选择符为节点对象 9 this[0] = selector; //把参数节点传递给实例对象的数组 10 this.length = 1; //并设置实例对象的length属性,定义包含的元素个数 11 this.context = selector; //设置实例的属性,返回选择范围 12 return this; //返回当前实例 13 } 14 if(typeof selector === "string") { //如果选择符是字符串 15 var e = context.getElementsByTagName(selector); //获取指定名称的元素 16 for(var i=0; i<e.length; i++) { //遍历元素集合,并把所有元素填入到当前实例数组中 17 this[i] = e[i]; 18 } 19 this.length = e.length; //设置实例的length属性,即定义包含的元素个数 20 this.context = context; //设置实例的属性,返回选择范围 21 return this; //返回当前实例 22 } else { 23 this.length = 0; //否则,设置实例的length属性值为0 24 this.context = context; //设置实例的属性,返回选择范围 25 return this; //返回当前实例 26 } 27 }, 28 jquery : "1.3.2", //原型属性 29 size : function() { //原型方法 30 return this.length; 31 } 32 }; 33 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象 34 //jQuery功能扩展函数 35 jQuery.extend = jQuery.fn.extend = function(obj) { 36 for(var prop in obj) { 37 this[prop] = obj[prop]; 38 } 39 return this; 40 }; 41 //扩展jQuery对象方法 42 jQuery.fn.extend({ 43 test : function() { 44 alert("测试扩展功能"); 45 } 46 });
在上面的代码中,先定义了一个功能扩展函数extend(),然后为jQuery.fn原型对象调用extend()函数,为其添加一个测试方法test()。这样就可以在实践中应用,如$("div").test()。
jQuery框架定义的extend()函数的功能要强大很多,它不仅能够完成基本的功能扩展,还可以实现对象合并等功能,代码和解释如下所示:
1 var $ = jQuery = function(selector, context) { //定义类 2 return new jQuery.fn.init(selector, context); //返回选择器的实例 3 }; 4 jQuery.fn = jQuery.prototype = { //jQuery类的原型对象 5 init : function(selector, context) { //定义选择器构造器 6 selector = selector || document; //设置默认值为document 7 context = context || document; //设置默认值为document 8 if(selector.nodeType) { //如果选择符为节点对象 9 this[0] = selector; //把参数节点传递给实例对象的数组 10 this.length = 1; //并设置实例对象的length属性,定义包含的元素个数 11 this.context = selector; //设置实例的属性,返回选择范围 12 return this; //返回当前实例 13 } 14 if(typeof selector === "string") { //如果选择符是字符串 15 var e = context.getElementsByTagName(selector); //获取指定名称的元素 16 for(var i=0; i<e.length; i++) { //遍历元素集合,并把所有元素填入到当前实例数组中 17 this[i] = e[i]; 18 } 19 this.length = e.length; //设置实例的length属性,即定义包含的元素个数 20 this.context = context; //设置实例的属性,返回选择范围 21 return this; //返回当前实例 22 } else { 23 this.length = 0; //否则,设置实例的length属性值为0 24 this.context = context; //设置实例的属性,返回选择范围 25 return this; //返回当前实例 26 } 27 }, 28 jquery : "1.3.2", //原型属性 29 size : function() { //原型方法 30 return this.length; 31 } 32 }; 33 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象 34 //jQuery功能扩展函数 35 jQuery.extend = jQuery.fn.extend = function(obj) { 36 //定义复制操作的目标对象 37 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 38 //获取是否深度复制处理 39 if( typeof target === "boolean") { 40 deep = target; 41 target = arguments[i] || {}; 42 //跳出布尔值和目标对象 43 i = 2; 44 } 45 //如果第一个参数是字符串,则设置为空对象 46 if(typeof target !== "object" && !jQuery.isFunction(target)) { 47 target = {}; 48 } 49 //如果只有一个参数,表示把参数对象的方法复制给当前对象,则设置target为this 50 if(length == i) { 51 target = this; 52 --i; 53 } 54 for( ; i<length; i++) { 55 //若参数值不为null,则进行处理 56 if((options = arguments[i]) != null) { 57 //扩展基类对象 58 for( var name in options) { //遍历参数对象 59 var src = target[name], copy = options[name]; 60 //防止死循环 61 if(target === copy) { 62 continue; 63 } 64 //递归运算 65 if(deep && copy && typeof copy === "object" && !copy.nodeType) { 66 target[name] = jQuery.extend(deep, 67 //不要复制原对象 68 src || (copy.length != null ? [] : {}), copy); 69 } else if(copy != undefined) { //不要传递未定义的值 70 target[name] = copy; 71 } 72 } 73 } 74 } 75 //返回修改后的对象 76 return target; 77 };
以上代码就是jQuery框架提供的功能扩展代码。
8. 延续--参数处理
jQuery的方法都要求传递的参数为对象结构,这是因为jQuery框架的很多方法都包含大量的参数,且都是可选的,位置也没有固定的要求,所以使用对象直接量是唯一的解决方法。
使用对象直接量作为参数传递的载体,如何解析并提出参数?如何处理参数的默认值?可以通过下面的方式:
1 var $ = jQuery = function(selector, context) { //定义类 2 return new jQuery.fn.init(selector, context); //返回选择器的实例 3 }; 4 jQuery.fn = jQuery.prototype = { //jQuery类的原型对象 5 init : function(selector, context) { //定义选择器构造器 6 selector = selector || document; //设置默认值为document 7 context = context || document; //设置默认值为document 8 if(selector.nodeType) { //如果选择符为节点对象 9 this[0] = selector; //把参数节点传递给实例对象的数组 10 this.length = 1; //并设置实例对象的length属性,定义包含的元素个数 11 this.context = selector; //设置实例的属性,返回选择范围 12 return this; //返回当前实例 13 } 14 if(typeof selector === "string") { //如果选择符是字符串 15 var e = context.getElementsByTagName(selector); //获取指定名称的元素 16 for(var i=0; i<e.length; i++) { //遍历元素集合,并把所有元素填入到当前实例数组中 17 this[i] = e[i]; 18 } 19 this.length = e.length; //设置实例的length属性,即定义包含的元素个数 20 this.context = context; //设置实例的属性,返回选择范围 21 return this; //返回当前实例 22 } else { 23 this.length = 0; //否则,设置实例的length属性值为0 24 this.context = context; //设置实例的属性,返回选择范围 25 return this; //返回当前实例 26 } 27 }, 28 setOptions : function(options) { 29 this.options = { //方法的默认值,可以扩展 30 StartColor : "#000", 31 EndColor : "#DDC", 32 Background : false, 33 Step : 20, 34 Speed : 10 35 }; 36 jQuery.extend(this.options, options || {}); //如果传递参数,则覆盖原默认参数 37 } 38 }; 39 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象 40 //jQuery功能扩展函数 41 jQuery.extend = jQuery.fn.extend = function(destination, source) {//重新定义extend()函数 42 for(var property in source) { 43 destination[property] = source[proprty]; 44 } 45 return destination; 46 };
在上面的示例中,定义了一个原型方法setOptions(),该方法能够对传递的参数对象进行处理,并覆盖默认值。
在jQuery框架中,extend()函数包含了所有功能,它既能为当前对象扩展方法,也能处理参数对象,并覆盖默认值。
9. 涅槃--名字空间
在介绍该内容前,先介绍一个JavaScript函数中的一个核心概念:闭包,其实就是匿名函数。闭包的一个很重要的作用就是将闭包内部的代码封装在一个封闭的空间中,不将内部的信息暴露出来,别的代码也不能随意访问闭包内的函数或变量等。只需要提供外接可以访问的接口,就可以方便的与外接进行联系。
我们看jQuery源代码时就会发现,它几千行的代码就是封装在一个闭包之中的,这样就解决了框架内部的变量名与其它框架或JavaScript代码重名冲突问题。另外jQuery框架的$和jQuery名字也很有可能发生名字冲突,这个问题jQuery框架是如何解决的呢?
首先,jQuery的所有代码全部封装在一个闭包(即匿名函数)中
其次,jQuery提供了一个noConfilit()函数,该函数实现禁止jQuery框架使用这两个名字。
那它又是如何做的呢?
jQuery在框架的最前面,先使用_$和_jQuery临时变量寄存$和jQuery这两个变量的内容,当需要禁用jQuery框架的名字时,可以使用一个临时变量_$和_jQuery恢复$和jQuery这两个变量的实际内容。代码如下:
1 (function(){ 2 var window = this, 3 undefined, 4 _jQuery = window.jQuery, //缓存jQuery变量内容 5 _$ = window.$, 6 jQuery = window.jQuery = window.$ = function(selector, context) { 7 return new jQuery.fn.init(selector, context); 8 }, 9 quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, 10 isSimple = /^.[^:#\[\.,]*$/; 11 jQuery.fn = jQuery.prototype = { 12 init : function(selector, context) {} 13 }; 14 })();
至此,jQuery框架的简单雏形就已经搭好了,虽然jQuery框架的功能是极其强大的,但是它也是在这个架构上建立起来的。后面的工作就是根据应用需要或者功能需要,使用extend()函数不断扩展jQuery框架的工具函数和jQuery对象的方法。
今天就先写到这里了,下次就要开始研究jQuery的选择器接口了,欢迎转载,转载时请注明出处。
个人微信公众号:programmlife,如有兴趣敬请关注,主要内容是一个码农的所看所思所想所叹,或扫描下方二维码关注: