一般咱們使用jquery的extend時,大都是爲了實現默認字段的覆蓋,即若傳入某個字段的值,則使用傳入值,不然使用默認值。以下面的代碼:jquery
function getOpt(option){ var _default = { name : 'wenzi', age : '25', sex : 'male' } $.extend(_default, option); return _default; } getOpt(); // {name: "wenzi", age: "25", sex: "male"} getOpt({name:'bing'}); // {name: "bing", age: "25", sex: "male"} getOpt({name:'bing', age:36, sex:'female'}); // {name: "bing", age: 36, sex: "female"}
那如今咱們就得須要知道這個extend具體是怎麼實現的了,除了實現上面的功能,還有其餘做用麼?那確定是有的啦,不然我也不會問那句話了((⊙﹏⊙)b)。咱們先來看看extend主要有哪些功能,而後再看實現這些功能的原理。數組
其實從extend的含義裏,咱們就能知道extend是作什麼的了。extend翻譯成漢語後就是:延伸、擴展、推廣。框架
咱們來看看$.extend()
提供的參數:jQuery.extend( target [, object1 ] [, objectN ] )
,extend方法須要至少傳入一個參數,第一個必需,後面的都是可選參數。若傳給extend是兩個或兩個以上的參數都是對象類型,那麼就會把後面全部對象的內容合併給target(第一個對象)上。函數
咱們再來看看上面的例子:oop
function getOpt(option){ var _default = { name : 'wenzi', age : '25', sex : 'male' } `$.extend(_default, option);` return _default; }
$.extend()
中接收了兩個參數_default和option,那麼extend方法執行時,就會把option對象上字段的值全給了_default。因而_default上設置的默認值就會被option上的值覆蓋。固然,若option上沒有這個字段,就不會覆蓋_default上字段的值。源碼分析
上面函數中的extend,只是傳入了兩個參數,那傳的參數再更多一些呢:this
function getOpt(target, obj1, obj2, obj3){ $.extend(target, obj1, obj2, obj3); return target; } var _default = { name : 'wenzi', age : '25', sex : 'male' } var obj1 = { name : 'obj1' } var obj2 = { name : 'obj2', age : '36' } var obj3 = { age : '67', sex : {'error':'sorry, I dont\'t kown'} } getOpt(_default, obj1, obj2, obj3); // {name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}}
這裏咱們傳入了4個參數,而後getOpt()返回第一個參數的值。從運行的獲得結果咱們能夠看到,屬性值永遠是最後一個屬性的值。插件
還有很重要的一點,$.extend()
實際上是有返回值的,返回的就是修改後的第一個參數的值。如咱們能夠把上面的函數修改爲這樣:翻譯
function getOpt(target, obj1, obj2, obj3){ var result = $.extend(target, obj1, obj2, obj3); return result; // // result即修改後的target值 }
若咱們傳入的參數不想被修改,咱們能夠用一個空對象來做爲第一個參數,而後獲取$.extend()
的返回值:code
function getOpt(target, obj1, obj2, obj3){ var result = $.extend({}, target, obj1, obj2, obj3); return result; // // result即爲{}修改後的值 }
剛纔咱們在1.1中講的$.extend()
的例子都是傳了兩個或兩個以上的參數,但其實只有一個參數是必須的。若只傳一個參數會怎樣呢。
若是隻有一個參數提供給
$.extend()
,這意味着目標參數被省略。在這種狀況下,jQuery對象自己被默認爲目標對象。這樣,咱們能夠在jQuery的命名空間下添加新的功能。這對於插件開發者但願向 jQuery 中添加新函數時是頗有用的。
$.extend({ _name : 'wenzi', _getName : function(){ return this._name; } }) $._name; // wenzi $._getName(); // wenzi
這樣咱們就爲jQuery擴展了_name
屬性和_getName
方法。
針對什麼是深度拷貝,什麼是淺度拷貝,咱們先來看一個簡單的例子。
var obj = {name:'wenzi', sex:'male'}; var obj1 = obj; // 賦值 obj1.name = 'bing'; console.log(obj.name); // bing
咱們修改了obj1中的name值,結果obj中的值也跟着發生了變化,這是爲何呢。其實這就是淺度拷貝
:這僅僅是將obj對象的引用地址簡單的複製了一份給予變量 obj1,而並非將真正的對象克隆了一份,所以obj和obj1指向的都是同一個地址。當修改obj1的屬性或給obj1添加新屬性時,obj都會受到影響。
但是若是變量的值不是對象和數組,修改後面的變量是不會影響到前面的變量:
var s = 'hello'; var t = s; t = 'world'; console.log(s); // hello
那麼深度拷貝就不是拷貝引用地址,而是實實在在的複製一份新對象給新的變量。 在上面使用$.extend()
中,都是使用的淺度拷貝,所以若後面的參數值是object類型或array類型,修改_default(target)的值,就會影響後面參數的值。
如咱們使用getOpt(_default, obj1, obj2, obj3);
獲得的_default值是{name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}},但是若:
_default.sex.error = 'hello world';
那麼obj3.sex.error也會跟着修改,由於obj3.sex是一個object類型。
不過$.extend()
也提供了深度拷貝的方法:jQuery.extend( [deep ], target, object1 [, objectN ] )。若第一個參數是boolean類型,且值是true,那麼就會把第二個參數做爲目標參數進行合併。
var obj = {name:'wenzi', score:80}; var obj1 = {score:{english:80, math:90}} $.extend(true, obj, obj1); obj.score.english = 10; console.log(obj.score.english); // 10 console.log(obj1.score.english); // 80
執行後咱們發現,不管怎麼修改obj.score裏的值,都不會影響到obj1.score了。
其實不看源碼,對extend大體的過程應該也是瞭解的:對後一個參數進行循環,而後把後面參數上全部的字段都給了第一個字段,若第一個參數裏有相同的字段,則進行覆蓋操做,不然就添加一個新的字段。
下面是jQuery中關於extend的源碼,我就在源碼上進行註釋講解了,隨後再在後面進行總結:
// 爲與源碼的下標對應上,咱們把第一個參數稱爲`第0個參數`,依次類推 jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, // 默認第0個參數爲目標參數 i = 1, // i表示從第幾個參數凱斯想目標參數進行合併,默認從第1個參數開始向第0個參數進行合併 length = arguments.length, deep = false; // 默認爲淺度拷貝 // 判斷第0個參數的類型,若第0個參數是boolean類型,則獲取其爲true仍是false // 同時將第1個參數做爲目標參數,i從當前目標參數的下一個 // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // 判斷目標參數的類型,若目標參數既不是object類型,也不是function類型,則爲目標參數從新賦值 // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // 若目標參數後面沒有參數了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'}) // 則目標參數即爲jQuery自己,而target表示的參數再也不爲目標參數 // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } // 從第i個參數開始 for ( ; i < length; i++ ) { // 獲取第i個參數,且該參數不爲null, // 好比$.extend(target, {}, null);中的第2個參數null是不參與合併的 // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // 使用for~in獲取該參數中全部的字段 // Extend the base object for ( name in options ) { src = target[ name ]; // 目標參數中name字段的值 copy = options[ name ]; // 當前參數中name字段的值 // 若參數中字段的值就是目標參數,中止賦值,進行下一個字段的賦值 // 這是爲了防止無限的循環嵌套,咱們把這個稱爲,在下面進行比較詳細的講解 // Prevent never-ending loop if ( target === copy ) { continue; } // 若deep爲true,且當前參數中name字段的值存在且爲object類型或Array類型,則進行深度賦值 // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { // 若當前參數中name字段的值爲Array類型 // 判斷目標參數中name字段的值是否存在,若存在則使用原來的,不然進行初始化 if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { // 若原對象存在,則直接進行使用,而不是建立 clone = src && jQuery.isPlainObject(src) ? src : {}; } // 遞歸處理,此處爲2.2 // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // deep爲false,則表示淺度拷貝,直接進行賦值 // 若copy是簡單的類型且存在值,則直接進行賦值 // Don't bring in undefined values } else if ( copy !== undefined ) { // 若原對象存在name屬性,則直接覆蓋掉;若不存在,則建立新的屬性 target[ name ] = copy; } } } } // 返回修改後的目標參數 // Return the modified object return target; };
源碼分析完了,下面咱們來說解下源碼中存在的幾個難點和重點。
在源碼中進行了一下這樣的判斷:
// Prevent never-ending loop if ( target === copy ) { continue; }
爲何要有這樣的判斷,咱們來看一個簡單的例子,若是沒有這個判斷會怎麼樣:
var _default = {name : 'wenzi'}; var obj = {name : _default} $.extend(_default, obj); console.log(_default);
輸出的_default是什麼呢:
_default = {name : _default};
_default是object類型,裏面有個字段name,值是_default,而_default是object類型,裏面有個字段name,值是_default......,無限的循環下去。因而jQuery中直接不進行操做,跳過這個字段,進行下一個字段的操做。
咱們在前面稍微的講解了一下,變量值爲簡單類型(如number, string, boolean)進行賦值時是不會影響上一個變量的值的,所以,若是當前字段的值爲Object
或Array
類型,須要對其進行拆分,直到字段的值爲簡單類型(如number, string, boolean)時才進行賦值操做。
上面講解的全都是$.extend(),根本就沒講$.fn.extend()。但是,你有沒有發現一個細節,在這段代碼的第一行是怎麼寫的:
jQuery.extend = jQuery.fn.extend = function(){}
也就是說$.extend()與$.fn.extend()共用的是同一個函數體,全部的操做都是同樣的,只不過兩個extend使用的對象不一樣罷了:$.extend()
是在jQuery($)上進行操做的;而$.fn.extend()
是在jQuery對象上進行操做的,如$('div').extend().
這就是jQuery中extend的實現,之後若咱們須要用到上面的功能時,除了使用$.extend(),咱們也能夠在不引入jQuery框架的狀況下,本身寫一個簡單的extend()來實現上面的功能。
本文正式地址:http://www.xiabingbao.com/jquery/2015/05/30/jquery-extend