從裝飾模式到裝飾器

從裝飾模式到裝飾器

裝飾模式

裝飾模式的做用是:在不修改原有的接口的狀況下,讓類表現的更好。
什麼叫更好?html

爲何須要裝飾模式

天然是繼承有一些問題
繼承會致使超類和子類之間存在強耦合性,當超類改變時,子類也會隨之改變;
超類的內部細節對於子類是可見的,繼承經常被認爲破壞了封裝性;python

js中的裝飾模式

js中的裝飾模式實際上把一個對象A嵌入到另一個對象B中,也就是B對象包裝了A對象,造成了一個包裝鏈。請求隨着這一條包裝鏈一次傳遞到了包裝鏈上的全部對象,每個對象都有機會處理這一個請求。git

設想一下場景,打農藥,我選了一個后羿,而後開始一級我學了第一個技能。程序員

var HouYi = {
    skill : function() {
        console.log('增長了攻速')
    }
    HP :function(){
        return 1999
    }
}

HouYi.skill()

結果,自覺得本身很6了的后羿去了下路,遇到了狄仁傑,二馬一錯蹬幾個回合下來就后羿就涼了。后羿以爲不服,回家一趟,發奮努力,又學了第二個技能和第三個技能。github

class Hero {
    buy(){
        console.log('有了一雙鞋子')
        // console.log('有了一件復活甲')
        // console.log('有了一把飲血劍')
    }
}


var Houyi = new Hero()
Houyi.buy()

那麼問題來了,咱們看了一下,后羿仍是回家買了新的武器裝備,而不是寒酸的只有一雙鞋。可是,咱們看到,每次後羿買東西都要回家,也就是都要修改buy方法,那麼怎麼樣在不回家,不修改buy的方法的基礎上又把東西賣了呢,也就是,如何動態的買東西。 npm

從英雄聯盟過渡到王者榮耀。babel

也就是在代碼運行期間,咱們很難切入某個函數的執行環境。架構

class Hero {
    buyShoes(){
        console.log('有了一雙鞋子')
    }
}

var Houyi = new Hero()

var buyAtk = function() {
    console.log('有了一把飲血劍')
}

var buyDef = function () {
    console.log('有了一件復活甲')
}

var buyShoes= Houyi.buy

Houyi.buybuybuy = function() {
    buyShoes()
    buyAtk()
    buyDef()
}

Houyi.buybuybuy()

總結一下:裝飾模式是爲已有功能動態地添加更多功能的一種方式,把每一個要裝飾的功能放在單獨的函數裏,而後用該函數包裝所要裝飾的已有函數對象,所以,當須要執行特殊行爲的時候,調用代碼就能夠根據須要有選擇地、按順序地使用裝飾功能來包裝對象。優勢是把類(函數)的核心職責和裝飾功能區分開了。ide

裝飾模式的缺點:缺點的話咱們也能看到咱們定義了不少很類似的細小對象到咱們的命名空間中,這樣使咱們的架構變得十分的複雜,窮於管理。這就有可能致使,咱們不是使用它而是被它使用。函數

它是一個語法糖

說完了裝飾模式,咱們再看一下在ES7中最新引入的裝飾器(decorator)。這個概念實際上是從python裏引進的。

def my_decorator(fn):
  def inner(name):
    print 'Hello ' + fn(name)
  return inner

@my_decorator
def greet(name):
  return name

greet('Decorator!')
# Hello Decorator!

這種@decorator的寫法其實就是一個語法糖。

語法糖意指那些沒有給計算機語言添加新功能,而只是對人類來講更「甜蜜」的語法。語法糖每每給程序員提供了更實用的編碼方式,有益於更好的編碼風格,更易讀。不過其並無給語言添加什麼新東西。

因此,ES7中的 decorator 一樣借鑑了這個語法糖,不過依賴於ES5的Object.defineProperty 方法 。

defineProperty 所作的事情就是,爲一個對象增長新的屬性,或者更改對象某個已存在的屬性。調用方式是 Object.defineProperty(obj, prop, descriptor) ,這 3 個參數分別表明:

  1. obj: 目標對象
  2. prop: 屬性名
  3. descriptor: 針對該屬性的描述符

關於descriptor表明的意思是對象描述符,它自己一個對象,用於描述目標對象的一個屬性的屬性。

  • value:屬性的值,默認爲undefined
  • writable:可否修改屬性的值,默認值爲true
  • enumerable:可否經過for-in循環返回屬性。默認爲ture
  • configurable:可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,可否把屬性修改成訪問器屬性,默認爲true.

關於最後一個configurable還要多說一句,當咱們把對象的一個屬性設置爲false以後,咱們就不能再二次修改了,也就是不能置成true,會報錯。

const object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,
  writable: false,
  configurable:false
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42

裝飾類

decorator大量用於裝飾ES6中的類。具體的寫法是:

@decoratorFun
class Base{}

function decoratorFun(target){
    target.bool = true
}

Base.bool  //   true

上面的代碼,decorator就是一個裝飾器,它裝飾了Base這個類的行爲(爲其增長了一個靜態屬性)。而函數的target屬性,就是被裝飾的類自己。

因此,就是說,若是裝飾器是一個對類作修改裝飾的通常函數,那麼他的第一個參數就是被裝飾類的引用。可是,若是一個參數不夠用,怎麼辦?

