一、 jQuery extend 方法介绍

jQuery API 手册中, extend 方法挂载在 jQuery jQuery.fn 两个不同对象上方法,但在 jQuery 内部代码实现的是相同的,只是功能却不太一样;

且看官方给出解释:

jQuery.extend ( ): Merge the contents of two or more objects together into the first object.( 把两个或者更多的对象合并到第一个当中 )

jQuery.fn.extend ( ):Merge the contents of an object onto the jQuery prototype to provide new jQuery instance methods.( 把对象挂载到 jQuery prototype 属性,来扩展一个新的 jQuery 实例方法 )

简单理解两者区别:

jQuery.extend (object); 为扩展 jQuery 类本身,为自身添加新的方法。

jQuery.fn.extend (object); jQuery 对象添加方法。

二、 jQuery extend 方法使用

1 jQuery. extend ( object);

(a) jQuery.extend ( target [, object1 ] [, objectN ] )

合并 object1, objectN target 对象,如果只有一个参数,则该 target 对象会被合并到 jQuery 对象中,如下代码:

1. var object1 = {

2. apple : 0,

3. banana : { weight: 52, price: 100 },

4. cherry : 97

5. };

6. var object2 = {

7. banana : { price: 200 },

8. durian : 100

9. };

11. // Merge object2 into object1

12. $. extend( object1, object2 );

13. console.log( object1.durian); //100

15. // Merge object1 into jQuery

16. $. extend( object1 );

17. console.log( $.apple ); //0

(2) jQuery.extend ( [deep ], target, object1 [, objectN ] )

深度复制合并对象,第一个参数是 boolean 类型的 true 时,将 object1, objectN 深度复制后合并到 target 中;关于深度复制,是将除 null, undefined, window 对象 , dom 对象,通过继承创建的对象 外的其它对象克隆后保存到 target 中;

所排除的对象,一是考虑性能,二是考虑复杂度(例如 dom window 对象,如果克隆复制,消耗过大,而通过继承实现的对象,复杂程度不可预知,因此也不进行深度复制);

深度与非深度复制区别是,深度复制的对象中如果有复杂属性值(如数组、函数、 json 对象等),那将会递归属性值的复制,合并后的对象修改属性值不影响原对象,如下面例子:

1. obj1 = { a : 'a', b : 'b' };

2. obj2 = {  x : { xxx : 'xxx', yyy : ' yyy ' },  y : 'y' };

3. $.extend(true, obj1, obj2);

4. alert(obj1.x.xxx);  // 得到 "xxx"

5. obj2.x.xxx = ' zzz '; // 修改 obj2 对象属性的内联值,不影响合并 后对象 obj1

6. alert(obj2.x.xxx); // 得到 " zzz "

7. alert(obj1.x.xxx); // 得到 "xxx"  // 值保持;如果不加 true ,则得到 zzz

后面分析源码时,可以看到具体为什么……

2 jQuery. fn.extend ( object);

jQuery.fn = jQuery.prototype 即指向 jQuery 对象的原型链,对其它进行的扩展,作用在 jQuery 对象上面;一般用此方法来扩展 jQuery 的对象插件

1. // hello 方法合并到 jquery 的实例对象中。

2. $. fn.extend ({

3. hello:function (){alert('hello');}

4. });

6. // jquery 全局对象中扩展一个 net 命名空间。

7. $.extend($.net,{

8. hello:function (){alert('hello');}

9. }); // 使用 jQuery.net.hello ();

二、 jQuery extend 实现原理

extend() 函数是 jQuery 的基础函数之一,作用是扩展现有的对象。例如下面的代码:

1. <script type="text/ javascript " src ="jquery-1.5.2.js"></script>

2. <script>

3. obj1 = { a : 'a', b : 'b' };

4. obj2 = { x : { xxx : 'xxx', yyy : ' yyy ' }, y : 'y' };

6. $.extend(true, obj1, obj2);

8. alert(obj1.x.xxx); // 得到 "xxx"

10. obj2.x.xxx = ' zzz ';

11. alert(obj2.x.xxx); // 得到 " zzz "

12. alert(obj1.x.xxx); // 得带 "xxx"

13. </script>

$.extend(true, obj1, obj2) 表示以 obj2 中的属性扩展对象 obj1 ,第一个参数设为 true 表示深复制。

虽然 obj1 中原来没有 "x" 属性,但经过扩展后, obj1 不但具有了 "x" 属性,而且对 obj2 中的 "x" 属性的修改也不会影响到 obj1 "x" 属性的值,这就是所谓的“深复制”了。

