設計模式概覽
設計模式是對軟體設計開發程序中反復出現的某類問題的通用解決方案,設計模式更多的是指導思想和方法論,而不是現成的代碼,當然每種設計模式都有每種語言中的具體實作方式,學習設計模式更多的是理解各種模式的內在思想和解決的問題,畢竟這是前人無數經驗總結成的最佳實踐,而代碼實作則是對加深理解的輔助,
設計模式可以分為三大類:
- 結構型模式(Structural Patterns): 通過識別系統中組件間的簡單關系來簡化系統的設計,
- 創建型模式(Creational Patterns): 處理物件的創建,根據實際情況使用合適的方式創建物件,常規的物件創建方式可能會導致設計上的問題,或增加設計的復雜度,創建型模式通過以某種方式控制物件的創建來解決問題,
- 行為型模式(Behavioral Patterns):用于識別物件之間常見的互動模式并加以實作,如此,增加了這些互動的靈活性,

上述中一共有23種設計模式,但我們作為前端開發人員,需要了解的大概有以下10種,
前端需要了解的設計模式(10種)
創建型模式
故名思意,這些模式都是用來創建實體物件的,
1. 工廠模式
我們從簡單的開始, 簡單工廠模式是由一個工廠物件決定創建出哪一種產品類的實體,

上圖為例,我們構造一個簡單的汽車工廠來生產汽車:
// 汽車建構式
function SuzukiCar(color) {
this.color = color;
this.brand = 'Suzuki';
}
// 汽車建構式
function HondaCar(color) {
this.color = color;
this.brand = 'Honda';
}
// 汽車建構式
function BMWCar(color) {
this.color = color;
this.brand = 'BMW';
}
// 汽車品牌列舉
const BRANDS = {
suzuki: 1,
honda: 2,
bmw: 3
}
/**
* 汽車工廠
*/
function CarFactory() {
this.create = (brand, color)=> {
switch (brand) {
case BRANDS.suzuki:
return new SuzukiCar(color);
case BRANDS.honda:
return new HondaCar(color);
case BRANDS.bmw:
return new BMWCar(color);
default:
break;
}
}
}
使用一下我們的工廠:
const carFactory = new CarFactory();
const cars = [];
cars.push(carFactory.create(BRANDS.suzuki, 'brown'));
cars.push(carFactory.create(BRANDS.honda, 'grey'));
cars.push(carFactory.create(BRANDS.bmw, 'red'));
function sayHello() {
console.log(`Hello, I am a ${this.color} ${this.brand} car`);
}
for (const car of cars) {
sayHello.call(car);
}
輸出結果:
Hello, I am a brown Suzuki car
Hello, I am a grey Honda car
Hello, I am a red BMW car
使用工廠模式之后,不再需要重復引入一個個建構式,只需要引入工廠物件就可以方便的創建各類物件,
2. 單例模式
首先我們需要理解什么是單例?
單:指的是一個,
例:指的是創建的實體,
單例:指的是創建的總是同一個實體,也就是使用類創建的實體始終是相同的,
先看下面的一段代碼:
class Person{
constructor(){}
}
let p1 = new Person();
let p2 = new Person();
console.log(p1===p2) //false
上面這段代碼,定義了一個Person類,通過這個類創建了兩個實體,我們可以看到最終這兩個實體是不相等的,也就是說,通過同一個類得到的實體不是同一個(這本就是理所應當),但是如果我們想始終得到的是同一個實體,那么這就是單例模式,那么下面就該介紹如何實作單例模式了:
想要實作單例模式,我們需要注意兩點:
- 需要使用return,使用new的時候如果沒有手動設定return,那么會默認回傳this,但是,我們這里要使得每次回傳的實體相同,也就是需要手動控制創建的物件,因此這里需要使用return,
- 我們需要每次return的是同一個物件,也就是說實際上在第一次實體的時候,需要把這個實體保存起來,再下一個實體的時候,直接return這個保存的實體,因此,這里需要用到閉包了,
const Person = (function(){
let instance = null;
return class{
constructor(){
if(!instance){
//第一次創建實體,那么需要把實體保存
instance = this;
}else{
return instance;
}
}
}
})()
let p3 = new Person();
let p4 = new Person();
console.log(p3===p4) //true
從上面的代碼中,我們可以看到在閉包中,使用instance變數來保存創建的實體,每次回傳的都是第一次創建的實體,這樣的話就實作了無論創建多少次,創建的都是同一個實體,這就是單例模式,
3. 原型模式
通俗點講就是創建一個共享的原型,并通過拷貝這些原型創建新的物件,
在我看來,其實原型模式就是指定新創建物件的模型,更通俗一點來說就是我想要新創建的物件的原型是我指定的物件,
最簡單的原型模式的實作就是通過Object.create(),Object.create(),會使用現有的物件來提供新創建的物件的__proto__,例如下方代碼:
let person = {
name:'hello',
age:24
}
let anotherPerson = Object.create(person);
console.log(anotherPerson.__proto__) //{name: "hello", age: 24}
anotherPerson.name = 'world'; //可以修改屬性
anotherPerson.job = 'teacher';
另外,如果我們想要自己實作原型模式,而不是使用封裝好的Object.create()函式,那么可以使用原型繼承來實作:
function F(){}
F.prototype.g = function(){}
//G類繼承F類
function G(){
F.call(this);
}
//原型繼承
function Fn(){};
Fn.prototype = F.prototype;
G.prototype = new Fn();
G.prototype.constructor = G;
原型模式就是創建一個指定原型的物件,如果我們需要重復創建某個物件,那么就可以使用原型模式來實作,
結構型模式
1. 裝飾器模式
裝飾器模式:為物件添加新功能,不改變其原有的結構和功能,
配接器模式是原有的不能用了,要重新封裝介面,裝飾器模式是原有的還能用,但是需要新增一些東西來完善這個功能,
比如手機殼,手機本身的功能不受影響,手機殼就是手機的裝飾器模式,

