<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 命名空間模式 在這一節中,我們將探索JavaScript中關于命名空間的模式。命名空間可被看作位于一個唯一標識符下的代碼單元的邏輯組合。標識符可以被很多命名空間引用,每一個命名空間本身可以包含一個分支的嵌套命名空間(或子命名空間)。 在應用開發過程中,出于很多原因,我們都要使用命名空間。在JavaScript中,它們幫助我們避免在全局空間中于其他對象或者變量出現沖突。它們對于在代碼庫中組織功能塊也非常有用,這樣使用代碼就更容易被使用。 將任何重要的腳本或者應用納入命名空間是非常重要的,因為這是我們代碼的一層重要保護,使其免于與頁面中使用相同變量或方法名的其它腳本發生沖突。現在由于許多第三方標記規律的插入頁面,這可能是我們在職業生涯的某個時刻都需要處理的一個普遍的問題。作為一個行為端正的全局命名空間的“公民”,同樣重要的是,因為同樣的問題,我們最好不要阻礙其他開發人員的腳本運行。 雖然JavaScript并沒有像其它語言一樣真正內置的支持名稱空間,它具有對象和閉包,也可以用來達到相似的效果。 ## 命名空間原理 幾乎所有重要的 Javascript 程序中都會用到命名空間。除非我們只是編寫簡單的代碼,否則盡力確保正確地實現命名空間是很有必要的。這也能避免自己的代碼收到第三方代碼的污染。本小節將闡述以下設計模式: 1. 單一全局變量 2. 對象序列化的表示 3. 內嵌的命名空間 4. 即時調用的函數表達式 5. 命名空間注入 ### 單一全局變量 在 JavaScript 中實現命名空間的一個流行模式是,選擇一個全局變量作為引用的主對象。下面顯示的是此方法的框架實現,示例代碼中返回一個包含函數和屬性的對象: ~~~ var myApplication = (function () { function(){ //... }, return{ //... } })(); ~~~ 雖然這段代碼能在特定的環境下運行,單一全局變量模式的最大挑戰是如何確保同一頁面中的其他代碼不會使用相同的全局變量名稱。 ### 前綴命名空間 一個解決上面所述問題的方法,正如Peter Michaux提到的, 是使用前綴命名空間. 它本質上是一個簡單的概念,但原理是,我們選擇一個我們想用的(這個例子中我們用的是myApplication_)唯一的前綴命名空間,然后在這個前綴的后面定義任意的方法,變量或者其他對象,就像下面一樣: ~~~ var myApplication_propertyA = {}; var myApplication_propertyB = {}; function myApplication_myMethod(){ //... } ~~~ 從減少全局變量的角度來講這是非常有效的,但請記住,使用一個具有唯一命名的對象也能達到同樣的效果。 另一方面,這種模式的最大問題在于,一旦我們的應用開始增長,它會產生大量的全局對象。全局區域中對于我們沒有被其他開發人員使用的前綴也存在嚴重的依賴,所以當你選擇使用的時候,一定要小心。 ### 對象文字表示 對象文字表示(我們在本書的模塊模式一節中也提到過)可被認為是一個對象包含了一個集合,這個集合中存儲的是鍵值對,它們使用分號將每個鍵值對的鍵和值分隔開,這樣這些鍵也可以表示新的命名空間。 ~~~ var myApplication = { // As we've seen, we can easily define functionality for // this object literal.. getInfo:function(){ //... }, // but we can also populate it to support // further object namespaces containing anything // anything we wish: models : {}, views : { pages : {} }, collections : {} }; ~~~ 你也可以直接給命名空間添加屬性: ~~~ myApplication.foo = function(){ return "bar"; } myApplication.utils = { toString:function(){ //... }, export: function(){ //... } } ~~~ 對象文字具有在不污染全局命名空間的情況下幫助組織代碼和參數的優點。如果我們希望創建易讀的可以支持深度嵌套的結構,這將非常有用。與簡單的全局變量不同,對象文字也經常考慮測試相同名字的變量的存在,這樣就極大的降低了沖突的可能性。 下面例子中,我們展示了幾種方法,它們檢查是否變量(對象或者插件命名空間)存在,如果不存在就定義該變量。 ~~~ // This doesn't check for existence of "myApplication" in // the global namespace. Bad practice as we can easily // clobber an existing variable/namespace with the same name var myApplication = {}; // The following options *do* check for variable/namespace existence. // If already defined, we use that instance, otherwise we assign a new // object literal to myApplication. // // Option 1: var myApplication = myApplication || {}; // Option 2 if( !MyApplication ){ MyApplication = {} }; // Option 3: window.myApplication || ( window.myApplication = {} ); // Option 4: var myApplication = $.fn.myApplication = function() {}; // Option 5: var myApplication = myApplication === undefined ? {} : myApplication; ~~~ 我們經常看到開發人員使用Option1或者Option2,它們都很容易理解,而且他們的結果也是一樣的。 Option 3 假定我們在全局命名空間中,但也可以寫成下面的方式: ~~~ myApplication || (myApplication = {}); ~~~ 這種改變假定myApplication已經被初始化,所以它只對參數有效,如下: ~~~ function foo() { myApplication || ( myApplication = {} ); } // myApplication hasn't been initialized, // so foo() throws a ReferenceError foo(); // However accepting myApplication as an // argument function foo( myApplication ) { myApplication || ( myApplication = {} ); } foo(); // Even if myApplication === undefined, there is no error // and myApplication gets set to {} correctly ~~~ Options 4 對于寫jQuery插件很有效: ~~~ // If we were to define a new plugin.. var myPlugin = $.fn.myPlugin = function() { ... }; // Then later rather than having to type: $.fn.myPlugin.defaults = {}; // We can do: myPlugin.defaults = {}; ~~~ 這樣的結果是代碼壓縮(最小化)效果好,而且可以節省查找范圍。 Option 5 跟Option 4有些類似,但它是一個較長的形式,它用內聯的方式驗證myApplication是否未定義,如果未定義就將它定義為一個對象,否則就把已經定義的值賦給myApplication。 Option 5的展示是為了完整透徹起見,但在大多數情況下Option 1-4就足夠滿足大多數需求了。 當然,在使用對象文字實習組織代碼結構方面有很多變體. 對于希望為一個內部封閉的模塊暴漏一個嵌套的API的小應用來說,我們會發現自己使用“展示模塊模式”, 這個模式之前在本書中講過: ~~~ var namespace = (function () { // defined within the local scope var privateMethod1 = function () { /* ... */ }, privateMethod2 = function () { /* ... */ } privateProperty1 = "foobar"; return { // the object literal returned here can have as many // nested depths as we wish, however as mentioned, // this way of doing things works best for smaller, // limited-scope applications in my personal opinion publicMethod1: privateMethod1, // nested namespace with public properties properties:{ publicProperty1: privateProperty1 }, // another tested namespace utils:{ publicMethod2: privateMethod2 } ... } })(); ~~~ 對象文字的好處就是他們為我們提供了一種非常優雅的Key/Value語法,使用它,我們可以很容易的封裝我們應用中任意獨特的邏輯,而且能夠清楚的將它與其他代碼區分開,同時它為代碼擴展提供了堅實的基礎。 一個可能的弊端就是,對象文字可能會導致很長的語法結構,你可以選擇利用嵌套命名空間模式(它也使用了同樣的模式作為基礎) 這種模式也有很多有用的應用。除了命名空間,它也被用來把應用的默認配置縮減到一個獨立的區域中,這樣一來就修改配置就不需要查遍整個代碼庫了,對象文字在這方面表現非常好。下面的例子是一個假想的配置: ~~~ var myConfig = { language: "english", defaults: { enableGeolocation: true, enableSharing: false, maxPhotos: 20 }, theme: { skin: "a", toolbars: { index: "ui-navigation-toolbar", pages: "ui-custom-toolbar" } } } ~~~ > 注意:JSON是對象文字表示的一個子集,它與上面的例子(比如:JSON的鍵必須是字符串)只有細微的語法差異。如果出于某種原因,有人想使用JSON來存儲配置信息(比如:當發送到前端的時候),也是可以的。想了解更多關于對象文字表示模式,我建議閱讀Rebecca Murphey 的優秀文章 ,她講到了很多我們上面沒有提到的問題。 ### 嵌套命名空間 文字對象表示的一個擴展就是嵌套命名空間.它也是一個常用的模式,它降低了代碼沖突的可能,即使某個命名空間已經存在,它嵌套的命名空間沖突的可能性卻很小。 下面的代碼看起來熟悉嗎? ~~~ YAHOO.util.Dom.getElementsByClassName("test"); ~~~ Yahoo!'s YUI 庫經常使用嵌套命名空間模式, 當我在AOL當工程師的時候,我們在很多大型應用中也使用過這種模式。下面是嵌套命名空間的一個簡單的實現: ~~~ var myApp = myApp || {}; // perform a similar existence check when defining nested // children myApp.routers = myApp.routers || {}; myApp.model = myApp.model || {}; myApp.model.special = myApp.model.special || {}; // nested namespaces can be as complex as required: // myApp.utilities.charting.html5.plotGraph(/*..*/); // myApp.modules.financePlanner.getSummary(); // myApp.services.social.facebook.realtimeStream.getLatest(); ~~~ > 注意: 上面的代碼與YUI3實現命名空間是不同的。上面的模塊使用沙盒API來保存對象,而且使用了更少、更短的命名空間。 我們也可以像下面一樣,選擇使用索引屬性來定義新的嵌套命名空間/屬性: ~~~ myApp["routers"] = myApp["routers"] || {}; myApp["models"] = myApp["models"] || {}; myApp["controllers"] = myApp["controllers"] || {}; ~~~ 兩種選擇可讀性都很強,而且很有條理,它們都提供了與我們可能在其他語言中使用的類似的一種相對安全的方式來給我們的應用添加命名空間.唯一需要注意的是,這需要我們瀏覽器中的JavaScript引擎首先定位到myApp對象,然后深入挖掘,直到找到我們想使用的方法為止。 這就以為著在查找方面會增加很多工作,然后開發人員比如Juriy Zaytsev 以前就做過測試,而且發現單個對象命名空間與嵌套命名空間在性能方面的差異是可以忽略不計的。 ### 即時調用的函數表達式(IIFE)s 早在本書中,我們就簡單的介紹過IIFE (即時調用的函數表達式) ,它是一個未命名的函數,在它被定義之后就會立即執行。如果聽起來覺得耳熟,是因為你以前遇到過并將它稱之為自動生效的(或者自動調用的)匿名函數,然而我個人更認為 Ben Alman的 IIFE 命名更準確。在JavaScript中,因為在一個作用域中顯示定義的變量和函數只能在作用域中可見,函數調用為實現隱私提供了簡單的方式。 IIFEs 將應用邏輯封裝從而將它在全局命名空間中保護起來,但可以在命名空間范圍內使用。 下面是IIFEs的例子: ~~~ // an (anonymous) immediately-invoked function expression (function () { /*...*/})(); // a named immediately-invoked function expression (function foobar () { /*..*/}()); // this is technically a self-executing function which is quite different function foobar () { foobar(); } ~~~ 對于第一個例子稍微進行一下擴展: ~~~ var namespace = namespace || {}; // here a namespace object is passed as a function // parameter, where we assign public methods and // properties to it (function( o ){ o.foo = "foo"; o.bar = function(){ return "bar"; }; })( namespace ); console.log( namespace ); ~~~ 雖然可讀,這個例子可以被更大范圍的擴展到說明通用的開發問題,例如定義隱私的級別(public/private函數和變量),以及方便的命名空間擴展。我們來瀏覽更多的代碼: ~~~ // namespace (our namespace name) and undefined are passed here // to ensure 1\. namespace can be modified locally and isn't // overwritten outside of our function context // 2\. the value of undefined is guaranteed as being truly // undefined. This is to avoid issues with undefined being // mutable pre-ES5. ;(function ( namespace, undefined ) { // private properties var foo = "foo", bar = "bar"; // public methods and properties namespace.foobar = "foobar"; namespace.sayHello = function () { speak( "hello world" ); }; // private method function speak(msg) { console.log( "You said: " + msg ); }; // check to evaluate whether "namespace" exists in the // global namespace - if not, assign window.namespace an // object literal }( window.namespace = window.namespace || {} )); // we can then test our properties and methods as follows // public // Outputs: foobar console.log( namespace.foobar ); // Outputs: hello world namescpace.sayHello(); // assigning new properties namespace.foobar2 = "foobar"; // Outputs: foobar console.log( namespace.foobar2 ); ~~~ 對任何可擴展的命名空間模式,可擴展性當然是關鍵,可以通過使用IIFEs很容易的達到這個目標。在下面的例子中,我們的"namespace"再次被當作參數傳遞給匿名函數,之后擴展(或裝飾)了更多的功能: ~~~ // let's extend the namespace with new functionality (function( namespace, undefined ){ // public method namespace.sayGoodbye = function () { console.log( namespace.foo ); console.log( namespace.bar ); speak( "goodbye" ); } }( window.namespace = window.namespace || {}); // Outputs: goodbye namespace.sayGoodbye(); ~~~ ### 命名空間注入 命名空間注入是關于IIFE的另外一種變種,為了一個來自函數封裝中使用this作為命名空間代理的特定的命名空間,我們將方法和屬性“注入”, 這一模式提供的好處就是對于多個對象或者命名空間的應用程序的功能性行為的便利性,并且在應用一堆晚些時候將被構建的基礎方法(如getter和setter),這將會變得很有用處。 這一模式的缺點就是,如我在本節前面所述,也許還會有達成此目的更加簡單并且更加優化的方法存在(如,深度對象擴展/混合)。 下面我們馬上可以看到這一模式的一個示例,我們使用它來填充兩個命名空間的行為:一個最開始就定義(utils),而另外一個我們則將其作為utils的功能性賦值的一部分來動態創建(一個稱作tools的新的命名空間)。 ~~~ var myApp = myApp || {}; myApp.utils = {}; (function () { var val = 5; this.getValue = function () { return val; }; this.setValue = function( newVal ) { val = newVal; } // also introduce a new sub-namespace this.tools = {}; }).apply( myApp.utils ); // inject new behaviour into the tools namespace // which we defined via the utilities module (function () { this.diagnose = function(){ return "diagnosis"; } }).apply( myApp.utils.tools ); // note, this same approach to extension could be applied // to a regular IIFE, by just passing in the context as // an argument and modifying the context rather than just // "this" // Usage: // Outputs our populated namespace console.log( myApp ); // Outputs: 5 console.log( myApp.utils.getValue() ); // Sets the value of `val` and returns it myApp.utils.setValue( 25 ); console.log( myApp.utils.getValue() ); // Testing another level down console.log( myApp.utils.tools.diagnose() ); ~~~ Angus Croll先前也出過使用調用API來提供上下文環境和參數之間自然分離的主意。這一模式感覺上像是一個模塊創建器,但是由于模塊仍然提供了一個封裝的解決方案, 為全面起見,我們還是將簡要的介紹一下它: ~~~ // define a namespace we can use later var ns = ns || {}, ns2 = ns2 || {}; // the module/namespace creator var creator = function( val ){ var val = val || 0; this.next = function () { return val++ }; this.reset = function () { val = 0; } } creator.call( ns ); // ns.next, ns.reset now exist creator.call( ns2 , 5000 ); // ns2 contains the same methods // but has an overridden value for val // of 5000 ~~~ 如前所述,這種類型的模式對于將一個類似的功能的基礎集合分派給多個模塊或者命名空間是非常有用的。然而我會只建議將它使用在要在一個對象/閉包中明確聲明功能,而直接訪問并沒有任何意義的地方。 ## 高級命名空間模式 接下來說說我在開發大型應用過程中發現的幾種有用的模式和工具,其中一些需要我們重新審視傳統應用的命名空間的使用方式.需要注意的是,我并非有意夸大以下幾種是正確的命名空間之路,只是我在工作中發現他們確實好用。 ### 自動嵌套命名空間 我們提到過,嵌套命名空間可以為代碼提供一個組織良好的層級結構.下邊是一個例子:application.utilities.drawing.canvas.2d . 可以用文字對象模式展開如下: ~~~ var application = { utilities:{ drawing:{ canvas:{ 2d:{ //... } } } } }; ~~~ 使用這種模式會遇到一些問題,一個顯而易見的就是每天加一個層級,就需要我們在頂級命名空間下的某個父級元素里定義一個額外的對象.當應用越來越復雜的時候,我們需要的層級增多,解決這個問題也就更加困難。 怎樣更好的解決這個問題呢? 在JavaScript設計模式中, Stoyan Stefanov 提出了一個非常精巧的方法以便在已存在的全局變量下定義嵌套的命名空間。 他建議的簡便方法是為每一層嵌套提供一個單字符聲明,解析這個聲明就可以自動算出包含必要對象的命名空間。 我(筆者)將他建議使用的方法改進為一個通用方法,以便對多重命名空間更容易地做出復用,方法如下: ~~~ // 頂級命名空間賦值為對象字面量 var myApp = myApp || {}; // 解析字符命名空間并自動生成嵌套命名空間的快捷方法 function extend( ns, ns_string ) { var parts = ns_string.split("."), parent = ns, pl; pl = parts.length; for ( var i = 0; i < pl; i++ ) { // create a property if it doesn't exist if ( typeof parent[parts[i]] === "undefined" ) { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; } // 用法: // extend為myApp加入深度嵌套的命名空間 var mod = extend(myApp, "modules.module2"); // 輸出深度嵌套的正確對象 console.log(mod); // 用于檢查mod的實例作為包含擴展的一個實體也能夠被myApp命名空間以外被使用的少量測試 // 輸出: true console.log(mod == myApp.modules.module2); // 進一步演示用extend賦予嵌套命名空間更簡單 extend(myApp, "moduleA.moduleB.moduleC.moduleD"); extend(myApp, "longer.version.looks.like.this"); console.log(myApp); ~~~ Web審查工具輸出: ![](http://wiki.jikexueyuan.com/project/javascript-design-patterns/images/Fmos.png) 一行簡潔的代碼就可以很輕松地,為他們的命名空間像以前的對象那樣明確聲明各種各樣的嵌套。 ## 依賴聲明模式 現在我們將探索一種對嵌套命名空間模式的一種輕微的增強,它將被我們引申為依賴聲明模式。我們都知道對于對象的本地引用能夠降低全局查找的時間,但讓我們來將它應用在命名空間中,看看實踐中它表現怎么樣: ~~~ // common approach to accessing nested namespaces myApp.utilities.math.fibonacci( 25 ); myApp.utilities.math.sin( 56 ); myApp.utilities.drawing.plot( 98,50,60 ); // with local/cached references var utils = myApp.utilities, maths = utils.math, drawing = utils.drawing; // easier to access the namespace maths.fibonacci( 25 ); maths.sin( 56 ); drawing.plot( 98, 50,60 ); // note that the above is particularly performant when // compared to hundreds or thousands of calls to nested // namespaces vs. a local reference to the namespace ~~~ 這里使用一個本地變量相比頂層上一個全局的(如,myApp)幾乎總是會更快。相比訪問其后每行嵌套的屬性/命名空間,這也更加的方便,性能表現更好,并且能夠在更加復雜的應用程序場景下面提升可讀性。 Stoyan建議在我們的函數范圍(使用單變量模式)的頂部聲明函數或者模塊需要的局部命名空間,并把這稱為依賴聲明模式。其中的一個好處是減少了定位和重定向依賴關系的時間,從而使我們有一個可擴展的架構,當需要時可以在命名空間里動態地加載模塊。 在我看來,這種方式應用于模塊化級別時,將被其他方法使用的命名空間局部化是最有效。我建議盡量避免把命名空間局部化在單個函數級別,尤其是對于命名空間的依賴關系上有明顯的重疊的情況。對應的方法是,在上部定義并使它們可以進入同一個引用。 ### 深度對象擴展 另一種實現自動命名空間的方式就是深度對象擴展. 使用對象文字表示的命名空間可以很容易地與其他對象(或命名空間)擴展(或者合并) 這樣兩個命名空間下的屬性和方法就可以在同一個合并后的命名空間下被訪問。 一些現代的JavaScript框架已經把這個變得非常容易(例如,jQuery的$.extend),然而,如果你想尋找一種使用普通的JS來擴展對象(命名空間)的方式,下面的內容將很有幫助。 ~~~ // extend.js // Written by Andrew Dupont, optimized by Addy Osmani function extend( destination, source ) { var toString = Object.prototype.toString, objTest = toString.call({}); for ( var property in source ) { if ( source[property] && objTest === toString.call(source[property]) ) { destination[property] = destination[property] || {}; extend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; }; console.group( "objExtend namespacing tests" ); // define a top-level namespace for usage var myNS = myNS || {}; // 1\. extend namespace with a "utils" object extend(myNS, { utils:{ } }); console.log( "test 1" , myNS); // myNS.utils now exists // 2\. extend with multiple depths (namespace.hello.world.wave) extend(myNS, { hello:{ world:{ wave:{ test: function(){ //... } } } } }); // test direct assignment works as expected myNS.hello.test1 = "this is a test"; myNS.hello.world.test2 = "this is another test"; console.log( "test 2", myNS ); // 3\. what if myNS already contains the namespace being added // (e.g. "library")? we want to ensure no namespaces are being // overwritten during extension myNS.library = { foo:function () {} }; extend( myNS, { library:{ bar:function(){ //... } } }); // confirmed that extend is operating safely (as expected) // myNS now also contains library.foo, library.bar console.log( "test 3", myNS ); // 4\. what if we wanted easier access to a specific namespace without having // to type the whole namespace out each time? var shorterAccess1 = myNS.hello.world; shorterAccess1.test3 = "hello again"; console.log( "test 4", myNS); //success, myApp.hello.world.test3 is now "hello again" console.groupEnd(); ~~~ 注意: 上面的實現對于所有的對象來說不是跨瀏覽器的而且只應該被認為是一個概念上的證明. 你可能會覺得前面帶下劃線的js.extend()方法更簡單一些,下面的鏈接提供了更多的跨瀏覽器實現,[](http://documentcloud.github.com/underscore/docs/underscore.html#section-67)[http://documentcloud.github.com/underscore/docs/underscore.html#section-67](http://documentcloud.github.com/underscore/docs/underscore.html#section-67)。 另外,從代碼中抽取出來的jQuery $.extend() 方法可以在這里找到:?[](https://github.com/addyosmani/jquery.parts)[https://github.com/addyosmani/jquery.parts](https://github.com/addyosmani/jquery.parts)。 對于那些將使用jQuery的開發者來說, 可以像下面一樣使用$.extend來達到同樣的對象命名空間擴展的目的: ~~~ // top-level namespace var myApp = myApp || {}; // directly assign a nested namespace myApp.library = { foo:function(){ //... } }; // deep extend/merge this namespace with another // to make things interesting, let's say it's a namespace // with the same name but with a different function // signature: $.extend( deep, target, object1, object2 ) $.extend( true, myApp, { library:{ bar:function(){ //... } } }); console.log("test", myApp); // myApp now contains both library.foo() and library.bar() methods // nothing has been overwritten which is what we're hoping for. ~~~ 為了透徹起見,請點擊這里 來查看jQuery $.extend來獲取跟這一節中其它實現命名空間的過程類似的功能。 ## 建議 回顧我們在本部分探討的命名空間模式,對于大多數更大的應用程序,我個人則是選擇嵌入用對象字面值模式為命名空間的對象。我盡可能地用自動嵌入命名空間,當然這只是個人偏好罷了。 IIFEs 和單個全局變量可能只在中小規模的應用程序中運轉良好。然而,更大的需要命名空間和深度子命名空間的代碼庫則需要一個簡明的,能提高可讀性和規模的解決方案。我認為是這種模式很好地達到了這些目標。我同樣推薦你嘗試一些拓展命名空間的高級實用的方法,因為它們能長期地節省我們的時間。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看