1 、浅复制的实现

如果仅仅需要实现浅复制,可以采用类似下面的写法:

1. $ = {

2. extend : function(target, options) {

3. for (name in options) {

4. target[name] = options[name];

5. }

6. return target;

7. }

8. };

也就是简单地将 options 中的属性复制到 target 中。我们仍然可以用类似的代码进行测试,但得到的结果有所不同 ( 假设我们的 js 命名为“ jquery-extend.js )

1. <script type="text/ javascript " src ="jquery-extend.js"></script>

2. <script>

3. obj1 = { a : 'a', b : 'b' };

4. obj2 = { x : { xxx : 'xxx', yyy : ' yyy ' }, y : 'y' };

6. $.extend(obj1, obj2);

8. alert(obj1.x.xxx); // 得到 "xxx"

10. obj2.x.xxx = ' zzz ';

11. alert(obj2.x.xxx); // 得到 " zzz "

12. alert(obj1.x.xxx); // 得带 " zzz "

13. </script>

obj1 中具有了 "x" 属性,但这个属性是一个对象,对 obj2 中的 "x" 的修改也会影响到 obj1 ,这可能会带来难以发现的错误。

2 、深复制的实现

如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用 extend 。如下代码是“深复制”的简单实现:

1. $ = {

2. extend : function(deep, target, options) {

3. for (name in options) {

4. copy = options[name];

5. if (deep && copy instanceof Array) {

6. target[name] = $.extend(deep, [], copy);

7. } else if (deep && copy instanceof Object) {

8. target[name] = $.extend(deep, {}, copy);

9. } else {

10. target[name] = options[name];

11. }

12. }

13. return target;

14. }

15. };

具体分为三种情况:

1. 属性是数组时,则将 target[name] 初始化为空数组,然后递归调用 extend

2. 属性是对象时,则将 target[name] 初始化为空对象,然后递归调用 extend

3. 否则,直接复制属性。

测试代码如下:

1. <script type="text/ javascript " src ="jquery-extend.js"></script>

2. <script>

3. obj1 = { a : 'a', b : 'b' };

4. obj2 = { x : { xxx : 'xxx', yyy : ' yyy ' }, y : 'y' };

5. $.extend(true, obj1, obj2);

6. alert(obj1.x.xxx); // 得到 "xxx"

7. obj2.x.xxx = ' zzz ';

8. alert(obj2.x.xxx); // 得到 " zzz "

9. alert(obj1.x.xxx); // 得到 "xxx"

10. </script>

现在如果指定为深复制的话,对 obj2 的修改将不会对 obj1 产生影响了;不过这个代码还存在一些问题,比如“ instanceof Array ”在 IE5 中可能存在不兼容的情况。 jQuery 中的实现实际上会更复杂一些。

3 、更完整的实现

下面的实现与 jQuery 中的 extend() 会更接近一些:

11. $ = function( ) {

12. var copyIsArray ,

13. toString = Object.prototype.toString ,

14. hasOwn = Object.prototype.hasOwnProperty ;

16. class2type = {

17. '[ object Boolean]' : ' boolean ',

18. '[ object Number]' : 'number',

19. '[ object String]' : 'string',

20. '[ object Function]' : 'function',

21. '[ object Array]' : 'array',

22. '[ object Date]' : 'date',

23. '[ object RegExp ]' : ' regExp ',

24. '[ object Object]' : 'object'

25. },

27. type = function( obj ) {

28. return obj == null ? String( obj ) : class2type[ toString.call ( obj )] || "object";

29. },

31. isWindow = function( obj ) {

32. return obj && typeof obj === "object" && " setInterval " in obj ;

33. },

35. isArray = Array.isArray || function( obj ) {

36. return type( obj ) === "array";

37. },

39. isPlainObject = function( obj ) {

40. if (! obj || type( obj ) !== "object" || obj.nodeType || isWindow ( obj )) {

41. return false;

42. }

44. if ( obj.constructor && ! hasOwn.call ( obj , "constructor")

45. & & ! hasOwn.call ( obj.constructor.prototype , " isPrototypeOf ")) {

46. return false;

47. }

49. var key;

50. for (key in obj ) {

51. }

53. return key === undefined || hasOwn.call ( obj , key);

54. },

56. extend = function(deep, target, options) {

57. for (name in options) {

58. src = target[name];

59. copy = options[name];

61. if (target === copy) { continue; }

63. if (deep && copy

64. && ( isPlainObject ( copy) || ( copyIsArray = isArray (copy)))) {

65. if ( copyIsArray ) {

66. copyIsArray = false;

67. clone = src && isArray ( src ) ? src : [];

69. } else {

70. clone = src && isPlainObject ( src ) ? src : {};

71. }

73. target[ name] = extend(deep, clone, copy);

74. } else if ( copy ! == undefined) {

75. target[ name] = copy;

76. }

77. }

79. return target;

80. };

82. return { extend : extend };

83. }();