class Circle {
draw() {
console.log('畫一個圓形');
}
}
class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
this.circle.draw();
this.setRedBorder(circle);
}
setRedBorder(circle) {
console.log('設定紅色邊框')
}
}
// 測驗
let circle = new Circle();
let client = new Decorator(circle);
client.draw();
輸出結果:
畫一個圓形
設定紅色邊框
如今都2021了,es7也應用廣泛,我們在es7中這么寫(ES7裝飾器):
1、安裝 yarn add babel-plugin-transform-decorators-legacy
2、新建.babelrc檔案,進行下面的配置
{
"presets": ["es2015", "latest"],
"plugins": ["transform-decorators-legacy"]
}
3、上代碼
@testDec
class Demo {
// ...
}
function testDec(target) {
target.isDec = true
}
console.log(Demo.isDec)
//輸出true
列印出來了true,說明@testDec這個裝飾器已經成功了,函式是個裝飾器,用@testDec給Demo裝飾了一遍,這個target其實就是class Demo,然后給她加一個isDec,
拆解后就是下面的內容:
// 裝飾器原理
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
裝飾器引數的形式
@testDec(false)
class Demo {
}
function testDec(isDec) {
return function (target) {
target.isDec = isDec
}
}
console.log(Demo.isDec);
驗證是否是一個真正的裝飾器模式需要驗證以下幾點:
1.將現有對戲那個和裝飾器進行分離,兩者獨立存在
2.符合開放封閉原則
2. 配接器模式
配接器模式:舊介面格式和使用者不兼容,中間加一個適配轉換介面,
比如國外的插座跟國內的插座不一樣,我們需要買個轉換器去兼容,

