摘要:更細(xì)致的為了應(yīng)對(duì)更為復(fù)雜的對(duì)象合并要求,提供了方法。注意到,當(dāng)兩者都為時(shí),得到的正是在配置對(duì)象合并中的一個(gè)理想結(jié)果。它們都類似于對(duì)象合并,而且有趣的是,其源碼中都調(diào)用了方法。其深合并的實(shí)現(xiàn)方法依然是遞歸。
起因:配置選項(xiàng)
“配置”(Config | Options | Settings)對(duì)大家來(lái)說(shuō)一定是非常熟悉的詞。就以一般玩的游戲?yàn)槔?,很多游戲的主界面,搭配的菜單?huì)是"start","load","config","exit"這樣的搭配。在"config"中,我們可以調(diào)整游戲參數(shù),比如音量、控制按鍵等,以更符合自己的游戲習(xí)慣。不過(guò),也有游戲并沒(méi)有"config",這些游戲其實(shí)就是只有一個(gè)不允許修改的“默認(rèn)”(Defaults)配置,而即使如此,這些游戲至少也會(huì)給你一份指南,告訴你應(yīng)該如何按照預(yù)設(shè)進(jìn)行游戲。
應(yīng)用的配置這一環(huán)節(jié)的設(shè)計(jì),其實(shí)就是要求要將配置數(shù)據(jù)從代碼中分離出來(lái)。也就是說(shuō),那些可以被修改,或者將來(lái)可能會(huì)被修改的內(nèi)容(這些內(nèi)容也是代碼),應(yīng)該和指令類的代碼區(qū)別開來(lái),多帶帶放在一個(gè)地方。這樣,源碼修改可以減少很多意外的錯(cuò)誤。
javascript編寫的封裝的功能模塊,比如常見的jQuery插件,一般都會(huì)這樣分離出配置數(shù)據(jù)。下面是[slidesjs][]的使用范例:
$("#slides").slidesjs({ width: 940, height: 528 });
這里的{width: 940, height: 528}即是配置數(shù)據(jù)。不過(guò),只是這些數(shù)據(jù)是不能獨(dú)立支撐起完整的功能的。在封裝的代碼之內(nèi)的,還隱藏著完整的初始配置數(shù)據(jù),這就是默認(rèn)配置。而像上面這樣在實(shí)際使用時(shí)傳入的配置數(shù)據(jù),一般叫做用戶配置。它們之間的關(guān)系是,如果同時(shí)包含對(duì)某一選項(xiàng)的配置值,用戶配置會(huì)覆蓋掉默認(rèn)配置。
比如在Sublime Text中,就是這種設(shè)計(jì)(Default和User):
可以想到,實(shí)際功能運(yùn)行時(shí),使用的配置數(shù)據(jù)既不是默認(rèn)配置,也不是用戶配置,而是這兩個(gè)配置的結(jié)合。在javascript中,配置數(shù)據(jù)都是對(duì)象(Object)的形式,因此,我們需要做對(duì)象合并。
在YUI的核心包(yui-core)中,就提供了對(duì)象合并有關(guān)的方法。
YUI中的對(duì)象合并方法 淺合并的Y.merge()YUI提供了合并一個(gè)或更多對(duì)象到一個(gè)新的合并對(duì)象上的方法Y.merge(),在這里先使用它。假如有一個(gè)封裝的功能模塊(簡(jiǎn)單的游戲?),然后內(nèi)部通過(guò)Y.merge()進(jìn)行配置對(duì)象的合并:
var myModule = function(userConfig){ var defaults = { volume: 0, quality: "normal", control: { left: "left", right: "right", attack: "space" } }; var config = Y.merge(defaults, userConfig); // use the merged config to run this module... };
對(duì)應(yīng)上面已有的默認(rèn)配置的形式,給出如下的用戶配置:
var userConfig = { quality: "high", control: { left: "a", right: "d", extra: "z" } };
這里通過(guò)Y.merge()合并得到的config,輸出結(jié)果是:
{ volume: 0, quality: "high", control: { left: "a", right: "d", extra: "z" } }
可以看到,volume和quality的屬性合并都達(dá)到了預(yù)想的要求。但是,屬性control卻像是忽略了默認(rèn)值的部分,并不符合預(yù)想結(jié)果。這是因?yàn)?,屬?b>control的值并不是基本數(shù)據(jù)類型,而是引用數(shù)據(jù)類型(例如對(duì)象、數(shù)組或函數(shù)),因此這里的配置對(duì)象是包含多層次的復(fù)雜配置對(duì)象。Y.merge()并沒(méi)有處理這樣的情況,它的源碼如下:
Y.merge = function () { var i = 0, len = arguments.length, result = {}, key, obj; for (; i < len; ++i) { obj = arguments[i]; for (key in obj) { if (hasOwn.call(obj, key)) { result[key] = obj[key]; } } } return result; };
其中hasOwn是Object.prototype.hasOwnProperty。從上面的源碼可以看出,Y.merge()只對(duì)直接屬性(層級(jí)數(shù)為1)進(jìn)行賦值,并沒(méi)有分析屬性的值類別。因此,在前面的對(duì)象合并中,config的control屬性,實(shí)際上就和userConfig的control是同一個(gè)引用,如果在config上修改control對(duì)象,則也會(huì)改變userConfig的control對(duì)象。
不過(guò),Y.merge()仍然是很有價(jià)值的對(duì)象合并方法。如果是單層次的配置對(duì)象,它足以實(shí)現(xiàn)預(yù)想的要求,這是它很有用的一個(gè)模式。此外,請(qǐng)注意這個(gè)方法創(chuàng)建了一個(gè)空對(duì)象result用作最終返回,而不是直接對(duì)參數(shù)進(jìn)行操作,所以,Y.merge()方法不會(huì)影響作為參數(shù)的任一對(duì)象。
更細(xì)致的Y.mix()為了應(yīng)對(duì)更為復(fù)雜的對(duì)象合并要求,YUI提供了方法Y.mix()。Y.mix()不能像Y.merge()那樣同時(shí)合并2個(gè)以上的對(duì)象,它的合并對(duì)象數(shù)目限定為2個(gè),對(duì)應(yīng)前2個(gè)參數(shù),此外的參數(shù)都用作合并模式、方法的設(shè)置。Y.mix()搭配其多種模式和配置的控制,可以實(shí)現(xiàn)相當(dāng)精細(xì)的兩個(gè)對(duì)象的合并(方法叫做"mix",所以也稱為混合吧)。
Y.mix()的源碼較多,不直接貼在本文中。YUI官方的注釋寫得很詳細(xì),不過(guò)在這里我也說(shuō)一些我自己的理解。它的源碼形式是:
Y.mix = function(receiver, supplier, overwrite, whitelist, mode, merge) { // Y.mix() detail );
Y.mix()有很多參數(shù)。前2個(gè)參數(shù)receiver和supplier如字面意思,分別對(duì)應(yīng)合并操作的接收者和提供者。其余4個(gè)參數(shù)的意義分別是:
overwrite,默認(rèn)為false。如果為true則會(huì)開啟屬性覆蓋。也就是說(shuō),默認(rèn)情況下,提供者的屬性是不會(huì)覆蓋接受者的同名屬性的。
whitelist,白名單。可以指定一個(gè)數(shù)組,只有當(dāng)提供者的屬性名包含于白名單數(shù)組內(nèi)時(shí),屬性才會(huì)被拷貝到接受者。
mode,混合模式。可選值0、1、2、3、4分別對(duì)應(yīng)5個(gè)模式。默認(rèn)值為0,是object to object的合并方式。
merge,默認(rèn)為false。如果為true,對(duì)象上的值為引用數(shù)據(jù)類型的屬性(也就是,當(dāng)這是一個(gè)多層次的復(fù)雜對(duì)象時(shí)),每一個(gè)層級(jí)上的對(duì)象都會(huì)依次被合并,而不是被忽略(overwrite為false)或被直接覆蓋(overwrite為true)。
上面的參數(shù)中,overwrite和merge的意義比較重要,其他的則一般使用null或默認(rèn)值。為了理解overwrite和merge,直接進(jìn)行不同搭配的合并測(cè)試(配置變量和前文一樣)。測(cè)試代碼和結(jié)果如下:
// overwrite = false , merge = false | Y.mix()的最簡(jiǎn)單形式,默認(rèn)值 var config = Y.mix(defaults, userConfig); // 結(jié)果輸出: // {volume => 0, quality => normal, control => {left => left, right => right, attack => space}} // overwrite = true , merge = false var config = Y.mix(defaults, userConfig, true, null, 0, false); // 結(jié)果輸出: // {volume => 0, quality => high, control => {left => a, right => d, extra => e}} // overwrite = false , merge = true var config = Y.mix(defaults, userConfig, false, null, 0, true); // 結(jié)果輸出: // {volume => 0, quality => normal, control => {left => left, right => right, attack => space, extra => e}} // overwrite = true , merge = true var config = Y.mix(defaults, userConfig, true, null, 0, true); // 結(jié)果輸出: // {volume => 0, quality => high, control => {left => a, right => d, attack => space, extra => e}}
可以看出,4個(gè)結(jié)果各不相同。從這些結(jié)果的具體情況,可以分析體會(huì)overwrite和merge的意義。注意到,當(dāng)兩者都為true時(shí),得到的正是在配置對(duì)象合并中的一個(gè)理想結(jié)果。而當(dāng)overwrite = true, merge = false時(shí),得到的結(jié)果和Y.merge()相同。
閱讀Y.mix()的源碼,可以了解到,merge決定是否進(jìn)行深合并。深合并的實(shí)現(xiàn)原理是遞歸,也就是對(duì)那些需要多層次合并的屬性值(對(duì)象、數(shù)組),再次調(diào)用Y.mix()。overwrite決定是否進(jìn)行覆蓋,這就類似于電腦里做復(fù)制操作時(shí),會(huì)提示同名文件是否覆蓋的選項(xiàng)。
對(duì)于用作配置數(shù)據(jù)的對(duì)象而言,預(yù)期的最佳的合并方式就是允許覆蓋,而且進(jìn)行深合并。因此,Y.mix(defaults, userConfig, true, null, 0, true)即可以得到最適當(dāng)?shù)暮喜⒑蟮呐渲脭?shù)據(jù)。
需要注意的是,Y.mix()直接對(duì)接收者進(jìn)行操作,并返回接收者。所以,和Y.merge()不同,它一定會(huì)影響到作為第一個(gè)參數(shù)的對(duì)象。
如果可以確定配置數(shù)據(jù)是單層次的簡(jiǎn)單配置對(duì)象,那么使用Y.merge()要更簡(jiǎn)單方便。
更多的工具YUI除了核心包內(nèi)的上面兩個(gè)方法外,還在oop模塊中提供了一些處理對(duì)象的附加工具。其中包括了Y.extend()(單詞意義:擴(kuò)展),Y.augment()(單詞意義:增強(qiáng)),Y.aggregate()(單詞意義:聚合)。它們都類似于對(duì)象合并,而且有趣的是,其源碼中都調(diào)用了Y.mix()方法。
這幾個(gè)方法和前面的有哪些不同呢?本文限于篇幅,只簡(jiǎn)單說(shuō)一些。Y.aggregate()是開啟深合并的Y.mix()的一個(gè)方法糖,Y.extend()則要求參數(shù)是構(gòu)造函數(shù),它操作的是傳入對(duì)象的prototype,并通過(guò)prototype建立繼承關(guān)系(原型鏈),Y.augment()是從提供者拷貝方法到接收者,但它巧妙地隔離了所有拷貝的方法并延遲調(diào)用提供者的構(gòu)造函數(shù),直到你第一次調(diào)用被拷貝上去的新方法。
有些關(guān)系的jQuery.extend()因?yàn)楸容^近似,所以在這里也提一下jQuery。
如果你寫過(guò)jQuery插件,你肯定知道jQuery.extend()這個(gè)方法。事實(shí)證明,不同的javascript庫(kù),在命名理念上也是各有想法,在此請(qǐng)注意,jQuery.extend()和Y.extend()是完全不同的兩個(gè)方法。閱讀jQuery.extend()的源碼可知,它仍然是在做傳統(tǒng)的對(duì)象合并,而且同時(shí)允許聲明一個(gè)可選的邏輯參數(shù)deep,來(lái)指定是否進(jìn)行深合并。其深合并的實(shí)現(xiàn)方法依然是遞歸。
此外,類似于Y.mix(),jQuery.extend()也會(huì)影響到作為接收者的對(duì)象。官方文檔對(duì)此還給了明確說(shuō)明,如果希望不影響原來(lái)的對(duì)象,就使用var object = $.extend({}, object1, object2);這樣以空對(duì)象作為接收者的寫法。
結(jié)語(yǔ)之所以想到寫這個(gè)話題,大概就是因?yàn)閅UI里有關(guān)對(duì)象合并很有幾個(gè)不同的方法,我已經(jīng)相當(dāng)被它們弄暈了。所以,果斷讀源碼,才能理清這其中的區(qū)別,也算是體會(huì)一下javascript庫(kù)設(shè)立這些不同的方法的本因吧。
默認(rèn)配置 + 用戶配置 → 實(shí)際配置,結(jié)果來(lái)看也是一個(gè)不簡(jiǎn)單的轉(zhuǎn)化計(jì)算式。
(重新編輯自我的博客,原文地址:http://acgtofe.com/posts/2014/07/object-toolkit-in-yui)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87600.html
摘要:所有依賴這個(gè)模塊的語(yǔ)句,都定義在一個(gè)回調(diào)函數(shù)中,等到所有依賴加載完成之后前置依賴,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行。如果將前面的代碼改寫成形式,就是下面這樣定義了一個(gè)文件,該文件依賴模塊,當(dāng)模塊加載完畢之后執(zhí)行回調(diào)函數(shù),這里并沒(méi)有暴露任何變量。 模塊化是我們?nèi)粘i_發(fā)都要用到的基本技能,使用簡(jiǎn)單且方便,但是很少人能說(shuō)出來(lái)但是的原因及發(fā)展過(guò)程。現(xiàn)在通過(guò)對(duì)比不同時(shí)期的js的發(fā)展,將JavaScript模...
摘要:參考精讀模塊化發(fā)展模塊化七日談前端模塊化開發(fā)那點(diǎn)歷史本文先發(fā)布于我的個(gè)人博客模塊化開發(fā)的演進(jìn)歷程,后續(xù)如有更新,可以查看原文。 Brendan Eich用了10天就創(chuàng)造了JavaScript,因?yàn)楫?dāng)時(shí)的需求定位,導(dǎo)致了在設(shè)計(jì)之初,在語(yǔ)言層就不包含很多高級(jí)語(yǔ)言的特性,其中就包括模塊這個(gè)特性,但是經(jīng)過(guò)了這么多年的發(fā)展,如今對(duì)JavaScript的需求已經(jīng)遠(yuǎn)遠(yuǎn)超出了Brendan Eich的...
摘要:執(zhí)行環(huán)境在很多方面都有其獨(dú)特之處全局變量和函數(shù)便是其中之一事實(shí)上的初始執(zhí)行環(huán)境是由多種多樣的全局變量所定義的這寫全局變量在腳本環(huán)境創(chuàng)建之初就已經(jīng)存在了我們說(shuō)這些都是掛載在全局對(duì)象上的全局對(duì)象是一個(gè)神秘的對(duì)象它表示了腳本最外層上下文在瀏覽器中 JavaScript執(zhí)行環(huán)境在很多方面都有其獨(dú)特之處. 全局變量和函數(shù)便是其中之一. 事實(shí)上, js的初始執(zhí)行環(huán)境是由多種多樣的全局變量所定義的,...
摘要:最近在全力整理高性能的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識(shí)點(diǎn)。 最近在全力整理《高性能JavaScript》的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識(shí)點(diǎn)。 前端開發(fā)文檔 高性能JavaScript 第1章:加載和執(zhí)行 腳本位置 阻止腳本 無(wú)阻塞的腳本 延遲的腳本 動(dòng)態(tài)腳本元素 XMLHTTPRequest腳本注入 推薦的無(wú)阻塞模式...
摘要:我是這一期的主持人黃子毅本期精讀的文章是。模塊化需要保證全局變量盡量干凈,目前為止的模塊化方案都沒(méi)有很好的做到這一點(diǎn)。精讀本次提出獨(dú)到觀點(diǎn)的同學(xué)有流形,黃子毅,蘇里約,,楊森,淡蒼,留影,精讀由此歸納。 這次是前端精讀期刊與大家第一次正式碰面,我們每周會(huì)精讀并分析若干篇精品好文,試圖討論出結(jié)論性觀點(diǎn)。沒(méi)錯(cuò),我們?cè)噲D通過(guò)觀點(diǎn)的碰撞,爭(zhēng)做無(wú)主觀精品好文的意見領(lǐng)袖。 我是這一期的主持人 ——...
閱讀 3332·2023-04-25 16:25
閱讀 3861·2021-11-15 18:01
閱讀 1620·2021-09-10 11:21
閱讀 3026·2021-08-02 16:53
閱讀 3094·2019-08-30 15:55
閱讀 2499·2019-08-29 16:24
閱讀 2112·2019-08-29 13:14
閱讀 1048·2019-08-29 13:00