看到 decorator
这个词的时候,让我回想起了python中的decorator.而,当我看到 decorator
中的 @
的时候, 我tm确定,这尼玛不就是python吗? 但, too young too naive
. es6中的decorator和python很相似,但却又非常的不一样.因为,在js中,decorator是不能用来装饰函数的.(因为有函数提升) so, decorator在js中是用来干嘛的呢?
decorator是以一种近乎trick的方式,让你写更少的代码,完成更多的事情. 事实上,es 是借助了python中的decorator来,完善自身的语法树. 在es6中,decorator可以用来装饰类 || 类方法 || 类属性。 不过,在修饰类,方法,属性时,会有很大的不同.
使用decorator修饰类,他传入的参数代表的是还未实例化的类
// 使用decorator 装饰 类
// @param {class} target_class 相当于就是Target
function dec(target_class){
target_class.prototype.demo=123
}
@dec
class Target{}
let target = new Target();
console.log(target.demo) // 123
ok, 这是比较简单的demo. 现在我们将decorator的数量增加,假设,现在想要在class上装饰两个decorator. 他们的执行顺序是怎样的呢?
实际让我们以一种通俗的方式, 来讲解一下。 ES6中的decorator和AOP编程中的before很类似. 先看一下运行的实例吧.
function dec_1(){
console.log(1);
}
function dec_2(){
console.log(2);
}
@dec_1
@dec_2
class Target{
}
// 执行结果
// 2
// 1
// 执行顺序为: dec_2 => dec_1
说白了, 这其实就是before的一个trick
function before (fn){
return function(){
fn()
// do sth...
}
}
这样, 你就可以在执行函数时,不断嵌套很多层的内容了. 或者更好的说,es6中的decorator是以一种由下到上的方式, 来执行装饰的内容的.
使用decorator修饰类中的属性, 其实和 Object.defineProperty
的行为一模一样.可以对类中的属性访问做一下限制.(感觉和Proxy差多啊) 所以, 这实际上是限制了decorator不能用来作为参数处理的一个trick. 但作为属性限制,也是极好用的. 修饰属性的decorator接受三个参数.
/**
* 装饰者
* @param {Object} 类为实例化的工厂类对象
* @param {String} name 修饰的属性名
* @param {Object} desc 描述对象
* @return {descr} 返回一个新的描述对象
*/
function decorator(target,name,desc){
}
所以, 由于一开始设计decorator的初衷就是为了可拆卸化保护属性. 如果大家,对于属性的了解有疑问的话,可以参考我前面一篇的文章.es中的属性. 接下来,我们可以利用decorator来简单的写一个readonly的保护权限.
function readonly(target,key,desc){
desc.writable=false;
return desc;
}
class Target{
@readonly
demo=123;
}
new Target().demo=345; // 报错
所以, 针对于这一特性.我们还可以写很多其他的trick. 比如, 无法被for...in...遍历的属性, 无法使用delete删除的属性.
function noLoop(target,key,desc){
desc.Enumerable=false;
return desc;
}
function banDelete(target,key,desc){
desc.configurable = false;
return desc;
}
有时候想要对函数传参做相关的属性和格式化设置,使用 decorator 的模式就非常方便了。前面已经知道,修饰一个函数可以获得的参数有:
/**
* 装饰者
* @param {Object} 类为实例化的工厂类对象
* @param {String} name 修饰的属性名
* @param {Object} desc 描述对象
* @return {descr} 返回一个新的描述对象
*/
function decorator(target,name,desc){
}
在这里对函数进行修饰,主要使用的是 desc 该对象。要对函数参数修改的基本步骤为:
...args
参数获取传参apply
调用原始函数现在,我们想对一个 Class Dog 的 setting 方法属性进行重新定义。原始 Dog 类为:
class Dog{
constructor(name){
this.setting(name)
}
setting(name){
this._name = name;
}
}
这里,我们额外增加一个 decoratorSetting 函数:
function decorateSetting(target, key, descriptor) {
const method = descriptor.value; // 保存原始函数
descriptor.value = (...args)=>{
// 动态添加 villainhr- 的前缀
args[0] = "villainhr-" + args[0]; // this is parameter (name)
// 通过 apply 方法调用原始函数
return method.apply(target, args);
}
return descriptor;
}
然后,使用 decorateSetting
修饰 Dog 类中的 setting 方法。
class Dog{
constructor(name){
this.setting(name)
}
@decorateSetting
setting(name){
this._name = name;
}
}
然后运行一下,可以得到:
let pony = new Dog('pony'); // pony.name is villainhr-pony
到这里,也就成功的对函数进行了修饰。如果想线上运行,可以使用:babel-decorator online repr。
ok, descorator的最好用的部分,上面已经差不多参数清楚了。最后,我们在补充一下,使用 decorator 必要的 babel 转码配置。
直接贴代码了:
// 先使用 npm 下载额外的包
npm i --save-dev babel-plugin-transform-decorators-legacy
// webpack 配置文件
{
test: /\.jsx?$/,
loader: 'babel',
query: {
plugins: ['transform-decorators-legacy' ],
presets: ['es2015', 'stage-0', 'react']
}
}
// babelrc 配置文件
{
"presets": ["es2015", "stage-0", "react"],
"plugins": [
["transform-decorators-legacy"],
]
}
如果,大家有什么疑问. 或者有什么新的用法,可以直接楼下回复.