面向切面编程
~~~~ 嗯,百度百科一下 ~~~~
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 作为前端的小萌理解为,就是将除主流程外的其他业务剥离出来,然后从侧面切入。对原功能无侵入式的修改。
话不多说,来个网上的经典例子:
业务逻辑:
主业务:修改数据库
切入点 1:修改前打印日志
切入点 2:修改后打印日志
Function.prototype.before = function(beforeFunc) {
let that = this
return function() {
beforeFunc.apply(this, arguments)
return that.apply(this, arguments)
}
}
Function.prototype.after = function(afterFunc) {
let that = this
return function() {
let ret = that.apply(this, arguments)
afterFunc.apply(this, arguments)
return ret
}
}
function updateDb() {
console.log(`update db`)
}
function beforeUpdateDb() {
console.log(`before update db`)
}
function afterUpdateDb() {
console.log(`updated db`)
}
updateDb = updateDb.before(beforeUpdateDb).after(afterUpdateDb)
updateDb()
原理:其实就是将主业务updateDb包裹2次,返回一个新的方法。新方法会在原方法调用前后调用切入方法,避免在主方法上直接改动。
- 这样的可读性和代码维护性是不是很差,下面用装饰器看看如何实现切面编程,
装饰器 decorator
- 装饰器是ES7现代游览器并不兼容,需要babel转译,插件(babel-plugin-transform-decorators-legacy)
- 装饰器只能作用于类本身、类的方法或属性、访问操作符
- 修饰器“@”为标识符
1 对类的装饰
@create
class Apes {
}
// 修饰类本身
function create(className) {
className.prototype.create = function() {
console.log('制造工具')
}
return descriptor
}
let apes1 = new Apes()
apes1.create()
// 制造工具
- 对类本身修饰:create(className)。装饰器本质就是编译时执行的函数。
- 要修饰子类,通过要className.prototype修饰子类。
2 对类的方法修饰
class Apes {
@eatMore
eat() {
console.log('吃水果')
}
}
// 修饰方法
function eatMore(className, propName, descriptor) {
//console.log(descriptor)
let value = descriptor.value
descriptor.value = function() {
console.log('吃土')
value()
}
return descriptor
}
let apes1 = new Apes()
apes1.eat()
- 对类的方法装饰 eatMore(className, propName, descriptor)
className - 被修饰的类
propName - 被修饰的属性名
descriptor - 该属性的描述对象
通过descriptor属性描述符看出 依赖于ES5的Object.defineProperty
复习一下Object.defineProperty
value:属性的值,默认为undefined
writable:能否修改属性的值,默认值为true
enumerable:能否通过for-in循环返回属性。默认为ture
configurable:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为访问器属性,默认为true.
- 上面对类的方法修饰 实际是通过 descriptor.value 拿到其方法,在进行包装返回
- 同时也可以直接修改descriptor上的其他属性或者返回一个新的descriptor
3. 针对 访问操作符的装饰
class test {
//@nonenumerable
get kidCount() {
return 111
}
}
function disWirte(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
let p = new test()
console.log(p.kidCount)
p.kidCount = 222n descriptor;
// 抛出异常
// TypeError: Cannot set property kidCount of # which has only a getter
4. 修饰传参
class Apes {
@say('可以说汉语了')
say() {
console.log('厵爨癵驫寶麣纞虋讟钃鸜麷鞻韽顟顠饙饙騳騱龗鱻爩麤')
}
}
// 修饰方法并传递参数
function say(str) {
return function(className, propName, descriptor) {
descriptor.value = function() {
console.log(str)
}
return descriptor
}
}
- 通过柯里化的方式传递参数
应用
1. 应用在斐波那契数列计算中
const memory = () => {
const cache = {} //缓存池
return (target, name, descriptor) => {
// 原方法
const method = descriptor.value
// 包裹原方法
descriptor.value = function(key) {
if (cache[key]) {
return cache[key]
}
const ret = method.apply(target, [key])
cache[key] = ret
return ret
}
return descriptor
}
}
let count = 0
class Test {
@memory()
fb(n) {
count++
if (n === 1) return 1
if (n === 2) return 1
return this.fb(n - 1) + this.fb(n - 2)
}
}
const t = new Test()
console.log(t.fb(10))
console.log(count)
2. 访问操作符-set 上作类型检查
class test {
constructor() {
this.a = 1
}
get a() {
return this.a
}
@check('number')
set a(v) {
return v
}
}
function check(type) {
return function(target, prop, descriptor) {
let v = descriptor.value
return {
enumerable: true,
configurable: true,
get: function() {
return v
},
set: function(c) {
var curType = typeCheck(c)
if (curType !== type) {
throw `${prop}必须为${type}类型`
}
v = c
}
}
}
}
function typeCheck(c) {
if (c === undefined) {
return undefined
}
if (c === null) {
return null
}
let type = typeof c
if (type === 'object') {
return c.constructor == Array ? 'array' : 'object'
}
return type
}
let t = new test(11)
t.a = 2
console.log(t.a) // 2
t.a = []
console.log(t.a)
// throw a必须为number类型