javascript 設計模式之單例模式javascript
javascript設計模式之代理模式github
裝飾者(decorator)模式,又名裝飾器模式,可以在不改變對象自身的基礎上,在程序運行期間給對像動態的添加職責。與繼承相比,裝飾者是一種更輕便靈活的作法。
就比如手機扣,有了手機扣會方便觀看視頻,但對於手機原先的全部功能,像是拍照仍然能夠直接使用。手機扣只是起到錦上添花做用,並不必定要有。
初始需求:
畫個圓
實現:
class Circle {
draw() {
console.info('畫圓')
}
}
let c = new Circle()
c.draw()
複製代碼
更改需求:
畫個紅色的圓
實現:
或許這時你二話不說,就是找到 Circle 的 draw 方法改爲以下:
class Circle {
draw() {
console.info('畫圓')
this.setRed()
}
setRed() {
console.info('設置紅色邊框')
}
}
let c = new Circle()
c.draw()
複製代碼
若是需求不改,這種實現方式倒也沒問題。 但若是哪天經理說我要實現個綠色邊框,是否是又得改爲:
class Circle {
draw() {
console.info('畫圓')
this.setGreen()
}
setRed() {
console.info('設置紅色邊框')
}
setGreen() {
console.info('設置綠色邊框')
}
}
let c = new Circle()
c.draw()
複製代碼
這種方式存在兩個問題:
爲了新的業務需求不影響到原有功能,須要將舊邏輯與新邏輯分離:
class Circle {
draw() {
console.info('畫圓')
}
}
class Decorator {
constructor(circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setRedBorder()
}
setRedBorder() {
console.info('設置紅色邊框')
}
}
let c = new Circle()
let d = new Decorator(c)
d.draw()
複製代碼
此時若是要畫別的顏色圓形,只要修改 Decorator 類便可。
如此一來,就實現了"只添加,不修改原有的類"的裝飾者模式,使用 Decorator 的邏輯裝飾了舊的圓形邏輯。 固然你仍然能夠單純只建立個不帶顏色的圓形,用 c.draw() 便可
AOP(Aspect Oriented Programming)面向切面編程。把一些與核心業務邏輯無關的功能抽離出來,再經過「動態織入」方式摻入業務邏輯模塊
與業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等等。
首先咱們要實現兩個函數:
一個用來前置裝飾,一個用來後置裝飾:
Function.prototype.before = function(beforeFunc){
var that = this;
return function(){
beforeFunc.apply(this, arguments);
return that.apply(this, arguments);
}
}
Function.prototype.after = function(afterFunc){
var that = this;
return function(){
var ret = that.apply(this, arguments);
afterFunc.apply(this, arguments);
return ret;
}
}
複製代碼
之前置裝飾 before 爲例,調用 before 時,傳入一個函數,這個函數即爲新添加的函數,它裝載了新添加的功能代碼。
接着把當前的 this 保存起來,而後返回一個「代理」函數。這樣在原函數調用前,先執行擴展功能的函數,並且他們共用同一個參數列表。
後置裝飾與前置裝飾基本相似,只是執行順序不一樣
驗證:
var foobar = function (x, y, z) {
console.log(x, y, z)
}
var foo = function (x, y, z) {
console.log(x / 10, y / 10, z / 10)
}
var bar = function (x, y, z) {
console.log(x * 10, y * 10, z * 10)
}
foobar = foobar.before(foo).after(bar)
foobar(1, 2, 3)
複製代碼
輸出:
0.1 0.2 0.3
1 2 3
10 20 30
複製代碼
以上設置 before 與 after 的方法污染了原型,能夠改爲:
var before = function (fn, beforeFunc) {
return function () {
beforeFunc.apply(this, arguments)
return fn.apply(this, arguments)
}
}
var after = function (fn, afterFunc) {
return function () {
var ret = fn.apply(this, arguments)
afterFunc.apply(this, arguments)
return ret
}
}
var a = before(
function () {
alert(3)
},
function () {
alert(2)
}
)
a = before(a, function () {
alert(1)
})
a()
複製代碼
輸出:
1
2
3
複製代碼
{
"presets":["es2015","latest"],
"plugins": ["transform-decorators-legacy"] // 加上插件支持 ES7 的裝飾語法
}
複製代碼
// 裝飾器函數,它的第一個參數是目標類
function classDecorator(target) {
target.hasAdd = true
// return target // 無關緊要, 默認就是返回 this 的
}
// 將裝飾器"安裝"到 Button 類上
@classDecorator
class Button {}
// 驗證裝飾器是否生效
alert('Button 是否被裝飾了:' + Button.hasAdd)
複製代碼
等價於
function classDecorator(target) {
target.hasAdd = true
return target // 此時必定要用, 由於這時是做爲函數使用,而非構造函數
}
class Button {}
Button = classDecorator(Button)
// 驗證裝飾器是否生效
alert('Button 是否被裝飾了:' + Button.hasAdd)
複製代碼
說明裝飾器的原理:
@decorator
class A{}
//等同於
A = decorator(A) || A
複製代碼
代表 ES7 中的裝飾器也是個語法糖
// 裝飾器要接收參數時,就要返回個函數,該函數的第一個參數是目標類
function classDecorator(name) {
return function (target) {
target.btnName = name
}
}
// 將裝飾器"安裝"到 Button 類上
@classDecorator('登陸')
class Button {}
// 驗證裝飾器是否生效
alert('按鈕名稱:' + Button.btnName)
複製代碼
等同於
// 裝飾器要接收參數時,就要返回個函數,該函數的第一個參數是目標類
function classDecorator(name) {
return function (target) {
target.btnName = name
return target
}
}
// 將裝飾器"安裝"到 Button 類上
class Button {}
Button = classDecorator('登陸')(Button)
// 驗證裝飾器是否生效
alert('按鈕名稱:' + Button.btnName)
複製代碼
function mixin(...list) {
console.info(...list, 'list')
// ...list 是個對象, key 爲 "foo",值爲 function() { alert('foo')}
return function (target) {
Object.assign(target.prototype, ...list)
console.dir(target, 'target')
}
}
const Foo = {
foo() {
alert('foo')
}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
複製代碼
能夠看到是往 Button 類的原型上加上了 foo 函數,那可能有人會問了,爲何不在類上直接加呢,即
function mixin(...list) {
console.info(...list, 'list') // ...list 是個對象, key 爲 "foo",值爲 function() { alert('foo')}
return function (target) {
Object.assign(target, ...list)
console.dir(target, 'target')
}
}
const Foo = {
foo() {
alert('foo')
}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
複製代碼
此時會報Uncaught TypeError: d.foo is not a function
錯誤
這是因爲實例是在代碼運行時動態生成的,而裝飾器函數則是在編譯階段就執行了,因此裝飾器 mixin 函數執行時, Button 實例還不存在。
爲了確保實例生成後能夠順利訪問到被裝飾好的方法(foo),裝飾器只能去修飾 Button 類的原型對象。
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class Person {
constructor() {
this.first = 'A'
this.last = 'B'
}
@readonly
name() {
return `${this.first} ${this.last}`
}
}
let p = new Person()
console.info(p.name())
// p.name = function () {
// console.info(100)
// } // 修改會報錯
複製代碼
function log(target, name, descriptor) {
let oldValue = descriptor.value
descriptor.value = function () {
console.log(`calling ${name} width`, arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
class Math {
@log
add(a, b) {
return a + b
}
}
let math = new Math()
const result = math.add(4, 6)
alert(result)
複製代碼
以上 readonly 與 log 都是經過修改 descriptor 實現的,那該裝飾方法的函數的三個參數都分別表示什麼呢?
Object.defineProperty(obj, prop, descriptor)
裏的 descriptor目前有個開源的第三方庫 core-decorators,提供了不少好用的裝飾方法。
// import { readonly } from 'core-decorators'
// class Person {
// @readonly
// name() {
// return 'zhang'
// }
// }
// let p = new Person()
// alert(p.name())
// // p.name = function () { /*...*/ } // 此處會報錯
import { deprecate } from 'core-decorators'
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', {
url: 'http://knowyourmeme.com/memes/facepalm'
})
facepalmHarder() {}
}
let person = new Person()
person.facepalm()
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard()
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder()
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
複製代碼
你的點贊是對我最大的確定,若是以爲有幫助,請留下你的讚揚,謝謝!!!