結構型模式主要用於處理類和對象的組合,對應思惟導圖:javascript
Facade Pattern
對接口二次封裝隱藏其複雜性,並簡化其使用。 外觀模式包含以下角色:css
Facade
: 外觀角色SubSystem
: 子系統角色當咱們將系統分紅多個子系統時,咱們會下降代碼複雜性。編程時的最佳實踐是最小化子系統之間的通訊和依賴關係。實現這一目標的一個好方法是引入一個facade
對象,爲子系統提供單一且統一的接口。 html
要保證處理事件的代碼在大多數瀏覽器下一致運行,須要關注冒泡階段。前端
在作跨瀏覽器網站時,你已經不經意間使用了外觀模式:vue
var addMyEvent = function( el,ev,fn ){
if( el.addEventListener ){//存在DOM2級方法,則使用並傳入事件類型、事件處理程序函數和第3個參數false(表示冒泡階段)
el.addEventListener( ev,fn, false );
}else if(el.attachEvent){ // 爲兼容IE8及更早瀏覽器,注意事件類型必須加上"on"前綴
el.attachEvent( "on" + ev, fn );
}else{
el["on" + ev] = fn;//其餘方法都無效,默認採用DOM0級方法,使用方括號語法將屬性名指定爲事件處理程序
}
};
複製代碼
jQuery $(document).ready(..)
咱們都熟悉$(document).ready(..)
。在源碼中,這其實是一個被調用的方法提供的bindReady()
:java
加載事件共用兩種方法
:window.onload()
和$(document).ready()
node
bindReady: function() {
...
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
複製代碼
Facade
外觀模式大量應用於 jQuery
庫以讓其更容易被使用。譬如咱們使用 jQuery
的$(el).css()
或 $(el).animate()
等方法 。react
使咱們沒必要手動在jQuery
內核中調用不少內部方法以便實現某些行爲,也同時避免了手動與 DOM API
交互。git
相似的還有
D3.js
github
Adapter Pattern
JS
: 可額外適配兩個及以上代碼庫、先後端數據等。使用時機 一般使用適配器的狀況:
jQuery.fn.css()
規範化顯示// Cross browser opacity:
// opacity: 0.9; Chrome 4+, FF2+, Saf3.1+, Opera 9+, IE9, iOS 3.2+, Android 2.1+
// filter: alpha(opacity=90); IE6-IE8
// Setting opacity
$( ".container" ).css( { opacity: .5 } );
// Getting opacity
var currentOpacity = $( ".container" ).css('opacity');
複製代碼
內部實現爲:
get: function( elem, computed ) {
return ropacity.test( (
computed && elem.currentStyle ?
elem.currentStyle.filter : elem.style.filter) || "" ) ?
( parseFloat( RegExp.$1 ) / 100 ) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style,
currentStyle = elem.currentStyle,
opacity = jQuery.isNumeric( value ) ?
"alpha(opacity=" + value * 100 + ")" : "",
filter = currentStyle && currentStyle.filter || style.filter || "";
style.zoom = 1;
// 若是將不透明度設置爲1,則移除其餘過濾器
//exist - attempt to remove filter attribute #6652
if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
style.removeAttribute( "filter" );
if ( currentStyle && !currentStyle.filter ) {
return;
}
}
// otherwise, set new filter values
style.filter = ralpha.test( filter ) ?
filter.replace( ralpha, opacity ) :
filter + " " + opacity;
}
};
複製代碼
Vue
中的computed
yck - 《前端面試之道》
在 Vue
中,咱們其實常用到適配器模式。
好比父組件傳遞給子組件一個時間戳屬性,組件內部須要將時間戳轉爲正常的日期顯示,通常會使用 computed
來作轉換這件事情,這個過程就使用到了適配器模式。
Proxy Pattern
爲其餘對象提供一種代理以便控制對這個對象的訪問。
能夠詳細控制訪問某個類(對象)的方法, 在調用這個方法前做的前置處理(統一的流程代碼放到代理中處理)。調用這個方法後作後置處理。例如:明星的經紀人,租房的中介等等都是代理
使用代理模式的意義是什麼?
「單一職責原則」:面向對象設計中鼓勵將不一樣的職責分佈到細粒度的對象中,Proxy 在原對象的基礎上進行了功能的衍生而又不影響原對象,符合鬆耦合高內聚的設計理念
遵循「開放-封閉原則」:代理能夠隨時從程序中去掉,而不用對其餘部分的代碼進行修改,在實際場景中,隨着版本的迭代可能會有多種緣由再也不須要代理,那麼就能夠容易的將代理對象換成原對象的調用。
特色:
分類:
Remote Proxy
):爲一個位於不一樣的地址空間的對象提供一個本地的代理對象Virtual Proxy
):若是須要建立一個資源消耗較大的對象,先建立一個消耗相對較小的對象來表示,真實對象只在須要時纔會被真正建立。Protect Proxy
):控制對一個對象的訪問,能夠給不一樣的用戶提供不一樣級別的使用權限。Cache Proxy
):爲某一個目標操做的結果提供臨時的存儲空間,以便多個客戶端能夠共享這些結果。Smart Reference Proxy
):當一個對象被引用時,提供一些額外的操做,例如將對象被調用的次數記錄下來等。缺點::
因爲在客戶端和真實主題之間增長了代理對象,所以有些類型的代理模式可能會形成請求的處理速度變慢,例如保護代理。
實現代理模式須要額外的工做,並且有些代理模式的實現過程較爲複雜,例如遠程代理。
前端用得最多的是 虛擬代理、保護代理、緩衝代理
ES6
中的Proxy
ES6
所提供Proxy
構造函數可以讓咱們輕鬆的使用代理模式:
// target: 表示所要代理的對象,handler: 用來設置對所代理的對象的行爲。
let proxy = new Proxy(target, handler);
複製代碼
目前通常的網站都會有圖片預加載機制,也就是在真正的圖片在被加載完成以前用一張菊花圖(轉圈的gif圖片)表示正在加載圖片。
const img = new Image();
img.src = '/some/big/size/image.jpg';
document.body.appendChild(img);
複製代碼
建立虛擬圖片節點virtualImg
並構造建立代理函數:
// 圖片懶加載: 虛擬代理
const createImgProxy = (img, loadingImg, realImg) => {
let hasLoaded = false;
const virtualImg = new Image();
virtualImg.src = realImg;
virtualImg.onload = () => {
Reflect.set(img, 'src', realImg);
hasLoaded = true;
}
return new Proxy(img, {
get(obj, prop) {
if (prop === 'src' && !hasLoaded) {
return loadingImg;
}
return obj[prop];
}
});
複製代碼
最後是將原始的圖片節點替換爲代理圖片進行調用:
const img = new Image();
const imgProxy = createImgProxy(img, '/loading.gif', '/some/big/size/img.jpg');
document.body.appendChild(imgProxy);
複製代碼
如,先後端分離,向後端請求分頁的數據的時候,每次頁碼改變時都須要從新請求後端數據,咱們能夠將頁面和對應的結果進行緩存,當請求同一頁的時候,就再也不請求後端的接口而是從緩存中去取數據。
const getFib = (number) => {
if (number <= 2) {
return 1;
} else {
return getFib(number - 1) + getFib(number - 2);
}
}
const getCacheProxy = (fn, cache = new Map()) => {
return new Proxy(fn, {
apply(target, context, args) {
const argsString = args.join(' ');
if (cache.has(argsString)) {
// 若是有緩存,直接返回緩存數據 console.log(`輸出${args}的緩存結果: ${cache.get(argsString)}`);
return cache.get(argsString);
}
const result = fn(...args);
cache.set(argsString, result);
return result;
}
})
}
const getFibProxy = getCacheProxy(getFib);
getFibProxy(40); // 102334155getFibProxy(40); // 輸出40的緩存結果: 102334155
複製代碼
事件代理就用到了代理模式。
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
複製代碼
經過給父節點綁定一個事件,讓父節點做爲代理去拿到真實點擊的節點。
Decorator Pattern
裝飾器相似於高階函數的概念。裝飾器將基本形式做爲參數,並在其上添加處理並將其返回。 優勢:
問題:
在JavaScript
中:
核心就是緩存上一次的函數
舉一個簡單的例子:
var xiaoming = function () {
this.run = function () {
return '跑步'
},
this.eat = function () {
return: '吃飯'
}
}
// 小明能夠跑步,也能夠吃飯
// 下面是一個裝飾類,給小明進行裝飾
var decor = function (xiaoming) {
this.run = function () {
return xiaoming.run + '很快'
}
this.eat = function () {
return xiaoming.eat + '不少'
}
}
複製代碼
經過一個裝飾類,實現了對小明類的裝飾。
TypeScript
函數修飾符: @
或者用句大白話描述:@: "下面的被我包圍了。"
舉個栗子,下面的一段代碼,裏面兩個函數,沒有被調用,也會有輸出結果:
test(f){
console.log("before ...");
f()
console.log("after ...");
}
@test
func(){
console.log("func was called");
}
複製代碼
直接運行,輸出結果:
before ...
func was called
after ...
複製代碼
React
中的裝飾器模式在React
中,裝飾器模式隨處可見:
import React, { Component } from 'react';
import {connect} from 'react-redux';
class App extends Component {
render() {
//...
}
}
// const mapStateToProps
// const actionCreators
export default connect(mapStateToProps,actionCreators)(App);
複製代碼
Ant Design
中建立表單的最後一步其實也算裝飾器模式
class CustomizedForm extends React.Component {}
CustomizedForm = Form.create({})(CustomizedForm);
複製代碼
Bridge Pattern
Abstraction
(抽象類)RefinedAbstraction
(擴充抽象類)Implementor
(實現類接口)ConcreteImplementor
(具體實現類)應用程序寫入定義的數據庫API,例如ODBC
,但在此API以後,會發現每一個驅動程序的實現對於每一個數據庫供應商(SQL Server,MySQL,Oracle
等)都是徹底不一樣的。
JavaScript
中不多見。在大型網站中,不一樣模塊可能會有不一樣主題,也有分白天/黑夜 或 用戶自主選擇的主題。
這時爲每一個主題建立每一個頁面的多個副本明顯不合理,而橋接模式是更好的選擇:
不一樣模塊:
class About{
constructor(theme) {
this.theme = theme
}
getContent() {
return "About page in " + this.theme.getColor()
}
}
class Careers{
constructor(theme) {
this.theme = theme
}
getContent() {
return "Careers page in " + this.theme.getColor()
}
}
複製代碼
以及不一樣主題:
class DarkTheme{
getColor() {
return 'Dark Black'
}
}
class LightTheme{
getColor() {
return 'Off white'
}
}
class AquaTheme{
getColor() {
return 'Light blue'
}
}
複製代碼
生成主題:
const darkTheme = new DarkTheme()
const about = new About(darkTheme)
const careers = new Careers(darkTheme)
console.log(about.getContent() )// "About page in Dark Black"
console.log(careers.getContent() )// "Careers page in Dark Black"
複製代碼
Composite Pattern
該模式包含如下角色:
Component
- 聲明組合中對象的接口並實現默認行爲(基於Composite
)Leaf
- 表示合成中的原始對象Composite
- 在Component
接口中實現與子相關的操做,並存儲Leaf(primitive)
對象。計算機文件結構是組合模式的一個實例。
若是你刪除某個文件夾,也將刪除該文件夾的全部內容,是嗎? 這實質上就是組合模式運行原理。 你你能夠調用結構樹上較高層次的組合對象,消息將沿這一層次結構向下傳輸。
DOM
HTML
文檔的DOM
結構就是天生的樹形結構,最基本的元素醉成DOM樹,最終造成DOM
文檔,很是適用適用組合模式。
咱們經常使用的jQuery
類庫,其中組合模式的應用更是頻繁,例如常常有下列代碼實現:
$(".test").addClass("noTest").removeClass("test");
複製代碼
不論$(「.test」)
是一個元素,仍是多個元素,最終都是經過統一的addClass
和removeClass
接口進行調用。
咱們簡單模擬一下addClass
的實現:
var addClass = function (eles, className) {
if (eles instanceof NodeList) {
for (var i = 0, length = eles.length; i < length; i++) {
eles[i].nodeType === 1 && (eles[i].className += (' ' + className + ' '));
}
}
else if (eles instanceof Node) {
eles.nodeType === 1 && (eles.className += (' ' + className + ' '));
}
else {
throw "eles is not a html node";
}
}
addClass(document.getElementById("div3"), "test");
addClass(document.querySelectorAll(".div"), "test");
複製代碼
對於NodeList
或者是Node
來講,客戶端調用都是一樣的使用了addClass
這個接口,這個就是組合模式的最基本的思想,使部分和總體的使用具備一致性。
Flyweight Pattern
享元(flyweight
)模式是一種用於性能優化的模式,「fly
」在這裏是蒼蠅的意思,意爲蠅量級。
享元模式的核心是運用共享技術來有效支持大量細粒度的對象。
若是系統中由於建立了大量相似的對象而致使內存佔用太高,享元模式就很是有用了。在JavaScript
中,瀏覽器特別是移動端的瀏覽器分配的內存並不算多,如何節省內存就成了一件很是有意義的事情。
享元模式有如下角色:
在下面的例子中,咱們建立了一個「Book」類來處理有關特定書籍,而後建立一個「BookFactory
」類來控制如何建立這些Book對象。
爲了得到更好的內存性能,若是同一對象被實例化兩次,則會重用這些對象。
class Book {
constructor(title, isbn, author, ratings) {
this.title = title;
this.isbn = isbn;
this.author = author;
this.ratings = ratings;
}
getAverageReview() {
let averageReview = (this.ratings.reduce((a,b) => a+b)) / this.ratings.length
return averageReview;
}
}
class BookFactory {
constructor() {
this._books = [];
}
createBook(title, isbn, author, ratings) {
let book = this.getBookBy(isbn);
if (book) { //重用對象
return book;
} else {
const newBook = new Book(title, isbn, author, ratings);
this._books.push(newBook);
return newBook;
}
}
getBookBy(attr) {
return this._books.find(book => book.attr === attr);
}
}
複製代碼
打開谷歌在線表格,提取打印其節點元素。
能夠看到就算是滾動至千行,它們都只是共用兩個視圖。
用的就是享元模式,來防止無限滾動形成卡頓。
如下是模擬實現:
首先是HTML<section id="app">
<table id="table"></table>
<div class="controls">
<input type="range" name="scroll" id="scroll" value="0">
</div>
</section>
複製代碼
樣式:
#app {
position: relative;
padding: 30px 0 30px 10px;
#table {
padding: 20px;
border-radius: 10px;
min-width: 450px;
transition: background 0.5s;
background: rgba(73, 224, 56, 0.1);
&.low-range {
background: rgba(73, 224, 56, 0.47);
td {
border-bottom: 1px solid rgba(73, 224, 56, 0.9)
}
}
&.mid-range {
background: rgba(224, 196, 56, 0.47);
td {
border-bottom: 1px solid rgba(224, 196, 56, 0.9)
}
}
&.high-range {
background: rgba(224, 56, 56, 0.47);
td {
border-bottom: 1px solid rgba(224, 56, 56, 0.9)
}
}
&.ultra-high-range {
background: rgba(224, 56, 56, 0.9);
td {
border-bottom: 1px solid black
}
}
td {
border-bottom: 1px solid black;
padding: 10px;
font-weight: bold;
}
}
.controls {
padding-top: 20px;
#scroll {
width: 450px;
box-sizing: border-box;
}
}
}
複製代碼
邏輯實現,請配合註釋食用:
// 生成單元格實例
const makeRowCells = data => data.map(value => new Cell(value));
// 定義常量
const scrollViewport = 10; // 當前表格視圖大小
const tableSize = 2000; // 行數
let scrollIndex = 0; // 初始滾動索引
let DATA = []; // 初始數據集
while (DATA.length < scrollViewport) {
const unit = DATA.length * 10;
DATA.push('12345678'.split('').map(() => unit));
}
/**
* cell類 - 列
*/
class Cell {
constructor(content) {
this.content = content;
}
// 更新列
updateContent(content) {
this.content = content;
this.cell.innerText = content;
}
// 渲染列
render() {
const cell = document.createElement('td');
this.cell = cell;
cell.innerText = this.content;
return cell;
}
}
/**
* row類 - 行
*/
class Row {
constructor(cellItems) {
this.cellItems = cellItems;
}
// 更新行
updateRowData(newData) {
this.cellItems.forEach((item, idx) => {
item.updateContent(newData[idx]);
});
}
// 渲染行
render() {
const row = document.createElement('tr');
this.cellItems.forEach(item => row.appendChild(item.render()));
return row;
}
}
/**
* 表格類
*/
class Table {
constructor(selector) {
this.$table = document.querySelector(selector);
}
// 添加行
addRows(rows) {
this.rows = rows;
this.rows.forEach(row => this.$table.appendChild(row.render()));
}
// 更新table數據
updateTableData(data) {
this.rows.forEach((row, idx) => row.updateRowData(data[idx]));
}
}
// 實例化新表
const table = new Table('#table');
// 匹配滾動條的DOM
const scrollControl = document.querySelector('#scroll');
// 在table下添加單元格行
table.addRows(
DATA.map(dataItem => new Row(makeRowCells(dataItem))));
const onScrollChange = event => {
// 爲視圖準備新數據
DATA = DATA.map((item, idx) => item.map(cell => parseInt(event.target.value, 10)*10 + idx*10));
// 更新當前table的數據
table.updateTableData(DATA);
// 添加顏色區別樣式
scrollIndex = event.target.value;
if (event.target.value >= 0) {
table.$table.classList = 'low-range';
}
if (event.target.value > tableSize * 0.4) {
table.$table.classList = 'mid-range';
}
if (event.target.value > tableSize * 0.7) {
table.$table.classList = 'high-range';
}
if (event.target.value > tableSize * 0.9) {
table.$table.classList = 'ultra-high-range';
}
};
// 設置滾動條最小和最大範圍
scrollControl.setAttribute('min', 0);
scrollControl.setAttribute('max', tableSize);
// 添加滾動事件
scrollControl.addEventListener('input', onScrollChange);
// 初始化事件
const event = {target: {value: 0}};
onScrollChange(event);
複製代碼
至此,結構型設計模式已經講(水)完了,其中享元模式值得單獨拿出來寫一篇博客。
參考文章若是你以爲這篇內容對你挺有啓發,我想邀請你幫我三個小忙:
公衆號後臺回覆「設計模式」 領取做者精心自制的思惟導圖。