JS前端中的设计模式和使用场景示例详解

目录
  • 引言
  • 策略模式
    • 1.绩效考核
    • 2.表单验证
    • 策略模式的优缺点:
  • 代理模式
    • 1.图片懒加载:
    • 2.缓存代理
  • 总结

引言

相信大家在日常学习和工作中都多多少少听说/了解/使用过 设计模式,我们都知道,使用恰当的设计模式可以优化我们的代码,那你是否知道对于前端开发哪些 设计模式 是日常工作经常用到或者必须掌握的呢?本文我将带大家一起学习下前端常见的设计模式以及它们的 使用场景!!!

本文主讲:

  • 策略模式
  • 代理模式

适合人群:

  • 前端人员
  • 设计模式小白/想知道如何在项目中使用设计模式

策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。从定义不难看出,策略模式是用来解决那些一个功能有多种方案、根据不同条件输出不同结果且条件很多的场景,而这些场景在我们工作中也经常遇到,接下来我将用几个例子来展示策略模式在哪里用以及如何用。

1.绩效考核

假如我们有这么一个需求,需要根据员工的绩效考核给员工发放年终奖(分为A/B/C/D四个等级 分别对应基础奖金的1/2/3/4倍),我们很容易就写出这样的代码

//level 评级 basicBonus 基础奖金
const computeBonus(level, basicBonus) = () => {
	if(level === 'A') {
        return basicBonus * 1;
    } else if(level === 'B') {
        return basicBonus * 2;
    } else if(level === 'C') {
        return basicBonus * 3;
    } else if(level === 'D') {
        return basicBonus * 4;
    }
}
computeBonus('A', 1000);//1000

我们发现,以上的代码可以轻松实现我们的需求,但是这些代码存在什么问题呢?

  • computedBonus方法十分臃肿,包含太多if-else
  • 拓展性差,后续如果想要更改评级或者规则都需要进入该函数内部调整。
  • 复用性差。

那策略模式是怎么解决这些问题的呢?我们都知道,设计模式的核心之一就是将可变的和不可变的部分抽离分装,那我们根据这个原则来修改我们的代码,其中可变的就是如何使用这些算法(多少个评级),不变的是算法的内容(评级对应的奖金),下面就是改变后的代码

//定义策略类
const strategies = {
    'A': function(basicBonus) {
        return basicBonus * 1;
    },
    'B': function(basicBonus) {
        return basicBonus * 2;
    },
    'C': function(basicBonus) {
        return basicBonus * 3;
    },
    'D': function(basicBonus) {
        return basicBonus * 4;
    },
}
//使用策略类
const computeBonus = (level, basicBonus) {
    return strategies[level](basicBonus);
}
computeBouns('A', 1000);//1000

从上面可以看出,我们将每种情况都单独弄成一个策略,然后根据传入评级和奖金计算年终奖,这样我们的computeBonus方法代码量大大减少,也不用冗杂的if-else分支,同时,如果我们想要改变规则,只需要在strategies中添加对应的策略,增加了代码的健壮性

2.表单验证

我们日常的工作中,不可避免地需要做表单相关的业务,毕竟这是前端最初始的职能之一。而表单绕不开表单验证,那接下来我将带大家看看策略模式在表单中如何使用。

需求: 假设我们有一个登录业务,提交表单前需要做验证,验证规则如下:1.用户名称不能为空,2.密码不能少于6位,3.手机格式要正确。

我们很容易写出以下代码

