整個應用之內全局共享一個實例的模式,但它在JS中竟然是一種反模式
所謂單例模式是指遵循這個模式設計的類,僅會被實例化一次,并且其實例允許全局獲取。單例模式下派生的示例允許我們在全局共享唯一實例,因此非常適合用于保存整個應用的全局狀態。
首先讓我們先看看在ES2015的語法下單例模式長什么樣子。比如我們想要創建一個計數器類,用于保存全局的某個行為發生的次數,那么對于這個類的設計,我們應該考慮實現如下4個方法:
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類,我們大概會需要如下幾個文件:
blueButton.js和redButton.js都引入了同一個counter.js的實例。也就是說這個實例作為Counter變量被導入到了兩個文件中。
無論在redButton.js或是blueButton.js中調用increment方法時,Counter實例的counter屬性值的更新會同步反應在兩個文件中。不管我們點擊的是紅色按鈕還是藍色按鈕:所有引用Counter實例的對象會共享同一個值。這就是為什么即便我們在不同的文件中調用遞增方法,計數器的值都會增加的原因。
對一次性實例化的嚴格限制顯然有節省內存空間的潛力。相對于每次都為新對象分配內存,通過單例模式我們僅需要為一個對象分配內存即可。然而,單例模式實際上被認為是一種反模式,并且應該避免在JavaScript中使用。
在很多編程語言中,比如Java或者C++,不可能像我們在JavaScript中一樣直接創建對象。在這些面向對象的編程語言中,需要創建類,類創建對象。這些創建了的對象都擁有類實例的值,正如上面JavaScript示例中的instance實例一樣。
但是像在上面示例代碼中那樣去聲明一個單例模式的類顯然有點大炮打蚊子。既然我們可以在JavaScript中直接創建對象,為什么不能直接創建一個對象來達到同樣的目的呢?接下來我們說說使用單例模式的缺陷!
仍然使用上面示例中的場景。只不過這一次我們將counter直接定義為一個擁有如下屬性的簡單對象:
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中,應用通常會依賴一些狀態管理工具,諸如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>
缺點:
運用策略模式的表單校驗
1.作用:網頁標簽非常多,在不同地方會用到不同類型的標簽,了解他們的特點可以更好的布局頁面
2.HTML元素一般分為塊元素和行內元素
1.div為最典型的塊元素,還有h1-h6,p,ul,ol,li等
2.特點
3.注意
1.span為最典型的行內元素,還有a,strong,b,em,i,del,s,ins,u等
2.特點
3.注意
1.同時有塊元素和行內元素的特點,如:img,input,td等
2.特點
版權聲明:本文為CSDN博主「依舊i248」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_65548623/article/details/124192437
*請認真填寫需求信息,我們會在24小時內與您取得聯系。