产品上线事繁多,测试产品催不离。
休问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.nameobj.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
。
我是子君,明天就写这么多,本文首发于【前端有的玩】,这是一个专一于前端技术,前端面试相干的公众号,同时关注之后即刻拉你退出前端交换群,咱们一起聊前端,欢送关注。
结语
不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高