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. };
10.
11. // Merge object2 into object1
12. $.extend( object1, object2 );
13. console.log(object1.durian); //100
14.
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. });
5.
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' };
5.
6. $.extend(true, obj1, obj2);
7.
8. alert(obj1.x.xxx); // 得到"xxx"
9.
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' };
5.
6. $.extend(obj1, obj2);
7.
8. alert(obj1.x.xxx); // 得到"xxx"
9.
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;
15.
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. },
26.
27. type = function(obj) {
28. return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
29. },
30.
31. isWindow = function(obj) {
32. return obj && typeof obj === "object" && "setInterval" in obj;
33. },
34.
35. isArray = Array.isArray || function(obj) {
36. return type(obj) === "array";
37. },
38.
39. isPlainObject = function(obj) {
40. if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
41. return false;
42. }
43.
44. if (obj.constructor && !hasOwn.call(obj, "constructor")
45. && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
46. return false;
47. }
48.
49. var key;
50. for (key in obj) {
51. }
52.
53. return key === undefined || hasOwn.call(obj, key);
54. },
55.
56. extend = function(deep, target, options) {
57. for (name in options) {
58. src = target[name];
59. copy = options[name];
60.
61. if (target === copy) { continue; }
62.
63. if (deep && copy
64. && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
65. if (copyIsArray) {
66. copyIsArray = false;
67. clone = src && isArray(src) ? src : [];
68.
69. } else {
70. clone = src && isPlainObject(src) ? src : {};
71. }
72.
73. target[name] = extend(deep, clone, copy);
74. } else if (copy !== undefined) {
75. target[name] = copy;
76. }
77. }
78.
79. return target;
80. };
81.
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. }
4.
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. }
6.
7. function X() {
8. this.xxx = 'xxx';
9. }
10.
11. X.prototype = new O();
12.
13. x = new X();
14.
15. obj1 = { a : 'a', b : 'b' };
16. obj2 = { x : x };
17. $.extend(true, obj1, obj2);
18.
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
7.
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;
7.
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. }
15.
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. }
20.
21. // extend jQuery itself if only one argument is passed
22. if ( length === i ) {
23. target = this;
24. --i;
25. }
26.
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 ];
34.
35. // Prevent never-ending loop
36. if ( target === copy ) {
37. continue;
38. }
39.
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 : [];
45.
46. } else {
47. clone = src && jQuery.isPlainObject(src) ? src : {};
48. }
49.
50. // Never move original objects, clone them
51. target[ name ] = jQuery.extend( deep, clone, copy );
52.
53. // Don't bring in undefined values
54. } else if ( copy !== undefined ) {
55. target[ name ] = copy;
56. }
57. }
58. }
59. }
60.
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;
13.
14. //當第一個參數為布爾類型是,次參數定義是否為深拷貝
15. //對接下來的參數進行處理
16. if ( typeof target === "boolean" ) {
17. deep = target;
18. target = arguments[1] || {};
19. // 當定義是否深拷貝時,參數往后移動一位
20. i = 2;
21. }
22.
23. // 如果要擴展的不是對象或者函數,則定義要擴展的對象為空
24. if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
25. target = {};
26. }
27.
28. // 當只含有一個參數時,被擴展的對象是jQuery或jQuery.fn
29. if ( length === i ) {
30. target = this;
31. --i;
32. }
33.
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 ];
42.
43. // 防止循環引用
44. if ( target === copy ) {
45. continue;
46. }
47.
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 : [];
53.
54. } else {
55. clone = src && jQuery.isPlainObject(src) ? src : {};
56. }
57.
58. target[ name ] = jQuery.extend( deep, clone, copy );
59.
60. // 不處理未定義值
61. } else if ( copy !== undefined ) {
62. //給target增加屬性或方法
63. target[ name ] = copy;
64. }
65. }
66. }
67. }
68.
69. //返回
70. return target;
71. };
部分內容借鑒網上博客資源,記不太清了,先謝謝了……