在了解装饰器之前,先来认识几个方法:
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参数
value属性的值,默认为 undefined。writable该属性是否可写,如果设置成 false,则任何对该属性改写的操作都无效(但不会报错)configurable如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化enumerable是否能在for-in循环中遍历出来或在Object.keys中列举出来。对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。get一旦目标对象访问该属性,就会调用这个方法,并返回结果。默认为 undefined。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>
}
}