首先是 $ = function(){...}(); 这种写法,可以理解为与下面的写法类似:

1. func = function(){...};

2. $ = func ();

也就是立即执行函数,并将结果赋给 $ 。这种写法可以利用 function 来管理作用域,避免局部变量或局部函数影响全局域。另外,我们只希望使用者调用 $.extend() ,而将内部实现的函数隐藏,因此最终返回的对象中只包含 extend:

1. return { extend : extend };

接下来,我们看看 extend 函数与之前的区别,首先是多了这句话:

1. if (target === copy) { continue; }

这是为了避免无限循环,要复制的属性 copy target 相同的话,也就是将“自己”复制为“自己的属性”,可能导致不可预料的循环。

然后是判断对象是否为数组的方式:

1. type = function( obj ) {

2. return obj == null ? String( obj ) : class2type[ toString.call ( obj )] || "object";

3. },

4. isArray = Array.isArray || function( obj ) {

5. return type( obj ) === "array";

6. }

如果浏览器有内置的 Array.isArray 实现,就使用浏览器自身的实现方式,否则将对象转为 String ,看是否为 "[object Array]"

最后逐句地看看 isPlainObject 的实现:

1. if (! obj || type( obj ) !== "object" || obj.nodeType || isWindow ( obj )) {

2. return false;

3. }

如果定义了 obj.nodeType ,表示这是一个 DOM 元素;这句代码表示以下四种情况不进行深复制:

1. 对象为 undefined

2. 转为 String 时不是 "[object Object]"

3. obj 是一个 DOM 元素;

4. obj window

之所以不对 DOM 元素和 window 进行深复制,可能是因为它们包含的属性太多了;尤其是 window 对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。

接下来是与构造函数相关的测试:

1. if ( obj.constructor && ! hasOwn.call ( obj , "constructor")

2. && ! hasOwn.call ( obj.constructor.prototype , " isPrototypeOf ")) {

3. return false;

4. }

如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过 prototye 继承来的,这种情况也不进行深复制。这一点可以结合下面的代码结合进行理解:

1. var key;

2. for (key in obj ) {

3. }

5. return key === undefined || hasOwn.call ( obj , key);

这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。

这说明如果对象是通过 prototype 方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了避免引入不确定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也可以看出来,进行深复制的只有 " PlainObject "

如果我们用如下代码进行测试:

1. <script type="text/ javascript " src ="jquery-1.5.2.js"></script>

2. <script>

3. function O() {

4. this.yyy = ' yyy ';

5. }

7. function X() {

8. this.xxx = 'xxx';

9. }

11. X.prototype = new O();

13. x = new X();

15. obj1 = { a : 'a', b : 'b' };

16. obj2 = { x : x };

17. $.extend(true, obj1, obj2);

19. alert(obj1.x.yyy); // 得到 "xxx"

20. obj2.x.yyy = ' zzz ';

21. alert(obj1.x.yyy); // 得到 " zzz "

22. </script>

可以看到,这种情况是不进行深复制的。

总之, jQuery 中的 extend() 的实现方式,考虑了兼容浏览器的兼容,避免性能过低,和避免引入不可预料的错误等因素。

三、 jQuery 源码实现

还是先加一个例子,区别 jQuery.extend jQuery.fn.extend :

1. jQuery.extend ({

2. sayhello:function (){

3. console.log( " Hello,This is jQuery Library");

4. }

5. })

6. $. sayhello ( ); //Hello, This is jQuery Library

8. jQuery.fn.extend ({

9. check : function() {

10. return this.each (function() {

11. this.checked = true;

12. });

13. },

14. uncheck : function() {

15. return this.each (function() {

16. this.checked = false;

17. });

18. }

19. })

20. $( "input[type='checkbox']" ).check(); // 所有的 checkbox 都会被选择

1 extend 无注释的源码

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

2. var options, name, src , copy, copyIsArray , clone,

3. target = arguments[0] || {},

4. i = 1,

5. length = arguments.length ,

6. deep = false;

8. // Handle a deep copy situation