若是不夠用,那麼直接能夠在外面再包一層。

function decorFun2(str){
    return function(target){
        target.name = str
    }
}

@decorFun2('zhangjingwei')
class Person {}
Person.name //   zhangjingwei

@decorFun2('susu')
class Person2 {}
Person2.name     // susu
class Foo1 {  
     classMethod() {  
        return 'hello';  
    }  
} 
class Foo2 {  
     static classMethod() {  
        return 'hello';  
    }  
} 

Foo1.classMethod()
var foo1 = new Foo1();  
foo1.classMethod() 

Foo2.classMethod()
var foo2 = new Foo2()
foo2.classMethod()
靜態屬性和實例屬性。靜態屬性是類自己的屬性,類生成的對象不能繼承該屬性,可是實例屬性是類生成的對象能夠繼承的。
ES6的規範說,類裏面沒有靜態屬性,只有靜態方法。

上面的都是給類添加靜態屬性,若是想要增長實例屬性,那麼能夠操做類的原型。

function decorFun3(name){
    return function(target) {
        target.prototype.name = name
    }
}

@decorFun3('lubanqihao')
class Nongyao {}

let ny1 = new Nongyao()
ny1.name    // lubanqihao

裝飾類的屬性

裝飾器不只能夠裝飾類,還能裝飾類的方法。

有的時候,咱們想把類中的某個屬性設置成只讀不支持修改,能夠來用裝飾器來實現。

function readonly(target,name,descriptr){
    descriptor.writable = false
    return descriptor
}

class Cat{
    @readonly
    say(){
        console.log('miaomiao)
    }
}

let kitty = new Cat()
kitty.say = function(){
    console.log('wangwang')
}

kitty.say()      //miaomiao

咱們看到經過裝飾器給類中的say方法,設置成了只讀。

參數有三個,target name descriptor。

第一個參數是類的原型對象,裝飾器的本意是裝飾類的實例,可是由於類的實例尚未生成,只能去修飾類的原型。第二個參數是要修飾的屬性名。第三個參數是該屬性的描述對象。

這個很眼熟是否是?

是否是有點相似於Object.defineProperty().其實裝飾器對類的屬性的做用,就是經過Object.defineProperty這個方法進行擴展和封裝。

實際上裝飾器的行爲原理是這樣的:

let descriptor = {
    value:function(){
        console.log('miaomiao)
    },
    enumerable:false,
    configable:true,
    writable:true
}

descriptor = readonly(Cat.protitype,'say',descriptor) || descriptor

Object.defineProperty(Cat.prototype, "say",descriptor);

因此,咱們看到,當裝飾器操做類自己的時候,操做的對象也是類自己,但裝飾器操做的是類中的方法的時候,操做的對象是是類的描述符descriptor,由於類中的屬性的所有信息都記錄在這個描述符裏面。

裝飾器不能修飾方法,是由於存在變量提高,不會生效

core-decorators.js第三方模塊

core-decorators.js是一個第三方模塊,提供了一些經常使用的裝飾器。

  • @autobind:這個裝飾器的做用是自動綁定this對象。

    import {autobind} from 'core-decorators';
    
    class Person {
        @autobind
        getPerson() {
            return this
        }
    }
    
    let p1 = new Person()
    let getPerson = p1.getPerson()
    
    getPerson() === p1     // true
  • @readonly 修飾方法使方法變得不可寫

    import {readonly} from 'core-decorators'
    
    class Dog {
        @readonly
        name = 'zhang'
    }
    
    let d1 = new Dog()
    d1.name = 'susu'    // 會報錯,由於屬性不可寫
  • override 會檢查子類的方法是否覆蓋了父類的同名方法。若是不正確,會報錯。
import {override} from 'core-decorators'

class Parent {
  speak(first, second) {}
}

class Child extends Parent {
  @override
  speak() {}
  // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}

// or

class Child extends Parent {
  @override
  speaks() {}
  // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
  //
  //   Did you mean "speak"?
}

Mixin混入

意思就是在一個對象中混入另一個對象的方法,是對象繼承的一種替代方式。

//一個混入的簡單實現:

class Foo {
    foo() {
        console.log('hahaha')
    }
}

class Bar {}

Object.assign(Bar.prototype,Foo)
let bar1 = new Bar()

bar1.foo()

上邊的例子是經過Object.assign方法進行的。Bar的實例都有foo方法。

因此呢,咱們能夠把混入的方法單獨拿出來,結合裝飾器使用。

// mixin.js

export function mixin(...list){
    return function (target) {
        Object.assign(target.prototype,...list)
    }
}
import {mixin} from './mixin.js

class Foo {
    foo() {
        console.log('lalala')
    }
}

@mixin(Foo)
class NewFoo {}

let nf = new NewFoo()
nf.foo()

這樣一個很差的地方,就是他會改寫新類的原型對象。

Babel轉碼

須要安裝下邊的東西:

npm install --save-dev babel-core babel-preset-stage-0

而後設置文件.babelrc

{
  "plugins": ["transform-decorators"]
}

這樣,就能在代碼裏實現裝飾器了。

參考資料https://github.com/zhiqiang21...
http://www.liuhaihua.cn/archi...

相關文章
相關標籤/搜索