本文是向大家介紹前端開發(fā)中常用的設計模式,它使我們編寫的代碼更容易被復用,也更容易被人理解,并且保證代碼的穩(wěn)定可靠性。
1.什么是設計模式
通俗來講,就是日常使用設計的一種慣性思維。
因為對應的這種思維,以及對具體的業(yè)務或者代碼場景,
有著具體的優(yōu)勢,而后成為行業(yè)中的一種“設計模式”。
2.為什么使用設計模式
設計模式是各種業(yè)務場景的最佳實踐,有助于我們在寫日常業(yè)務中時,提高自身的思路。例如單例模式,就可以用于登陸框,模態(tài)框等場景。每種設計模式,必然有其適合應用的場景,靈活運用設計模式,可以提高代碼的可維護性,還可提升自身思維能力。
3.設計模式的基本準則
優(yōu)化代碼第一步:單一職責原則
讓程序更穩(wěn)定更靈活:開閉原則
構建擴展性更好的系統(tǒng):里式替換原則
讓項目擁有變化的能力:依賴倒置原則
系統(tǒng)有更高的靈活性:接口隔離原則
更好地擴展性:迪米特原則
4.設計模式的種類
a.創(chuàng)建型模式
一般用于創(chuàng)建對象。包括:
單例模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式。
b.結構型模式
重點為“繼承”關系,有著一層繼承關系,且一般都有“代理”。
包括:適配器模式,橋接模式,組合模式,裝飾器模式,
外觀模式,享元模式,代理模式,過濾器模式
c.行為型模式
職責的劃分,各自為政,減少外部的干擾。
包括:命令模式,解釋器模式,迭代器模式,中介者模式,
備忘錄模式,觀察者模式,狀態(tài)模式,策略模式,
模板方法模式,訪問者模式,責任鏈模式
5.前端比較常用的設計模式
1.組合模式
定義:將對象組合成樹形結構以表示“部分-整體”的層次結構。
class Folder {
? ? constructor(name) {
? ? ? ? this.fileName = name;
? ? ? ? this.children = [];
? ? }
? ? add(child) {
? ? ? ? this.children.push(child);
? ? }
? ? scan() {
? ? ? ? console.log("Folder:" + this.fileName);
? ? ? ? for (const child of this.children) {
? ? ? ? ? ? child.scan();
? ? ? ? }
? ? }
}
class File {
? ? constructor(name) {
? ? ? ? this.fileName = name;
? ? }
? ? add() {}
? ? scan() {
? ? ? ? console.log("File:" + this.fileName);
? ? }
}
const folder1 = new Folder("first");
folder1.add(new File("file1"));
folder1.add(new File("file2"));
const folder2 = new Folder("secend");
folder2.add(new File("file3"));
folder1.add(folder2);
folder1.scan();
這樣,通過調用第一個文件夾的掃描方法就能調用所有子元素的掃描方法。
a) 組合模式在組件開發(fā)中的應用:
組合模式適合一些容器組件場景,通過外層組件包裹內層組件,這種方式在 Vue 中稱為 slot 插槽,在React中為props.children,外層組件可以輕松的獲取內層組件的 props 狀態(tài),還可以統(tǒng)一控制內層組件的渲染,組合模式能夠直觀反映出 父 -> 子組件的包含關系,如下最簡單的組合模式例子。
<GroupPatterns>
? ? <GroupPatternsItem name='《React進階實踐指南》' />
? ? <GroupPatternsItem name='《React設計模式》' />
? ? <GroupPatternsItem isShow name='《React進階實踐指南》' />
? ? <GroupPatternsItem isShow={false} name='《Nodejs深度學習手冊》' />
? ? <div>hello,world</div>
</GroupPatterns>
我們直觀上看到 GroupPatterns 和 GroupPatternsItem 并沒有做某種關聯(lián),但是卻無形的聯(lián)系起來。這種就是組合模式的精髓所在,這種組合模式的組件,給使用者感覺很舒服,因為大部分工作,都在開發(fā)組合組件的時候處理了。所以編寫組合模式的嵌套組件,對鍛煉開發(fā)者的 組件封裝能力是很有幫助的。
我們通過如下代碼分析組合模式原理:
// GroupPatterns.js
import React, {isValidElement} from 'react'
const GroupPatterns = (props) => {
? ? const handleCallback = (val) =>? {
? ? ? ? console.log(' children 內容:',val )
? ? };
? ? let newChilren = [];
? ? // 遍歷children,根據屬性處理每一項節(jié)點
? ? React.Children.forEach(props.children,(item)=>{
? ? ? ? const { type ,props } = item || {}
? ? ? ? const {isShow = true} = props || {}
? ? ? ? // 根據子組件屬性是否展示
? ? ? ? if(isValidElement(item) && type.name === 'GroupPatternsItem' && isShow? ){ // 或者 type.displayName === 'Item'
? ? ? ? ? ? // 增強要展示的子組件,給元素添加額外props
? ? ? ? ? ? const enhanceElement = React.cloneElement( item , { callback:handleCallback } )
? ? ? ? ? ? newChilren.push(enhanceElement)
? ? ? ? }
? ? })
? ? console.log(' React.newChildren', newChilren, props.children)
? ? return newChilren
}
const GroupPatternsItem = (props) => {
? ? console.log('GroupPatternsItem', props) // {name: "《React進階實踐指南》", author: "alien"}
? ? return (
? ? ? ? <div
? ? ? ? ? ? style={{
? ? ? ? ? ? ? ? display: 'flex',
? ? ? ? ? ? ? ? flexDirection: 'column',
? ? ? ? ? ? ? ? alignItems: 'flex-start',
? ? ? ? ? ? ? ? textAlign:'left',
? ? ? ? ? ? ? ? width: '100%',
? ? ? ? ? ? ? ? marginBottom: '20px'}
? ? ? ? ? ? }>
? ? ? ? ? ? ? ? <div>
? ? ? ? ? ? ? ? ? ? 名稱: {props.name}
? ? ? ? ? ? ? ? ? ? <button onClick={()=> props.callback(`let us learn ${props.name}!`)} >點擊</button>
? ? ? ? ? ? ? ? </div>
? ? ? ? ? </div>
? ? )
}
GroupPatternsItem.displayName = 'Item'
通過 React.cloneElement 隱式混入 props 這個是組合模式的精髓所在,就是可以通過 React.cloneElement 向 children 中混入其他的 props,那么子組件就可以使用容器父組件提供的特有的 props 。所以在外層容器中,進行多次組合,這樣組件就會一層一層的包裹,一次又一次的強化。
這種組合模式能夠一層層強化原始組件,外層組件不用過多關心內層到底做了些什么? 只需要處理 children 就可以,同樣內層 children 在接受業(yè)務層的 props 外,還能使用來自外層容器組件的狀態(tài),方法等。
2.單例模式
定義:保證一個類只有一個實例, 一般先判斷實例是否存在,如果存在直接返回, 不存在則先創(chuàng)建再返回,這樣就可以保證一個類只有一個實例對象.
作用:1.模塊間通信。2.保證某個類的對象的唯一性3.防止變量污染
單例模式適用于全局只能有一個實例對象的場景,單例模式的一般結構如下:
let CreateSinglePattern = (function(){
? ? let instance;
? ? return function(age) {
? ? ? ? if (instance) {
? ? ? ? ? ? return instance;
? ? ? ? }
? ? ? ? this.age = age;
? ? ? ? return instance = this;
? ? }
})();
CreateSinglePattern.prototype.getAge = function() {
? ? return? this.age
}
let young = new CreateSinglePattern('18');
let old = new CreateSinglePattern('108');
console.log(young === old); // true
console.log(young.getAge());? // '18'
console.log(old.getAge());? // '18'
通過閉包緩存實例,后續(xù)不管new多少次都是同一個實例。
a) 單例模式在vuex中的應用:
對應的源碼行數(shù)(vuex2 - - - 777行)
我們看看vuex的源碼處理,在vue.use(vuex) 注冊vuex的時候,會去調用vuex的install方法,如下:
var Vue; // bind on install
function install (_Vue) {
? if (Vue && _Vue === Vue) {
? ? {
? ? ? console.error(
? ? ? ? '[vuex] already installed. Vue.use(Vuex) should be called only once.'
? ? ? );
? ? }
? ? return
? }
? Vue = _Vue;
? applyMixin(Vue);
}
可以看出,如果重復注冊的話會進行報錯處理,通過這種方式,可以保證一個 Vue 實例(即一個 Vue 應用)只會被 install 一次 Vuex 插件,所以每個 Vue 實例只會擁有一個全局的 Store。
3.工廠模式
a.簡單工廠模式
簡單工廠模式是由一個工廠對象來創(chuàng)建某一類產品的實例。
比如去買書,我們不用自己去找這些書,而是口頭告訴給bookShop,讓它幫我找,并且告訴我價格。
function bookShop (name, year, version) {
? var book = new Object();
? book.name = name;
? book.year = year;
? book.version = version;
? book.price = '暫無標價';
? if (name === 'JS高級編程') {
? ? book.price = '89';
? }
? if (name === 'react進階') {
? ? book.price = '79';
? }
? if (name === 'js設計模式') {
? ? book.price = '59';
? }
? return book;
}
var book1 = bookShop('JS高級編程', '2013', '第三版');
var book2 = bookShop('react進階', '2017', '第六版');
var book3 = bookShop('js設計模式', '2015', '第一版');
console.log(book1)
console.log(book2)
console.log(book3)
b.工廠方法模式
工廠方法模式可以理解為升級版的簡單工廠模式,是對產品類的抽象,使其創(chuàng)建多類產品的實例。
簡單工廠模式是創(chuàng)建同一類的某個產品,而工廠方法模式是創(chuàng)建多類產品的實例。它其實是將多個產品類進行抽象化,可以通過這個工廠對這些類創(chuàng)建相應類的實例。
比如現(xiàn)在,我不想買編程類的書了,我要買科學類或者社會學類的書,那么工廠方法模式的作用就體現(xiàn)出來了。
var BookShop = function (name) {
? // 如果外部直接調用了BookShop而不是new關鍵字調用,則返回new BookShop調用,否則直接調用
? if (this instanceof BookShop) {
? ? var book = new this[name]()
? ? return book
? } else {
? ? return new BookShop(name)
? }
}
BookShop.prototype = {
? Programme: function () {
? ? this.books = ['JS高級編程', 'react進階', 'js設計模式']
? },
? Science: function () {
? ? this.books = ['a', 'b', 'c']
? },
? Society: function () {
? ? this.books = ['aa', 'bb', 'cc']
? }
}
var programme = new BookShop('programme');
var science = BookShop('science');
var society = BookShop('society');
console.log(programme) // books: (3) ['JS高級編程', 'react進階', 'js設計模式']
console.log(science) // books: (3) ['a', 'b', 'c']
console.log(society) // books: (3) ['aa', 'bb', 'cc']
注意:這種方法重寫了prototype,所以后續(xù)只能往里面增加,不能直接修改prototype。
c.抽象工廠模式
抽象工廠模式是對類的工廠的抽象,用于創(chuàng)建不同的類,而不是創(chuàng)建類的實例。
首先創(chuàng)建這個工廠以及所屬其的種類:
該工廠可用于生產(Animals,F(xiàn)ruits, Books,Peoples)等類
// 工廠接收兩個參數(shù)分別是產品子類和產品類
const Factorys = function (subType, superType) {
? ? // 如果產品類在這個工廠中存在,就利用寄生繼承方式,讓產品子類繼承產品類中的所有屬性
? ? if (typeof Factorys[superType] === 'function') {
? ? ? const F = function() {}
? ? ? F.prototype = new Factorys[superType]()
? ? ? subType.constructor = subType
? ? ? subType.prototype = new F()
? ? } else {
? ? ? throw new Error('沒有找到' + superType + '抽象類')
? ? }
? }
? // 以下均為產品類
? // Animals產品類
? Factorys.Animals = function () {
? ? this.type = 'Animals'
? }
? // 直接訪問方法拋出錯誤,之所以這樣做,是為了不讓外部在沒重寫子類方法之前訪問這些方法
? // 需通過繼承產品類之后重寫這個方法才能訪問
? Factorys.Animals.prototype = {
? ? getInfo: function () {
? ? ? return new Error('請先重寫子類')
? ? }
? }
? // Fruits產品類
? Factorys.Fruits = function () {
? ? this.type = 'Fruits'
? }
? Factorys.Fruits.prototype = {
? ? getInfo: function () {
? ? ? return new Error('請先重寫子類')
? ? }
? }
? // Books產品類
? Factorys.Books = function () {
? ? this.type = 'Books'
? }
? Factorys.Books.prototype = {
? ? getInfo: function () {
? ? ? return new Error('請先重寫子類')
? ? }
? }
? // Peoples產品類
? Factorys.Peoples = function () {
? ? this.type = 'Peoples'
? }
? Factorys.Peoples.prototype = {
? ? getInfo: function () {
? ? ? return new Error('請先重寫子類getInfo方法')
? ? }
? }
工廠以及產品類創(chuàng)建完成了,那么我們還需要一些產品子類,產品子類是針對產品類的,它是繼承于某個產品類,擁有該產品類的所有屬性和方法。
? // 動物產品子類
? const Animals = function (name, price) {
? ? this.name = name
? ? this.price = price
? }
? // 工廠開始生產
? Factorys(Animals, 'Animals')
? // 生產后重新標價,即重寫價格方法,使可以訪問
? Animals.prototype.getInfo = function () {
? ? return this.price;
? }
? // 水果產品子類
? const Fruits = function (name, price) {
? ? this.name = name
? ? this.price = price
? }
? Factorys(Fruits, 'Fruits')
? Fruits.prototype.getInfo = function () {
? ? return this.price
? }
? // 人物產品子類
? const Peoples = function (name, price) {
? ? this.name = name
? ? this.price = price
? }
? Factorys(Peoples, 'Peoples')
? Peoples.prototype.getInfo = function () {
? ? return this.price
? }
? // 書籍產品子類
? const Books = function (name, price) {
? ? this.name = name
? ? this.price = price
? }
? Factorys(Books, 'Books')
? Books.prototype.getInfo = function () {
? ? return this.price
? }
? const dog = new Animals('小狗', 3000)
? console.log('價錢:' + dog.getInfo() + ',類型:' + dog.type)
? const tomato = new Fruits('西紅柿', 20)
? console.log('價錢:' + tomato.getInfo() + ',類型:' + tomato.type)
? const martian = new Peoples('火星人', 6989)
? console.log('價錢:' + martian.getInfo() + ',類型:' + martian.type)
? const book = new Books('js設計模式', 100)
? console.log('價錢:' + book.getInfo() + ',類型:' + book.type)
d.總結
簡單工廠模式是創(chuàng)建某一種類的實例,是針對一個類的實例創(chuàng)建,而工廠方法模式是多個類創(chuàng)建實例。
簡單工廠模式與工廠方法模式只是創(chuàng)建實例,而并不知道它是什么類,但是抽象工廠模式創(chuàng)建了類之后,類的實例會知道自己屬于什么類,這也是抽象工廠模式的一大優(yōu)點。
4.原型模式
定義:用原型實例指向創(chuàng)建對象的類,使用于創(chuàng)建新的對象的類共享原型對象的屬性以及方法。
在原型模式下,當我們想要創(chuàng)建一個對象時,會先找到一個對象作為原型,然后通過克隆原型的方式來創(chuàng)建出一個與原型一樣(共享一套數(shù)據/方法)的對象。在 JavaScript 里,Object.create方法就是原型模式的天然實現(xiàn)——準確地說,只要我們還在借助Prototype來實現(xiàn)對象的創(chuàng)建和原型的繼承,那么我們就是在應用原型模式。
// 創(chuàng)建一個Dog構造函數(shù)
function Dog(name, age) {
? ? this.name = name
? ? this.age = age
}
Dog.prototype.eat = function () {
? ? console.log('肉骨頭真好吃')
}
Dog.prototype.play = function () {
? ? console.log('肉骨頭真好玩')
}
// 使用Dog構造函數(shù)創(chuàng)建dog實例
const dog = new Dog('旺財', 3)
console.log(111, Dog.prototype)
//111 Dog { eat: [Function], play: [Function] }
console.log(222, Dog.prototype.constructor === Dog)
//222 true
console.log(333, dog)
//333 Dog { name: '旺財', age: 3 }
console.log(444, dog.__proto__)
444 Dog { eat: [Function], play: [Function] }
可見原型編程范式的核心思想就是利用實例來描述對象,用實例作為定義對象和繼承的基礎。在 JavaScript 中,原型編程范式的體現(xiàn)就是基于原型鏈的繼承。這其中,對原型、原型鏈的理解是關鍵。
淺析一下原型和原型鏈:
(a) 原型
在 JavaScript 中,每個構造函數(shù)都擁有一個prototype屬性,它指向構造函數(shù)的原型對象,這個原型對象中有一個 construtor 屬性指回構造函數(shù);每個實例都有一個__proto__屬性,當我們使用構造函數(shù)去創(chuàng)建實例時,實例的__proto__屬性就會指向構造函數(shù)的原型對象。
比如如上這段代碼里的幾個實體之間就存在著這樣的關系:

