前端中的工廠模式是一種建立物件的設計模式,它可以讓我們封裝建立物件的細節,我們使用工廠方法而不是直接呼叫 new 關鍵字來建立物件,使得程式碼更加清晰、簡潔和易於維護。在前端開發中,工廠模式通常用於建立多個相似但稍有不同的物件,比如建立一系列具有相同樣式和行為的按鈕或者表單。
在實現工廠模式時,通常需要建立一個工廠函數(或者叫做工廠類),該函數可以接受一些引數,並根據這些引數來建立物件。例如,我們可以建立一個ButtonFactory函數,它接受一個type引數,用於指定按鈕的型別,然後根據type引數建立不同型別的按鈕物件。範例程式碼如下:
function ButtonFactory(type) { switch (type) { case 'primary': return new PrimaryButton(); case 'secondary': return new SecondaryButton(); case 'link': return new LinkButton(); default: throw new Error('Unknown button type: ' + type); } } function PrimaryButton() { this.type = 'primary'; this.text = 'Click me!'; this.onClick = function() { console.log('Primary button clicked!'); }; } function SecondaryButton() { this.type = 'secondary'; this.text = 'Click me too!'; this.onClick = function() { console.log('Secondary button clicked!'); }; } function LinkButton() { this.type = 'link'; this.text = 'Click me as well!'; this.onClick = function() { console.log('Link button clicked!'); }; }
在上面的範例中,ButtonFactory函數接受一個type引數,根據這個引數來建立不同型別的按鈕物件。例如,如果type為primary,則返回一個PrimaryButton物件,該物件具有type、text和onClick屬性,表示一個主要按鈕。其他型別的按鈕也類似。
使用工廠模式可以讓我們將物件建立的過程與具體的業務邏輯分離開來,從而提高程式碼的可重用性和可維護性。
在JavaScript中,單例模式可以通過多種方式實現,以下是一些常見的實現方式:
使用物件字面量可以輕鬆地建立單例物件,例如:
const singleton = { property1: "value1", property2: "value2", method1: function () { // ... }, method2: function () { // ... }, };
上述程式碼中,使用了一個物件字面量來建立單例物件,該物件包含了一些屬性和方法。由於JavaScript中物件字面量本身就是單例的,因此不需要額外的程式碼來保證單例。
在JavaScript中,每個建構函式都可以用於建立單例物件,例如:
function Singleton() { // 判斷是否存在範例 if (typeof Singleton.instance === "object") { return Singleton.instance; } // 初始化單例物件 this.property1 = "value1"; this.property2 = "value2"; Singleton.instance = this; } const instance1 = new Singleton(); const instance2 = new Singleton(); console.log(instance1 === instance2); // 輸出 true
上述程式碼中,使用了一個建構函式來建立單例物件。在建構函式中,首先判斷是否存在單例範例,如果存在則直接返回該範例,否則建立單例物件並將其儲存在 Singleton.instance
屬性中。由於JavaScript中每個建構函式本身就是一個單例,因此不需要額外的程式碼來保證單例。
使用模組模式可以建立一個只有單個範例的物件,例如:
const Singleton = (function () { let instance; function init() { // 建立單例物件 const object = new Object("I am the instance"); return object; } return { getInstance: function () { if (!instance) { instance = init(); } return instance; }, }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // 輸出 true
上述程式碼中,使用了一個立即執行函數來建立單例物件。在該函數中,定義了一個私有變數 instance
用於儲存單例範例,而 init
函數則是用於建立單例範例的方法。最後,返回一個物件,該物件包含一個 getInstance
方法,該方法用於獲取單例範例。
通過上述方式實現的單例模式,可以確保在程式執行期間,某個類只有一個範例,並且該範例可以在任何地方存取。
JavaScript中的釋出/訂閱模式(Pub/Sub)是一種常用的設計模式。它允許在應用程式中定義物件之間的一對多的依賴關係,當一個物件的狀態發生變化時,所有依賴於它的物件都會被通知和更新。
在釋出/訂閱模式中,有兩種型別的物件:釋出者和訂閱者。釋出者是事件的發出者,它通常維護一個事件列表,並且可以向列表中新增或刪除事件。當某個事件發生時,它會將這個事件通知給所有訂閱者。訂閱者則是事件的接收者,它們訂閱感興趣的事件,並且在事件發生時接收通知。。
釋出訂閱模式可以幫助我們實現鬆耦合的設計,讓物件之間的依賴關係變得更加靈活。它在前端開發中的應用非常廣泛,例如 Vue.js 中的事件匯流排、Redux 中的 store 等。
以下是一個簡單的實現釋出/訂閱模式的範例程式碼:
// 定義一個釋出者物件 var publisher = { // 定義一個事件列表 events: {}, // 新增事件到列表中 addEvent: function(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }, // 從事件列表中刪除事件 removeEvent: function(event, callback) { if (this.events[event]) { for (var i = 0; i < this.events[event].length; i++) { if (this.events[event][i] === callback) { this.events[event].splice(i, 1); break; } } } }, // 釋出事件 publishEvent: function(event, data) { if (this.events[event]) { for (var i = 0; i < this.events[event].length; i++) { this.events[event][i](data); } } } }; // 定義一個訂閱者物件 var subscriber = { // 處理事件的回撥函數 handleEvent: function(data) { console.log(data); } }; // 訂閱一個事件 publisher.addEvent('event1', subscriber.handleEvent); // 釋出一個事件 publisher.publishEvent('event1', 'Hello, world!'); // 取消訂閱一個事件 publisher.removeEvent('event1', subscriber.handleEvent);
在這個例子中,釋出者物件維護了一個事件列表(events),並且提供了新增、刪除和釋出事件的方法。訂閱者物件則提供了一個處理事件的回撥函數(handleEvent),它可以被新增到釋出者物件的事件列表中。當釋出者釋出一個事件時,所有訂閱了這個事件的訂閱者都會收到通知,並執行相應的處理常式。
在前端開發中,觀察者模式常被用來實現元件間的資料傳遞和事件處理。比如,當一個元件的狀態發生改變時,可以通過觀察者模式來通知其他元件更新自身的狀態或檢視。
在觀察者模式中,通常會定義兩種角色:
以下是一個簡單的實現範例:
class Subject { constructor() { this.observers = [] } addObserver(observer) { this.observers.push(observer) } removeObserver(observer) { this.observers = this.observers.filter(obs => obs !== observer) } notify(data) { this.observers.forEach(obs => obs.update(data)) } } class Observer { update(data) { console.log(`Received data: ${data}`) } } // Usage const subject = new Subject() const observer1 = new Observer() const observer2 = new Observer() subject.addObserver(observer1) subject.addObserver(observer2) subject.notify('Hello, world!') // Output: // Received data: Hello, world! // Received data: Hello, world! subject.removeObserver(observer1) subject.notify('Goodbye, world!') // Output: // Received data: Goodbye, world!
在上面的範例中,我們定義了一個 Subject 類和一個 Observer 類。Subject 類有三個方法,addObserver 用於新增觀察者,removeObserver 用於移除觀察者,notify 用於通知所有觀察者。
Observer 類只有一個方法 update,用於接收主題傳遞的資料。我們建立了兩個 Observer 範例並將它們新增到了 Subject 範例中,然後呼叫了 notify 方法來通知它們更新資料。
在實際開發中,我們通常會使用現成的庫或框架來實現觀察者模式,比如 React 中的狀態管理庫 Redux 和事件處理庫 EventEmitter。
在前端開發中,中介者模式常常被用於管理複雜的使用者介面或元件之間的互動,比如 GUI 元件、聊天室、遊戲等等。通過引入一箇中介者物件,各個元件可以向中介者物件傳送訊息或事件,而不需要知道訊息或事件的接收者是誰。中介者物件負責接收並分發訊息或事件,從而實現元件之間的解耦和統一管理。
下面是一個簡單的例子,展示瞭如何在前端中使用中介者模式:
// 中介者物件 const Mediator = { components: [], addComponent(component) { this.components.push(component); }, broadcast(source, message) { this.components .filter(component => component !== source) .forEach(component => component.receive(message)); } }; // 元件物件 class Component { constructor() { this.mediator = Mediator; this.mediator.addComponent(this); } send(message) { this.mediator.broadcast(this, message); } receive(message) { console.log(`Received message: ${message}`); } } // 使用中介者模式進行元件之間的通訊 const componentA = new Component(); const componentB = new Component(); componentA.send("Hello from Component A"); componentB.send("Hi from Component B"); // Received message: Hello from Component A // Received message: Hi from Component B
在上面的例子中,我們定義了一箇中介者物件 Mediator
和兩個元件物件 ComponentA
和 ComponentB
。當元件物件傳送訊息時,它會將訊息傳送給中介者物件,中介者物件負責將訊息分發給其他元件物件。這樣,我們就實現了元件之間的解耦和統一管理。
需要注意的是,在實際開發中,我們可能需要使用不同的中介者物件來管理不同的元件之間的互動行為。此外,我們還可以使用其他方式來實現中介者模式,比如使用觀察者模式來實現。
JavaScript 中的裝飾者模式可以通過以下幾種方式實現:
// 定義一個原始物件 const obj = { foo() { console.log('foo'); } } // 定義一個裝飾函數,用於擴充套件原始物件的方法 function barDecorator(obj) { obj.bar = function() { console.log('bar'); } return obj; } // 使用裝飾函數來擴充套件原始物件 const decoratedObj = barDecorator(obj); decoratedObj.foo(); // 輸出 "foo" decoratedObj.bar(); // 輸出 "bar"
在上面的範例中,我們首先定義了一個原始物件 obj
,它包含一個方法 foo
。然後,我們定義了一個裝飾函數 barDecorator
,它接收一個物件作為引數,用於為這個物件新增一個新的方法 bar
。最後,我們使用 barDecorator
函數來擴充套件原始物件 obj
,並得到了一個新的物件 decoratedObj
,它包含了原始物件的方法 foo
和新的方法 bar
。
// 定義一個原始物件 function Foo() {} // 在原型上定義一個方法 Foo.prototype.foo = function() { console.log('foo'); } // 定義一個裝飾函數,用於擴充套件原型的方法 function barDecorator(clazz) { clazz.prototype.bar = function() { console.log('bar'); } } // 使用裝飾函數來擴充套件原型 barDecorator(Foo); // 建立一個新的物件,並使用擴充套件後的方法 const obj = new Foo(); obj.foo(); // 輸出 "foo" obj.bar(); // 輸出 "bar"
在上面的範例中,我們首先定義了一個原始物件 Foo
,它是一個建構函式,用於建立一個物件。然後,我們在原型上定義了一個方法 foo
。接著,我們定義了一個裝飾函數 barDecorator
,它接收一個建構函式作為引數,用於在原型上新增一個新的方法 bar
。最後,我們使用 barDecorator
函數來擴充套件原始物件的原型,然後建立一個新的物件 obj
,並使用擴充套件後的方法 foo
和 bar
。
需要注意的是,裝飾者模式可以巢狀使用,也就是說,我們可以通過多個裝飾函數來依次為一個元件新增多個不同的功能。同時,裝飾者模式也可以用於對已有的元件進行擴充套件,使得我們可以在不改變原有程式碼的情況下,給元件新增新的行為和樣式。
它可以讓我們在不改變物件本身的情況下,通過修改其內部的演演算法實現不同的行為。策略模式常常被用於實現一些複雜的業務邏輯,特別是需要根據不同的條件進行處理的情況。
下面是一個簡單的範例,演示瞭如何使用策略模式來實現一個計算器:
// 定義一個策略物件 const strategies = { add: function(num1, num2) { return num1 + num2; }, subtract: function(num1, num2) { return num1 - num2; }, multiply: function(num1, num2) { return num1 * num2; }, divide: function(num1, num2) { return num1 / num2; } }; // 定義一個計算器物件 const Calculator = function(strategy) { this.calculate = function(num1, num2) { return strategy(num1, num2); } }; // 使用策略模式來建立一個計算器物件 const addCalculator = new Calculator(strategies.add); const subtractCalculator = new Calculator(strategies.subtract); const multiplyCalculator = new Calculator(strategies.multiply); const divideCalculator = new Calculator(strategies.divide); // 使用計算器物件進行計算 console.log(addCalculator.calculate(10, 5)); // 輸出 15 console.log(subtractCalculator.calculate(10, 5)); // 輸出 5 console.log(multiplyCalculator.calculate(10, 5)); // 輸出 50 console.log(divideCalculator.calculate(10, 5)); // 輸出 2
在上面的範例中,我們首先定義了一個策略物件,其中包含了四個不同的演演算法:加、減、乘和除。然後我們定義了一個計算器物件,它接收一個策略物件作為引數,並將其儲存在內部。最後,我們使用策略模式來建立四個不同的計算器物件,每個物件使用不同的演演算法進行計算。
這個範例展示瞭如何使用策略模式來實現一個簡單的計算器,但實際上它可以應用於許多其他的場景中,例如表單驗證、圖表繪製等。策略模式可以讓我們通過修改策略物件來改變物件的行為,從而實現更加靈活和可延伸的程式碼。
介面卡模式通常包含三個角色:使用者端、目標物件和介面卡物件。使用者端呼叫介面卡物件的介面,介面卡物件再呼叫目標物件的介面,將目標物件的介面轉換為使用者端需要的介面,從而實現相容性。
另外,介面卡模式也可以用於將不同的第三方元件或外掛進行整合和相容。例如,當一個網站需要使用不同的圖表庫來繪製圖表時,可以使用介面卡模式將這些圖表庫進行封裝,從而實現統一的呼叫介面,方便使用和維護。
下面是一個簡單的例子,演示如何使用介面卡模式將不同的 API 介面進行統一封裝:
// 目標介面 class Target { request() { return 'Target: 請求完成!'; } } // 需要適配的物件 class Adaptee { specificRequest() { return 'Adaptee: 請求完成!'; } } // 介面卡物件 class Adapter extends Target { constructor(adaptee) { super(); this.adaptee = adaptee; } request() { const result = this.adaptee.specificRequest(); return `Adapter: ${result}`; } } // 使用介面卡模式 const adaptee = new Adaptee(); const adapter = new Adapter(adaptee); console.log(adapter.request()); // 輸出:Adapter: Adaptee: 請求完成!
在上面的程式碼中,我們定義了一個目標介面 Target
和一個需要適配的物件 Adaptee
,它們之間的介面不相容。然後我們使用介面卡模式,將 Adaptee
物件適配為 Target
介面,從而實現了相容性。
介面卡物件 Adapter
繼承了目標介面 Target
,並在其內部使用了需要適配的物件 Adaptee
。在 Adapter
的 request
方法中,我們呼叫了 Adaptee
的 specificRequest
方法,將其返回值包裝為符合 Target
介面的形式。
通過介面卡模式,我們可以將不同介面的物件進行統一封裝,從而方便我們使用和維護程式碼。
職責鏈模式通常涉及一系列處理物件,每個物件都負責處理請求的一部分,並將請求傳遞給下一個物件,直到請求得到滿足或者處理結束。這種方式可以將系統中的不同操作解耦,從而提高系統的靈活性和可維護性。
在 JavaScript 中,職責鏈模式的實現通常涉及使用一個處理物件的連結串列,其中每個物件都有一個指向下一個物件的參照。當請求進入系統時,它首先被傳遞給連結串列中的第一個物件。如果這個物件不能處理請求,則將請求傳遞給連結串列中的下一個物件,直到找到能夠處理請求的物件為止。
下面是一個簡單的 JavaScript 職責鏈模式的範例:
class Handler {
constructor() {
this.nextHandler = null;
}
setNextHandler(handler) {
this.nextHandler = handler;
}
handleRequest(request) {
if (this.nextHandler) {
this.nextHandler.handleRequest(request);
}
}
}
class ConcreteHandler1 extends Handler {
handleRequest(request) {
if (request === 'request1') {
console.log('ConcreteHandler1 handles the request');
} else {
super.handleRequest(request);
}
}
}
class ConcreteHandler2 extends Handler {
handleRequest(request) {
if (request === 'request2') {
console.log('ConcreteHandler2 handles the request');
} else {
super.handleRequest(request);
}
}
}
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();
handler1.setNextHandler(handler2);
handler1.handleRequest('request1'); // Output: "ConcreteHandler1 handles the request"
handler1.handleRequest('request2'); // Output: "ConcreteHandler2 handles the request"
handler1.handleRequest('request3'); // Output: Nothing is printed
在上面的範例中,Handler
類是職責鏈模式的基礎類別,它包含一個指向下一個處理物件的參照。ConcreteHandler1
和 ConcreteHandler2
類是具體的處理物件,它們根據請求的型別來決定是否能夠處理請求。如果不能處理,則將請求傳遞給下一個處理物件。最後,我們將 handler1
物件的下一個處理物件設定為 handler2
物件,然後依次呼叫 handleRequest
方法來模擬不同型別的請求。
代理模式在前端開發中經常被用來處理一些複雜或者耗時的操作,例如圖片的懶載入、快取等。代理物件可以在載入圖片時顯示預留位置,當圖片載入完成後再替換預留位置,從而提高頁面載入速度和使用者體驗。
另外,代理模式還可以用來實現一些許可權控制的功能。例如,在使用者登入後,代理物件可以檢查使用者的許可權,只有具有相應許可權的使用者才能夠存取某些功能或者頁面。
在 JavaScript 中,代理模式通常使用 ES6 中新增的 Proxy 物件來實現。Proxy 物件允許攔截對物件的各種操作,包括讀取、賦值、函數呼叫等。通過使用 Proxy 物件,我們可以在不改變原始物件的情況下,控制對原始物件的存取。
當我們需要為某個類或者物件新增一些額外的行為或者控制存取時,可以使用代理模式。下面是一個簡單的範例,使用代理模式實現圖片懶載入的功能。
// 原始物件 - 圖片
class Image {
constructor(url) {
this.url = url;
}
// 載入圖片
load() {
console.log(`Image loaded: ${this.url}`);
}
}
// 代理物件 - 圖片
class ProxyImage {
constructor(url) {
this.url = url;
this.image = null; // 延遲載入
}
// 載入圖片
load() {
if (!this.image) {
this.image = new Image(this.url); // 延遲載入圖片
console.log(`Placeholder loaded for ${this.url}`);
}
this.image.load(); // 顯示圖片
}
}
// 使用者端程式碼
const img1 = new ProxyImage('https://example.com/image1.jpg');
const img2 = new ProxyImage('https://example.com/image2.jpg');
img1.load(); // Placeholder loaded for https://example.com/image1.jpg, Image loaded: https://example.com/image1.jpg
img1.load(); // Image loaded: https://example.com/image1.jpg
img2.load(); // Placeholder loaded for https://example.com/image2.jpg, Image loaded: https://example.com/image2.jpg
在上面的範例中,原始物件是 Image
類,代理物件是 ProxyImage
類。當用戶端程式碼呼叫 load()
方法時,代理物件會首先載入預留位置,並延遲載入圖片。如果圖片已經被載入過了,代理物件會直接顯示圖片,否則代理物件會載入圖片並顯示。通過使用代理模式,我們可以在不影響原始物件的情況下,實現了圖片的懶載入功能,提高了頁面載入速度和使用者體驗。
在前端開發中,命令模式可以被用於實現可復原和重做的操作。例如,在一個文字編輯器中,可以使用命令模式來實現復原和重做操作。對於每一個編輯操作,可以建立一個命令物件來表示這個操作,然後將這個命令物件儲存在一個歷史列表中。當需要復原操作時,可以從歷史列表中取出最近的命令物件並執行它的反向操作。
命令模式還可以被用於實現選單和工具列等使用者介面元素。例如,可以建立一個選單項物件來表示一個命令,並將這個物件新增到選單中。當用戶點選這個選單項時,選單項物件將執行對應的操作。
下面是一個簡單的實現可復原操作的例子:
// 定義一個命令物件
class Command {
constructor(receiver, args) {
this.receiver = receiver;
this.args = args;
this.executed = false;
}
execute() {
if (!this.executed) {
this.receiver.execute(this.args);
this.executed = true;
}
}
undo() {
if (this.executed) {
this.receiver.undo(this.args);
this.executed = false;
}
}
}
// 定義一個接收者物件
class Receiver {
constructor() {
this.value = 0;
}
execute(args) {
this.value += args;
console.log(`執行操作,value = ${this.value}`);
}
undo(args) {
this.value -= args;
console.log(`復原操作,value = ${this.value}`);
}
}
// 建立一個接收者物件和一些命令物件
const receiver = new Receiver();
const command1 = new Command(receiver, 1);
const command2 = new Command(receiver, 2);
const command3 = new Command(receiver, 3);
// 建立一個歷史列表並將命令物件新增到其中
const history = [command1, command2, command3];
// 依次執行命令物件
history.forEach((command) => {
command.execute();
});
// 復原最後一個操作
history.pop().undo(); // 復原操作,value = 3
在這個例子中,Command
類表示一個命令物件,它包含了一個接收者物件、命令引數以及一個 executed
屬性,用於標記命令是否已經被執行過。Receiver
類表示接收者物件,它實現了具體的操作。在這個例子中,命令物件執行的操作是將 args
引數加到 Receiver
物件的 value
屬性上。命令物件的 execute
和 undo
方法分別執行和復原這個操作,並通過 executed
屬性來避免重複執行操作。
在實際的前端開發中,命令模式的應用還有很多,比如實現動畫效果的控制器、網路請求的佇列等。命令模式可以讓程式碼更加靈活和可延伸,同時也可以更好地實現程式碼的解耦。
在迭代器模式中,集合物件包含一個方法,用於返回一個迭代器,該迭代器可以按順序存取該集合中的元素。迭代器提供了一種通用的介面,使得可以使用相同的方式遍歷不同型別的集合物件。
在前端開發中,迭代器模式經常用於處理集合資料,例如陣列、列表等。通過使用迭代器模式,可以輕鬆地遍歷集合物件的元素,而不必擔心它們的實現方式。
以下是一個使用迭代器模式的範例:
// 定義一個集合類
class Collection {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
}
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { done: true };
}
}
};
}
}
// 建立一個集合物件
const collection = new Collection();
collection.add('item 1');
collection.add('item 2');
collection.add('item 3');
// 使用迭代器遍歷集合物件
const iterator = collection[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
// item 1
// item 2
// item 3
// {done: true}
在上面的範例中,定義了一個名為 Collection 的集合類,該類包含一個 add 方法,用於向集合中新增元素。該類還實現了一個名為 [Symbol.iterator] 的特殊方法,用於返回一個迭代器物件。迭代器物件包含一個 next 方法,用於返回集合中的下一個元素,直到集合的所有元素都被遍歷完畢。
通過使用迭代器模式,我們可以輕鬆地遍歷集合物件的元素,而不必擔心它們的實現方式。
組合模式(Composite Pattern)是一種結構型設計模式,它允許將物件組合成樹形結構,並且可以像操作單個物件一樣操作整個樹形結構。
組合模式的核心思想是將物件組織成樹形結構,其中包含組合物件和葉子物件兩種型別。組合物件可以包含葉子物件或其他組合物件,從而形成一個樹形結構。
組合模式可以應用於以下場景:
實現組合模式通常有兩種方式:
下面是一個使用物件字面量和原型繼承的組合模式實現範例:
// Component
const Component = {
add: function () {},
remove: function () {},
getChild: function () {},
};
// Composite
function createComposite() {
const composite = Object.create(Component);
composite.children = [];
composite.add = function (child) {
this.children.push(child);
};
composite.remove = function (child) {
const index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
}
};
composite.getChild = function (index) {
return this.children[index];
};
return composite;
}
// Leaf
function createLeaf() {
const leaf = Object.create(Component);
// Leaf 類實現自己的 add、remove、getChild 方法
return leaf;
}
// 使用範例
const root = createComposite();
const branch1 = createComposite();
const branch2 = createComposite();
const leaf1 = createLeaf();
const leaf2 = createLeaf();
const leaf3 = createLeaf();
root.add(branch1);
root.add(branch2);
branch1.add(leaf1);
branch2.add(leaf2);
branch2.add(leaf3);
console.log(root); // 輸出樹形結構
上述範例中,通過使用物件字面量和原型繼承,模擬了組合模式的結構,從而實現了樹形結構的物件。在實際應用中,根據具體的需求和程式碼架構,可以選擇適合自己的實現方式。
在JavaScript中,所有的物件都有一個原型鏈。原型鏈是一種機制,它允許我們在物件上定義屬性和方法,並且可以從它的原型中繼承屬性和方法。當我們存取一個物件的屬性或方法時,JavaScript會在原物件上查詢,如果找不到,它會繼續查詢原型鏈上的物件,直到找到該屬性或方法或者到達原型鏈的末端。
原型模式是一種利用原型鏈的設計模式,它允許我們通過克隆現有物件來建立新物件。JavaScript中的原型模式使用Object.create()
方法來建立一個物件,並且可以通過修改原型鏈上的屬性和方法來修改新物件的行為。
使用原型模式的主要優點是它可以減少物件建立的時間和成本。它避免了在建立物件時需要執行許多計算或呼叫其他物件的建構函式的開銷。相反,它使用現有物件作為基礎,並通過克隆來建立新物件,從而提高了效能和效率。
下面是一個使用原型模式建立表單物件的範例程式碼:
// 定義一個表單物件的原型
var formPrototype = {
fields: [],
addField: function(field) {
this.fields.push(field);
},
getFields: function() {
return this.fields;
},
clone: function() {
// 克隆表單物件並返回新物件
var newForm = Object.create(this);
newForm.fields = Object.create(this.fields);
return newForm;
}
};
// 建立一個表單物件
var form = Object.create(formPrototype);
// 新增表單欄位
form.addField('name');
form.addField('email');
form.addField('phone');
// 克隆表單物件並修改其中的欄位
var newForm = form.clone();
newForm.addField('address');
// 列印表單物件和新表單物件的欄位
console.log(form.getFields()); // ["name", "email", "phone"]
console.log(newForm.getFields()); // ["name", "email", "phone", "address"]
在這個範例中,我們首先定義了一個表單物件的原型,它包含一個空的欄位陣列、新增欄位和獲取欄位的方法以及一個克隆方法。然後,我們建立了一個表單物件,並向其新增了三個欄位。接下來,我們使用原型模式克隆表單物件並新增一個新欄位。最後,我們列印了表單物件和新表單物件的欄位,以驗證克隆方法是否正常工作。
在前端開發中,橋接模式通常用於處理 UI 元件的複雜性,將元件的抽象與實現分離,使得它們能夠獨立地變化。通過橋接模式,我們可以讓元件的行為和樣式分別獨立變化,從而避免在程式碼中出現過多的重複和複雜度。
具體來說,橋接模式包含兩個關鍵部分:
通過將抽象部分與實現部分解耦,我們可以在不影響原有程式碼的情況下,方便地擴充套件和修改元件的行為和樣式。同時,橋接模式也可以提高程式碼的可讀性和可維護性,使得程式碼更加清晰、簡潔和易於維護。
以下是一個簡單的前端橋接模式範例,假設我們需要實現一個 UI 元件庫,其中包含多種樣式的按鈕。
首先,我們建立一個抽象部分(Button)和兩個實現部分(DefaultButton 和 OutlineButton),它們分別定義了按鈕的抽象介面和樣式,然後,我們可以建立不同型別的按鈕,並將其與不同樣式的按鈕相結合:
// 抽象部分
class Button {
constructor(implementation) {
this.implementation = implementation;
}
click() {
this.implementation.onClick();
}
render() {
return this.implementation.render();
}
}
// 實現部分 - 預設樣式
class DefaultButton {
onClick() {
console.log("DefaultButton clicked");
}
render() {
return "<button class='default'>Default Button</button>";
}
}
// 實現部分 - 輪廓樣式
class OutlineButton {
onClick() {
console.log("OutlineButton clicked");
}
render() {
return "<button class='outline'>Outline Button</button>";
}
}
// 建立不同型別的按鈕
const primaryButton = new Button(new DefaultButton());
const secondaryButton = new Button(new OutlineButton());
// 渲染並繫結按鈕事件
document.body.innerHTML = `
${primaryButton.render()}
${secondaryButton.render()}
`;
document.querySelector(".default").addEventListener("click", () => {
primaryButton.click();
});
document.querySelector(".outline").addEventListener("click", () => {
secondaryButton.click();
});
最後,當用戶點選按鈕時,會觸發相應的行為,同時也會根據按鈕的樣式渲染出不同的外觀效果。
這是一個非常簡單的範例,但是它展示瞭如何使用橋接模式來處理 UI 元件的複雜性,通過將抽象和實現分離,可以方便地擴充套件和修改元件的行為和樣式,從而提高程式碼的可維護性和可讀性。
在狀態模式中,物件的行為取決於其內部狀態,當狀態發生變化時,物件的行為也會相應地發生改變。這種模式通過將狀態抽象為獨立的類來實現,每個狀態類都有其自己的行為和相應的方法。
在前端開發中,狀態模式通常用於處理複雜的使用者互動邏輯,例如根據使用者的操作更改頁面上的狀態。以下是狀態模式的一些常見應用場景:
總之,狀態模式是一種靈活而有用的設計模式,可以使程式碼更加清晰和可維護,它可以幫助開發者輕鬆管理和處理複雜的使用者互動邏輯。
以下是一個簡單的狀態模式範例,假設我們正在開發一個購物車頁面,使用者可以向購物車中新增或刪除商品。購物車的狀態可以是「空的」或「非空的」,當購物車為空時,使用者需要被提示新增商品,當購物車非空時,使用者可以檢視商品列表和刪除商品。
首先,我們定義一個購物車狀態的抽象類:
class CartState {
addToCart() {}
removeFromCart() {}
showMessage() {}
}
接下來,我們定義購物車的兩種狀態:空狀態和非空狀態。空狀態下使用者需要被提示新增商品,非空狀態下使用者可以檢視商品列表和刪除商品。
class EmptyCartState extends CartState {
addToCart() {
console.log('Product added to cart');
// 將購物車狀態設定為非空狀態
this.cart.setState(new NonEmptyCartState(this.cart));
}
showMessage() {
console.log('Your cart is empty, please add some products.');
}
}
class NonEmptyCartState extends CartState {
removeFromCart() {
console.log('Product removed from cart');
// 如果商品列表為空,則將購物車狀態設定為空狀態
if (this.cart.products.length === 0) {
this.cart.setState(new EmptyCartState(this.cart));
}
}
showMessage() {
console.log(`Your cart contains ${this.cart.products.length} products:`);
console.log(this.cart.products.join(', '));
}
}
最後,我們定義購物車類,並在購物車類中使用狀態模式:
class ShoppingCart {
constructor() {
// 初始狀態為購物車為空
this.state = new EmptyCartState(this);
this.products = [];
}
setState(state) {
this.state = state;
}
addToCart(product) {
this.products.push(product);
this.state.addToCart();
}
removeFromCart(product) {
const index = this.products.indexOf(product);
if (index !== -1) {
this.products.splice(index, 1);
this.state.removeFromCart();
}
}
showMessage() {
this.state.showMessage();
}
}
在上面的程式碼中,我們建立了一個ShoppingCart
類,它包含了兩個主要方法addToCart
和removeFromCart
,這些方法將商品新增到購物車中或從購物車中刪除。當這些方法被呼叫時,購物車的狀態將會根據商品列表的狀態自動更新。另外,我們還提供了一個showMessage
方法,用於顯示購物車當前的狀態資訊。
現在,我們可以使用這個購物車類來管理我們的購物車狀態了:
const cart = new ShoppingCart();
cart.showMessage(); // Your cart is empty, please add some products.
cart.addToCart('apple');
cart.addToCart('banana');
cart.showMessage(); // Your cart contains 2 products: apple, banana.
cart.removeFromCart('apple');
cart.showMessage(); // Your cart contains 1 products: banana.
cart.removeFromCart('banana');
cart.showMessage(); // Your cart is empty, please add some products.
通過使用狀態模式,我們可以輕鬆管理購物車的狀態,並且程式碼更加清晰和可維護。
這些步驟被稱為「具體操作」(Concrete Operations),而整個行為的結構和順序則被稱為「模板方法」(Template Method)。
模板方法模式的核心思想是封裝行為中的不變部分,同時允許可變部分通過子類來進行擴充套件。這樣做的好處是可以避免重複程式碼,提高程式碼的複用性和可維護性。
在前端開發中,模板方法模式通常用於處理頁面的渲染和事件處理。例如,我們可以定義一個基礎的頁面渲染演演算法,並在其中定義一些抽象方法,如初始化資料、繫結事件、渲染模板等,然後在子類中實現這些具體操作。這樣可以使得我們在開發頁面時,只需要關注具體的業務邏輯,而不用過多關注頁面的渲染細節。
下面是一個簡單的模板方法模式的範例程式碼:
class Algorithm {
templateMethod() {
this.stepOne();
this.stepTwo();
this.stepThree();
}
stepOne() {
throw new Error("Abstract method 'stepOne' must be implemented in subclass.");
}
stepTwo() {
throw new Error("Abstract method 'stepTwo' must be implemented in subclass.");
}
stepThree() {
throw new Error("Abstract method 'stepThree' must be implemented in subclass.");
}
}
class ConcreteAlgorithm extends Algorithm {
stepOne() {
console.log('ConcreteAlgorithm: step one.');
}
stepTwo() {
console.log('ConcreteAlgorithm: step two.');
}
stepThree() {
console.log('ConcreteAlgorithm: step three.');
}
}
const algorithm = new ConcreteAlgorithm();
algorithm.templateMethod();
在這個範例中,我們定義了一個 Algorithm 類,其中包含了一個模板方法 templateMethod()
和三個基本方法 stepOne()
、stepTwo()
和 stepThree()
。這些基本方法都是抽象方法,需要在子類中進行實現。
我們還定義了一個 ConcreteAlgorithm 類,它繼承自 Algorithm 類,並實現了父類別中的三個基本方法。然後,我們建立了一個 ConcreteAlgorithm 的範例,並呼叫了其 templateMethod()
方法,該方法會按照父類別定義的順序執行三個基本方法。
總的來說,模板方法模式是一種非常實用的設計模式,在 JavaScript 中也同樣適用。它可以幫助我們將程式碼的結構和行為進行分離,從而提高程式碼的可讀性和可維護性。
在過濾器模式中,我們有一個包含多個物件的列表,需要根據一些條件來篩選出符合條件的物件。通常情況下,可以使用多個過濾器來實現這個功能。每個過濾器都是一個獨立的類,它實現了一個過濾條件,我們可以將這些過濾器組合在一起,來實現複雜的過濾操作。
在實際開發中,可以使用過濾器模式來實現諸如搜尋、過濾、排序等功能。例如,在一個商品列表頁面中,我們可以使用過濾器模式來根據價格、品牌、型別等條件來篩選出使用者感興趣的商品。
以下是一個簡單的 JavaScript 範例程式碼,演示瞭如何使用過濾器模式來篩選陣列中的元素:
class Filter {
constructor(criteria) {
this.criteria = criteria;
}
meetCriteria(elements) {
return elements.filter(element => this.criteria(element));
}
}
class PriceFilter extends Filter {
constructor(price) {
super(element => element.price <= price);
}
}
class BrandFilter extends Filter {
constructor(brand) {
super(element => element.brand === brand);
}
}
const products = [
{ name: 'Product A', price: 10, brand: 'Brand A' },
{ name: 'Product B', price: 20, brand: 'Brand B' },
{ name: 'Product C', price: 30, brand: 'Brand C' },
];
const priceFilter = new PriceFilter(20);
const brandFilter = new BrandFilter('Brand A');
const filteredProducts = priceFilter.meetCriteria(products);
const finalProducts = brandFilter.meetCriteria(filteredProducts);
console.log(finalProducts);
// Output: [{ name: 'Product A', price: 10, brand: 'Brand A' }]
在上面的範例程式碼中,我們定義了一個 Filter
類作為過濾器模式的基礎類別,然後我們定義了兩個子類 PriceFilter
和 BrandFilter
,它們分別根據價格和品牌來過濾商品。我們還定義了一個商品陣列 products
,然後使用這兩個過濾器來篩選出價格低於等於 20 並且品牌為 'Brand A' 的商品,最後列印出符合條件的商品列表。
在前端設計中,備忘錄模式通常用於處理使用者介面的狀態。當用戶與應用程式互動時,應用程式會根據使用者的輸入更改其狀態。它可以使您儲存和還原應用程式狀態的快照,以便使用者可以隨時返回以前的狀態。
以下是備忘錄模式的幾個關鍵元件:
在前端中,備忘錄模式的一個實際應用是瀏覽器歷史記錄。當用戶在瀏覽器中導航時,瀏覽器將當前頁面的狀態儲存到歷史記錄中。使用者可以隨時返回以前的狀態,並恢復頁面的先前狀態。
在 JavaScript 中,備忘錄模式的實現和其他程式語言非常相似。下面是一個簡單的 JavaScript 實現範例:
// Originator
class Editor {
constructor() {
this.content = '';
}
type(words) {
this.content += words;
}
save() {
return new EditorMemento(this.content);
}
restore(memento) {
this.content = memento.getContent();
}
}
// Memento
class EditorMemento {
constructor(content) {
this.content = content;
}
getContent() {
return this.content;
}
}
// Caretaker
class History {
constructor() {
this.states = [];
}
push(state) {
this.states.push(state);
}
pop() {
return this.states.pop();
}
}
// Usage
const editor = new Editor();
const history = new History();
// 編輯器輸入內容
editor.type('Hello, ');
editor.type('World!');
// 將當前狀態儲存到備忘錄中,並將備忘錄新增到歷史記錄中
history.push(editor.save());
// 繼續編輯器輸入內容
editor.type(' How are you today?');
// 輸出當前編輯器內容
console.log(editor.content); // 'Hello, World! How are you today?'
// 從歷史記錄中恢復上一個狀態
editor.restore(history.pop());
// 輸出恢復的編輯器內容
console.log(editor.content); // 'Hello, World!'
在這個例子中,Editor
類是發起人,它具有儲存和恢復狀態的方法。EditorMemento
類是備忘錄,它儲存發起人物件的狀態。History
類是管理者,它儲存和管理備忘錄物件。
在使用備忘錄模式時,我們首先建立一個編輯器物件 editor
,並將其狀態儲存到歷史記錄中。然後我們繼續輸入一些內容,並再次將狀態儲存到歷史記錄中。接著,我們從歷史記錄中恢復先前的狀態,並輸出恢復的編輯器內容。
需要注意的是,在 JavaScript 中,我們可以直接存取物件的屬性,而不需要使用 getter 和 setter 方法,因此在上面的範例中,我們可以直接使用 editor.content
來存取編輯器的內容。
在前端開發中,外觀模式常常被用於封裝一些常用的操作,以簡化程式碼複雜度和提高程式碼可維護性。比如,一個用於處理資料的模組可能包含很多複雜的程式碼邏輯和 API 呼叫,但是我們可以使用外觀模式將這些複雜的操作封裝到一個簡單的介面中,讓其他部分的程式碼可以通過這個介面來運算元據,而無需關心具體的實現細節。
外觀模式的優點在於它可以將系統的複雜性隱藏起來,從而降低程式碼的複雜度和耦合度。同時,外觀模式也可以提高程式碼的可讀性和可維護性,因為它可以將一些常用的操作封裝到一個統一的介面中,讓程式碼更加清晰易懂。
下面是一個外觀模式的範例程式碼:
// 複雜的系統或子系統
const moduleA = {
method1: () => {
console.log('method1 from moduleA');
},
method2: () => {
console.log('method2 from moduleA');
},
method3: () => {
console.log('method3 from moduleA');
}
};
const moduleB = {
method4: () => {
console.log('method4 from moduleB');
},
method5: () => {
console.log('method5 from moduleB');
},
method6: () => {
console.log('method6 from moduleB');
}
};
// 外觀物件,封裝了底層的操作,提供了一個簡單的介面
const facade = {
method1: () => {
moduleA.method1();
},
method2: () => {
moduleA.method2();
},
method3: () => {
moduleA.method3();
},
method4: () => {
moduleB.method4();
},
method5: () => {
moduleB.method5();
},
method6: () => {
moduleB.method6();
}
};
// 使用者端呼叫外觀物件的方法
facade.method1(); // 輸出:method1 from moduleA
facade.method2(); // 輸出:method2 from moduleA
facade.method4(); // 輸出:method4 from moduleB
facade.method6(); // 輸出:method6 from moduleB
在這個例子中,我們定義了兩個模組 moduleA
和 moduleB
,它們都包含了一些方法。然後,我們定義了一個名為 facade
的外觀物件,它包含了這兩個模組的所有方法,並提供了一個簡單的介面,讓使用者端可以直接呼叫這些方法。最後,我們在使用者端呼叫外觀物件的方法,實際上是間接呼叫了底層模組的方法。
需要注意的是,外觀模式並不是一種萬能的設計模式,它並不能解決所有的問題。在某些情況下,使用外觀模式可能會增加程式碼的複雜度和冗餘度,因此需要謹慎使用。
在前端開發中,存取者模式通常用於處理DOM樹上的操作。由於DOM樹結構通常很深,而且節點型別不同,因此對DOM樹進行一系列的操作,常常需要寫很多程式碼。而存取者模式可以將這些操作抽象出來,封裝到存取者物件中,從而簡化了程式碼量。
在存取者模式中,有兩種角色:存取者(Visitor)和被存取者(Element)。被存取者是一組物件,它們具有不同的介面,用於接受存取者的存取。存取者則是一組物件的操作,用於處理被存取者。存取者通常會遍歷整個被存取者的結構,並對每個節點進行操作。
下面是一個簡單的存取者模式範例:
// 定義被存取者
class Element {
accept(visitor) {
visitor.visit(this);
}
}
// 定義存取者
class Visitor {
visit(element) {
// 處理元素
}
}
// 定義具體的元素
class ConcreteElement extends Element {
// 具體的實現
}
// 定義具體的存取者
class ConcreteVisitor extends Visitor {
visit(element) {
// 處理具體的元素
}
}
// 使用存取者模式
const element = new ConcreteElement();
const visitor = new ConcreteVisitor();
element.accept(visitor);
在這個例子中,我們首先定義了一個被存取者 Element,它有一個 accept 方法,用於接受存取者的存取。然後定義了一個存取者 Visitor,它有一個 visit 方法,用於處理被存取者 Element。接下來,我們定義了具體的元素 ConcreteElement 和具體的存取者 ConcreteVisitor,並實現了它們的具體邏輯。最後,在主程式中,我們建立了一個 ConcreteElement 範例和一個 ConcreteVisitor 範例,將 ConcreteElement 範例作為引數傳遞給 ConcreteVisitor 的 visit 方法。
總之,存取者模式可以將操作和物件分離開來,從而實現程式碼的解耦和靈活性。在前端開發中,它常常被用來處理複雜的DOM樹結構。
在委託模式中,一個物件(稱為委託物件)將一些特定的任務委託給另一個物件(稱為代理物件)來執行。代理物件通常具有和委託物件相同的介面,因此可以完全替代委託物件,而且可以根據需要動態地改變委託物件,從而實現了物件之間的鬆耦合。
在實際應用中,委託模式常常和其他模式一起使用,比如組合模式、單例模式、觀察者模式等。例如,我們可以使用委託模式來實現組合模式中的葉節點和枝節點的統一介面,從而實現對整個樹形結構的遞迴遍歷。
下面是一個使用委託模式的簡單範例:
// 委託物件
const delegate = {
greet(name) {
return `Hello, ${name}!`;
}
};
// 代理物件
const proxy = {
delegate: delegate,
greet(name) {
return this.delegate.greet(name);
}
};
// 使用代理物件
console.log(proxy.greet("world")); // 輸出:Hello, world!
在上面的例子中,我們定義了一個委託物件 delegate
,它有一個 greet
方法用於向指定的名稱打招呼。然後,我們又定義了一個代理物件 proxy
,它將委託物件儲存在自己的屬性 delegate
中,並且實現了和委託物件相同的 greet
方法,但是它的實現其實是通過呼叫委託物件的 greet
方法來實現的。
最後,我們通過呼叫代理物件的 greet
方法來向世界打招呼,實際上代理物件內部會委託給委託物件來執行這個任務。
計算屬性模式用於將物件的某些屬性值與其他屬性值相關聯。該模式常用於Vue.js等框架中。
計算屬性模式的基本思想是,定義一個函數作為物件的屬性,並在該函數中計算出相關聯的屬性值。當存取該屬性時,實際上是呼叫該函數並返回計算結果。
例如,假設有一個物件包含長度和寬度屬性,需要計算出它們的面積。可以定義一個計算屬性area,該屬性為一個函數,返回長度和寬度的乘積:
const rectangle = {
length: 10,
width: 5,
get area() {
return this.length * this.width;
}
};
console.log(rectangle.area); // 50
在上面的程式碼中,當存取rectangle
物件的area
屬性時,實際上是呼叫了該物件的area
函數,並返回計算結果50。
計算屬性模式的優點是,可以使物件屬性之間的關係更加清晰和易於維護。例如,在上面的例子中,如果需要修改面積計算公式,只需要修改area
函數即可,而不需要修改存取該屬性的程式碼。
需要注意的是,在計算屬性模式中,計算屬性的值並不是固定的,而是根據其他屬性值的變化而變化的。因此,在物件的屬性值發生變化時,需要確保計算屬性的值也能及時更新。在Vue.js等框架中,可以通過watcher
等機制來實現自動更新。
路由模式通常用於實現單頁面應用(SPA)的頁面導航和狀態管理。具體來說,路由模式通過解析URL路徑來確定應該顯示哪個頁面,並使用歷史記錄API來管理頁面狀態。
一般來說,路由模式包含以下幾個關鍵部分:
常見的前端路由框架有Vue Router、React Router和Angular Router等。這些框架都提供了一系列API和元件來幫助開發者快速構建SPA應用程式,並實現靈活、可延伸的路由功能。
下面是一個基於Vue Router的簡單路由範例:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/contact',
component: Contact
}
]
const router = new VueRouter({
routes
})
new Vue({
router,
render: h => h(App)
}).$mount('#app')
首先,我們需要在Vue專案中安裝並匯入Vue Router
模組,然後,我們可以定義一個路由表,指定每個URL路徑對應的頁面元件,在這個範例中,我們定義了三個路由路徑,分別對應Home
、About
和Contact
三個頁面元件。
接著,我們可以建立一個VueRouter
範例,並將路由表作為引數傳遞進去,最後,我們需要將VueRouter
範例掛載到Vue
根範例中。
現在,我們就可以在應用程式中使用路由了。例如,我們可以在App.vue
元件中新增一個路由出口,並使用<router-link>
元件來定義導航連結:
<template>
<div id="app">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
<router-view></router-view>
</div>
</template>
在這個範例中,<router-view>
元件將渲染當前路由路徑對應的頁面元件。當用戶點選導航連結時,Vue Router
將自動更新URL路徑,並根據路由表匹配對應的頁面元件進行渲染。
直譯器模式可以用來處理例如資料格式化、表單驗證等業務場景。在這些場景中,我們需要定義一些語法規則,然後使用直譯器來解釋這些規則。
直譯器模式的基本結構包括四個角色:抽象表示式、終結符表示式、非終結符表示式和上下文。
在使用直譯器模式時,我們需要先定義好語言的語法規則,然後再根據這些規則建立相應的表示式物件,並將其組合成一個完整的表示式樹。最後,我們可以使用直譯器來解釋這棵表示式樹,並得到相應的結果。
以下是一個簡單的範例,演示瞭如何使用直譯器模式來處理一個簡單的算術表示式。在這個範例中,我們定義了一個語法規則,用於表示加法和減法運算,並使用直譯器模式來解釋這個表示式。
// 定義抽象表示式
class Expression {
interpret() {}
}
// 定義終結符表示式
class NumberExpression extends Expression {
constructor(number) {
super();
this.number = number;
}
interpret() {
return this.number;
}
}
// 定義非終結符表示式
class AddExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class SubtractExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() - this.right.interpret();
}
}
// 定義上下文
class Context {
constructor() {
this.expression = null;
}
setExpression(expression) {
this.expression = expression;
}
evaluate() {
return this.expression.interpret();
}
}
// 使用範例
const context = new Context();
const expression = new SubtractExpression(
new AddExpression(new NumberExpression(10), new NumberExpression(5)),
new NumberExpression(2)
);
context.setExpression(expression);
console.log(context.evaluate()); // 輸出 13
在這個範例中,我們定義了四個表示式類:Expression
、NumberExpression
、AddExpression
和 SubtractExpression
,並分別實現了它們的 interpret()
方法。同時,我們還定義了一個上下文類 Context
,用於儲存直譯器解釋時的中間結果。
在範例的最後,我們使用 SubtractExpression
、AddExpression
和 NumberExpression
等表示式物件來建立一個表示式樹,並將其儲存在上下文中。最後,我們使用 Context
物件的 evaluate()
方法來求出表示式的值,並輸出結果。
具體來說,享元模式涉及兩個主要的物件:享元工廠和具有共用狀態的享元物件。享元工廠負責建立和管理共用物件,以確保每個物件只被建立一次。享元物件則包含需要共用的狀態資訊,並提供介面以存取該狀態。
通過使用享元模式,可以顯著減少記憶體消耗和提高效能,尤其是在處理大量相似物件時。常見的使用享元模式的場景包括:DOM元素的複用、快取資料、減少ajax請求等。
需要注意的是,享元模式雖然可以優化記憶體和效能,但是也可能會犧牲一定的可讀性和維護性。因此,應該在合適的場景下使用該模式。
以下是一個使用享元模式的簡單範例,其中我們建立了一個享元工廠和一個具有共用狀態的享元物件:
// 定義享元工廠
const FlyweightFactory = function () {
const flyweights = {};
const get = function (key) {
if (flyweights[key]) {
return flyweights[key];
}
const flyweight = {
// 共用的狀態資訊
key: key,
// 具體的操作方法
operation: function () {
console.log('Executing operation for key: ' + this.key);
}
};
flyweights[key] = flyweight;
return flyweight;
};
return {
get: get
};
};
// 使用享元工廠建立享元物件
const factory = new FlyweightFactory();
const flyweight1 = factory.get('key1');
const flyweight2 = factory.get('key2');
// 呼叫共用的操作方法
flyweight1.operation(); // 輸出: "Executing operation for key: key1"
flyweight2.operation(); // 輸出: "Executing operation for key: key2"
在上面的範例中,我們定義了一個名為 FlyweightFactory
的享元工廠,並實現了 get
方法來獲取共用狀態的享元物件。當請求一個新的享元物件時,我們首先檢查它是否已經存在於工廠的內部快取中,如果存在則返回它,否則建立一個新的物件並將其新增到快取中。
我們然後使用 factory
範例來建立兩個享元物件 flyweight1
和 flyweight2
,它們分別具有鍵值為 'key1'
和 'key2'
的共用狀態資訊。最後,我們呼叫每個物件的 operation
方法來執行共用的操作。
值得注意的是,在上面的範例中,我們建立了兩個不同的享元物件,因為它們具有不同的鍵值。如果我們嘗試再次獲取具有相同鍵值的物件,將會返回已存在的物件,而不是建立一個新的。這就是享元模式的核心思想——通過共用具有相同狀態的物件來減少記憶體消耗和提高效能。
在傳統的程式設計模式中,一個物件可能會直接建立或者獲取它需要的其他物件,這樣會造成物件之間的緊耦合關係,難以維護和擴充套件。而使用依賴注入模式,則可以將物件的依賴關係從物件內部移到外部,從而實現鬆耦合的設計,便於維護和擴充套件。
依賴注入模式可以通過建構函式、屬性、方法等方式來實現。在前端開發中,通常使用框架(如Angular、Vue、React等)來實現依賴注入,這些框架提供了依賴注入容器,可以自動管理物件之間的依賴關係。
以下是一個使用依賴注入模式的範例程式碼:
class UserService {
constructor(apiService) {
this.apiService = apiService;
}
getUser(id) {
return this.apiService.get(`/users/${id}`);
}
}
class ApiService {
constructor(httpService) {
this.httpService = httpService;
}
get(url) {
return this.httpService.get(url);
}
}
class HttpService {
get(url) {
// 傳送HTTP請求
}
}
const httpService = new HttpService();
const apiService = new ApiService(httpService);
const userService = new UserService(apiService);
userService.getUser(1);
在上面的程式碼中,UserService
、ApiService
、HttpService
三個類之間都存在依賴關係。使用依賴注入模式,可以將這些依賴關係從內部移到外部,從而實現物件之間的解耦。在範例化UserService
物件時,將依賴的ApiService
物件作為引數傳入建構函式;在範例化ApiService
物件時,將依賴的HttpService
物件作為引數傳入建構函式。這樣就實現了依賴注入。
每個設計模式都有其適用的場景和優缺點,需要根據具體情況來選擇使用。其實,還有很多其他設計模式,MVC模式(Model-View-Controller)
、MVVM模式(Model-View-ViewModel)
、元件模式(Component Pattern)
等等,這裡就不多介紹了,上文講到的面試肯定夠用了,但要真正融合進自己的專案中,還要多思考多理解多實踐,認識和應用設計模式可以幫助我們編寫更好的程式碼,提高程式碼的可讀性、可維護性和可延伸性。