ES6装饰器

装饰器

装饰器又叫修饰器(Decorators),是一种特殊类型的声明,它可以附加到类声明、方法、参数或者属性上。装饰器由@符号紧接一个函数名称,形如@expression,expression求值后必须是一个函数,在函数执行的时候装饰器的声明方法会被立即执行。装饰器是用来给附着的主体进行装饰,添加额外行为的一种方式。
许多面向对象的语言都有装饰器函数。

ES6中也引入了关于装饰器的一个提案,但是目前大多数浏览器中以及node环境中都不支持装饰器修饰,若是需要学习装饰器的功能,需要先将装饰器的代码转换成浏览器或者node能够识别的低版本的js代码,这就需要用到转换js代码的babel工具。为了学习装饰器的效果,需要在本地安装好环境,做如下操作:

1.确保本机中安装node和npm环境
    例如我的环境下node版本是v6.9.5,npm版本是3.10.10

2.在本地任何文件夹下创建learn-decorator文件夹,执行npm init生成package.json文件。

3.安装babel
    npm install --save-dev babel-cli babel-preset-env
    在本地安装上述两个库,在工程根目录上创建babel的配置文件.babelrc,并写入:
    {
        "presets": ["env"]
    }

4.安装decorators插件
    如果不安装插件,会有语法错误提示。按照说明,继续安装插件。
    npm install babel-plugin-transform-decorators-legacy --save-dev
    之后再配置文件添加插件,现在.babelrc文件应如下所示:
    {
      "presets": ["env"],
      "plugins": ["transform-decorators-legacy"]
    }

这样准备工作就已经完成了。用vscode打开工程项目,创建一个main.js文件,打开终端,可以在终端中按照如下方式
使用babel命令(安装babel-cli时,会安装babel和babel-node命令):windows中
.\node_modules\.bin\babel-node main.js // 直接运行js代码
.\node_modules\.bin\babel main.js > test.js // 将main.js转换成

关于babel中为什么要配置成babel-preset-env方式,是为了以兼容的形式把babel-preset-env嵌入到babel里面,
babel preset将基于你的实际浏览器及运行环境,自动的确定babel插件及polyfills,转译ES2015及此版本以上的语言。这里不再具体叙述babel的配置,
详情请参考[babel配置](https://segmentfault.com/a/1190000011639765)。
1. 类的装饰器

显然,类的装饰器是修饰类的,可以修饰类本身,比如添加静态变量,也可以修饰类实例,比如添加一个属性。

1.1 修饰类本身
@testDecorator
class ClassA {
}

function testDecorator(target) {
  target.addedParam = "I am decorator";
}

console.log(ClassA.addedParam);

// 在终端中执行.\node_modules\.bin\babel-node main.js 输出 I am decorator

上述代码中testDecorator就是一个修饰器。它修改了ClassA这个类的行为,为这个类添加了一个静态的属性addedParam。testDecorator函数的参数是ClassA类本身。

可以将上述操作理解如下:

@testDecorator
class ClassA {}

// 等同于

class ClassA {}
ClassA = testDecorator(ClassA) || ClassA;

也就是说修饰器是一个对类进行处理的函数,修饰器的第一个参数就是所要修饰的目标,在本例中就是ClassA。
修饰的目标是默认传入的参数,还可以传入其他的参数。

function testDecorator(desc) {
  return function(target) {
    target.addedParam = desc;
  }
}

//调用testDecorator函数之后,其又返回一个匿名函数,利用闭包的特性使用了decs变量 
@testDecorator("operate  one") 
class ClassA {
}
console.log(ClassA.addedParam) // operate  one

@testDecorator("operate  two")
class ClassB {
}
console.log(ClassB.addedParam) // operate  two

修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着修饰器能在编译阶段运行代码,也就是说修饰器本质就是编译时执行的函数。

通过将main.js转换为node支持的格式看一下:运行.\node_modules.bin\babel main.js > test.js命令

"use strict";

var _dec, _class;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function testDecorator(desc) {
  return function (target) {
    target.addedParam = desc;
  };
}

var ClassA = (_dec = testDecorator("operate  one"), _dec(_class = function ClassA() {
  _classCallCheck(this, ClassA);
}) || _class);

console.log(ClassA.addedParam);

若是想添加实例属性,可以通过目标类的prototype对象操作。


function paramDecorator(target) {
  target.prototype.isTestable = "yes";
}

@paramDecorator
class ClassB{

}
let B = new ClassB();
console.log(B.isTestable); // 输出 yes

又如下面的例子,通过msxins装饰器给类添加上新属性。

function maxins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list);
  }
}
let Foo = {
  foo: () => {
    console.log('foo');
  }
}
Foo.foo();

@maxins(Foo)
class ClassC {
}

let C = new ClassC();
C.foo();

console.log(C)
=========================
foo
foo
ClassC {} // 可见ClassC本身是没有foo属性的
1.2 修饰类的方法
class Person{
  @readonly
  name() {
    console.log('is readonly')
  }
}

function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  console.log('name', name)
  descriptor.writable = false;
  return descriptor;
}

let person = new Person();
person.name();

============================
name name
is readonly

实现输出日志的操作:

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);

================================
Calling add with { '0': 2, '1': 4 }

装饰器还具有注释的功能,根据装饰器的名称,能够看出类或者方法有哪些功能。如果同一个方法有多个装饰器,则会向剥洋葱一样,从外到内进入,然后从内到外依次执行。

function dec(id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id); // 必须要定义的回调
  函数,否则运行会报错。
}

class Example {
    @dec(1)
    @dec(2)
    method(){
      console.log('method');
    }
}
let example = new Example();
example.method();

=================================
evaluated 1
evaluated 2
executed 2
executed 1
method

除了注释功能,装饰器还可以用来类型检查,因此这一功能相当重要。

1.3 为什么不能将装饰器用于函数?

装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

上面的代码,意图是执行后counter等于 1,但是实际上结果是counter等于 0。因为函数提升,使得实际执行的代码是下面这样。

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};

总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。

如果一定要修饰函数,可以采用高阶函数的形式直接执行。
“`
function doSomething(name) {
console.log(‘Hello, ’ + name);
}

function loggingDecorator(wrapped) {
return function() {
console.log(‘Starting’);
const result = wrapped.apply(this, arguments);
console.log(‘Finished’);
return result;
}
}

const wrapped = loggingDecorator(doSomething);

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值