上代碼:
class Adaptee {
specificRequest() {
return '德國標準的插頭';
}
}
class Target {
constructor() {
this.adaptee = new Adaptee();
}
request() {
let info = this.adaptee.specificRequest();
return `${info} -> 轉換器 -> 中國標準的插頭`
}
}
// 測驗
let client = new Target();
client.request();
結果:
德國標準的插頭 -> 轉換器 -> 中國標準的插頭
場景上可封裝舊介面:
// 自己封裝的ajax,使用方式如下:
ajax({
url: '/getData',
type: 'Post',
dataType: 'json',
data: {
id: '123'
}
}).done(function(){
})
// 但因為歷史原因,代碼中全都是:
// $.ajax({...})
這個時候需要一個配接器:
// 做一層配接器
var $ = {
ajax: function (options) {
return ajax(options)
}
}
3. 代理模式
代理模式:使用者無權訪問目標物件,中間加代理,通過代理做授權和控制,
明星經紀人:比如有個演出,要請明星,要先聯系經紀人,
或者理解為:為一個物件提供一個代用品或者占位符,以便控制對它的訪問,例如圖片懶加載、中介等,
/**
* pre:代理模式
* 小明追求A,B是A的好朋友,小明比不知道A什么時候心情好,不好意思直接將花交給A,
* 于是小明將花交給B,再由B交給A.
*/
// 花的類
class Flower{
constructor(name){
this.name = name
}
}
// 小明擁有sendFlower的方法
let Xioaming = {
sendFlower(target){
var flower = new Flower("玫瑰花")
target.receive(flower)
}
}
// B物件中擁有接受花的方法,同時接收到花之后,監聽A的心情,并且傳入A心情好的時候函式
let B = {
receive(flower){
this.flower =flower
A.listenMood(()=>{
A.receive(this.flower)
})
}
}
// A接收到花之后輸出花的名字
let A = {
receive(flower){
console.log(`A收到了${flower.name} `)
// A收到了玫瑰花
},
listenMood(func){
setTimeout(func,1000)
}
}
Xioaming.sendFlower(B)
虛擬代理用于圖片的預加載
圖片很大,頁面加載時會空白,體驗不好,所以我們需要個占位符,來短暫替代這個圖片,等圖片加載好了放上去,
let myImage = (function(){
let img = new Image
document.body.appendChild(img)
return {
setSrc:(src)=>{
img.src = src
}
}
})()
let imgProxy =(function(){
let imgProxy = new Image
// 這個地方我使用了setTimeout來增強演示效果,否則本地加載太快,根本看不到,
imgProxy.onload=function(){
setTimeout(()=>{
myImage.setSrc(this.src)
},2000)
}
return (src)=>{
myImage.setSrc("../../img/bgimg.jpeg")
imgProxy.src=src
}
})()
imgProxy("../../img/background-cover.jpg")
ES6 Proxy
其實在ES6中,已經有了Proxy,這個內置的函式,我們來用一個例子來演示一下他的用法,這是一個明星代理的問題,
let star={
name : "張XX",
age:25,
phone : "1300001111"
}
let agent = new Proxy(star,
{
get:function(target,key){
if(key === "phone"){
return "18839552597"
}else if(key === "name"){
return "張XX"
}else if(key === "price"){
return "12W"
}else if(key === "customPrice"){
return target.customPrice
}
},
set:function(target,key,value){
if(key === "customPrice"){
if(value < "10"){
console.log("太低了!!!")
return false
}else{
target[key] = value
return true
}
}
}
}
)
console.log(agent.name)
console.log(agent.price)
console.log(agent.phone)
console.log(agent.age)
agent.customPrice = "12"
console.log(agent)
console.log(agent.customPrice)
設計原則驗證
代理類和目標類分離,隔離開目標類和使用者
符合開放封閉原則
行為型模式
1. 策略模式
策略模式是一種簡單卻常用的設計模式,它的應用場景非常廣泛,我們先了解下策略模式的概念,再通過代碼示例來更清晰的認識它,
策略模式由兩部分構成:一部分是封裝不同策略的策略組,另一部分是 Context,通過組合和委托來讓 Context 擁有執行策略的能力,從而實作可復用、可擴展和可維護,并且避免大量復制粘貼的作業,