9. if ( typeof target === " boolean " ) {

10. deep = target;

11. target = arguments[1] || {};

12. // skip the boolean and the target

13. i = 2;

14. }

16. // Handle case when target is a string or something (possible in deep copy)

17. if ( typeof target !== "object" && ! jQuery.isFunction (target) ) {

18. target = {};

19. }

21. // extend jQuery itself if only one argument is passed

22. if ( length === i ) {

23. target = this;

24. -- i ;

25. }

27. for ( ; i < length; i ++ ) {

28. // Only deal with non-null/undefined values

29. if ( (options = arguments[ i ]) != null ) {

30. // Extend the base object

31. for ( name in options ) {

32. src = target[ name ];

33. copy = options[ name ];

35. // Prevent never-ending loop

36. if ( target === copy ) {

37. continue ;

38. }

40. // Recurse if we're merging plain objects or arrays

41. if ( deep && copy && ( jQuery.isPlainObject (copy) || ( copyIsArray = jQuery.isArray (copy)) ) ) {

42. if ( copyIsArray ) {

43. copyIsArray = false;

44. clone = src && jQuery.isArray ( src ) ? src : [];

46. } else {

47. clone = src && jQuery.isPlainObject ( src ) ? src : {};

48. }

50. // Never move original objects, clone them

51. target[ name ] = jQuery.extend ( deep, clone, copy );

53. // Don't bring in undefined values

54. } else if ( copy !== undefined ) {

55. target[ name ] = copy;

56. }

57. }

58. }

59. }

61. // Return the modified object

62. return target;

63. };

代码的大部分都是用来实现 jQuery.extend () 中有多个参数时的对象合并,深度拷贝问题,如果去掉这些功能,让 extend 只有扩展静态和实例方法的功能,那么代码如下 :

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

2. // obj 是传递过来扩展到 this 上的对象

3. var target=this;

4. for ( var name in obj ){

5. //name 为对象属性

6. //copy 为属性值

7. copy= obj [name];

8. // 防止循环调用

9. if( target === copy) continue;

10. // 防止附加未定义值

11. if( typeof copy === 'undefined') continue;

12. // 赋值

13. target[ name]=copy;

14. }

15. return target;

16. }

2 extend 方法进行注释解释 :

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

2. // 定义默认参数和变量

3. // 对象分为扩展对象和被扩展的对象

4. //options 代表扩展的对象中的方法

5. //name 代表扩展对象的方法名

6. // i 为扩展对象参数起始值

7. //deep 默认为浅复制

8. var options, name, src , copy, copyIsArray , clone,

9. target = arguments[0] || {},

10. i = 1,

11. length = arguments.length ,

12. deep = false;

14. // 当第一个参数为布尔类型是,次参数定义是否为深拷贝

15. // 对接下来的参数进行处理

16. if ( typeof target === " boolean " ) {

17. deep = target;

18. target = arguments[1] || {};

19. // 当定义 是否深拷贝 时,参数往后移动一位

20. i = 2;

21. }

23. // 如果要扩展的不是对象或者函数,则定义要扩展的对象为空

24. if ( typeof target !== "object" && ! jQuery.isFunction (target) ) {

25. target = {};

26. }

28. // 当只含有一个参数时,被扩展的对象是 jQuery jQuery.fn

29. if ( length === i ) {

30. target = this;

31. -- i ;

32. }

34. // 对从 i 开始的多个参数进行遍历

35. for ( ; i < length; i ++ ) {

36. // 只处理有定义的值

37. if ( (options = arguments[ i ]) != null ) {

38. // 展开扩展对象

39. for ( name in options ) {

40. src = target[ name ];

41. copy = options[ name ];

43. // 防止循环引用

44. if ( target === copy ) {

45. continue;

46. }

48. // 递归处理深拷贝

49. if ( deep && copy && ( jQuery.isPlainObject (copy) || ( copyIsArray = jQuery.isArray (copy)) ) ) {

50. if ( copyIsArray ) {

51. copyIsArray = false;

52. clone = src && jQuery.isArray ( src ) ? src : [];

54. } else {

55. clone = src && jQuery.isPlainObject ( src ) ? src : {};

56. }

58. target[ name ] = jQuery.extend ( deep, clone, copy );

60. // 不处理未定义值

61. } else if ( copy !== undefined ) {

62. // target 增加属性或方法

63. target[ name ] = copy;

64. }

65. }

66. }

67. }

69. // 返回

70. return target;

71. };

部分内容借鉴网上博客资源,记不太清了,先谢谢了……