产品上线事繁多,测试产品催不离。
休问 Bug 剩多少,眼圈如漆身如泥。
作为一个已经的 Java coder
, 当我第一次看到js
外面的装璜器 (Decorator
) 的时候,就马上想到了 Java
中的注解,当然在理论原理和性能下面,Java
的注解和 js
的装璜器还是有很大差异的。本文题目是Vue 中应用装璜器,我是认真的
,但本文将从装璜器的概念开发聊起,一起来看看吧。
通过本文内容,你将学到以下内容:
- 理解什么是装璜器
- 在办法应用装璜器
- 在
class
中应用装璜器 - 在
Vue
中应用装璜器
本文首发于公众号【前端有的玩】,不想当咸鱼,想要换工作,关注公众号,带你每日一起刷大厂面试题,关注
===
大厂offer
。
什么是装璜器
装璜器是 ES2016
提出来的一个提案,以后处于 Stage 2
阶段,对于装璜器的体验,能够点击 https://github.com/tc39/proposal-decorators 查看详情。装璜器是一种与类相干的语法糖,用来包装或者批改类或者类的办法的行为,其实装璜器就是设计模式中装璜者模式的一种实现形式。不过后面说的这些概念太干了,咱们用人话来翻译一下,举一个例子。
在日常开发写 bug
过程中,咱们常常会用到防抖和节流,比方像上面这样
class MyClass {follow = debounce(function() {console.log('我是子君,关注我哦')
}, 100)
}
const myClass = new MyClass()
// 屡次调用只会输入一次
myClass.follow()
myClass.follow()
下面是一个防抖的例子,咱们通过 debounce
函数将另一个函数包起来,实现了防抖的性能,这时候再有另一个需要,比方心愿在调用 follow
函数前后各打印一段日志,这时候咱们还能够再开发一个 log
函数,而后持续将 follow
包装起来
/**
* 最外层是防抖,否则 log 会被调用屡次
*/
class MyClass {
follow = debounce(log(function() {console.log('我是子君,关注我哦')
}),
100
)
}
下面代码中的 debounce
和log
两个函数,实质上是两个包装函数,通过这两个函数对原函数的包装,使原函数的行为产生了变动,而 js
中的装璜器的原理就是这样的,咱们应用装璜器对下面的代码进行革新
class MyClass {@debounce(100)
@log
follow() {console.log('我是子君,关注我哦')
}
}
装璜器的模式就是 @ + 函数名
,如果有参数的话,前面的括号外面能够传参
在办法上应用装璜器
装璜器能够利用到 class
上或者 class
外面的属性下面,但个别状况下,利用到 class
属性下面的场景会比拟多一些,比方像下面咱们说的 log
,debounce
等等,都个别会利用到类属性下面,接下来咱们一起来具体看一下如何实现一个装璜器,并利用到类下面。在实现装璜器之前,咱们须要先理解一下属性描述符
理解一下属性描述符
在咱们定义一个对象外面的属性的时候,其实这个属性下面是有许多属性描述符的,这些描述符表明了这个属性能不能批改,能不能枚举,能不能删除等等,同时 ECMAScript
将这些属性描述符分为两类,别离是数据属性和拜访器属性,并且数据属性与拜访器属性是不能共存的。
数据属性
数据属性蕴含一个数据值的地位,在这个地位能够读取和写入值。数据属性蕴含了四个描述符,别离是
configurable
示意能不能通过
delete
删除属性,是否批改属性的其余描述符个性,或者是否将数据属性批改为拜访器属性。当咱们通过let obj = {name: ''}
申明一个对象的时候,这个对象外面所有的属性的configurable
描述符的值都是true
enumerable
示意能不能通过
for in
或者Object.keys
等形式获取到属性,咱们个别申明的对象外面这个描述符的值是true
, 然而对于class
类外面的属性来说,这个值是false
writable
示意是否批改属性的数据值,通过将这个批改为
false
, 能够实现属性只读的成果。value
示意以后属性的数据值,读取属性值的时候,从这里读取;写入属性值的时候,会写到这个地位。
拜访器属性
拜访器属性不蕴含数据值,他们蕴含了 getter
与setter
两个函数,同时 configurable
与enumerable
是数据属性与拜访器属性共有的两个描述符。
getter
在读取属性的时候调用这个函数,默认这个函数为
undefined
setter
在写入属性值的时候调用这个函数,默认这个函数为
undefined
理解了这六个描述符之后,你可能会有几个疑难:我如何去定义批改这些属性描述符?这些属性描述符与明天的文章主题有什么关系?接下来是揭晓答案的时候了。
应用Object.defineProperty
理解过 vue2.0
双向绑定原理的同学肯定晓得,Vue
的双向绑定就是通过应用 Object.defineProperty
去定义数据属性的 getter
与setter
办法来实现的,比方上面有一个对象
let obj = {
name: '子君',
officialAccounts: '前端有的玩'
}
我心愿这个对象外面的用户名是不能被批改的,用 Object.defineProperty
该如何定义呢?
Object.defineProperty(obj,'name', {
// 设置 writable 是 false, 这个属性将不能被批改
writable: false
})
// 批改 obj.name
obj.name = "小人"
// 打印仍然是子君
console.log(obj.name)
通过 Object.defineProperty
能够去定义或者批改对象属性的属性描述符,然而因为数据属性与拜访器属性是互斥的,所以一次只能批改其中的一类,这一点须要留神。
定义一个防抖装璜器
装璜器实质上仍然是一个函数,不过这个函数的参数是固定的,如下是防抖装璜器的代码
/**
*@param wait 提早时长
*/
function debounce(wait) {return function(target, name, descriptor) {descriptor.value = debounce(descriptor.value, wait)
}
}
// 应用形式
class MyClass {@debounce(100)
follow() {console.log('我是子君,我的公众号是【前端有的玩】,关注有惊喜哦')
}
}
咱们逐行去剖析一下代码
- 首先咱们定义了一个
debounce
函数,同时有一个参数wait
,这个函数对应的就是在上面调用装璜器时应用的@debounce(100)
-
debounce
函数返回了一个新的函数,这个函数即装璜器的外围,这个函数有三个参数,上面逐个剖析target
: 这个类属性函数是在谁下面挂载的,如上例对应的是MyClass
类name
: 这个类属性函数的名称,对应下面的follow
descriptor
: 这个就是咱们后面说的属性描述符,通过间接descriptor
下面的属性,即可实现属性只读,数据重写等性能
- 而后第三行
descriptor.value = debounce(descriptor.value, wait)
, 后面咱们曾经理解到, 属性描述符下面的value
对应的是这个属性的值,所以咱们通过重写这个属性,将其用debounce
函数包装起来,这样在函数调用follow
时理论调用的是包装后的函数
通过下面的三步,咱们就实现了类属性下面可应用的装璜器,同时将其利用到了类属性下面
在 class
上应用装璜器
装璜器不仅能够利用到类属性下面,还能够间接利用到类下面,比方我心愿能够实现一个相似 Vue
混入那样的性能,给一个类混入一些办法属性,应该如何去做呢?
// 这个是要混入的对象
const methods = {logger() {console.log('记录日志')
}
}
// 这个是一个登陆登出类
class Login{login() {}
logout() {}
}
如何将下面的 methods
混入到 Login
中,首先咱们先实现一个类装璜器
function mixins(obj) {return function (target) {Object.assign(target.prototype, obj)
}
}
// 而后通过装璜器混入
@mixins(methods)
class Login{login() {}
logout() {}
}
这样就实现了类装璜器。对于类装璜器,只有一个参数,即target
, 对应的就是这个类自身。
理解完装璜器,咱们接下来看一下如何在 Vue
中应用装璜器。
在 Vue
中应用装璜器
应用 ts
开发 Vue
的同学肯定对 vue-property-decorator
不会感到生疏,这个插件提供了许多装璜器,不便大家开发的时候应用,当然本文的中点不是这个插件。其实如果咱们的我的项目没有应用ts
,也是能够应用装璜器的,怎么用呢?
配置根底环境
除了一些老的我的项目,咱们当初个别新建 Vue
我的项目的时候,都会抉择应用脚手架 vue-cli3/4
来新建,这时候新建的我的项目曾经默认反对了装璜器,不须要再配置太多额定的货色,如果你的我的项目应用了 eslint
, 那么须要给eslint
配置以下内容。
parserOptions: {
ecmaFeatures:{
// 反对装璜器
legacyDecorators: true
}
}
应用装璜器
尽管 Vue
的组件,咱们个别书写的时候 export
进来的是一个对象,然而这个并不影响咱们间接在组件中应用装璜器,比方就拿上例中的 log
举例。
function log() {
/**
* @param target 对应 methods 这个对象
* @param name 对应属性办法的名称
* @param descriptor 对应属性办法的修饰符
*/
return function(target, name, descriptor) {console.log(target, name, descriptor)
const fn = descriptor.value
descriptor.value = function(...rest) {console.log(` 这是调用办法【${name}】前打印的日志 `)
fn.call(this, ...rest)
console.log(` 这是调用办法【${name}】后打印的日志 `)
}
}
}
export default {created() {this.getData()
},
methods: {@log()
getData() {console.log('获取数据')
}
}
}
看了下面的代码,是不是发现在 Vue
中应用装璜器还是很简略的,和在 class
的属性下面应用的形式截然不同,但有一点须要留神,在 methods
外面的办法下面应用装璜器,这时候装璜器的 target
对应的是methods
。
除了在 methods
下面能够应用装璜器之外,你也能够在生命周期钩子函数下面应用装璜器,这时候 target
对应的是整个组件对象。
一些罕用的装璜器
上面小编列举了几个小编在我的项目中罕用的几个装璜器,不便大家应用
1. 函数节流与防抖
函数节流与防抖利用场景是比拟广的,个别应用时候会通过 throttle
或debounce
办法对要调用的函数进行包装,当初就能够应用上文说的内容将这两个函数封装成装璜器,防抖节流应用的是 lodash
提供的办法,大家也能够自行实现节流防抖函数哦
import {throttle, debounce} from 'lodash'
/**
* 函数节流装璜器
* @param {number} wait 节流的毫秒
* @param {Object} options 节流选项对象
* [options.leading=true] (boolean): 指定调用在节流开始前。* [options.trailing=true] (boolean): 指定调用在节流完结后。*/
export const throttle = function(wait, options = {}) {return function(target, name, descriptor) {descriptor.value = throttle(descriptor.value, wait, options)
}
}
/**
* 函数防抖装璜器
* @param {number} wait 须要提早的毫秒数。* @param {Object} options 选项对象
* [options.leading=false] (boolean): 指定在提早开始前调用。* [options.maxWait] (number): 设置 func 容许被提早的最大值。* [options.trailing=true] (boolean): 指定在提早完结后调用。*/
export const debounce = function(wait, options = {}) {return function(target, name, descriptor) {descriptor.value = debounce(descriptor.value, wait, options)
}
}
封装完之后,在组件中应用
import {debounce} from '@/decorator'
export default {
methods:{@debounce(100)
resize(){}
}
}
2. loading
在加载数据的时候,为了个用户一个敌对的提醒,同时避免用户持续操作,个别会在申请前显示一个 loading, 而后在申请完结之后关掉 loading,个别写法如下
export default {
methods:{async getData() {const loading = Toast.loading()
try{const data = await loadData()
// 其余操作
}catch(error){
// 异样解决
Toast.fail('加载失败');
}finally{loading.clear()
}
}
}
}
咱们能够把下面的 loading
的逻辑应用装璜器从新封装,如下代码
import {Toast} from 'vant'
/**
* loading 装璜器
* @param {*} message 提示信息
* @param {function} errorFn 异样解决逻辑
*/
export const loading = function(message = '加载中...', errorFn = function() {}) {return function(target, name, descriptor) {
const fn = descriptor.value
descriptor.value = async function(...rest) {
const loading = Toast.loading({
message: message,
forbidClick: true
})
try {return await fn.call(this, ...rest)
} catch (error) {
// 在调用失败,且用户自定义失败的回调函数时,则执行
errorFn && errorFn.call(this, error, ...rest)
console.error(error)
} finally {loading.clear()
}
}
}
}
而后革新下面的组件代码
export default {
methods:{@loading('加载中')
async getData() {
try{const data = await loadData()
// 其余操作
}catch(error){
// 异样解决
Toast.fail('加载失败');
}
}
}
}
3. 确认框
当你点击删除按钮的时候,个别都须要弹出一个提示框让用户确认是否删除,这时候惯例写法可能是这样的
import {Dialog} from 'vant'
export default {
methods: {deleteData() {
Dialog.confirm({
title: '提醒',
message: '确定要删除数据,此操作不可回退。'
}).then(() => {console.log('在这里做删除操作')
})
}
}
}
咱们能够把下面确认的过程提出来做成装璜器,如下代码
import {Dialog} from 'vant'
/**
* 确认提示框装璜器
* @param {*} message 提示信息
* @param {*} title 题目
* @param {*} cancelFn 勾销回调函数
*/
export function confirm(
message = '确定要删除数据,此操作不可回退。',
title = '提醒',
cancelFn = function() {}
) {return function(target, name, descriptor) {
const originFn = descriptor.value
descriptor.value = async function(...rest) {
try {
await Dialog.confirm({
message,
title: title
})
originFn.apply(this, rest)
} catch (error) {cancelFn && cancelFn(error)
}
}
}
}
而后再应用确认框的时候,就能够这样应用了
export default {
methods: {
// 能够不传参,应用默认参数
@confirm()
deleteData() {console.log('在这里做删除操作')
}
}
}
是不是霎时简略多了, 当然还能够持续封装很多很多的装璜器,因为文章内容无限,临时提供这三个。
装璜器组合应用
在下面咱们将类属性下面应用装璜器的时候,说道装璜器能够组合应用,在 Vue
组件下面应用也是一样的,比方咱们心愿在确认删除之后,调用接口时候呈现loading
,就能够这样写(肯定要留神程序)
export default {
methods: {@confirm()
@loading()
async deleteData() {await delete()
}
}
}
本节定义的装璜器,均已利用到这个我的项目中 https://github.com/snowzijun/vue-vant-base, 这是一个基于
Vant
开发的开箱即用挪动端框架,你只须要fork
下来,无需做任何配置就能够间接进行业务开发,欢送应用,喜爱麻烦给一个star
。
我是子君,明天就写这么多,本文首发于【前端有的玩】,这是一个专一于前端技术,前端面试相干的公众号,同时关注之后即刻拉你退出前端交换群,咱们一起聊前端,欢送关注。
结语
不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。——文森特・梵高