策略模式的典型應用場景是表單校驗中,對于校驗規則的封裝,接下來我們就通過一個簡單的例子具體了解一下:
/**
* 登錄控制器
*/
function LoginController() {
this.strategy = undefined;
this.setStrategy = function (strategy) {
this.strategy = strategy;
this.login = this.strategy.login;
}
}
/**
* 用戶名、密碼登錄策略
*/
function LocalStragegy() {
this.login = ({ username, password }) => {
console.log(username, password);
// authenticating with username and password...
}
}
/**
* 手機號、驗證碼登錄策略
*/
function PhoneStragety() {
this.login = ({ phone, verifyCode }) => {
console.log(phone, verifyCode);
// authenticating with hone and verifyCode...
}
}
/**
* 第三方社交登錄策略
*/
function SocialStragety() {
this.login = ({ id, secret }) => {
console.log(id, secret);
// authenticating with id and secret...
}
}
const loginController = new LoginController();
// 呼叫用戶名、密碼登錄介面,使用LocalStrategy
app.use('/login/local', function (req, res) {
loginController.setStrategy(new LocalStragegy());
loginController.login(req.body);
});
// 呼叫手機、驗證碼登錄介面,使用PhoneStrategy
app.use('/login/phone', function (req, res) {
loginController.setStrategy(new PhoneStragety());
loginController.login(req.body);
});
// 呼叫社交登錄介面,使用SocialStrategy
app.use('/login/social', function (req, res) {
loginController.setStrategy(new SocialStragety());
loginController.login(req.body);
});
從以上示例可以得出使用策略模式有以下優勢:
- 方便在運行時切換演算法和策略
- 代碼更簡潔,避免使用大量的條件判斷
- 關注分離,每個strategy類控制自己的演算法邏輯,strategy和其使用者之間也相互獨立
2. 觀察者模式
觀察者模式又叫發布訂閱模式(Publish/Subscribe),它定義了一種一或一對多的關系,讓多個觀察者物件同時監聽某一個主題物件,這個主題物件的狀態發生變化時就會通知所有的觀察者物件,使得它們能夠自動更新自己,典型代表vue/react等,
使用觀察者模式的好處:
- 支持簡單的廣播通信,自動通知所有已經訂閱過的物件,
- 目標物件與觀察者存在的是動態關聯,增加了靈活性,
- 目標物件與觀察者之間的抽象耦合關系能夠單獨擴展以及重用,
當然給元素系結事件的addEventListener()也是一種:
target.addEventListener(type, listener [, options]);
Target就是被觀察物件Subject,listener就是觀察者Observer,
觀察者模式中Subject物件一般需要實作以下API:
subscribe(): 接收一個觀察者observer物件,使其訂閱自己unsubscribe(): 接收一個觀察者observer物件,使其取消訂閱自己fire(): 觸發事件,通知到所有觀察者
用JavaScript手動實作觀察者模式:
// 被觀察者
function Subject() {
this.observers = [];
}
Subject.prototype = {
// 訂閱
subscribe: function (observer) {
this.observers.push(observer);
},
// 取消訂閱
unsubscribe: function (observerToRemove) {
this.observers = this.observers.filter(observer => {
return observer !== observerToRemove;
})
},
// 事件觸發
fire: function () {
this.observers.forEach(observer => {
observer.call();
});
}
}
驗證一下訂閱是否成功:
const subject = new Subject();
function observer1() {
console.log('Observer 1 Firing!');
}
function observer2() {
console.log('Observer 2 Firing!');
}
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.fire();
輸出:
Observer 1 Firing!
Observer 2 Firing!
驗證一下取消訂閱是否成功:
subject.unsubscribe(observer2);
subject.fire();
輸出:
Observer 1 Firing!
3. 迭代器模式
ES6中的迭代器 Iterator 相信大家都不陌生,迭代器用于遍歷容器(集合)并訪問容器中的元素,而且無論容器的資料結構是什么(Array、Set、Map等),迭代器的介面都應該是一樣的,都需要遵循 迭代器協議,
迭代器模式解決了以下問題:
- 提供一致的遍歷各種資料結構的方式,而不用了解資料的內部結構
- 提供遍歷容器(集合)的能力而無需改變容器的介面

一個迭代器通常需要實作以下介面:
hasNext():判斷迭代是否結束,回傳Booleannext():查找并回傳下一個元素
為Javascript的陣列實作一個迭代器可以這么寫:
const item = [1, 'red', false, 3.14];
function Iterator(items) {
this.items = items;
this.index = 0;
}
Iterator.prototype = {
hasNext: function () {
return this.index < this.items.length;
},
next: function () {
return this.items[this.index++];
}
}
驗證一下迭代器:
const iterator = new Iterator(item);
while(iterator.hasNext()){
console.log(iterator.next());
}
輸出:
1, red, false, 3.14
ES6提供了更簡單的迭代回圈語法 for...of,使用該語法的前提是操作物件需要實作 可迭代協議(The iterable protocol),簡單說就是該物件有個Key為 Symbol.iterator 的方法,該方法回傳一個iterator物件,
比如我們實作一個 Range 類用于在某個數字區間進行迭代:
function Range(start, end) {
return {
[Symbol.iterator]: function () {
return {
next() {
if (start < end) {
return { value: start++, done: false };
}
return { done: true, value: end };
}
}
}
}
}
驗證:
for (num of Range(1, 5)) {
console.log(num);
}
結果:
1, 2, 3, 4
4. 狀態模式
狀態模式:一個物件有狀態變化,每次狀態變化都會觸發一個邏輯,不能總是用if...else來控制,

比如紅綠燈:
// 狀態(紅燈,綠燈 黃燈)
class State {
constructor(color) {
this.color = color;
}
// 設定狀態
handle(context) {
console.log(`turn to ${this.color} light`);
context.setState(this)
}
}
// 主體
class Context {
constructor() {
this.state = null;
}
// 獲取狀態
getState() {
return this.state;
}
setState(state) {
this.state = state;
}
}
// 測驗
let context = new Context();
let green = new State('green');
let yellow = new State('yellow');
let red = new State('red');
// 綠燈亮了
green.handle(context);
console.log(context.getState())
// 黃燈亮了
yellow.handle(context);
console.log(context.getState())
// 紅燈亮了
red.handle(context);
console.log(context.getState())
設計原則驗證
將狀態物件和主體物件分離,狀態的變化邏輯單獨處理
符合開放封閉原則
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/294984.html
標籤:其他
上一篇:冒泡排序法(超詳細)
