在了解装饰器之前,先来认识几个方法:

Object.defineProperty

The Object.defineProperty() method defines a new property directly on an object, or modifies an exisiting property on an object, and returns the object.

语法

Object.defineProperty(object, propertyname, descriptor)

descriptor参数

  1. value 属性的值,默认为 undefined。
  2. writable 该属性是否可写,如果设置成 false,则任何对该属性改写的操作都无效(但不会报错)
  3. configurable如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化
  4. enumerable 是否能在for-in循环中遍历出来或在Object.keys中列举出来。对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
  5. get一旦目标对象访问该属性,就会调用这个方法,并返回结果。默认为 undefined。
  6. set 一旦目标对象设置该属性,就会调用这个方法。默认为 undefined。

注意:通过.defineProperty 定义属性时,两者的区别:

var person={}
person.name = 'neo'
Object.defineProperty(person,'age',{
    value:'12'
})
console.log(Object.getOwnPropertyDescriptor(person,'name'))
//{"value":"neo","writable":true,"enumerable":true,"configurable":true}

console.log(Object.getOwnPropertyDescriptor(person,'age'))
//{"value":"12","writable":false,"enumerable":false,"configurable":false}

另外注意,一个合法的descriptor 参数,必须遵循以下的规则:

Property descriptors present in objects come in two main flavors: data descriptors and accessor descriptors. A data descriptor is a property that has a value, which may or may not be writable. An accessor descriptor is a property described by a getter-setter pair of functions. A descriptor must be one of these two flavors; it cannot be both.

也就是说,可以使用下面的两者之一,但是不能组合使用:
- writable and value
- get and set

当下面这样定义的时候,是非法的:

Object.defineProperty(person,'age',{
    value:'12',
    set:()=>{
        return 11;
    }
})

装饰器

装饰器内部都是依靠Object.defineProperty 来实现的

在结合ES6时,主要用到的场景是 属性/方法装饰器类装饰器

属性/方法装饰器

应用了装饰器之后的执行流程:先去执行装饰器,在装饰器内接受到如下的三个参数,装饰器可以依据需求选择修改或者不修改descriptor,执行完毕后,再执行原方法或者经过装饰器修改后的方法。

// 注意这里的 `target` 是 `Dog.prototype`
function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor//这里不用手动return也可以
}

//需要穿参数的装饰器
let logger =(type) =>{
    return (target,key,descriptor)=>{//接收到bark方法的descriptor,可以依据需要操作相关的属性,或者不操作
      console.log('prepare to brak',type);
    }
}

//不需要传参的装饰器
let loggerWithoutParam =(target,key,descriptor)=>{
//一个logger装饰器。作用是在函数执行之前后执行之后输出logger信息,但是不影响原函数的执行以及原函数返回值
    let method = descriptor.value;//首先使用  method = descriptor.value; 将原有方法提取出来,保障原有方法的纯净;
    descriptor.value=(...args) =>{// ...args接受到原函数的所有参数
        let result;
        console.log('logger begin');
        result = method.apply(target,args);
        console.log('logger over');
        return result;//返回原函数的返回值
    }
    return descriptor;//不用return也可以
}

class Dog {
//   @readonly
  name = '123'

  @logger('dog')
  bark(){
      console.log('bark')
  }

  @loggerWithoutParam
  work(time,qq){
      console.log('work');
      return time+'is fine'
  }
}

let dog = new Dog()
dog.name ='123' //Cannot assign to read only property 'name' of object '#<Dog>'
dog.bark();//prepare to brak dog &&  bark
let a=dog.work('today','sss');//logger begin work logger over
console.log(a);//todayis fine

类装饰器

类装饰器只接受一个参数,Target即类本身,而非prototype

下面是一个结合react组件的类装饰器,在不改变原有组件结构的前提下,可以给原有组件装饰一个title

import {Component} from 'react'
//需要穿参的装饰器写法,与上面的相同,只不过这里结合了箭头函数,简化了书写。
let connectTitle = name => Target => class DemoWithTitle extends Component{// 类似高阶函数的写法,不同的是,这里最终返回一个组件类,而不是函数
        render(){
            return <div>
                <h2>name</h2>
                <Target/>
            </div>
        }
    }

//类装饰器只接受一个参数,Target即类本身,而非prototype    

@connectTitle('nice')
export default class Demo extends Component{
    render(){
        return <div>content</div>
    }
}

Reference

  1. 不会Object.defineProperty你就out了
  2. stackoverflow

本文转载:CSDN博客