const verifyForm = (formData) => {
    if(formData.userName == '') {
        console.log('用户名不能为空');
        return false
    };
    if(formData.password.length < 6) {
        console.log('密码长度不能小于6位');
        return false;
    }
    if(( !/(^1[3|5|8][0-9]{9}$)/.test(formData.phone)) {
       console.log('手机格式错误');
    	return false
       }
}

显然,这样也可以完成表单校验的功能,但是这样写同样存在着上面说的问题,接下来,我们看下用策略模式如何改写

//编写策略对象
const strategies = {
	isEmpty: function(value, error) {
        if(value === '' {
           return error;
           })
    },
    minLength: function(value, len, error) {
        if(value.length < len {
           return error;
           })
    },
    isPhone: function(value, error) {
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
 			return errorMsg;
 			}
    };
}
//接下来我们编写实现类 用于生成对应的策略实例
class Validator {
    controustor(cache) {
        this.cache = cache || []; //保存校验规则
    };
    add(dom, rule, error) {
        const arr = rule.splt(':');//分离参数
        this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入 cache
 		const strategy = arr.shift(); // 用户挑选的 strategy
 		arr.unshift( dom.value ); // 把 input 的 value 添加进参数列表
 		arr.push( errorMsg ); // 把 error 添加进参数列表
 		return strategies[ strategy ].apply( dom, ary );
 	});
    };
    start() {
        for ( let i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
 			var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
 				if ( msg ){ // 如果有确切的返回值,说明校验没有通过
 							return msg;
 							}
 				}
    }
}
//编写完策略对象和实例类后我们就可以看看如何使用了
const validataFunc = function(){
 let validator = new Validator(); // 创建一个 validator 对象
 /***************添加一些校验规则****************/
 validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' );
 validator.add( registerForm.password, 'minLength:6', '密码长度不能少于 6 位' );
 validator.add( registerForm.phoneNumber, 'isMobile', '手机号码格式不正确' );
 var errorMsg = validator.start(); // 获得校验结果
 return errorMsg; // 返回校验结果
}
 var registerForm = document.getElementById( 'registerForm' );
 registerForm.onsubmit = function(){
 var errorMsg = validataFunc(); // 如果 errorMsg 有确切的返回值,说明未通过校验
 if ( errorMsg ){
 alert ( errorMsg );
 return false; // 阻止表单提交
 }
};

这样,我们就用策略模式将需求改好了,之后如果我们的校验规则改变了,修改起来也是很方便的,比如:

validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); // 改成:

validator.add( registerForm.userName, 'minLength:10', '用户名长度不能小于 10 位' );

而且,我们也可以给文本框添加多个校验规则,只需要修改下策略对象以及策略方法即可!大大地增强了代码地健壮性。

策略模式的优缺点:

优点:

  • 避免多重条件选择语句(if-else
  • 具有可拓展性,可独立抽离封装,避免重复复制粘贴

缺点:

  • 增加很多策略类或者策略对象,但是这其实不算什么大缺点
  • 比起直接编写业务代码需要思考策略对象以及其他细节

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式应该是我们日常用到比较多的设计模式了(我们日常工作中不知不觉就会用到代理模式,可能你没发现而已)。

代理模式分为保护代理(用于控制不同权限的对象对目标对象的访问)和虚拟代理(把开销很大的对象或者操作延迟到真正需要的时候再去创建 类比引入时动态引入)两种,但是前端基本不用到保护代理,或者说很难实现保护代理,所以大部分情况下我们用的都是虚拟代理,接下来我主要也是讲虚拟代理!

举个例子,加入A想要给C送情书,但是A没有直接把情书交给C,而是让B代为传送情书,那么B就是代理,他的职责就是替A做事,这个就是最简单的代理模式,接下来我们还是老样子,边写需求边讲解

1.图片懒加载:

相信大家对于图片懒加载都不陌生吧,他可以在我们加载出目标图片前预加载占位图片,避免空白区域影响体验,那我们很容易就能写出下面的代码

const lazyImage = (function() {
    let imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    let image = new Image;
    image.onload = function() {
        imgNode.src = image.src;//在这里设置图片的真正路由
    };
    return {
        setSrc: function(src) {
            imgNode.src = '....'//预加载本地图片;
            image.src = src
        }
    }
})()
lazyImage.setSrc('https://olddog.jpg');//加载真正的图片

我们看上面的代码,也可以完成预加载的功能,但是这样的代码存在着什么样的问题呢

  • 违反了单一职责原则,而且耦合度太高,如果后期我们不需要懒加载了,或者需要根据判断条件判断是否懒加载,就不得不去动lazyImage的代码

接下来,我们就用代理模式来改写一下这个例子

const lazyImage = (function() {
    let imageNode = document.createElement('img');
    document.body.appendChild(imageNode);
    return {
        setSrc: function(src) {
            imageNode.src = src;//设置目标src
        }
    }
})()
//代理函数
const proxyImage = (function() {
    let image = new Image;
    image.onload = function() {
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            myImage.setSrc('....')//预加载本地图片
            img.src = src
        }
    }
})()
proxyImage.setSrc('https://olddog.jpg');//使用代理加载

