JavaScript设计模式(五):装饰器模式

定义

是一种结构型设计模式。在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求。

装饰函数

装饰函数可以让我们在不修改原方法的情况加对原函数进行包装,增加额外的功能。在日常开发中难免遇到原来的需求现在要增加新的功能,如果原来的代码是自己写的还好,如果是接手别人写的代码,直接修改源代码就很冒风险,甚至有可能导致其他功能异常。

保存原引用方法

1
2
3
4
5
6
7
8
9
function method() {
//...
}
const _method = method1;
function newMethod() {
_method();
// 新功能...
}
newMethod();

以上我们通过中间变量保存原方法的引用,然后把它放进新方法中就可以做到既不修改原方法,还可以添加额外功能。但这种方式存在一些弊端。

1
2
3
4
5
var _getElementById = document.getElementById;
document.getElementById = function (id) {
// ...
return _getElementById(id);
}

document.getElementById 方法内部用到了 this 正常我们调用 document.getElementById 它的内部 this 指向 document
,但我们执行 _getElementById 时,this 指向了 window,这时就会发生报错。

使用 AOP(Aspect Oriented Programming)

1
2
3
4
5
6
7
8
9
10
11
12
const before = function (fn, beforeFn) {
return function () {
// 指向正确的 this
beforeFn.apply(this, arguments);
return fn.apply(this, arguments);
}
}
function beforFn () {
console.log('before');
}
document.getElementById = before(document.getElementById, beforFn);
const button = document.getElementById('button');

把新增的 beforeFn 通过 before 装饰起来,在 before 中会确保 this 指向正确。

使用装饰器模式做表单校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const username = document.getElementById('username');
const password = document.getElementById('password');
const submit = document.getElementById('submit');
const handleSubmit = function () {
if ( username.value === '' ){
return alert ( '用户名不能为空' );
}
if ( password.value === '' ){
return alert ( '密码不能为空' );
}
const params = {
username: username.value,
password: password.value,
}
// 提交 params ...
}
submit.addEventListener('click', handleSubmit);

这是一个用户登录时的一个表单校验,在 handleSubmit 方法中进行了表单校验和整合数据最后提交的过程,这样写会让 handleSubmit 方法过于臃肿,尤其是在表单校验过多或者数据整合逻辑复杂的情况下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const before = function (fn, beforeFn) {
return function () {
if (!beforeFn.apply(this, arguments)) return;
return fn.apply(this, arguments);
}
}
const validate = function () {
if ( username.value === '' ){
alert ( '用户名不能为空' )
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' )
return false;
}
return true;
}
let handleSubmit = function () {
const params = {
username: username.value,
password: password.value,
}
console.log(params)
// 提交 params ...
}
handleSubmit = before(handleSubmit, validate)

首先定义一个 before 用来将表单校验装饰进提交方法,然后把表单校验的过程抽离出来,这样更方便维护,提交方法只需要做提交的逻辑即可。

和代理模式的区别

装饰器模式用起来感觉和代理模式很像,都是在访问或操作一个对象时通过这两种模式来进行一些额外的操作。但两者的目的就不相同,代理模式是因为不希望直接访问或者操作原对象,而是希望通过代理来使用,装饰器模式是原对象不满足我现在的使用需求,但又不好直接修改原对象,只能通过装饰对原对象进行扩展。

小结

装饰器模式在不改变原对象的情况下,给对象动态扩展额外的功能,这点比对象的继承更具优势。另外也更方便功能的移植,因为我们只是在个性化的场景使用装饰强化功能,基础功能不会受到任何影响。


JavaScript设计模式(五):装饰器模式
https://l1ushun.github.io/2023/10/17/desigin-decorator/
作者
liu shun
发布于
2023年10月17日