b) 原型鏈
現(xiàn)在我在上面那段代碼的基礎上,進行兩個方法調用:
// 輸出"肉骨頭真好吃"
dog.eat()
// 輸出"[object Object]"
dog.toString()
明明沒有在 dog 實例里手動定義 eat 方法和 toString 方法,它們還是被成功地調用了。這是因為當我試圖訪問一個 JavaScript 實例的屬性/方法時,它首先搜索這個實例本身;當發(fā)現(xiàn)實例沒有定義對應的屬性/方法時,它會轉而去搜索實例的原型對象;如果原型對象中也搜索不到,它就去搜索原型對象的原型對象,這個搜索的軌跡,就叫做原型鏈。
以我們的 eat 方法和 toString 方法的調用過程為例,它的搜索過程就是這樣子的:

上面這些彼此相連的prototype,就組成了一個原型鏈。 注: 幾乎所有 JavaScript 中的對象都是位于原型鏈頂端的 Object 的實例,除了Object.prototype(當然,如果我們手動用Object.create(null)創(chuàng)建一個沒有任何原型的對象,那它也不是 Object 的實例)。
5.觀察者模式
定義了一種一對多的關系, 所有觀察對象同時監(jiān)聽某一主題對象,當主題對象狀態(tài)發(fā)生變化時就會通知所有觀察者對象,使得他們能夠自動更新自己.
觀察者模式是非常經典的設計模式, vue/react等各種框架,類庫中均有使用。
class Subject {
? constructor() {
? ? this.subs = {}
? }
? addSub(key, fn) {
? ? const subArr = this.subs[key]
? ? if (!subArr) {
? ? ? this.subs[key] = []
? ? }
? ? this.subs[key].push(fn)
? }
? trigger(key, message) {
? ? const subArr = this.subs[key]
? ? if (!subArr || subArr.length === 0) {
? ? ? return false
? ? }
? ? for(let i = 0, len = subArr.length; i < len; i++) {
? ? ? const fn = subArr[i]
? ? ? fn(message)
? ? }
? }
? unSub(key, fn) {
? ? const subArr = this.subs[key]
? ? if (!subArr) {
? ? ? return false
? ? }
? ? if (!fn) {
? ? ? this.subs[key] = []
? ? } else {
? ? ? for (let i = 0, len = subArr.length; i < len; i++) {
? ? ? ? const _fn = subArr[i]
? ? ? ? if (_fn === fn) {
? ? ? ? ? subArr.splice(i, 1)
? ? ? ? }
? ? ? }
? ? }
? }
}
// 測試
// 訂閱
let subA = new Subject()
let A = (message) => {
? console.log('訂閱者收到信息: ' + message)
}
subA.addSub('A', A)
// 發(fā)布
subA.trigger('A', '我是xxx')? // A收到信息: --> 我是xxx
a) 觀察者模式在redux中的應用:
redux推崇單一數(shù)據源,即一個web應用狀態(tài)只有一個數(shù)據來源。以一種比較清晰的方式去維護應用的狀態(tài)。這其實也和react的單向數(shù)據流吻合。
關鍵概念:
1. store提供數(shù)據的get鉤子(store.getState),不直接提供數(shù)據的set,所以必須通過dispatch(action)來set數(shù)據。
2. 利用觀察者模式(sub/ pub)連接model和view的中間對象。
model層通過調用store.subscribe訂閱視圖更新事件(setstate),該事件會在數(shù)據改變之后被調用。對應sub。
view層通過調用store.dispatch方法進行發(fā)布,觸發(fā)reducer改變model。對應pub。
簡化如下:
function createStore(reducers) {
? ? const subList = []; // 注冊事件列表
? ? let? currentState = initState = reducers(init); // 初始化initState
? ? function subscribe(fun) { // sub 訂閱
? ? ? ? subList.push(fun);
? ? }
? ? function dispatch(action) { // pub 發(fā)布
? ? ? ? currentState = reducers(initState, action);
? ? ? ? subList.forEach(fn => fn()); // 將事先訂閱注冊的事件遍歷執(zhí)行。
? ? }
? ? function getState() { // state的get鉤子,返回目前的state
? ? ? ? return currentState;
? ? }
? ? const store = { dispatch, suscribe, getState };
? ? return store;
}
6.策略模式
定義 : 要實現(xiàn)某一個功能,有多種方案可以選擇。我們定義策略,把它們一個個封裝起來,并且使它們可以相互轉換。
作用:1.避免大量冗余的代碼判斷,比如if else等。2.降低了使用成本以及不同算法之間的耦合
a) 未使用策略模式
function checkInfo(data) {
? if (data.role !== 'admin') {
? ? return '不是管理員用戶';
? }
? if (data.age < 18) {
? ? return '您還未成年!';
? }
? if (data.time !== '10') {
? ? return '不是指定時間!';
? }
? if (data.type !== 'eat melons') {
? ? return '不是吃瓜群眾';
? }
}
這段代碼的問題 :
checkInfo 函數(shù)會爆炸
策略項無法復用
違反開閉原則
b) 使用策略模式
// 維護權限列表
const timeList = ['10', '11'];
// 策略
var strategies = {
? checkRole: function(value) {
? ? return value === 'admin';
? },
? checkAge: function(value) {
? ? return value >= 18;
? },
? checkTime: function(value) {
? ? return jobList.indexOf(value) > 1;
? },
? checkEatType: function(value) {
? ? return value === 'eat melons';
? }
};
驗證:
// 校驗規(guī)則
var Validator = function() {
? this.cache = [];
? // 添加策略事件
? this.add = function(value, method) {
? ? this.cache.push(function() {
? ? ? return strategies[method](value);
? ? });
? };
? // 檢查
? this.check = function() {
? ? for (let i = 0; i < this.cache.length; i++) {
? ? ? let valiFn = this.cache[i];
? ? ? var data = valiFn(); // 開始檢查
? ? ? if (!data) {
? ? ? ? return false;
? ? ? }
? ? }
? ? return true;
? };
};
7.裝飾器模式
定義:是為了給一個函數(shù)賦能,增強它的某種能力,它能動態(tài)的添加對象的行為。
裝飾器模式在前端中的應用:
裝飾器模式在前端中的應用比較常見的就是hoc 高階組件模式了,這也是 React 比較常用的一種包裝強化模式之一 。高階函數(shù)是接收一個函數(shù),返回一個函數(shù)。而所謂高階組件,就是接收一個組件,返回一個組件,返回的組件是根據需要對原始組件的強化。
hoc 的實現(xiàn)有兩種方式,屬性代理 和 反向繼承。
a) 屬性代理
所謂正向屬性代理,就是用組件包裹一層代理組件,在代理組件上,我們可以做一些,對源組件的代理操作。 我們可以理解為父子組件關系,父組件對子組件進行一系列強化操作。而 hoc 本身就是返回強化子組件的父組件。
@HOC
class Index extends React.Component{
? render(){
? ? return <div> hello,world? </div>
? }
}
//HOC 本質是一個函數(shù),傳入 Component ,也就是原始組件本身
function Hoc (Component){
? ? //返回一個新的包裝的組件 Wrap ,我們可以在 Wrap 中做一些強化原始組件的事。
? ? return class Wrap extends React.Component{
? ? ? ? //---------
? ? ? ? // 強化操作
? ? ? ? //---------
? ? ? ? render(){
? ? ? ? ? ? //Wrap 中掛載原始組件本身 Component。
? ? ? ? ? ? return <Component { ...this.props } />
? ? ? ? }
? ? }
}
可以看出屬性代理特點:
1.同樣適用于 class 聲明組件,和 function 聲明的組件。
2.可以嵌套使用,多個 hoc 是可以嵌套使用的,而且一般不會限制包裝HOC的先后順序。
3.可以完全隔離業(yè)務組件的渲染,相比反向繼承,屬性代理這種模式??梢酝耆刂茦I(yè)務組件渲染與否,可以避免反向繼承帶來一些副作用,比如生命周期的執(zhí)行。
4.正常屬性代理可以和業(yè)務組件低耦合,零耦合,對于條件渲染和 props 屬性增強,只負責控制子組件渲染和傳遞額外的 props 就可以,所以無須知道,業(yè)務組件做了些什么。所以正向屬性代理,更適合做一些開源項目的 hoc ,目前開源的 HOC 基本都是通過這個模式實現(xiàn)的。
b) 反向繼承
反向繼承和屬性代理有一定的區(qū)別,在于包裝后的組件繼承了業(yè)務組件本身,所以我們我無須再去實例化我們的業(yè)務組件。 當前高階組件就是繼承后,加強型的業(yè)務組件。這種方式類似于組件的強化,所以你必須要知道當前繼承的組件的狀態(tài),內部做了些什么?
@HOC
class Index extends React.Component{
? render(){
? ? return <div> hello,world? </div>
? }
}
function HOC(Component){
? ? return class wrapComponent extends Component{ /* 直接繼承需要包裝的組件 */
? ? }
}
export default Index
可以看出反向繼承特點:
1.方便獲取組件內部狀態(tài),比如state,props ,生命周期,綁定的事件函數(shù)等
2.es6繼承可以良好繼承靜態(tài)屬性。我們無須對靜態(tài)屬性和方法進行額外的處理。
8.代理模式
定義:一個對象通過某種代理方式來控制對另一個對象的訪問.
作用
遠程代理(一個對象對另一個對象的局部代理)
虛擬代理(對于需要創(chuàng)建開銷很大的對象如渲染網頁大圖時可以先用縮略圖代替真圖)
安全代理(保護真實對象的訪問權限)
緩存代理(一些開銷比較大的運算提供暫時的存儲,下次運算時,如果傳遞進來的參數(shù)跟之前相同,則可以直接返回前面存儲的運算結果)
a) 代理模式在前端的應用-計算緩存器:
function sum(a, b){
? return a + b
}
// 緩存代理
let proxySum = (function(){
? let cache = {}
? return function(){
? ? ? let args = Array.prototype.join.call(arguments, ',');
? ? ? if(args in cache){
? ? ? ? ? return cache[args];
? ? ? }
? ? ? cache[args] = sum.apply(this, arguments)
? ? ? return cache[args]
? }
})()
9.外觀模式
定義:為子系統(tǒng)中的一組接口提供一個一致的表現(xiàn),使得子系統(tǒng)更容易使用而不需要關注內部復雜而繁瑣的細節(jié)。
作用:對接口和調用者進行了一定的解耦,創(chuàng)造經典的三層結構MVC,在開發(fā)階段減少不同子系統(tǒng)之間的依賴和耦合,方便各個子系統(tǒng)的迭代和擴展。
a) 外觀模式在前端的應用:
我們可以使用外觀模式來設計兼容不同瀏覽器的事件綁定的方法以及其他需要統(tǒng)一實現(xiàn)接口的方法或者抽象類.
function on(type, fn){
? // 對于支持dom2級事件處理程序
? if(document.addEventListener){
? ? ? dom.addEventListener(type,fn,false);
? }else if(dom.attachEvent){
? // 對于IE9一下的ie瀏覽器
? ? ? dom.attachEvent('on'+type,fn);
? }else {
? ? ? dom['on'+ type] = fn;
? }
}
10.迭代器模式
定義:提供一種方法順序訪問一個聚合對象中的各個元素,使用者并不需要關心該方法的內部表示.
作用:為遍歷不同集合提供統(tǒng)一接口,保護原集合但又提供外部訪問內部元素的方式
a) 應用:
封裝的一個遍歷函數(shù)來讓大家更加理解迭代器模式的使用,該方法不僅可以遍歷數(shù)組和字符串,還能遍歷對象。
function _each(el, fn = (v, k, el) => {}) {
? // 判斷數(shù)據類型
? function checkType(target){
? ? return Object.prototype.toString.call(target).slice(8,-1)
? }
? // 數(shù)組或者字符串
? if(['Array', 'String'].indexOf(checkType(el)) > -1) {
? ? for(let i=0, len = el.length; i< len; i++) {
? ? ? fn(el[i], i, el)
? ? }
? }else if(checkType(el) === 'Object') {
? ? for(let key in el) {
? ? ? fn(el[key], key, el)
? ? }
? }
}
參考:
1.JavaScript設計模式
2.vuex部分源碼
3.redux部分源碼