整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Javascript設計模式-單例模式

          Javascript設計模式-單例模式

          整個應用之內全局共享一個實例的模式,但它在JS中竟然是一種反模式

          所謂單例模式是指遵循這個模式設計的類,僅會被實例化一次,并且其實例允許全局獲取。單例模式下派生的示例允許我們在全局共享唯一實例,因此非常適合用于保存整個應用的全局狀態。

          首先讓我們先看看在ES2015的語法下單例模式長什么樣子。比如我們想要創建一個計數器類,用于保存全局的某個行為發生的次數,那么對于這個類的設計,我們應該考慮實現如下4個方法:

          • getInstance方法,用于返回全局唯一的實例
          • getCount方法用于返回當前實例的counter實例變量的值
          • increment方法用于為counter屬性的值加一
          • decrement方法用于為counter屬性的值減一
          let counter=0;
          
          class Counter {
            getInstance() {
              return this;
            }
          
            getCount() {
              return counter;
            }
          
            increment() {
              return ++counter;
            }
          
            decrement() {
              return --counter;
            }
          }

          然而上面這個類的實現并不符合單例模式的標準。單例模式應該僅被允許實例化一次。但現在我們可以使用上面的Counter類反復實例化出新的對象。

          let counter=0;
          
          class Counter {
            getInstance() {
              return this;
            }
          
            getCount() {
              return counter;
            }
          
            increment() {
              return ++counter;
            }
          
            decrement() {
              return --counter;
            }
          }
          
          const counter1=new Counter();
          const counter2=new Counter();
          
          console.log(counter1.getInstance()===counter2.getInstance()); // false

          通過調用兩次new方法,此時counter1和counter2兩個實例看上去應該是擁有同樣的初試屬性。但是通過分別調用兩個實例各自的getInstance方法卻返回了兩個不同對象的引用:他們不是嚴格相等的。

          接下來我們需要想辦法保證通過Counter類僅允許一個實例被創建。

          一種解決方案就是創建一個instance變量。在Counter類的構造函數中,當新的實例被創建時,將實例對象的引用賦值給instance。在第一個實例被創建之后我們就可以通過instance變量是否有值來判斷是否需要阻斷新進入的實例化過程。如果需要阻斷,那就意味著已經有一個被創建的對象存在。這顯然不符合單例模式的標準,于是我們拋出一個異常以便用戶明白哪里出了問題。

          let instance;
          let counter=0;
          
          class Counter {
            constructor() {
              if (instance) {
                throw new Error("You can only create one instance!");
              }
              instance=this;
            }
          
            getInstance() {
              return this;
            }
          
            getCount() {
              return counter;
            }
          
            increment() {
              return ++counter;
            }
          
            decrement() {
              return --counter;
            }
          }
          
          const counter1=new Counter();
          const counter2=new Counter();
          // Error: You can only create one instance!

          完美!現在我們已經不允許重復創建Counter類的實例了。

          接下來讓我們從counter.js文件中導出Counter實例。但在這之前,我們應該先對此實例執行freeze操作。Object.freeze方法能夠保證實例的消費者無法修改單例。在被凍結的實例中的屬性不可被添加或者修改,因此就降低了不小心覆蓋單例屬性值的風險。

          let instance;
          let counter=0;
          
          class Counter {
            constructor() {
              if (instance) {
                throw new Error("You can only create one instance!");
              }
              instance=this;
            }
          
            getInstance() {
              return this;
            }
          
            getCount() {
              return counter;
            }
          
            increment() {
              return ++counter;
            }
          
            decrement() {
              return --counter;
            }
          }
          
          const singletonCounter=Object.freeze(new Counter());
          export default singletonCounter;

          假設我們有一個應用使用了上面的Counter類,我們大概會需要如下幾個文件:

          • counter.js:包含Counter類的聲明,以及Counter類單一實例的默認導出
          • index.js:加載一個名為redButton.js的模塊和另一個名為blueButton.js的模塊
          • redButton.js:導入Counter類,并且向紅色按鈕添加Counter實例的increment方法作為事件監聽的回調函數,并通過調用counter實例的getCounter方法獲取當前的計數器值
          • blueButton.js:導入Counter類,并且向藍色按鈕添加Counter實例的increment方法作為事件監聽的回調函數,并通過調用counter實例的getCounter方法獲取當前的計數器值

          blueButton.js和redButton.js都引入了同一個counter.js的實例。也就是說這個實例作為Counter變量被導入到了兩個文件中。

          無論在redButton.js或是blueButton.js中調用increment方法時,Counter實例的counter屬性值的更新會同步反應在兩個文件中。不管我們點擊的是紅色按鈕還是藍色按鈕:所有引用Counter實例的對象會共享同一個值。這就是為什么即便我們在不同的文件中調用遞增方法,計數器的值都會增加的原因。


          優 / 劣勢

          對一次性實例化的嚴格限制顯然有節省內存空間的潛力。相對于每次都為新對象分配內存,通過單例模式我們僅需要為一個對象分配內存即可。然而,單例模式實際上被認為是一種反模式,并且應該避免在JavaScript中使用。

          在很多編程語言中,比如Java或者C++,不可能像我們在JavaScript中一樣直接創建對象。在這些面向對象的編程語言中,需要創建類,類創建對象。這些創建了的對象都擁有類實例的值,正如上面JavaScript示例中的instance實例一樣。

          但是像在上面示例代碼中那樣去聲明一個單例模式的類顯然有點大炮打蚊子。既然我們可以在JavaScript中直接創建對象,為什么不能直接創建一個對象來達到同樣的目的呢?接下來我們說說使用單例模式的缺陷!

          也,可以直接使用一個普通對象

          仍然使用上面示例中的場景。只不過這一次我們將counter直接定義為一個擁有如下屬性的簡單對象:

          • count屬性
          • increment方法為count屬性遞增一
          • decrement方法為count屬性遞減一
          let count=0;
          
          const counter={
            increment() {
              return ++count;
            },
            decrement() {
              return --count;
            }
          };
          
          Object.freeze(counter);
          export { counter };

          由于對象傳遞的是引用,所以redButton.js和blueButton.js都導入了counter對象的同一個引用。無論在哪個文件中修改count屬性的值都會修改counter對象的值,這個結果可以在兩個引用它的文件中觀察到。

          測試時的麻煩事

          測試單例模式的測試代碼需要點技巧。由于我們不能每次都創建一個新的實例,因此所有測試都依賴于上一個測試用例對于全局引入的那個對象的修改。在此例中,測試用例的順序關系到整個測試套件的成敗。在測試之后,我們還需要注意重置對象的狀態,以便其他不相關但也引入了單例對象的其他測試用例不受影響。

          import Counter from "../src/counterTest";
          
          test("incrementing 1 time should be 1", ()=> {
            Counter.increment();
            expect(Counter.getCount()).toBe(1);
          });
          
          test("incrementing 3 extra times should be 4", ()=> {
            Counter.increment();
            Counter.increment();
            Counter.increment();
            expect(Counter.getCount()).toBe(4);
          });
          
          test("decrementing 1  times should be 3", ()=> {
            Counter.decrement();
            expect(Counter.getCount()).toBe(3);
          });

          代碼的壞味道:隱藏對于單例模塊的依賴

          當引入某一個其他模塊,在此例中是superCounter.js時,引用superCounter的代碼可能并不很清楚它的深層依賴是一個單例對象。而假設同時其他文件比如index.js中引入這個superCounter類,并且調用了遞增或者遞減方法。如此一來我們可能會在無意間改變了單例對象的值。這會導致意外行為的發生,由于多個superCounter的實例運行與整個應用中,而多個superCounter實例實際上對單例對象進行了多次引用,于是所有對于單例對象的引用都會導致counter屬性的修改。而這一修改很可能并不是代碼作者的本意,但卻無意間導致了意外行為的產生。

          import Counter from "./counter";
          
          export default class SuperCounter {
            constructor() {
              this.count=0;
            }
          
            increment() {
              Counter.increment();
              return (this.count +=100);
            }
          
            decrement() {
              Counter.decrement();
              return (this.count -=100);
            }
          }

          全局行為

          單例實例本應在整個應用中被全局引用。但是全局變量本質上都會具有統一的特質:既然全局變量可以在全局作用域中使用,那我們理所當然的能夠在全局任何地方獲取到全局變量的引用。

          于是問題出現了,在程序工程領域中,全局變量通常被認為是一個糟糕的設計。無意中對全局變量的覆蓋最終造成全局變量污染,而這也是自有工程化的編寫代碼以來最常見的以外行為產生的原因。

          在ES2015時代開啟之后,創建全局變量已經不太常見。新引入的let和const關鍵字通過綁定代碼塊作用域,能夠阻止開發人員無意間污染全局作用域。另外新引入的模塊系統在更易于創建全局可獲取的變量之余,免除了對全局作用域的污染。

          但是單例模式最常見使用場景反而正是這種需要在整個應用中保存某種全局狀態的用例。多個模塊依賴于同一個可變對象的編程范式容易導致不可預知的行為發生。

          常見的使用場景是代碼庫中的一方修改數據,另一方消費數據。因此各自的執行順序則至關重要:畢竟我們可不希望一不小心在還沒有任何數據的時候開始執行消費數據的代碼。當整個應用越來越大,組件之間的依賴越來越復雜時,想要搞明白龐雜應用之內的相互調用關系也就變得愈加棘手。

          React中的狀態管理

          在React中,應用通常會依賴一些狀態管理工具,諸如Redux或者React Context,但單例模式的數據管理并不是選擇之一。雖然這些工具的狀態管理行為看上去似乎與單例模式有些相像,但他們通常會提供一種狀態只讀的能力以區別于單例模式下的可變數據模型。當使用Redux時,只有純函數類型的reducer在收到組件通過dispatcher向其發送的action之后才允許更新狀態。

          雖然使用全局狀態的缺點并不會因為引入了這些狀態管理工具而奇跡般的消失,但由于組件無法直接更新狀態,因此至少可以保證全局狀態在變更時是來自于代碼的真實意圖。

          原文地址:https://www.patterns.dev/posts/singleton-pattern/

          么是策略模式

          定義一系列的算法,封裝起來,并且他們之間可以相互替換。將算法的使用和算法的實現分離開來。

          現在有這么一個功能,過年發年終獎根據績效來發不同的獎金。
          績效為 S 的有4倍工資,績效為 A 的有3倍工資,績效為 B 的有2倍工資。

          通過構造函數實現

          // 算法
          var ps=function () { }
          ps.prototype.calculate=function (salary) {
              return salary * 4;
          }
          
          var pA=function () { }
          pA.prototype.calculate=function (salary) {
              return salary * 3;
          }
          
          var pB=function () { }
          pB.prototype.calculate=function (salary) {
              return salary * 2;
          }
          
          
          // 算法的使用
          var Bouns=function () {
              this.salary=null;     // 原始工資
              this.strategy=null;   // 績效等級的策略對象
          }
          
          // 設置原始工資
          Bouns.prototype.setSalary=function (salary) {
              this.salary=salary;
          }
          
          // 設置績效等級的策略對象
          Bouns.prototype.setStrategy=function (strategy) {
              this.strategy=strategy;
          }
          
          Bouns.prototype.getBouns=function () {
              return this.strategy.calculate(this.salary)
          }
          
          var bouns=new Bouns();
          bouns.setSalary(1000)
          bouns.setStrategy(new ps())
          console.log(bouns.getBouns());  //=> 4000

          函數對象的方式

          var strategies={
              S: function (salary) {
                  return salary * 4
              },
              A: function (salary) {
                  return salary * 3
              },
              B: function (salary) {
                  return salary * 2
              }
          }
          
          // 定義使用方式
          var bouns=function (level, salary){
              return strategies[level](salary)
          }
          
          console.log(bouns('S',1000));  //=> 4000
          console.log(bouns('A',1000))   //=> 3000

          表單驗證

          一般表單驗證

          <body>
              <form action="#" method="post" id="form">
                請輸入用戶名:<input type="text" name="username" /><br />
                請輸入密碼:<input type="password" name="password" /><br />
                請輸入手機號:<input type="text" name="phone" /><br />
                <button>提交</button>
              </form>
              <script>
                var form=document.getElementById("form");
                form.onsubmit=function () {
                  if (form.username.value==="") {
                    alert("用戶名不能為空");
                    return false;
                  }
                  if (form.password.value.length < 6) {
                    alert("密碼長度不能少于 6 位");
                    return false;
                  }
                  if (!/(^1[3|5|8][0-9]{9}$)/.test(form.phone.value)) {
                    alert("手機號碼格式不正確");
                    return false;
                  }
                };
              </script>
          </body>

          缺點:

          • 如果表單繼續增加那么代碼不斷變大,太多的if-else語句來覆蓋全部的驗證規則
          • 缺乏拓展性,如果新增一種校驗規則或者密碼的長度變了,那我們就需要對代碼進行破壞
          • 復用性太差,其他表單使用那么就要把這一長串校驗邏輯復制黏貼



          運用策略模式的表單校驗

          、CSS的元素顯示模式

          1.作用:網頁標簽非常多,在不同地方會用到不同類型的標簽,了解他們的特點可以更好的布局頁面
          2.HTML元素一般分為塊元素和行內元素

          (一)塊元素

          1.div為最典型的塊元素,還有h1-h6,p,ul,ol,li等
          2.特點

          • 比較霸道,自己獨占一行
          • 高度,寬度,外邊距以及內邊距都可以控制
          • 寬度默認是容器(父級寬度)的100%
          • 是一個容器及盒子,里面可以放行內或塊級元素

          3.注意

          • 文字類的元素內不能使用塊級元素,如:p
          • h1-h6等都為文字類的塊級標簽,里面也不能放其他塊級元素

          (二)行內元素

          1.span為最典型的行內元素,還有a,strong,b,em,i,del,s,ins,u等
          2.特點

          • 相鄰行內元素在一行上,一行可以顯示多個
          • 高度、寬度直接設置是無效的
          • 默認寬度就是它本身內容的寬度
          • 行內元素只能容納文本或其他行內元素

          3.注意

          • 鏈接里面不能再放鏈接
          • 特殊情況,鏈接a里面可以放塊級元素,但給a鏈接轉換一下塊級模式最安全

          (三)行內塊元素

          1.同時有塊元素和行內元素的特點,如:img,input,td等
          2.特點

          • 和相鄰行內元素(行內塊)在一行上,但是他們之間會有空白縫隙,一行可以顯示多個(行內元素特點)
          • 默認寬度就是它本身內容的寬度(行內元素特點)
          • 高度,行高外邊距以及內邊距都可以控制(塊級元素特點)

          (四)元素顯示模式轉換

          1.轉化為塊元素(display:block;)

          2.轉化為行內元素(display:inline;)

          3.轉化為行內塊(display:inline-block;)

          單行文字垂直居中的小技巧

          總結


          版權聲明:本文為CSDN博主「依舊i248」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。


          原文鏈接:https://blog.csdn.net/weixin_65548623/article/details/124192437


          主站蜘蛛池模板: 精品一区二区久久久久久久网精| 国产av一区二区精品久久凹凸| 精品亚洲一区二区| 伊人精品视频一区二区三区| 日韩精品无码一区二区三区AV| 91亚洲一区二区在线观看不卡| 亚洲午夜一区二区三区| 国产福利一区二区| 亚洲午夜精品一区二区公牛电影院 | 亚洲国产AV一区二区三区四区 | 国产爆乳无码一区二区麻豆| 国内精品视频一区二区三区| 人妻体体内射精一区二区| 国产精品区AV一区二区| 免费看AV毛片一区二区三区| 在线精品亚洲一区二区| 无码少妇一区二区| 国产精品被窝福利一区 | 久久AAAA片一区二区| 99无码人妻一区二区三区免费| 亚洲毛片αv无线播放一区| 天美传媒一区二区三区| 美女视频在线一区二区三区| 色狠狠一区二区三区香蕉| 一区二区三区免费在线观看| 国产一区在线电影| 国产精品一区二区三区免费| 亚洲日本一区二区三区| 国模丽丽啪啪一区二区| 成人免费区一区二区三区| 2021国产精品一区二区在线| 亚洲一区在线视频| 久久久国产一区二区三区 | 国产激情一区二区三区| 免费看一区二区三区四区| 久久精品一区二区东京热| 精品国产鲁一鲁一区二区| 国产99精品一区二区三区免费| 国产色综合一区二区三区| 伊人色综合视频一区二区三区| 四虎永久在线精品免费一区二区|