jQuery技术

jQuery :技术解密(二)

字号+ 作者:H5之家 来源:H5之家 2016-08-02 16:00 我要评论( )

jQuery :技术解密(二) 2.2.6 延续 -- 迭代器在 jQuery 框架中,jQuery 对象是一个很奇怪的概念,具有多重身份,所以很多初学者一听说 jQuery 对象就感觉很是不解,误以为它是 John Resig 制造的新概念。我们可以对jQuery 对象进行如下分解。 第一,jQuery

jQuery :技术解密(二)


2.2.6 延续 -- 迭代器在 jQuery 框架中,jQuery 对象是一个很奇怪的概念,具有多重身份,所以很多初学者一听说 jQuery 对象就感觉很是不解,误以为它是 John Resig 制造的新概念。我们可以对jQuery 对象进行如下分解。

第一,jQuery 对象是一个数据集合,它不是一个个体对象。因此,你无法直接使用 JavaScript 的方法来操作它。

第二,jQuery 对象实际上就是一个普通的对象,因为它是通过 new 运算符创建的一个新的实例对象。它可以继承原型方法或属性,同时也拥有 Object 类型的方法和属性。

第三,jQuery 对象包含数组特性,因为它赋值了数组元素,以数组结构存储返回的数据。我们可以以 JavaScript 的概念理解 jQuery 对象,例如下面的示例。

[html] view
plain

<script type="text/javascript">

var jquery = { // 定义对象直接量

name: "jQuery", // 以属性方式存储信息

value: "1.3.2"

};

jquery[0] = "jQuery"; // 以数组方式存储信息

jquery[1] = "1.3.2";

alert(jquery.name); // 返回 "jQuery"

alert(jquery[0]); // 返回 "jQuery"

</script>

上面的 jQuery 对象就是一个典型的 jQuery 对象,jQuery 对象的结构就是按这种形式设计的。可以说,jQuery
对象就是对象和数组的混合体,但是它不拥有数组的方法,因为它的数组结构是人为附加的,也就是说它不是 Array 类型数据,而是 Object 类型数据。

第四,jQuery 对象包含的数据都是 DOM 元素,是通过数组形式存储的,即通过 jQuery
形式获取。同时 jQuery 对象又定义了几个模仿 Array 基本特性的属性,如 length 等。

所以,jQuery 对象是不允许直接操作的,只有分别读取它包含的每一个 DOM 元素,才能实现各种操作,如插入、删除、嵌套、赋值和读写 DOM 元素属性等。

那么如何实现直接操作 jQuery 对象中的 DOM 元素呢?

在实际应用中,我们可以看到类似下面的 jQuery 用法。

$("div").html()

也就是直接在 jQuery 对象上调用 html(),并实现操作 jQuery 包含的所有 DOM 元素。那么这个功能是怎么实现的呢?

jQuery 定义了一个工具函数 each(),利用这个工具可以遍历 jQuery 对象中所有的 DOM 元素,并把需要操作的内容封装到一个回调函数中,然后通过在每个 DOM 元素上调用这个回调函数即可。实现代码如下所示,演示效果如图 2.2 所示。

[html] view
plain

<script type="text/javascript">

var $ = jQuery = function(selector, context){ // 定义类

return new jQuery.fn.init(selector, context); // 返回选择器的实例

};



