目录: javascript
What
(是什么)-Why
(为何)-How
(怎么用)-Where
(哪里用)阐述方法论;AOP
编程思想;JavaScript Decorator
的弊端;Decorator
;Decorator
如何传参; 先了解一下火于后端的一个编程思想:AOP
( Aspect Oriented Programming :面向切面编程)。 也叫作面向方法编程,是经过预编译方式和运行期动态代理的方式实现不修改源代码的状况下给程序动态统一添加功能的技术 。详见:AOP 面向切面编程。归纳文章主要思想:前端
AOP
面对业务处理过程当中的某个步骤或阶段,下降业务流程步骤间的耦合度;Aspect
切面AOP
是OOP
(封装、继承,多态)的补充和完善,AOP
实现分散关注点;AOP
是典型的代理模式体现; JavaScript
:同为C
系列语言,Java
的AOP
那么好用,我也要(磨刀霍霍向猪羊,期待的小眼神)。java
Decorator
无疑是对AOP
最有力的设计,在ES5
时代,能够经过 Object.defineProperty
来对对象属性/方法 进行访问修饰,但用起来须要写一堆东西;在ES6
时代,能够经过Proxy
来对对象属性 / 方法进行访问修饰。Decorator
已经在ES7
的提案中,也就是叫你敬请期待;借助Babel转码工具,咱们能够先吃螃蟹。webpack
Decorator
,能够不侵入原有代码内部的状况下修改类代码的行为,处理一些与具体业务无关的公共功能,常见:日志,异常,埋码等。ES7 Decorator提案描述以下:ios
A decorator is:git
思想却是理解了,上面的翻译可能有出入,由于老感受第4点翻译的不够贴切,欢迎斧正。github
首先抛开“迎合”后端开发人员的Class
写法,对应会引入的相关概念和特性,固然随着前端业务的发展,有时候也须要对应的特性。好比:private
,static
等见于Java
的特性,现现在经过Class
语法糖能在前端实现一样的特性,换汤不换药,语法糖底层仍是经过原生JavaScript
来模拟实现相关特性的。前端Class
编写风格更加"后端",那么就更容易吸引一大波后端人员学习和使用Javascript
,Javascript
一统编程界“指日可待”。后端都学Javascript
了,这让纯前端压力山大,咱们要加快学习的脚步才行,技多不压身,触类旁通学习,把后端的空气都咬完,让后端没法呼吸。web
其次举个栗子阐述为何要用Decorator
:现实生活中咱们可能也遇到过,百度过一个商品后,打开淘宝京东后,淘宝京东便能精准的推荐该商品的相关广告,大数据时代,咱们慢慢愈来愈透明。转换为专业术语:埋码。express
埋码具体需求以下:大数据时代,数据就是金钱,业务方须要统计用户对某些功能的使用状况,好比使用时间,频率,用户习惯等。对应后端会提供一个埋码接口,用户调用功能的时候,前端须要 侵入式 的在全部须要统计的功能前调用后端埋码接口。编程
原始写法:
// 埋码 监听用户使用状况
function monitor(name) {
console.log(`调用监听接口,发送监听数据 : ${name}`)
}
class PageAPI {
onWatch() {
monitor('侵入式:帅哥靓妹X访问了xxx')
console.log('原访问页面逻辑')
}
onLike() {
monitor('侵入式:帅哥靓妹X点赞了xxx')
console.log('原点赞正常逻辑')
}
onAttention() {
monitor('侵入式:帅哥靓妹X关注了xxx')
console.log('原关注正常逻辑')
}
onBack(){
console.log('退出相关逻辑,不须要监听')
}
}
const page = new PageAPI()
// 各类暗示点赞,关注,各位看官你懂的,哈哈
page.onWatch()
page.onLike()
page.onAttention()
page.onBack()
复制代码
打印结果:
使用Decorator
写法以下:
// 埋码 监听用户使用状况
function monitor(name) {
// 注意:
// 实际中埋码数据是从一个单例store里面取
// 好比,用户名,访问时间等
// 操做类型可做为`Decorator`参数
console.log(`调用监听接口,发送监听数据 : ${name}`)
}
/** Decorator 定义: 1. an expression(一个表达式) 2. that evaluates to a function(等价于一个函数) 3. that takes the target, name, and decorator descriptor as arguments(参数有target,name,descriptor ) 4. and optionally returns a decorator descriptor to install on the target object(可选的返回一个装饰器描述符以安装在目标对象上) * @param {*} name */
const monitorDecorator = (name) => { // Decorator 定义2
return (target, propertyKey, descriptor) => {// Decorator 定义3
const func = descriptor.value
return { // Decorator 定义4
get() {
return (...args) => {
monitor(name) // 埋码
func.apply(this, args) // 原来逻辑
}
},
set(newValue) {
return newValue
}
}
}
}
class PageAPI {
@monitorDecorator('Decorator:帅哥靓妹X访问了xxx') // Decorator 定义1
onWatch() {
console.log('原访问页面逻辑')
}
@monitorDecorator('Decorator:帅哥靓妹X点赞了xxx')
onLike() {
console.log('原点赞正常逻辑')
}
@monitorDecorator('Decorator:帅哥靓妹X关注了xxx')
onAttention() {
console.log('原关注正常逻辑')
}
onBack() {
console.log('退出相关逻辑,不须要监听')
}
}
const page = new PageAPI()
// 各类暗示点赞,关注,各位看官你懂的,哈哈
page.onWatch()
page.onLike()
page.onAttention()
page.onBack()
复制代码
打印结果:
通过上面的栗子应该能直观的感觉到面向切面编程核心:非侵入式,解耦。
不火的缘由主要为:
ES7
提案中,还未获得官方支持;Function
写法支持不友善,不少用户和框架依然都用Function写法,好比:Vue 3.0
、React Hook
等都推崇Function
写法,毕竟Javascript
从骨子里就是用Function编程。Decorator
暂时不能串联,存在覆盖问题; 目前标准还未支持Decorator,可是Babel已经支持相关写法,咱们能够经过get
、set
来模拟实现。根据Decorator
不一样的切入点能够分为:Class
,Method
和Property
三种Decorator
。顺带介绍一下原生Function如何实现面向切面编程。
在自我搭建的Webpack
项目中使用Decorator
,运行项目编译失败,终端报错,并提供了对应的解决方法。按照提示操做,便能在自我搭建的webpack项目使用Decorator
了。
另外,亲测,在新版Vue-cli
项目中已经默认支持Decorator
写法了
切入点为Class
,修饰整个Class
,能够读取和修改类的方法和属性。须要传递参数,能够经过高阶的函数来实现传递参数,以下面的classDecoratorBuilder
。
// 埋码 监听用户使用状况
function monitor(name) {
console.log(`调用监听接口,发送监听数据 : ${name}`)
}
const classDecoratorBuilder = (dataMap) => {
return target => {
// ! 此处不能用 Object.entries(target.prototype) --> []
Object
.getOwnPropertyNames(target.prototype)
.forEach(key => {
console.log(target)
if (!['onBack'].includes(key)) { // 屏蔽某些操做
const func = target.prototype[key]
target.prototype[key] = (...args) => {
monitor(dataMap[key] || '埋码数据') // 埋码
func.apply(this, args) // 原来逻辑
}
}
})
return target
}
}
const dataMap = {
onWatch: 'class Decorator:帅哥靓妹X访问了xxx',
onLike: 'class Decorator:帅哥靓妹X点赞了xxx',
onAttention: 'class Decorator:帅哥靓妹X关注了xxx',
}
const classDecorator = classDecoratorBuilder(dataMap)
@classDecorator
class PageAPI {
onWatch() {
console.log('原访问页面逻辑')
}
onLike() {
console.log('原点赞正常逻辑')
}
onAttention() {
console.log('原关注正常逻辑')
}
onBack() {
console.log('退出相关逻辑,不须要监听')
}
}
const page = new PageAPI()
// 各类暗示点赞,关注,各位看官你懂的,哈哈
page.onWatch()
page.onLike()
page.onAttention()
page.onBack()
复制代码
运行结果以下:
切入点为Method
,修饰方法,和Class Decorator
功能类似,能额外的获取修饰的方法名。详见 Why 中的栗子。这里就不赘述了。
切入点为属性,修饰属性,和Class注解功能功能相同,能额外的获取修饰的属性名。
const propertyDecorator = (target, propertyKey) => {
Object.defineProperty(target, propertyKey, {
get() {
return 'property-decorator-value'
},
set(val) {
return val
}
})
}
class Person {
@propertyDecorator
private name = 'default name'
sayName(){
console.log(`class Person name = ${this.name}`)
}
}
new Person().sayName()
复制代码
运行结果以下:
Decorator
优先级,串联 Java的Decorator
功能强大,不只有丰富的Decorator
,并且Decorator
还能够串联。坏消息:亲测JavaScript Decorator
不能串联,存在覆盖问题,也就是优先级关系:Method Decorator
> Class Decorator
。当一个Method
上定义了Decorator
,则Class Decorator
则不起做用。但愿ES7
标准能解决这个痛点。
const classDecoratorBuilder = (name) => {
return target => {
Object
.getOwnPropertyNames(target.prototype)
.forEach(key => {
const func = target.prototype[key]
target.prototype[key] = (...args) => {
console.log(`>>>>> class-decorator ${name}`)
func.apply(this, args)
}
})
return target
}
}
const methodDecoratorBuilder = (name) => {
return (target, propertyKey, descriptor) => {
const func = descriptor.value
return {
get() {
return (...args) => {
console.log(`>>>>> method-decorator ${name}`)
func.apply(this, args)
}
},
set(newValue) {
return newValue
}
}
}
}
const classDecorator1 = classDecoratorBuilder(1)
const classDecorator2 = classDecoratorBuilder(2)
const methodDecorator1 = methodDecoratorBuilder(1)
const methodDecorator2 = methodDecoratorBuilder(2)
const propertyDecorator = (target, propertyKey) => {
Object.defineProperty(target, propertyKey, {
get() {
return 'property-decorator-value'
},
set(val) {
return val
}
})
}
// Decorator不能串联
// @classDecorator1
@classDecorator2
class Person {
@propertyDecorator
private name = 'default name'
// @methodDecorator1 // 不能串联,会报错
@methodDecorator2 // class Decorator会被覆盖
sayName() {
console.log('sayName : ', this.name)
}
eat(food) {
console.log('eat : ', food)
}
}
const person = new Person()
person.sayName()
person.eat('rice')
复制代码
运行结果以下:
Decorator
” Decorator
目前只能应用于Class
,不能用于修饰Function
,由于Function
的执行上下文是不肯定的,太灵活了。可是AOP
编程思想是先进的,合理的。咱们能够采用不一样的形式来实现Function
的AOP
,虽然没Decorator
那么优雅。经过这种方式还能够解决Decorator
串联的痛点。
function monitor(name) {
console.log(`调用监听接口,发送监听数据 : ${name}`)
}
const functionAOP = (name, fn) => {
return (...args) => {
monitor(name)
fn.apply(this, args)
}
}
let onWatch = (pageName) => {
console.log('原访问页面逻辑,访问页面:', pageName)
}
let onLike = (pageName) => {
console.log('原点赞正常逻辑,求点赞:', pageName)
}
let onAttention = (author) => {
console.log('原关注正常逻辑,求关注:', author)
}
// 相似`Decorator`
onWatch = functionAOP(
'****我串联啦****',
functionAOP('functionAOP:帅哥靓妹X访问了xxx', onWatch)
)
onLike = functionAOP('functionAOP:帅哥靓妹X点赞了xxx', onLike)
onAttention = functionAOP('functionAOP:帅哥靓妹X关注了xxx', onAttention)
onWatch('JavaScript好用还未火的`Decorator`@Decorator')
onLike('JavaScript好用还未火的`Decorator`@Decorator')
onAttention('JS强迫症患者')
复制代码
运行结果以下:
AOP在前端的应用场景包括日志记录、统计、安全控制、事务处理、异常处理、埋码等与业务关联性不强的功能。上面栗子已经详细介绍了AOP在埋码上的应用,下面再详细介绍一个经常使用场景:异常处理。
一个好的应用,用户体验要良好,当用户使用核心功能,不管功能是否成功,都但愿获得一个信息反馈,而不是感受不到功能是否有运行,是否成功。核心功能运行成功的时候弹出消息:xxx功能成功;失败的时候弹出错误:xxx功能失败,请xxx之类。
废话很少说,直接撸代码。因为是模拟代码,一是为了节省时间,二是为了各位看官能够一览无遗,博主就不拆解文件了。合理的结构应该将API
,Decorator
,页面逻辑拆解到对应文件中,以供复用。
生成模拟接口的公共代码:
const promiseAPIBuilder = (code) => { // 模拟生成各类接口
return new Promise((resolve, reject) => {
setTimeout(() => {
if (code === 0) {
resolve({
code,
message: 'success',
data: []
})
} else if (code === 404) {
reject({
code,
message: '接口不存在'
})
} else {
reject({
code,
message: '服务端异常'
})
}
}, 1000)
})
}
复制代码
咱们能够修改axios
拦截器,当状态code
非0的时候一概认为功能失败,统一reject
错误信息,最后在API
调用处catch
内统一作错误信息弹出。相应弊端:多处接口调用处都须要增长与业务无关的catch
方法或者用try catch
处理。
const api = {
successAPI() {
return promiseAPIBuilder(0)
},
error404API() {
return promiseAPIBuilder(404)
},
errorWithoutCatchAPI() { // 没有catch error
return promiseAPIBuilder(500)
}
}
const successAPI = async () => {
const res = await api
.successAPI()
.catch(error => console.log(`多个catch的error : ${error.message}`))
if (!res) return
console.log('接口调用成功后的逻辑1')
}
successAPI()
const error404API = async () => {
const res = await api
.error404API()
.catch(error => console.log(`消息提示:多个catch的error : ${error.message}`))
if (!res) return
console.log('接口调用成功后的逻辑2')
}
error404API()
const errorWithoutCatchAPI = async () => {
const res = await api.errorWithoutCatchAPI() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑3')
}
errorWithoutCatchAPI()
复制代码
运行结果:
定义全局异常处理函数。相应弊端:状况多的话须要作不少case
判断,由于引用不少没拦截的异常都会跑到全局异常处理函数。
const api = {
successAPI() {
return promiseAPIBuilder(0)
},
error404API() {
return promiseAPIBuilder(404)
},
errorWithoutCatchAPI() { // 没有catch error
return promiseAPIBuilder(500)
}
}
// 统一处理
window.addEventListener('unhandledrejection', (event) => {
if (event.reason.code === 404) {
console.log(` 消息提示:统一catch的error, 须要经过if或者switch判断处理流程 : ${event.reason.message} `)
}
})
const successAPI = async () => {
const res = await api.successAPI() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑1')
}
successAPI()
const error404API = async () => {
const res = await api.error404API() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑2')
}
error404API()
const errorWithoutCatchAPI = async () => {
const res = await api.errorWithoutCatchAPI() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑3')
}
errorWithoutCatchAPI()
复制代码
运行结果:
Decorator
Decorator
修饰API
接口管理文件。虽然说也有Class
写法的限制,可是咱们能够经过其余方式避开这个限制。注意带*号的代码
// ****** catch error Decorator 构造器
const showTipDecoratorBulder = (errorHandler) => (target, propertyKey, descriptor) => {
const func = descriptor.value
return {
get() {
return (...args) => {
return Promise
.resolve(func.apply(this, args))
.catch(error => {
errorHandler && errorHandler(error)
})
}
},
set(newValue) {
return newValue
}
}
}
// ****** 构造一个提示错误的`Decorator`
const showTipDecorator = showTipDecoratorBulder((error) => {
console.log(`Decorator error 消息提示 : ${error.message}`)
})
// ****** class 写法避开限制
class PageAPI {
@showTipDecorator
successAPI() {
return promiseAPIBuilder(0)
}
@showTipDecorator
error404API() {
return promiseAPIBuilder(404)
}
errorWithoutCatchAPI() {
return promiseAPIBuilder(500)
}
}
const api = new PageAPI()
const successAPI = async () => {
const res = await api.successAPI() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑1')
}
successAPI()
const error404API = async () => {
const res = await api.error404API() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑2')
}
error404API()
const errorWithoutCatchAPI = async () => {
const res = await api.errorWithoutCatchAPI() // error 没有 catch
if (!res) return
console.log('接口调用成功后的逻辑3')
}
errorWithoutCatchAPI()
复制代码
运行结果:
附送:如何判断一个函数为AsyncFucntion
。
/** * 附送:如何判断一个函数为AsyncFucntion */
const asyncFn = async _ => _
const fn = _ => _
// AsyncFucntion非JS内置对象,不能直接经过以下方式判断
// console.log('<<<< asyncFn instanceof AsyncFucntion <<<', asyncFn instanceof AsyncFucntion)
console.log('<<<< asyncFn instanceof Function <<<', asyncFn instanceof Function) // true
console.log('<<<< fn instanceof Function <<<', fn instanceof Function) // true
const AsyncFucntion = Object.getPrototypeOf(async _ => _).constructor
console.log('<<<< asyncFn instanceof AsyncFucntion <<<', asyncFn instanceof AsyncFucntion) // true
console.log('<<<< fn instanceof AsyncFucntion <<<', fn instanceof AsyncFucntion) // false
复制代码
运行结果:
都看到这里了,点个赞,关注再走呗。