利用策略模式与装饰模式重构JavaScript表单验证
本文深入解析如何利用JavaScript中的策略模式与装饰模式重构表单验证逻辑。通过AOP前置装饰解决函数职责耦合问题,利用策略对象管理校验规则以提升扩展性,并结合工厂模式处理多表单场景。文中还详细剖析了闭包、this劫持及数组引用等常见陷阱,提供完整代码与调试经验。
简单表单验证实现
基础表单校验
HTML布局
<!-- validata.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Validata</title>
</head>
<body>
<form id="form">
<label for="username">账号:</label><input id="username" type="text"><br>
<label for="password">密码:</label><input id="password" type="password"><br>
<label for="phonenum">手机:</label><input id="phonenum" type="text"><br>
<input id="submit" type="button" value="提交">
</form>
<p id="warn"></p>
<script src="validata.js"></script>
</body>
</html>

首先实现基础功能
随后引入设计模式完善
// validata.js
var form = document.getElementById('form'),
warn = document.getElementById('warn');
var validata = function(){
if(form.username.value === ''){
return warn.textContent = '账号不能为空';
}
if(form.password.value === ''){
return warn.textContent = '密码不能为空';
}
if(form.phonenum.value === ''){
return warn.textContent = '手机号不能为空';
}
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg); ajax提交数据略
return warn.textContent = '用户信息已成功提交至服务器';
}
form.submit.onclick = function(){
validata();
}
现有代码问题分析
接着分析现有代码
validata函数缺乏复用性,且存在两个缺陷
- 函数混合了验证与提交逻辑,违反单一职责原则
- 验证扩展性不足,添加规则需修改源码,违反开放-封闭原则
因此需进行优化
使用装饰模式重构
装饰模式重构
利用装饰模式解决函数职责过多问题
实现方式简洁
优化AOP前置装饰函数(Function.prototype.before)
若扩展函数(beforeFn)返回false,则中断当前函数执行
将表单验证函数设为提交函数的前置装饰器
提交前执行验证,验证失败则阻止数据提交
var form = document.getElementById('form'),
warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
var self = this;
return function(){
if(beforeFn.apply(this, arguments) === false)
return;
return self.apply(this, arguments);
}
}//改进的AOP前置装饰函数
var validata = function(){
if(form.username.value === ''){
warn.textContent = '账号不能为空';
return false;
}
if(form.password.value === ''){
warn.textContent = '密码不能为空';
return false;
}
if(form.phonenum.value === ''){
warn.textContent = '手机号不能为空';
return false;
}
}
var submitMsg = function(){ //将提交的功能从validata函数中提取出来
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg);
return warn.textContent = '用户信息已成功提交至服务器';
}
submitMsg = submitMsg.before(validata);
//让表单验证函数成为表单提交函数的装饰者
form.submit.onclick = function(){
submitMsg();
};
使用策略模式重构
策略模式重构
解决函数灵活性不足的问题
策略模式需包含策略对象/类及环境对象/类
策略对象应封装校验规则
考虑到页面可能存在多个验证表单
建议采用工厂类模式
完整代码示例如下
var form = document.getElementById('form'),
warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
var self = this;
return function(){
if(beforeFn.apply(this, arguments) === false)
return;
return self.apply(this, arguments);
}
}
var vldStrategy = { //策略对象-验证规则
isNonEmpty: function(value, warnMsg){ //输入不为空
if(value === '')
return warnMsg;
},
isLongEnough: function(value, length, warnMsg){ //输入足够长
if(value.length < length)
return warnMsg;
},
isShortEnough: function(value, length, warnMsg){ //输入足够短
if(value.length > length)
return warnMsg;
},
isMobile: function(value, warnMsg){ //手机号验证
var reg = /^1[3|5|8][0-9]{9}$/;
if(!reg.test(value))
return warnMsg;
}
}
var Validator = function(){ //环境类
this.rules = []; //数组用于存放负责验证的函数
};
Validator.prototype.add = function(domNode, ruleArr){ //添加验证规则
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
var tempArr = strategyArr.concat();
var ruleName = tempArr.shift();
tempArr.unshift(domNode.value);
tempArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, tempArr);
});
})(rule);
}
return this;
};
Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg){
warn.textContent = warnMsg;
return false;
}
}
}
var vld = new Validator();
vld.add(form.username, [
{
strategy: 'isNonEmpty',
warnMsg: '账号不能为空'
},
{
strategy: 'isLongEnough:4',
warnMsg: '账号不能小于4位'
},
{
strategy: 'isShortEnough:20',
warnMsg: '账号不能大于20位'
}
]).add(form.password, [
{
strategy: 'isNonEmpty',
warnMsg: '密码不能为空'
}
]).add(form.phonenum, [
{
strategy: 'isNonEmpty',
warnMsg: '手机号不能为空'
},
{
strategy: 'isMobile',
warnMsg: '手机号格式不正确'
}
]);
var submitMsg = function(){
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg);
return warn.textContent = '用户信息已成功提交至服务器';
}
submitMsg = submitMsg.before(vld.start.bind(vld));
form.submit.onclick = function(){
submitMsg();
};
//这里只是模拟提交,实际应该用form.onsubmit



常见错误与调试分析
问题排查
总结常见错误及开发中遇到的问题
Validator.prototype.add = function(domNode, ruleArr){
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
var tempArr = strategyArr.concat();
var ruleName = tempArr.shift();
tempArr.unshift(domNode.value);
tempArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, tempArr);
});
})(rule);
}
return this;
};
Validator原型链上的add函数需注意几点
首先使用IIFE立即执行函数解决闭包问题,此处不再赘述
函数嵌套导致this指向被劫持,必须缓存this引用
var self = this;
起初未拷贝数组而直接引用strategyArr
Validator.prototype.add = function(domNode, ruleArr){ //添加验证规则
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
// var tempArr = strategyArr.concat();
var ruleName = strategyArr.shift();
strategyArr.unshift(domNode.value);
strategyArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, strategyArr);
});
})(rule);
}
return this;
};
首次提交正常,二次提交报错

因首次提交后闭包内strategyArr状态已变更
后续提交对该数组操作将产生非预期结果
此处疏忽导致断点调试耗时较长 __冏rz
Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg){
warn.textContent = warnMsg;
return false;
}
}
}
修正前的错误代码如下
Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg)
warn.textContent = warnMsg;
return false;
}
}
仅因遗漏一层大括号
原仅一行,添加return false时遗漏大括号
此处为求简洁省略大括号
日常编码严禁此写法,易埋隐患
submitMsg = submitMsg.before(vld.start.bind(vld));
添加装饰者环节亦需留意
未使用bind将导致this劫持并报错
以上为利用策略模式与装饰模式扩展JavaScript表单验证功能的介绍,希望对各位有所帮助,如有任何疑问请留言,小编会及时回复。在此非常感谢大家对本站网站的支持!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