jQuery.fn = jQuery.prototype = { // jQuery 类的原型对象

init: function(selector, context){ // 定义选择器构造器

selector = selector || document; // 设置默认值为 document

context = context || document; // 设置默认值为 document



if(selector.nodeType){ // 如果选择符为节点对象

this[0] = selector; // 把参数节点传递给实例对象的数组

this.length = 1; // 并设置实例对象的 length 属性,定义包含的元素个数

this.context = selector; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

if(typeof selector === "string"){ // 如果选择符是字符串

var e = context.getElementsByTagName(selector); // 获取指定名称的元素

for(var i = 0; i<e.length; i++){ // 遍历元素集合,并把所有元素填入到当前实例数组中

this[i] = e[i];

}

this.length = e.length; // 设置实例的 length 属性,即定义包含的元素个数

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

} else {

this.length = 0; // 否则,设置实例的 length 属性值为 0

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

},

html: function(val){ // 模仿 jQuery 框架中的 html() 方法,为匹配的每一个DOM元素插入html代码

jQuery.each(this, function(val){ // 调用 jQuery.each() 工具函数,为每一个 DOM 元素执行回调函数

this.innerHTML = val;

}, val);

},

jquery: "1.3.2", // 原型属性

size: function(){ // 原型方法

return this.length;

}

};



jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象

// 扩展 jQuery 工具函数

jQuery.each = function(object, callback, args){

for(var i=0; i<object.length; i++){

callback.call(object[i], args);

}

return object;

};



$("div").html("测试代码");

</script>

在上面的示例中,通过先为自己的 jQuery 对象绑定 html() 方法,然后利用 jQuery() 选择器获取页面中所有的 div 元素,再调用 html() 方法,为所有匹配的元素插入 HTML 源码。

注意,在上面的代码中,each() 函数的当前作用对象是 jQuery 对象,故 this 指向当前 jQuery 对象,即 this 表示一个集合对象;而在 html() 方法中,由于 each() 函数是在指定 DOM 元素上执行的,所以该函数内的 this 指针指向的是当前 DOM 元素对象,即 this 表示一个元素。




2.2.7 延续 -- 功能扩展根据一般设计习惯,如果要为 jQuery 或者 jQuery.prototype 添加函数或方法,可以直接通过点语法实现,或者在 jQuery.prototype 对象结构中增加一个属性即可。但是,如果分析 jQuery 框架的源代码,你会发现它是通过 extend() 函数来实现功能扩展的。例如,下面两段代码都是 jQuery 框架通过 extend() 函数来扩展功能的。

jQuery.extend({ // 扩展工具函数

noConflict: function(deep){},

isFunction: function(obj){},

isArray: function(obj){},

isXMLDoc: function(elem){},

globalEval: function(data){}

});

或者

jQuery.fn.extend({ // 扩展 jQuery 对象方法

show: function(speed, callback){},

hide: function(speed, callback){},

toggle: function(fn, fn2){},

fadeTo: function(speed, to, callback){},

animate: function(prop, speed, easing, callback){},

stop: function(clearQueue, gotoEnd){}

});

这样做的好处是什么呢?

extend() 函数能够方便用户快速扩展 jQuery 框架的功能,但是不会破坏 jQuery 框架的原型结构,从而避免后期人工手动添加工具函数或者方法破坏 jQuery 框架的单纯性,同时也方便管理。如果不需要某个插件,只需要简单地删除即可,而不需要在 jQuery 框架源代码中去筛选和删除。

extend() 函数的功能实现起来也很简单,它只是把指定对象的方法复制给 jQuery 对象或者 jQuery.prototype 对象。例如,在下面的示例中,我们为 jQuery 类和原型定义了一个扩展功能的函数 extend() ,该函数的功能很简单,它能够把指定参数对象包含的所有属性复制给 jQuery 或者 jQuery.prototype 对象,这样就可以在应用中随时调用它,并动态扩展 jQuery 对象的方法。

[html] view
plain

<div></div>

<div></div>

<div></div>

<script type="text/javascript">

var $ = jQuery = function(selector, context){ // 定义类

return new jQuery.fn.init(selector, context); // 返回选择器的实例

};



jQuery.fn = jQuery.prototype = { // jQuery 类的原型对象

init: function(selector, context){ // 定义选择器构造器

selector = selector || document; // 设置默认值为 document

context = context || document; // 设置默认值为 document



if(selector.nodeType){ // 如果选择符为节点对象

this[0] = selector; // 把参数节点传递给实例对象的数组

this.length = 1; // 并设置实例对象的 length 属性,定义包含的元素个数

this.context = selector; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

if(typeof selector === "string"){ // 如果选择符是字符串

var e = context.getElementsByTagName(selector); // 获取指定名称的元素

for(var i = 0; i<e.length; i++){ // 遍历元素集合,并把所有元素填入到当前实例数组中

this[i] = e[i];

}

this.length = e.length; // 设置实例的 length 属性,即定义包含的元素个数

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

} else {

this.length = 0; // 否则,设置实例的 length 属性值为 0

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

},

jquery: "1.3.2", // 原型属性

size: function(){ // 原型方法

return this.length;

}

};



jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象

// jQuery 功能扩展函数

jQuery.extend = jQuery.fn.extend = function(obj){

for(var prop in obj){

this[prop] = obj[prop];

}

return this;

};

// 扩展 jQuery 对象方法

jQuery.fn.extend({

test: function(){

alert("测试扩展功能");

}

});

// 测试代码

$("div").test();

</script>

在上面的示例中,先定义了一个功能扩展函数 extend(),然后为 jQuery.fn 原型对象调用 extend() 函数,为其添加一个测试方法 test()。这样就可以在实践中应用,如 $("div").test() 。

jQuery 框架定义的 extend() 函数的功能要强大很多,它不仅能够完成基本的功能扩展,还可以实现对象合并等功能。

2.2.8 延续 -- 参数处理在很多时候,你会发现 jQuery 的方法都要求传递的参数为对象结构,例如:

$.ajax({

type: "GET",

url: "test.js",

dataType: "script"

});

使用对象直接量作为参数进行传递,方便参数管理。当方法或者函数的参数长度不固定时,使用对象直接量作为参数存在很多优势。例如,对于下面的用法,ajax()函数就需要进行更加复杂的参数排查和过滤。

$.ajax("GET", "test.js", "script");

如果 ajax() 函数的参数长度是固定的,且是必须的,那么通过这种方式进行传递也就无所谓了,但是如果参数的个数和排序是动态的,那么使用 $.ajax("GET", "test.js", "script"); 这种方法是无法处理的。而 jQuery 框架的很多方法都包含大量的参数,且都是可选的,位置也没有固定要求,所以使用对象直接量是惟一的解决方法。

使用对象直接量作为参数传递的载体,这里就涉及参数处理问题。如何解析并提出参数?如何处理参数和默认值?我们可以通过下面的方法来实现。

[html] view
plain

<script type="text/javascript">

var $ = jQuery = function(selector, context){ // 定义类

return new jQuery.fn.init(selector, context); // 返回选择器的实例

};



jQuery.fn = jQuery.prototype = { // jQuery 类的原型对象

init: function(selector, context){ // 定义选择器构造器

selector = selector || document; // 设置默认值为 document

context = context || document; // 设置默认值为 document



if(selector.nodeType){ // 如果选择符为节点对象

this[0] = selector; // 把参数节点传递给实例对象的数组

this.length = 1; // 并设置实例对象的 length 属性,定义包含的元素个数

this.context = selector; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

if(typeof selector === "string"){ // 如果选择符是字符串

var e = context.getElementsByTagName(selector); // 获取指定名称的元素

for(var i = 0; i<e.length; i++){ // 遍历元素集合,并把所有元素填入到当前实例数组中

this[i] = e[i];

}

this.length = e.length; // 设置实例的 length 属性,即定义包含的元素个数

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

} else {

this.length = 0; // 否则,设置实例的 length 属性值为 0

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

},

setOptions: function(options){

this.options = { // 方法的默认值,可以扩展

StartColor: "#000",

EndColor: "#DDC",

Step: 20,

Speed: 10

};

jQuery.extend(this.options, options || {}); // 如果传递参数,则覆盖原默认参数

},

jquery: "1.3.2", // 原型属性

size: function(){ // 原型方法

return this.length;

}

};



jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象



jQuery.extend = jQuery.fn.extend = function(destination, source){ // 重新定义 extend() 函数

for (var property in source){

destination[property] = source[property];

}

return destination;

};

</script>

在上面的示例中,定义了一个原型方法 setOptions(),该方法能够对传递的参数对象进行处理,并覆盖默认值。这种用法在本书插件部分还将进行讲解。

在 jQuery 框架中, extend() 函数包含了所有功能,它既能够为当前对象扩展方法,也能够处理参数对象,并覆盖默认值。

2.2.9 涅槃 -- 名字空间现在,我们终于模拟出了 jQuery 框架的雏形,虽然它还比较稚嫩,经不起风雨,但至少能够保证读者理解 jQuery 框架构成的初期状态。不过对于一个成熟的框架来说,需要设计者考虑的问题还是很多的,其中最核心的问题就是名字空间冲突问题。

当一个页面中存在多个框架,或者自己写了很多 JavaScript 代码,我们是很难确保这些代码不发生冲突的,因为任何人都无法确保自己非常熟悉 jQuery 框架中的每一行代码,所以难免会出现名字冲突,或者功能覆盖现象。为了解决这个问题,我们必须把 jQuery 封装在一个孤立的环境中,避免其他代码的干扰。

在详细讲解名字空间之前,我们先来温习两个 JavaScript 概念。首先,请看下面的代码。

var jQuery = function(){};

jQuery = function(){};

上面所示的代码是两种不同的写法,且都是合法的,但是它们的语义完全不同。第一行代码声明了一个变量,而第二行代码定义了 Window 对象的一个属性,也就是说它等同于下面的语句。

window.jQuery = function();

在全局作用域中,变量和 Window 对象的属性是可以相等的,也可以是互通的,但是当在其他环境中 (如局部作用域中),则它们是不相等的,也是无法互通的。

因此如果希望 jQuery 具有类似 $.method(); 调用方式的能力,就需要将 jQuery 设置为 Window 对象的一个属性,所以你就会看到 jQuery 框架中是这样定义的。

[html] view
plain

<script type="text/javascript">

var jQuery = window.jQuery = window.$ = function(selector, context){

return new jQuery.fn.init(selector, context);

};

</script>

你可能看到过下面的函数用法。

(function(){

alert("观察我什么时候出现");

})();

这是一个典型的匿名函数基本形式。为什么要用到匿名函数呢?

这时就要进入正题了,如果希望自己的 jQuery 框架与其他任何代码完全隔离开来,也就是说如果你想把 jQuery 装在一个封闭空间中,不希望暴露内部信息,也不希望别的代码随意访问,匿名函数就是一种最好的封闭方式。此时我们只需要提供接口,就可以方便地与外界进行联系。例如,在下面的示例中分别把 f1 函数放在一个匿名函数中,而把 f2 函数放在全局作用域中。可以发现,全局作用域中的 f2 函数可以允许访问,而匿名函数中的
f1 函数是禁止外界访问的。

[html] view
plain

<script type="text/javascript">

(function(){

function f1(){

return "f1()";

}

})();



function f2(){

return "f2()";

}



alert(f2()); // 返回 "f2()"

alert(f1()); // 抛出异常,禁止访问

</script>

实际上,上面的匿名函数就是所谓的闭包,闭包是 JavaScript 函数中一个最核心的概念。

当然,$ 和 jQuery 名字并非是 jQuery 框架的专利,其他一些经典框架中也会用到 $ 名字,也许读者也会定义自己的变量 jQuery 。

在这之前我们需要让它与其他框架协同工作,这就带来一个问题,如果我们都使用 $ 作为简写形式就会发生冲突,为此 jQuery 提供了一个 noConflit() 方法,该方法能够实现禁止 jQuery 框架使用这两个名字。为了实现这样的目的,jQuery 在框架的最前面,先使用 _$ 和 _jQuery 临时变量寄存 $ 和 jQuery 这两个变量的内容,当需要禁用 jQuery 框架的名字时,可以使用一个临时变量 _$ 和 _jQuery
恢复 $ 和 jQuery 这两个变量的实际内容。实现代码如下。

[html] view
plain

<script type="text/javascript">

(function(){

var

window = this,

undefined,

_jQuery = window.jQuery, // 暂存 jQuery 变量内容

_$ = window.$, // 暂存 $ 变量内容

jQuery = window.jQuery = window.$ = function(selector, context){

return new jQuery.fn.init(selector, context);

},

quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,

isSimple = /^.[^:#\[\.,]*$/;



jQuery.fn = jQuery.prototype = {

init: function(selector, context){ // 定义选择器构造器

selector = selector || document; // 设置默认值为 document

context = context || document; // 设置默认值为 document



if(selector.nodeType){ // 如果选择符为节点对象

this[0] = selector; // 把参数节点传递给实例对象的数组

this.length = 1; // 并设置实例对象的 length 属性,定义包含的元素个数

this.context = selector; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

if(typeof selector === "string"){ // 如果选择符是字符串

var e = context.getElementsByTagName(selector); // 获取指定名称的元素

for(var i = 0; i<e.length; i++){ // 遍历元素集合,并把所有元素填入到当前实例数组中

this[i] = e[i];

}

this.length = e.length; // 设置实例的 length 属性,即定义包含的元素个数

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

} else {

this.length = 0; // 否则,设置实例的 length 属性值为 0

this.context = context; // 设置实例的属性,返回选择范围

return this; // 返回当前实例

}

},

setOptions: function(options){

this.options = { // 方法的默认值,可以扩展

StartColor: "#000",

EndColor: "#DDC",

Step: 20,

Speed: 10

};

jQuery.extend(this.options, options || {}); // 如果传递参数,则覆盖原默认参数

},

jquery: "1.3.2", // 原型属性

size: function(){ // 原型方法

return this.length;

}

};



jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象



jQuery.extend = jQuery.fn.extend = function(destination, source){ // 重新定义 extend() 函数

for (var property in source){

destination[property] = source[property];

}

return destination;

};

})();

</script>

至此,jQuery 框架的设计模式就初见端倪了,后面的工作就是根据应用需要或者功能需要,使用 extend() 函数不断扩展 jQuery 的工具函数和 jQuery 对象的方法。

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • Javascript/Ajax/jQuery

    Javascript/Ajax/jQuery

    2016-08-02 17:00

  • jQuery 许愿墙代码

    jQuery 许愿墙代码

    2016-08-02 15:01

  • 用JQuery在网页中实现分隔条功能的代码

    用JQuery在网页中实现分隔条功能的代码

    2016-08-02 13:01

  • jQuery基础学习技巧总结)

    jQuery基础学习技巧总结)

    2016-08-02 10:00

网友点评