裝飾模式的做用是:在不修改原有的接口的狀況下,讓類表現的更好。
什麼叫更好?html
天然是繼承有一些問題
繼承會致使超類和子類之間存在強耦合性,當超類改變時,子類也會隨之改變;
超類的內部細節對於子類是可見的,繼承經常被認爲破壞了封裝性;python
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 個參數分別表明:
關於descriptor表明的意思是對象描述符,它自己一個對象,用於描述目標對象的一個屬性的屬性。
關於最後一個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是一個第三方模塊,提供了一些經常使用的裝飾器。
@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' // 會報錯,由於屬性不可寫
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"? }
意思就是在一個對象中混入另一個對象的方法,是對象繼承的一種替代方式。
//一個混入的簡單實現: 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()
這樣一個很差的地方,就是他會改寫新類的原型對象。
須要安裝下邊的東西:
npm install --save-dev babel-core babel-preset-stage-0
而後設置文件.babelrc
{ "plugins": ["transform-decorators"] }
這樣,就能在代碼裏實現裝飾器了。
參考資料https://github.com/zhiqiang21...
http://www.liuhaihua.cn/archi...