我们观察用代理模式写的代码,发现我们将预加载的逻辑转移到了代理函数中,这样有啥好处呢

  • 如果后期不需要预加载了,只需要取消代理,即将proxyImage.setSrc(...)改成lazyImage.setSrc(...)
  • 代理函数的使用方式和原函数一模一样,使用者不需要知道代理的实现细节也能使用

不知道大家有没有发现,代理函数和原函数有一部分相似的逻辑和操作,只是代理函数的功能更多,这其实也是代理模式的特征之一,代理函数在保证实现原函数的基本功能的前提下实现更多功能,这样即使使用者不清楚逻辑也能直接使用,而且后期改动成本很低,只需要改回原函数的使用即可!!

2.缓存代理

设想一下,如果现在要你写一个简单的求积函数,你会怎么写

const mult = function() {
	let result = 1;
    for(let i = 0, len = arguments.length; i < len; i++) {
        result *= arguments[i];
    }
    return result
}
mult(1, 2, 3);//6

我们来看一下上面的代码有啥缺点,上面的代码虽然实现了求积,但是如果我们mult(1,2,3)之后再去mult(1,2,3),那么系统还是会再计算一遍,这样无疑是性能浪费,那么我们就能用代理模式来改写:

const proxyMult = (function() {
	let cache = {};//缓存计算结果
	return function() {
		const args = Array.prototype.join.call( arguments, ',');
        if(args in cache) {
            return cache[args]
        }
        return cache[args] = mult.apply(this.arguments)
	}
})();
proxyMult(1,2,3);//6
proxyMult(1, 2, 3);//输出6 但是不会重新计算

可以看到,我们用代理模式改写后避免了重复运算的浪费,这只是一种情景,还有其他相似情景,比如我们分页请求数据,可以使用相似的思路,避免对同页的数据重复请求,这在工作中非常有用!!

总结

我们日常工作中还有很多地方用到代理,比如代理合并请求(间断性合并而不是全部合并,减少服务器压力)、惰性加载或创建申请资源等等,而什么时候使用代理其实不需要提前花很多精力去思考,当我们写着写着发现可以抽离使用代理模式的时候再去使用也不迟。由于文章篇幅有限,本文就先讲解策略模式和代理模式,后续将继续更新其他实用的设计模式,喜欢的小伙伴可以点个赞和关注一下,有啥问题可以评论区一起学习交流!

前端设计模式之发布-订阅模式

