深入理解jquery中extend的實(shí)現(xiàn)
Jquery的擴(kuò)展方法extend是我們?cè)趯懖寮倪^程中常用的方法,該方法有一些重載原型,下面來看看詳細(xì)的介紹吧。
通常我們使用jquery的extend時(shí),大都是為了實(shí)現(xiàn)默認(rèn)字段的覆蓋,即若傳入某個(gè)字段的值,則使用傳入值,否則使用默認(rèn)值。
如下面的代碼:
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"}
那現(xiàn)在我們就得需要知道這個(gè)extend具體是怎么實(shí)現(xiàn)的了,除了實(shí)現(xiàn)上面的功能,還有其他作用么?那肯定是有的啦,否則我也不會(huì)問那句話了((⊙﹏⊙)b)。我們先來看看extend主要有哪些功能,然后再看實(shí)現(xiàn)這些功能的原理。
1. extend能實(shí)現(xiàn)的功能
其實(shí)從extend的含義里,我們就能知道extend是做什么的了。extend翻譯成漢語后就是:延伸、擴(kuò)展、推廣。
1.1 將兩個(gè)或更多對(duì)象的內(nèi)容合并到第一個(gè)對(duì)象
我們來看看$.extend()提供的參數(shù):jQuery.extend( target [, object1 ] [, objectN ] )
,extend方法需要至少傳入一個(gè)參數(shù),第一個(gè)必需,后面的都是可選參數(shù)。若傳給extend是兩個(gè)或兩個(gè)以上的參數(shù)都是對(duì)象類型,那么就會(huì)把后面所有對(duì)象的內(nèi)容合并給target(第一個(gè)對(duì)象)上。
我們?cè)賮砜纯瓷厦娴睦樱?br />
function getOpt(option){ var _default = { name : 'wenzi', age : '25', sex : 'male' } `$.extend(_default, option);` return _default; }
$.extend()
中接收了兩個(gè)參數(shù)_default和option,那么extend方法執(zhí)行時(shí),就會(huì)把option對(duì)象上字段的值全給了_default。于是_default上設(shè)置的默認(rèn)值就會(huì)被option上的值覆蓋。當(dāng)然,若option上沒有這個(gè)字段,就不會(huì)覆蓋_default上字段的值。
上面函數(shù)中的extend,只是傳入了兩個(gè)參數(shù),那傳的參數(shù)再更多一些呢:
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個(gè)參數(shù),然后getOpt()
返回第一個(gè)參數(shù)的值。從運(yùn)行的得到結(jié)果我們可以看到,屬性值永遠(yuǎn)是最后一個(gè)屬性的值。
還有很重要的一點(diǎn),$.extend()
其實(shí)是有返回值的,返回的就是修改后的第一個(gè)參數(shù)的值。如我們可以把上面的函數(shù)修改成這樣:
function getOpt(target, obj1, obj2, obj3){ var result = $.extend(target, obj1, obj2, obj3); return result; // // result即修改后的target值 }
若我們傳入的參數(shù)不想被修改,我們可以用一個(gè)空對(duì)象來作為第一個(gè)參數(shù),然后獲取$.extend()
的返回值:
function getOpt(target, obj1, obj2, obj3){ var result = $.extend({}, target, obj1, obj2, obj3); return result; // // result即為{}修改后的值 }
1.2 為JQUERY擴(kuò)展方法或?qū)傩?/strong>
剛才我們?cè)?.1中講的$.extend()
的例子都是傳了兩個(gè)或兩個(gè)以上的參數(shù),但其實(shí)只有一個(gè)參數(shù)是必須的。若只傳一個(gè)參數(shù)會(huì)怎樣呢。
如果只有一個(gè)參數(shù)提供給$.extend()
,這意味著目標(biāo)參數(shù)被省略。在這種情況下,jQuery對(duì)象本身被默認(rèn)為目標(biāo)對(duì)象。這樣,我們可以在jQuery的命名空間下添加新的功能。這對(duì)于插件開發(fā)者希望向 jQuery 中添加新函數(shù)時(shí)是很有用的。
$.extend({ _name : 'wenzi', _getName : function(){ return this._name; } }) $._name; // wenzi $._getName(); // wenzi
這樣我們就為jQuery擴(kuò)展了_name屬性和_getName方法。
1.3 深度拷貝和淺度拷貝
針對(duì)什么是深度拷貝,什么是淺度拷貝,我們先來看一個(gè)簡(jiǎn)單的例子。
var obj = {name:'wenzi', sex:'male'}; var obj1 = obj; // 賦值 obj1.name = 'bing'; console.log(obj.name); // bing
我們修改了obj1中的name值,結(jié)果obj中的值也跟著發(fā)生了變化,這是為什么呢。其實(shí)這就是淺度拷貝:這僅僅是將obj對(duì)象的引用地址簡(jiǎn)單的復(fù)制了一份給予變量 obj1,而并不是將真正的對(duì)象克隆了一份,因此obj和obj1指向的都是同一個(gè)地址。當(dāng)修改obj1的屬性或給obj1添加新屬性時(shí),obj都會(huì)受到影響。
可是如果變量的值不是對(duì)象和數(shù)組,修改后面的變量是不會(huì)影響到前面的變量:
var s = 'hello'; var t = s; t = 'world'; console.log(s); // hello
那么深度拷貝就不是拷貝引用地址,而是實(shí)實(shí)在在的復(fù)制一份新對(duì)象給新的變量。 在上面使用$.extend()中,都是使用的淺度拷貝,因此若后面的參數(shù)值是object類型或array類型,修改_default(target)的值,就會(huì)影響后面參數(shù)的值。
如我們使用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
也會(huì)跟著修改,因?yàn)閛bj3.sex是一個(gè)object類型。
不過$.extend()也提供了深度拷貝的方法:jQuery.extend( [deep ], target, object1 [, objectN ] )
。若第一個(gè)參數(shù)是boolean類型,且值是true,那么就會(huì)把第二個(gè)參數(shù)作為目標(biāo)參數(shù)進(jìn)行合并。
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
執(zhí)行后我們發(fā)現(xiàn),無論怎么修改obj.score里的值,都不會(huì)影響到obj1.score了。
2. jQuery中extend實(shí)現(xiàn)原理
其實(shí)不看源碼,對(duì)extend大致的過程應(yīng)該也是了解的:對(duì)后一個(gè)參數(shù)進(jìn)行循環(huán),然后把后面參數(shù)上所有的字段都給了第一個(gè)字段,若第一個(gè)參數(shù)里有相同的字段,則進(jìn)行覆蓋操作,否則就添加一個(gè)新的字段。
下面是jQuery中關(guān)于extend的源碼,我就在源碼上進(jìn)行注釋講解了,隨后再在后面進(jìn)行總結(jié):
// 為與源碼的下標(biāo)對(duì)應(yīng)上,我們把第一個(gè)參數(shù)稱為`第0個(gè)參數(shù)`,依次類推 jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, // 默認(rèn)第0個(gè)參數(shù)為目標(biāo)參數(shù) i = 1, // i表示從第幾個(gè)參數(shù)凱斯想目標(biāo)參數(shù)進(jìn)行合并,默認(rèn)從第1個(gè)參數(shù)開始向第0個(gè)參數(shù)進(jìn)行合并 length = arguments.length, deep = false; // 默認(rèn)為淺度拷貝 // 判斷第0個(gè)參數(shù)的類型,若第0個(gè)參數(shù)是boolean類型,則獲取其為true還是false // 同時(shí)將第1個(gè)參數(shù)作為目標(biāo)參數(shù),i從當(dāng)前目標(biāo)參數(shù)的下一個(gè) // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // 判斷目標(biāo)參數(shù)的類型,若目標(biāo)參數(shù)既不是object類型,也不是function類型,則為目標(biāo)參數(shù)重新賦值 // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // 若目標(biāo)參數(shù)后面沒有參數(shù)了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'}) // 則目標(biāo)參數(shù)即為jQuery本身,而target表示的參數(shù)不再為目標(biāo)參數(shù) // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } // 從第i個(gè)參數(shù)開始 for ( ; i < length; i++ ) { // 獲取第i個(gè)參數(shù),且該參數(shù)不為null和undefind,在js中null和undefined,如果不區(qū)分類型,是相等的,null==undefined為true, // 因此可以用null來同時(shí)過濾掉null和undefind // 比如$.extend(target, {}, null);中的第2個(gè)參數(shù)null是不參與合并的 // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // 使用for~in獲取該參數(shù)中所有的字段 // Extend the base object for ( name in options ) { src = target[ name ]; // 目標(biāo)參數(shù)中name字段的值 copy = options[ name ]; // 當(dāng)前參數(shù)中name字段的值 // 若參數(shù)中字段的值就是目標(biāo)參數(shù),停止賦值,進(jìn)行下一個(gè)字段的賦值 // 這是為了防止無限的循環(huán)嵌套,我們把這個(gè)稱為,在下面進(jìn)行比較詳細(xì)的講解 // Prevent never-ending loop if ( target === copy ) { continue; } // 若deep為true,且當(dāng)前參數(shù)中name字段的值存在且為object類型或Array類型,則進(jìn)行深度賦值 // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { // 若當(dāng)前參數(shù)中name字段的值為Array類型 // 判斷目標(biāo)參數(shù)中name字段的值是否存在,若存在則使用原來的,否則進(jìn)行初始化 if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { // 若原對(duì)象存在,則直接進(jìn)行使用,而不是創(chuàng)建 clone = src && jQuery.isPlainObject(src) ? src : {}; } // 遞歸處理,此處為2.2 // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // deep為false,則表示淺度拷貝,直接進(jìn)行賦值 // 若copy是簡(jiǎn)單的類型且存在值,則直接進(jìn)行賦值 // Don't bring in undefined values } else if ( copy !== undefined ) { // 若原對(duì)象存在name屬性,則直接覆蓋掉;若不存在,則創(chuàng)建新的屬性 target[ name ] = copy; } } } } // 返回修改后的目標(biāo)參數(shù) // Return the modified object return target; };
源碼分析完了,下面我們來講解下源碼中存在的幾個(gè)難點(diǎn)和重點(diǎn)。
2.1 若參數(shù)中字段的值就是目標(biāo)參數(shù),停止賦值
在源碼中進(jìn)行了一下這樣的判斷:
// Prevent never-ending loop if ( target === copy ) { continue; }
為什么要有這樣的判斷,我們來看一個(gè)簡(jiǎn)單的例子,如果沒有這個(gè)判斷會(huì)怎么樣:
var _default = {name : 'wenzi'}; var obj = {name : _default} $.extend(_default, obj); console.log(_default);
輸出的_default是什么呢:
_default = {name : _default};
_default是object類型,里面有個(gè)字段name,值是_default,而_default是object類型,里面有個(gè)字段name,值是_default……,無限的循環(huán)下去。于是jQuery中直接不進(jìn)行操作,跳過這個(gè)字段,進(jìn)行下一個(gè)字段的操作。
2.2 深度拷貝時(shí)進(jìn)行遞歸處理
我們?cè)谇懊嫔晕⒌闹v解了一下,變量值為簡(jiǎn)單類型(如number, string, boolean)進(jìn)行賦值時(shí)是不會(huì)影響上一個(gè)變量的值的,因此,如果當(dāng)前字段的值為Object或Array類型,需要對(duì)其進(jìn)行拆分,直到字段的值為簡(jiǎn)單類型(如number, string, boolean)時(shí)才進(jìn)行賦值操作。
3. $.extend()與$.fn.extend()
上面講解的全都是$.extend(),
根本就沒講$.fn.extend()
。可是,你有沒有發(fā)現(xiàn)一個(gè)細(xì)節(jié),在這段代碼的第一行是怎么寫的:
jQuery.extend = jQuery.fn.extend = function(){}
也就是說$.extend()
與$.fn.extend()
共用的是同一個(gè)函數(shù)體,所有的操作都是一樣的,只不過兩個(gè)extend使用的對(duì)象不同罷了:$.extend()
是在jQuery($)
上進(jìn)行操作的;而$.fn.extend()
是在jQuery對(duì)象上進(jìn)行操作的,如$(‘div').extend()
4. 總結(jié)
這就是jQuery中extend的實(shí)現(xiàn),以后若我們需要用到上面的功能時(shí),除了使用$.extend()
,我們也可以在不引入jQuery框架的情況下,自己寫一個(gè)簡(jiǎn)單的extend()來實(shí)現(xiàn)上面的功能。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
jQuery實(shí)現(xiàn)帶動(dòng)畫效果的二級(jí)下拉導(dǎo)航方法
這篇文章主要介紹了jQuery實(shí)現(xiàn)帶動(dòng)畫效果的二級(jí)下拉導(dǎo)航方法,涉及jQuery操作css樣式及鼠標(biāo)事件的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03淺析jQuery Ajax請(qǐng)求參數(shù)和返回?cái)?shù)據(jù)的處理
這篇文章主要介紹了淺析jQuery Ajax請(qǐng)求參數(shù)和返回?cái)?shù)據(jù)的處理的相關(guān)資料,需要的朋友可以參考下2016-02-02jQuery獲取父節(jié)點(diǎn)、子節(jié)點(diǎn)、兄弟節(jié)點(diǎn)的代碼
這篇文章主要介紹了jQuery獲取父節(jié)點(diǎn)、子節(jié)點(diǎn)、兄弟節(jié)點(diǎn)的代碼,需要的朋友可以參考下2023-06-06jQuery控制div實(shí)現(xiàn)隨滾動(dòng)條滾動(dòng)效果
這篇文章主要介紹了jQuery控制div實(shí)現(xiàn)隨滾動(dòng)條滾動(dòng)效果,對(duì)比分析了兩種實(shí)現(xiàn)方法供大家參考選擇,需要的朋友可以參考下2016-06-06jQuery+Ajax實(shí)現(xiàn)限制查詢間隔的方法
這篇文章主要介紹了jQuery+Ajax實(shí)現(xiàn)限制查詢間隔的方法,涉及jQuery的ajax方法參數(shù)設(shè)置及asp.net后臺(tái)交互的相關(guān)技巧,需要的朋友可以參考下2016-06-06jQuery+ajax中g(shù)etJSON() 用法實(shí)例
這篇文章主要介紹了jQuery+ajax中g(shù)etJSON() 用法實(shí)例,需要的朋友可以參考下2014-12-12