1、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對象添加方法。 2、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(); 2、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()的實現方式,考慮了兼容瀏覽器的兼容,避免性能太低,和避免引入不可預料的錯誤等因素。 3、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都會被選擇 一、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.} 二、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.};