装饰模式的做用是:在不修改原有的接口的状况下,让类表现的更好。
什么叫更好?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...