以上就是JS前端中的设计模式和使用场景示例详解的详细内容,更多关于前端设计模式场景的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS 设计模式之:单例模式定义与实现方法浅析

    本文实例讲述了JS 设计模式之:单例模式定义与实现方法.分享给大家供大家参考,具体如下: 良好的设计模式可以显著提高代码的可读性,降低复杂度和维护成本.笔者打算通过几篇文章通俗地讲一讲常见的或者实用的设计模式. 今天先从最简单的一个入手:单例模式. 文中的示例代码会使用 ES6 语法,尽量简化不必要的细节 概念 单例模式(Singleton)属于创建型的设计模式,它限制我们只能创建单一对象或者某个类的单一实例. 通常情况下,使用该模式是为了控制整个应用程序的状态.在日常的开发中,我们遇到的单例模

  • javascript设计模式之策略模式

    目录 一. 认识策略模式 二. 具体实现和思想 三. 策略模式的实际运用 四. 总结 一. 认识策略模式 策略模式的定义:定义一系列的算法,将他们一个个封装起来,使他们直接可以相互替换. 策略模式是开发中常用的第二种设计模式,它在开发中非常常见,由两部分组成.第一部分是策略类,封装了许多具体的,相似的算法.第二部分是环境类,接受客户请求,随后将请求委托给策略类.说的通俗一点就是将相同算法的函数存放在一个包装里边,每个函数用相同的方式拿出来,就叫做策略模式.下面我们来通过代码实现深入了解一下. 二

  • JS 设计模式之:工厂模式定义与实现方法浅析

    本文实例讲述了JS 设计模式之:工厂模式定义与实现方法.分享给大家供大家参考,具体如下: 前言 上次我们介绍了单例模式,没看过的小伙伴可以看这个链接: 浅析 JS 设计模式之:单例模式 今天来说一说一种常见的设计模式:工厂模式. 工厂模式是一种创建对象的 创建型模式,遵循 DRY(Don't Repeat Yourself)原则.在该模式下,代码将会根据具体的输入或其他既定规则,自行决定创建哪种类型的对象.简单点儿说就是,动态返回需要的实例对象. 回顾上次的例子 让我们继续使用单例模式中的例子,

  • JavaScript设计模式---单例模式详解【四种基本形式】

    本文实例讲述了JavaScript设计模式---单例模式.分享给大家供大家参考,具体如下: 单例模式也称为单体模式,其中: 1,单体模式用于创建命名空间,将系列关联的属性和方法组织成一个逻辑单元,减少全局变量. 逻辑单元中的代码通过单一的变量进行访问. 2,三个特点: ① 该类只有一个实例: ② 该类自行创建该实例,即在该类内部创建自身的实例对象: ③ 向整个系统公开这个实例接口 3,单体模式有四种基本形式: 第一种,最简单的单体,只被实例化一次    我简记为json对象 (1)基本结构 va

  • 详解js中的几种常用设计模式

    工厂模式 function createPerson(name, age){ var o = new Object(); // 创建一个对象 o.name = name; o.age = age; o.sayName = function(){ console.log(this.name) } return o; // 返回这个对象 } var person1 = createPerson('ccc', 18) var person2 = createPerson('www', 18) 工厂函数

  • JS前端中的设计模式和使用场景示例详解

    目录 引言 策略模式 1.绩效考核 2.表单验证 策略模式的优缺点: 代理模式 1.图片懒加载: 2.缓存代理 总结 引言 相信大家在日常学习和工作中都多多少少听说/了解/使用过 设计模式,我们都知道,使用恰当的设计模式可以优化我们的代码,那你是否知道对于前端开发哪些 设计模式 是日常工作经常用到或者必须掌握的呢?本文我将带大家一起学习下前端常见的设计模式以及它们的 使用场景!!! 本文主讲: 策略模式 代理模式 适合人群: 前端人员 设计模式小白/想知道如何在项目中使用设计模式 策略模式 策略

  • JS前端二维数组生成树形结构示例详解

    目录 问题描述 实现步骤 完整代码 问题描述 前端在构建国家的省市区结构时,接口返回的不是树形结构,这个时候就需要我们进行转化.以下数据为例 [ [ { "districtId": 1586533852834, "parentCode": "000", "nodeCode": "000001", "name": "浙江省", "districtType&qu

  • JS前端画布与组件元信息数据流示例详解

    目录 正文 拓展应用状态与静态方法 总结 正文 接下来需要解决两个问题: 可视化搭建的其他业务元素如何与画布交互.比如拓展属性配置面板.图层列表.拖拽添加组件.定位锚点.主题等等. runtimeProps 如何访问到当前组件实例的 props. 这两个问题非常重要,而恰好又可以通过良好的数据流设计一次性解决,接下来让我们分别分析讨论一下. 问题一:可视化搭建的其他业务元素如何与画布交互.比如拓展属性配置面板.图层列表.拖拽添加组件.定位锚点.主题等等 需要设计一个 Hooks API,可以访问

  • JS前端使用canvas动态绘制函数曲线示例详解

    目录 前言 第一步:绘制坐标系 1.如何确定 x 轴和 y 轴的边界值 2.不是传入多少网格数就是多少网格 3.如何让坐标原点位于画布中心 4.刻度总是会有浮点数 第二步:画函数曲线 第三步:绘制辅助线和交点坐标 第四步:平移 第五步:缩放 第六步:动态绘制曲线 第七步:模糊到高清 前言 不说废话,我们直入主题.先来看看读了这篇文章你将得到什么,就是下面这个东西啦(是不是很清晰很顺滑): 那具体要做什么呢,我们来简单拆解一下步骤: 绘制坐标系 绘制多条函数曲线 绘制辅助线和坐标点 支持平移.缩放

  • 详解polyfills如何按需加载及场景示例详解

    目录 前言 青铜时代 火枪时代 webpack添加babel-loader @babel/preset-env @babel/polyfill 配置 useBuiltIns 加入 @babel/plugin-transform-runtime 前言 青铜时代 最使前端头痛的问题,莫过于浏览器兼容性,无论是js,还是css都要考虑浏览器兼容性问题,在webpack出来之前,这无非是一个非常头疼的问题,查到一个兼容性问题,查找很多资料,解决一下,再出来一个问题又要花很长时间解决一下,这无疑要花费很长

  • javascrip高级前端开发常用的几个API示例详解

    目录 MutationObserver API 特点 IntersectionObserver API 举个例子 图片懒加载 无限滚动 getComputedStyle() API 与style的异同 getBoundingClientRect API 应用场景 1.获取 dom 元素相对于网页左上角定位的距离 2.判断元素是否在可视区域内 MutationObserver MutationObserver 是一个可以监听 DOM 结构变化的接口. 当 DOM 对象树发生任何变动时,Mutati

  • Java设计模式之原型设计示例详解

    目录 简单说一下(定义) 稍微夸一下(优缺点) 顺便提一下(适用场景) 着重讲一下(深.浅克隆) 多多用一下(结构.代码实现) 简单说一下(定义) 什么是原型模式:原型模式是用于创建重复的对象,同时又能保证性能.用一个已经创建的实例作为原型,通过复制该原型对象来创建一个或者多个和原型相同或者相似的新对象 举例说明:我们都玩过打飞机的游戏,敌军的飞机可谓是数不胜数,但是如果每出一架敌机都要重新实例化的话,那么自然我们的功能很复杂.所以这个时候我们的原型模式就派上用场了,只实例化一架飞机出来,其他的

  • java设计模式策略模式图文示例详解

    目录 策略模式 意图 问题 解决方案 真实世界类比 策略模式结构 伪代码 策略模式适合应用场景 实现方式 策略模式优缺点 策略模式优缺点 与其他模式的关系 策略模式 亦称:Strategy 意图 策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换. 问题 一天,你打算为游客们创建一款导游程序.该程序的核心功能是提供美观的地图,以帮助用户在任何城市中快速定位. 用户期待的程序新功能是自动路线规划:他们希望输入地址后就能在地图上看到前往目的

  • vue+three.js实现炫酷的3D登陆页面示例详解

    目录 前言: Three.js的基础知识 关于场景 关于光源 关于相机(重要) 关于渲染器 完善效果 创建一个左上角的地球 使地球自转 创建星星 使星星运动 创建云以及运动轨迹 使云运动 完成three.js有关效果 结语 前言: 大家好,我是xx传媒严导(xx这两个字请自行脑补) . 该篇文章用到的主要技术:vue3.three.js 我们先看看成品效果: 高清大图预览(会有些慢): 座机小图预览: 废话不多说,直接进入正题 Three.js的基础知识 想象一下,在一个虚拟的3D世界中都需要什

  • JS技巧动手实现红包兔子雨效果示例详解

    目录 前言 展示效果 技术栈 思考与实现 红包下落效果 生成红包雨 打开红包效果 兔子雨效果 拓展 设置中奖概率 后记 前言 人生天地之间,若白驹过隙,忽然而已.不知不觉中,2022年已然逝去,2023年也过去了半个月了.看到「兔了个兔」这个活动几天了,不过这周比较忙,没时间参与. 心血来潮,捣鼓了一晚上,实现一个兔年的红包雨(兔子雨)效果~ 展示效果 技术栈 Vue2 SCSS:实现红包雨(兔子雨)效果.按钮交互效果等. 思考与实现 首先,思考一下最终的展示效果:在屏幕上会有很多随机下落的兔子

随机推荐