5

为什么我的装饰器没别人的强?

 3 years ago
source link: https://zhuanlan.zhihu.com/p/83613489
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

为什么我的装饰器没别人的强?

装饰器(Decorator)是一种设计模式,允许向一个对象添加功能,但是又不改变其内部结构。经评论区点出,装饰器只是ES7中提案,目前处于Stage 2阶段,但是不久的将来就会变成规范。它主要用来修饰类以及类属性。

本文总共分为五个部分:

  • 修饰类属性
  • 在React的应用
  • Babel编译
@isPerson
class Person {}

function isPerson(target) {
  target.prototype.isPerson = true;
}

const person1 = new Person();
console.log(person1.isPerson); // true

上面有一个Person类,我们写了一个isPerson装饰器修饰它,最终我们实例化Person时,实例上多了isPerson属性。

@isPerson(true)
class Person {}

function isPerson(bol) {
  return function(target) {
    target.prototype.isPerson = true;
  }
}

同样我们也可以给装饰器isPerson添加参数,而装饰器内容就会多一层结构,return对一个对象。所以装饰器是支持传参和不传参的。

本质上可以把Person看作一个方法,而实际上装饰器就是将方法当参数传入。在Babel编译我会讲解装饰器的本质。

isPerson(true)(function Person() {})

修饰类属性

class Person {
  firstName = 'Peter';
  lastName = 'Cheng';

  @readonly
  realName() { return `${this.firstName} ${this.lastName}` };
}

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

const person2 = new Person();
console.log(person2.realName = 1);

同时给类PersonrealName方法添加了readonly装饰器,当输出实例的realName属性时,程序会报错,Uncaught TypeError: Cannot assign to read only property 'realName' of object '#<Person>'。注意,这里实际上修饰的是类方法,装饰器目前不能修饰类里面的变量,比如firstNamelastName

  • target

如果修饰类,那target就是目标本身,第一个例子中就是Person类。如果你修饰的是类方法,那target就是类实例。

类名或者类方法名称,同样第一个例子,打印出来就是Person

  • descriptor

属性的描述对象。它具有如下几个属性,valueenumerableconfigurablewritable。value是修饰对象本身,而其他值和Object.defineProperty的属性一样,控制值的行为。

{
   value: ƒ realName(),
   enumerable: false,
   configurable: true,
   writable: false
 };

在React的应用

装饰器在React中的应用我们随处看见,比如Redux,自定义HOC等,其实这些都是高阶函数的应用。

下面我们实现一个打点装饰器,当我们触发postClick方法时,会输出一个打点的log,最终会输出postClick 2。我们通过拦截value,并重写value,将参数id打印了出来。

class App extends Component {
  @analytic()
  postClick(id = 1) {}
}

function analytic(args) {
  return function decorator(target, name, descriptor) {
    if (typeof descriptor === 'undefined') {
      throw new Error('@analytic decorator can only be applied to class methods');
    }

    const value = descriptor.value;
    function newValue(...args) {
      console.log(`${name} ${args}`);
      return value.bind(this)(args);
    };


    return {
      ...descriptor,
      value: newValue
    };
  }
}

const app = new App();
app.postClick(2);

Babel编译

我们选择修饰类方法的例子,看一下最简单的装饰器如果编译成ES5代码会是怎么样。可以用Babel官方的网址 https://babeljs.io/repl,看一下第一个例子中代码被编译成什么样子。

class Person {
  firstName = 'Peter';
  lastName = 'Cheng';

  @readonly
  realName() { return `${this.firstName} ${this.lastName}` };
}

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

const person2 = new Person();
console.log(person2.realName = 1);
function _decorate(decorators, factory, superClass, mixins) {}

// 省略中间一大推

var Person = _decorate(null, function (_initialize) {
  var Person = function Person() {
    _classCallCheck(this, Person);

    _initialize(this);
  };

  return {
    F: Person,
    d: [{
      kind: "field",
      key: "firstName",
      value: function value() {
        return 'Peter';
      }
    }, {
      kind: "field",
      key: "lastName",
      value: function value() {
        return 'Cheng';
      }
    }, {
      kind: "method",
      decorators: [readonly],
      key: "realName",
      value: function realName() {
        return "".concat(this.firstName, " ").concat(this.lastName);
      }
    }]
  };
});

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

var person2 = new Person();
console.log(person2.realName = 1);

其实主要看Person对象有那些变化,Babel将类编译成了ES5的function,并且外面套一层装饰器,但是装饰器最终还是赋值给Person变量。内部Person对象最终返回一个对象,而key为realName的对象有一个decorators,它是一个数组。我们看看decorators做了什么。其实就是遍历数组,将参数最终映射到Object.defineProperty,操作对象的可写入等属性。

通过本文我们知道了装饰器是什么,并且用来做什么,以及实质是什么。最近看了一部电影《斯隆女士》,里面就有一个片段阐述专业性的重要性,作为前端,首先需要掌握的就是ES相关的知识。终于整理完装饰器的知识了,我最近正在用Node + Flutter做一个App,最终计划是发布上线,敬请期待。

我用create-react-app生成了一个项目,可以直接使用装饰器。

http://es6.ruanyifeng.com/#docs/decorator


写作时间: 20190922


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK