前言
最近公司晉升崗位裡面的一道小題《使用 es3 實現簡單工廠模式、單例模式、觀察者模式、釋出訂閱模式》,結束之後發現自己還要補好多´д` ;,抽空把裡面幾道小題,重新複習了一遍,擴充到es3、es5、es6,並做了筆記(再次膜拜公司出題大佬)。
完整版github程式碼地址:github.com/xuhuihui/sm…
待做的筆記:
- 實現簡易模板字串(6分)
- 模擬實現es6模板字串的${}即可;
- 無需考慮特殊符號($、{、})的轉義和巢狀等問題;
- 模擬實現簡易版React-Redux
簡單工廠模式
定義
定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。Factory Method使一個類的例項化延遲到其子類。
適用範圍
適用於複雜邏輯判斷的情況,例如購物的商品,可以有上架中、出售中、下單中、出貨中、派送中、到手等一系列複雜邏輯判斷。
關於複雜邏輯的判斷,有兩種解決方案,策略模式和工廠模式。
1、策略模式vs工廠模式的區別
2、策略模式:JavaScript 複雜判斷的更優雅寫法
實現
es3程式碼
var createPop = function (type, text) {
// 建立一個物件,並對該物件做出擴充套件
var o = new Object();
o.content = text;
o.show = function () {
// 顯示相同部分
alert(type + ':' + this.content);
switch (type) {
case 'alert':
// 警示框差異部分
break;
case 'confirm':
// 確認框差異部分
break;
case 'prompt':
// 提示框差異部分
break;
}
};
return o;
};
複製程式碼
es6程式碼
class Image {}
class Link {}
class Text {}
class ElementFactory {
createElement(type, option){
const ELEMENT = {
"image" : Image,
"text" : Text,
"link" : Link
}
let ElementConstructor = ELEMENT(type),
element = null;
if(ElementConstructor){
element = new ElementConstructor(option);
}
return element;
}
}
複製程式碼
單例模式
定義
- 確保只有一個例項
- 可以全域性訪問
適用範圍
適用於彈框的實現, 全域性快取。
實現彈框的一種做法是先建立好彈框, 然後使之隱藏, 這樣子的話會浪費部分不必要的 DOM 開銷, 我們可以在需要彈框的時候再進行建立, 同時結合單例模式實現只有一個例項, 從而節省部分 DOM 開銷。
實現
es3程式碼
簡易版程式碼
1、優點:利用initialized屬性,保持永遠只建立一個EasySingletonIns例項,
2、缺點:不夠封閉,可以從外部訪問修改initialized屬性。
var EasySingletonIns = {
initialized: false,
// 例項擴充套件寫在此處:
belongTo: undefined,
getBelongTo: function () {
return EasySingletonIns.belongTo;
}
};
var EasySingleton = {
getInstance: function () {
if (EasySingletonIns.initialized) {
return EasySingletonIns;
}
EasySingletonIns.initialized = true;
// 例項擴充套件也可寫在此處:
EasySingletonIns.belongTo = 'EasySingleton';
return EasySingletonIns;
}
};
複製程式碼
進階版程式碼
1、優點:利用函式的閉包,阻止了內部屬性被改變。
2、缺點:不好動態地傳入想要的屬性。
var AdvancedSingleton = (function () {
var ins = null;
return function () {
if (ins) {
return ins;
}
if (!(this instanceof AdvancedSingleton)) {
return new AdvancedSingleton();
}
function Inner() {
// 例項擴充套件寫在此處:
this.belongTo = 'AdvancedSingleton @ constructor'; }
Inner.prototype = this.constructor.prototype;
// 原型擴充套件也可寫在此處:
ins = new Inner();
return ins;
};
})();
複製程式碼
酷炫版程式碼
1、優點:利用apply,可以給CoolSingletonCreator例項的prototype增加方法。
2、缺點:建構函式返回非自身例項的情況下,會出現問題。
function CoolSingletonCreator(fn) {
if (typeof fn !== 'function') {
throw new Error('CoolSingletonCreator fn param must be function!');
}
var AdvancedSingletonForCool = (function () {
var ins = null;
return function () {
if (ins) {
return ins;
}
if (!(this instanceof AdvancedSingletonForCool)) {
return new AdvancedSingletonForCool();
}
var args = arguments;
function Inner() {
fn.apply(this, args);
}
Inner.prototype = this.constructor.prototype;
ins = new Inner();
return ins;
};
})();
AdvancedSingletonForCool.prototype = fn.prototype;
AdvancedSingletonForCool.getInstance = function () {
// 動態引數:(另外,針對只支援es3語法的瀏覽器的bind方法是沒有的,需要自己實現,不在此處展開)
var args = [null];
return new (Function.prototype.bind.apply(AdvancedSingletonForCool, args.concat.apply(args, arguments)))();
};
return AdvancedSingletonForCool;
}
複製程式碼
es5程式碼
var Singleton = function(name) {
this.name = name;
//一個標記,用來判斷是否已將建立了該類的例項
this.instance = null;
}
// 提供了一個靜態方法,使用者可以直接在類上呼叫
Singleton.getInstance = function(name) {
// 沒有例項化的時候建立一個該類的例項
if(!this.instance) {
this.instance = new Singleton(name);
}
// 已經例項化了,返回第一次例項化物件的引用
return this.instance;
}
複製程式碼
es6程式碼
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
// 構造一個廣為人知的介面,供使用者對該類進行例項化
static getInstance(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
複製程式碼
觀察者模式
定義
定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動重新整理。
適用範圍
1、當觀察的資料物件發生變化時, 自動呼叫相應函式。比如 vue 的雙向繫結;
2、每當呼叫物件裡的某個方法時, 就會呼叫相應'訪問'邏輯。比如給測試框架賦能的 spy 函式;
實現
es3程式碼
1、方法:使用prototype繫結函式實現。
var Subject = (function () {
// 觀察者列表(不是必須的,可以由Subject自己處理)
function ObserverList () {
this.observerList = [];
}
ObserverList.prototype.add = function (observer) {
return this.observerList.push(observer);
};
ObserverList.prototype.remove = function (observer) {
this.observerList = this.observerList.filter(function (item) {return item !== observer;});
};
ObserverList.prototype.count = function () {
return this.observerList.length;
};
ObserverList.prototype.get = function (index) {
return this.observerList[index];
};
// 主題
function Subject () {
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function (observer) {
this.observers.add(observer);
};
Subject.prototype.removeObserver = function (observer) {
this.observers.remove(observer);
};
Subject.prototype.notify = function () {
var observerCount = this.observers.count();
for (let i = 0; i < observerCount; i++) {
var observer = this.observers.get(i);
observer.update.apply(observer, arguments);
}
}
return Subject;
})();
複製程式碼
es5程式碼
1、方法:使用 Object.defineProperty(obj, props, descriptor) 實現觀察者模式。
2、缺點:Object.defineProperty() 不會監測到陣列引用不變的操作(比如 push/pop 等);Object.defineProperty() 只能監測到物件的屬性的改變, 即如果有深度巢狀的物件則需要再次給之繫結 Object.defineProperty();
<input id="input" type="text" />
複製程式碼
const data = {}
const input = document.getElementById('input')
Object.defineProperty(data, 'text', {
set(value) {
input.value = value
this.value = value
}
})
input.onchange = function(e) {
data.text = e.target.value
}
複製程式碼
es6程式碼
1、方法:Proxy/Reflect 是 ES6 引入的新特性, 也可以使用其完成觀察者模式。
2、優點:可以劫持陣列的改變;defineProperty 是對屬性的劫持, Proxy 是對物件的劫持;
var obj = {
value: 0
}
var proxy = new Proxy(obj, {
set: function(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
}
})
proxy.value = 1 // 呼叫相應函式
複製程式碼
釋出訂閱模式
定義
定義:在觀察者模式中間,增加訊息代理進行通訊,來實現更更鬆的解耦。
釋出訂閱模式和觀察者模式的差異:
1、在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們只有通過訊息代理進行通訊。
2、在釋出訂閱模式中,元件是鬆散耦合的,正好和觀察者模式相反。
3、觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去呼叫觀察者的方法。而釋出-訂閱模式大多數時候是非同步的(使用訊息佇列)。
4、觀察者模式需要在單個應用程式地址空間中實現,而釋出-訂閱更像交叉應用模式。
適用範圍
1、範圍:MVC、MVVC的架構、Vue的原始碼實現和一些小遊戲等。
2、優點: 在非同步程式設計中實現更深的解耦。
3、缺點: 建立訂閱者本身要消耗一定的時間和記憶體,而且當你訂閱一個訊息以後,可能此訊息最後都未發生,但是這個訂閱者會始終存在於記憶體中。如果程式中大量使用釋出-訂閱的話,也會使得程式跟蹤bug變得困難。
實現
es5程式碼
var Event = function() {
this.obj = {}
}
Event.prototype.on = function(eventType, fn) {
if (!this.obj[eventType]) {
this.obj[eventType] = []
}
this.obj[eventType].push(fn) // 推入陣列
}
Event.prototype.emit = function() {
var eventType = Array.prototype.shift.call(arguments)
var arr = this.obj[eventType]
for (let i = 0; i < arr.length; i++) { //推出呼叫函式
arr[i].apply(arr[i], arguments)
}
}
var ev = new Event()
ev.on('click', function(a) { // 訂閱函式
console.log(a) // 1
})
ev.emit('click', 1) // 釋出函式
複製程式碼
es6程式碼
class Subject {
constructor() {
this.subs = []
this.state = '張三' // 觸發更新的狀態
}
getState() {
return this.state
}
setState(state) {
if (this.state === state) {
// 釋出者一樣
return
}
this.state = state
this.notify() // 有更新,觸發通知
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
const idx = this.subs.findIndex(i => i === sub)
if (idx === -1) {
// 不存在該觀察者
return
}
this.subs.splice(idx, 1)
}
notify() {
this.subs.forEach(sub => {
sub.update() // 與觀察者原型方法update對應!
})
}
}
// 觀察人,相當於訂閱者
class Observer {
update() {
console.log('update')
}
}
// 測試程式碼
const subject = new Subject()
const ob = new Observer()
const ob2 = new Observer()
ob2.update = function() {
//修改update方法,實現不同邏輯
console.log('laifeipeng')
}
//目標新增觀察者了
subject.addSub(ob)
subject.addSub(ob2)
//目標釋出訊息呼叫觀察者的更新方法了
// subject.notify(); // 不使用手動觸發,通過內部狀態的設定來觸發
subject.setState('李四')
複製程式碼
JavaScript 中常見設計模式
瞭解到的設計模式如下(゚o゚;;,以後每週有時間大概會補的吧(・_・;。
設計模式分為三種型別,共24種。
- 建立型模式:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。
- 結構型模式:介面卡模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
- 行為型模式:模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、直譯器模式(Interpreter模式)、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式。