JavaScript 设计模式核⼼原理与应⽤实践 之 结构型设计模式
装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求”。 这个和Python 中的装饰器是一样的概念,在Javascript中 对对象使用就是在原有的方法上,加上了一层逻辑
应用场景
先简单说一下装饰器的应用场景,如何在开发过程中,我们需要在业务逻辑上加一层是否登录的判断 ——弹窗。简单方法如下
// 首先先使用闭包的概念去 定义构造函数,这也是为了不会每次调用方法都会重新生成一个全新的弹窗。
const Modal = (function () {
let modal = null
return function () {
if (!modal) {
modal = document.createElement('div')
modal.innerHTML = '您还未登陆,请先登录!'
modal.id = 'login-modal'
model.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
// Modal 就是生成弹窗组件的 构造函数、下面就先定义一个modal 弹窗,后续就是基本进行对该 弹窗实例进行修改
const LoginModal = new Modal()
document.querySelector('#open').addEventListener('click', function () {
LoginModal.style.display = 'block'
})
document.querySelector('#close').addEventListener('click', function () {
LoginModal.style.display = 'none'
})
以上就基本完成了 简单的业务逻辑, 验证弹窗的 显示和隐藏。
但是如果 后续 产品又说 这里的文案需要修改, 或者这个弹窗的一些配置需要修改,如 弹窗上的按钮置灰等 一系列的 特殊化处理。
针对以上的 弹窗特殊化,解决方法有很多,比如我们首先会想到的 弹窗 构造函数的传参,可以提供参数的传递来进行处理,有一套通用的特殊处理。但是,这样的话,该构造函数 里面就会有加了一层特殊化逻辑上去。这里的话,就引出《装饰器》的概念。
“只添加,不修改”的装饰器模式
ES6中,我们可以以一种更加面向对象化的方式去写
// 首先先定义, 但是为了继续引入闭包的概念,实例化弹窗的方法 还是依旧是Modal
const Modal = (function () {
let modal = null
return function () {
if (!modal) {
modal = document.createElement('div')
modal.innerHTML = '您还未登陆,请先登录!'
modal.id = 'login-modal'
modal.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
// ModalControl 就是简单对 Modal进行封装,并提供了 open 和 close的方法
class ModalControl {
constructor () {
this._modal = this.createModal()
}
createModal () {
return new Modal()
}
openModal () {
this._modal.style.display = 'block'
}
closeModal () {
this._modal.style.display = 'none'
}
}
// 简单装饰器
class ModalControlDecorator {
constructor (ModalControl) {
this._modalControl = new ModalControl()
this._modal = this._modalControl._modal
this.somethingChange()
}
somethingChange () {
this._modal.innerHTML += 'ModalControlDecorator 发生了改变'
}
openModal () {
this._modal.style.display = 'block'
}
closeModal () {
this._modal.style.display = 'none'
}
}
// 实例化 装饰器后的 弹窗
const mcd = new ModalControlDecorator(ModalControl)
大家这里需要特别关注一下 ES6 这个版本的实现。在 ES7 中,Decorator 作为一种语法被直接支持了,它的书写会变得更加简单,但背后的原理其实与此大同小异
ES7 的装饰器
在 ES7 中,我们可以像写 python 一样通过一个@语法糖轻松地给一个类装上装饰器
// 装饰器函数,一个参数就是 目标类
function classDecorator (target) {
target.hasDecorator = true
return target
}
// 将装饰器 ‘安装到’ Button 类上去
@classDecorator
class Button {
// Button 的相关逻辑
}
console.log('Button 是否被装饰:', Button.hasDecorator)
装饰器也是可以直接装饰 类方法的
// 定义 类方法装饰器
function funcDecorator (target, name, descriptor) {
console.log(target, name, descriptor) // 这里的 target 就是 Button, name 是onClick
let originMethod = descriptor.value
descriptor.value = function () {
console.log('funcDecorator 装饰器逻辑')
originMethod.apply(this, arguments)
}
return descriptor
}
class Button {
@funcDecorator
onClick () {
console.log('onCLick 原有逻辑')
}
}
let button = new Button()
button.onClick()
注:以上代码直接放进浏览器/Node 中运行会报错,因为浏览器和 Node 目前都不支持装器语法,需要大家安装 Babel 进行转码
安装 Babel 及装饰器相关的 Babel 插件 (注意自己测试的时候 得在文件夹下,生成npm项目
npm init
然后安装依赖)
npm install babel-preset-env babel-plugin-transform-decorators-legacy --save-dev
编写配置文件.babelrc:
{
"presets": ["env"],
"plugins": ["transform-decorators-legacy"]
}
最后别忘了下载全局的 Babel 命令行工具用于转码:
npm install babel-cli -g
执行完这波操作,我们首先是对目标文件进行转码,比如说你的目标文件叫做 test.js,想要把它转码后的结果输出到 babel_test.js,就可以这么写:
--out-file
babel test.js --out-file babel_test.js
生产实践
React中的装饰器:HOC 。 没错React 中大名鼎鼎的 HOC(高阶组件)就是用的装饰器的逻辑。
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
我们现在编写一个高阶组件,它的作用是把传入的组件丢进一个有红色边框的容器里(拓展其样式)
import React, { Component } from 'react'
const BorderHoc = WrappedComponent => class extends Component {
render() {
return <div style={{ border: 'solid 1px red' }}>
<WrappedComponent />
</div>
}
}
export default borderHoc
用它来装饰目标组件
import React, { Component } from 'react'
import BorderHoc from './BorderHoc'
// 用BorderHoc装饰目标组件
@BorderHoc
class TargetComponent extends React.Component {
render() {
// 目标组件具体的业务逻辑
}
}
// export出去的其实是一个被包裹后的组件
export default TargetComponent
可以看出,高阶组件从实现层面来看其实就是上文我们提到的类装饰器。在高阶组件的辅助下,我们不必因为一个小小的拓展而大费周折地编写新组件或者把一个新逻辑重写 N 多次,只需要轻轻 @ 一下装饰器即可