设计模式:就是将常用的代码套路,归纳总结后系统的表达出来。
鉴于js语言特性以及前端实际开发的应用场景,笔者只在此总结几个经典的设计模式。
#工厂模式
工厂模式是用来创建对象最常用的设计模式,它不暴露创建对象的具体逻辑;而是将所有逻辑封装在函数中,通过这个函数批量化的创建对象。
哪些场景适合工厂模式?
- 对象的构建十分复杂
- 需要依赖具体环境创建不同实例
- 处理大量具有相同属性的小对象
示例1
我们需要一个可以生成用户的构造函数,这个用户的实例可能是普通用户、商户、管理员。每种角色都有对应权限,只可访问有权限的页面。
function createUser(type) {
function User(option) {
this.name = option.name
this.viewPage = option.viewPage
}
switch (type) {
case "user":
return new User({ name: "user", viewPage: ["主页", "设置页"] })
break;
case "merchant":
return new User({ name: "merchant", viewPage: ["主页", "设置页", "XXX页"] })
break;
case "admin":
return new User({ name: "admin", viewPage: ["主页", "设置页", "XXX页", "XXXX页"] })
break;
default:
throw new Error("参数错误")
break;
}
}
console.log(createUser("user"));
示例二
function createUser(role) {
return new createUser.prototype[role]()
}
createUser.prototype.user = function () {
this.name = "user";
this.viewPage = ["主页", "设置页"];
}
createUser.prototype.admin = function () {
this.name = "merchant";
this.viewPage = ["主页", "设置页", "XXX页"];
}
createUser.prototype.merchant = function () {
this.name = "merchant";
this.viewPage = ["主页", "设置页", "XXX页", "XXXX页"];
}
#单例模式
单例模式可以确保一个特定的类最多只能存在一个实例,这意味着你第二次使用相同的类创建实例对象时,应该得到和第一次创建相同的对象,它的实现原理往往是通过闭包。
哪些场景适合单例模式?
- 全局只需要唯一对象的,例如、弹窗、遮罩。包括ES6模块化和CommonJS模块化导出的对象也是唯一对象。
示例一
var singleUser = (function () {
function User (name) {
this.name = name;
}
var instance = null
if(instance) return instance
return instance = new User(Name)
})();
示例二
通过传入新的构造函数得到新的生成单例对象的构造函数
var singleUser = function (fn) {
var instance = null
return function (args) {
if(instance) return instance
return instance = new fn(args)
}
})
#适配器模式
适配器模式是将一个类(对象)的接口(方法或属性)转换成另外一个符合我们使用规范的类(对象)。它可已经将一些不兼容的接口放在一起正常的工作
哪些场景适合适配器模式
- 当我们需要对接口的提供者喝消费者进行兼容的时候,对旧代码的渐进式的改造以及对某些已有老接口的改造。
示例一
当前场景中,有一个110伏电压、三口插头的电源插座,而我们的设备需要的电源为220伏电压、两口插座。所以我们需要一个适配器帮我们完成适配
class Power{
constructor() {
this.serverVoltage = 100
this.serverShape = "triangle"
}
}
class Device{
constructor() {
this.consumerVoletage = 220
this.consumerShape = "douple"
}
usePower(power){
if(power) throw new Error("请接入电源")
if(power.serverVoltage!==220||power.serverShape!=="douple) throw new Error("电源不符合规范“)
}
}
class Adapter {
constructor() {
this.serverVoltage = 220
this.serverShape = "douple"
}
usePower(power){
if(power) throw new Error("请接入电源")
if(power.serverVoltage!==100||power.serverShape!=="triangle) throw new Error("电源不符合规范“)
this.consumerVoletage = 100
this.consumerShape = "triangle"
}
}
var myPower = new Power()
var myDevice = new Device()
var myAdapter = new Adapter()
myAdapter.usePwer(myPower)
myDevice.usePower(myAdapter)
#装饰器模式
装饰器模式是指向现有的对象添加新的新的功能,同时又不改变其结构。使多个不同的类或对象之间共享或拓展一些方法或行为。
哪些场景适合适配器模式
- 装饰器可以极大的提高我们的开发效率,通过对非业务逻辑代码的封装快速完成可复用工作,例如React的高阶组件、Vue的TS语法,但是装饰器不要滥用 否则会让我们的代码混乱。
装饰器语法
- 饰器装饰类时,装饰器传入的参数仅有一个,参数为被装饰的类的实例
- 饰器装饰类实例的方法时,会获取三个参数:target(类的原型对象)、name(被装饰的方法名)、descriptor(属性描述符);属性描述符:value(属性值)、enumberable(是否可配置)、configurable(是否可配置)、writable(是否可重写)
示例一
使用js装饰器需要通过babel将这个高语语法进行转义变成浏览器能够识别的es2015语法
新建项目文件
/src、/src/index.html、/src/index.js
/src/index.js
function addSayHi(target){
target.prototype.sayHi = function() {console.log("你好方法被调用“)}
}
@addSayHi
class Person {
constructor(name){
this.name = ame
}
}
var zs = new Person("张三")
zs.sayHi()
安装babel依赖
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
安装解析装饰器相关的插件
npm install --save-dev @babel/plugin-proposal-decorators
npm install --save-dev @babel/plugin-proposal-class-properties
配置babel
在项目根目录新建babel.config.json
{
"presets": [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage",
"corejs": "3.6.5",
}
]
],
// 用来解析装饰器和Class语法的插件
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
配置npm指令
"script":{
"build":"babel index.js -o build.js" //将index.js解析编译到build.js中
}
示例二
function useTool(target,name,descriptor){
let oldValue = descriptor
descriptor.value = function() {
console.log("这是新方法")
return oldValue.apply(this,arguments)
}
}
class Tool {
constructor() {}
add(a,b){
return a+b
}
}
#代理模式
代理模式为一个对象提供另外一个对象,通过这个代理对象来控制对这个对象的访问。
proxy对象
proxy可以实现对一个对象的代理功能,proxy构造函数需要传入两个参数:target(需要被代理的源对象),handle(代理配置对象);handle具有两个拦截器方法,set(修改源对象属性时会被触发),get(访问源对象属性时会被触发)。set和get拦截器的都有两个形参:target(当前操作的源对象),key(当前操作的属性名)
哪些场景适合代理模式?
- 调用对象需要被保护,我们通过代理对象来完成程序的逻辑操作。将代理对象与调用对象分离开降低系统上的耦合度。
示例1
var gege = {
name:"哥哥",
isHandsome:true
}
var proxy= new Proxy(gege,{
get(target,key) {
if(key=="isHandsome"&&!target[key]) {
console.log("不,我们的哥哥很帅")
return true
}
return target[key]
},
set(target,key,value) {
if(key=="isHandsome"&&!value){
console.log("修改成功,但是哥哥还是最帅的")
}
target[key] = value
}
})
代理模式、装饰器模式、适配器模式的区别?
适配器模式提供了不同的新接口,通常用作接口的兼容处理 代理模式提供了一模一样上的接口,对行为进行拦截 装饰器模式直接访问原接口,对原接口进行改造。
#外观模式
外观模式就是对不同接口的封装,实现了所有接口的兼容处理。例如DOM绑定事件的兼容处理。
示例1
function addEvent(DOM,type,handle) {
if(DOM.addEventListener) return DOM.addEventListener(type,handle,false)
if(DOM.attachEvent) return DOM.attachEvent("on"+type,handle)
DOM["on"+type] = handle
}
#观察者模式
观察者模式定义了对象与对象间的一种依赖关系,只要当一个对象的状态发生变化时,所有依赖它的对象会得到通知并被自动更新。
示例1
// 创建发布者的类
class Publisher{
// 实例的私有状态
_state = null
// 该发布者实例的订阅者列表
substribers = null
get state() {
return this._state
}
// 当时state状态更新时,调用notify通知所有订阅者
set state (value) {
this._state = value
this.notify({ key:"state",value})
}
notify(newState) {
if(!this.substribers) return
this.substribers.forEach(substriber =>substriber.update(newState));
}
// collect用来添加订阅者
collect(substriber){
if(!this.substribers) return this.substribers = [substriber]
this.substribers.push(substriber)
}
}
let id = 0
// 创建订阅者的类
class Substriber{
publishers = null
subId = ++id
// 创建订阅者时需要传入被订阅的发布者
constructor(publisher) {
this.subscribe(publisher)
}
// subscribe用来添加订阅
subscribe(publisher) {
publisher.collect(this)
if(!this.publishers) return this.publishers = [publisher]
this.publishers.push(publisher)
}
// 订阅对象的状态被更改时执行的逻辑
update(data) {
console.log('发布新动态'+this.subId+"-接收到新属性",data);
}
}
#迭代器模式
迭代器模式 是指按照一中方法顺序的访问同一个聚合对象的各个元素,不用关心对象的内部结构,也可以按顺序访问各个元素。
迭代器分为内部迭代器和外部迭代器
内部迭代器示例
let arr = [1,2,3,4,5,6,7]
let each = function(arr,callback) {
for(let index = 0;index<arr.length;index++){
callback.call(null,i,arr[i])
}
}
each(arr,function(i,value) {console.log("当前索引"+i+"当前值"+value)})
外部迭代器示例
class Iterator{
constructor(list) {
this.list = list
this.index = 0
}
next() {
// 每次迭代器调用next方法返回最新的值和 迭代完成的状态
return {
value:this.list[this.index++],
done:this.index > this.list.length-1
}
}
}
var myIterator = new Iterator([1,2,3,,4])
myIterator.next()
可迭代对象
ES6提供了迭代器接口,一些对象是默认可以使用的。例如:Array、Set、函数的arguments、NodeList、String。
以上对象都具备Symbol.iterator属性,调用该属性会返回外部迭代器对象,通过调用这个对象的next方法会迭代到下一步。
以上可迭代对象可以使用for...of迭代对象
Generator函数
Generator函数是ES6提供生产可迭代对象的函数,通过它可以实现同步代码写异步的逻辑。 generator生成器:他必须使用function* 来声明,函数的内部的必须使用yield来声明迭代的进度
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
var res1 = foo(1)
var res2 = res1.next()
#状态模式
状态模式是将将主体对象和状态对象分离开,根据主体对象内部的状态分别委托不同的状态对象,每个状态对象都有不同的逻辑。
哪些场景适合状态模式?
- 一个对象的行为取决于它的状态,并且他能在运行的时刻改变的他的行为
- 一个操作中含有大量的if-else语句,并且这些分支语句都依赖于对象的状态
非状态模式
非状态模式 只能通过if判断不同的状态并据此执行不同逻辑。如果我们中间插入新的状态可能整个程序代码都有影响。
class Clame {
this.state = "off"
pressButton () {
if(this.state === "off") {
console.log("切换到弱光模式")
this.state = "weakLight"
} else if(this.state === "weakLight") {
console.log("切换到强光模式")
this.state = "strongLight"
} else if(this.state === "strongLight") {
console.log("切换到关闭模式")
this.state = "off"
}
}
}
状态模式
class Lamp {
this.state = FSM.offLight
pressButton() {
this.state.trigger.call(this)
}
}
/* FSM:有限状态机
* 1、状态的总数是有限的
* 2、任意时刻只会处于一个状态
* 3、在某些情况下会从一种状态变为另外一种状态
var FSM = {
offLight:{
trigger() {
console.log("切换到弱光模式")
this.state = FSM.weakLight
}
},
weakLight:{
trigger() {
console.log("切换到强光模式")
this.state = FSM.strongLight
}
},
strongLight:{
trigger() {
console.log("切换到关灯模式")
this.state = FSM.offLight
}
},
}
#享元模式
享元模式是将对象的公共部分抽离出来并做缓存处理,当创建大量对象可以节省内存开销。
哪些场景适合享元模式?
- 页面中的表格,如果存在大量表格DOM,可以将他们抽离出来看上去是在滚动实际只改变当前单元格的内容。
示例
// IPHONE示例除了SN每个都是唯一的以外,型号、屏幕尺寸、内存是公共的部分
function Iphone (model,screen,memory,SN) {
this.flyWeight = flyWeightFactory.get(model,screen,memory)
this.SN = SN
}
function IphoneFlyWeight(model,screen,memory) {
this.model = model
this.screen = screen
this.memory = memory
}
var flyWeightFactory = ()(
// 通过闭包的函数来做一个函数处理
var = iphones = {}
return {
get(model,screen,memory) {
var key = model+screen+memory
if(key) return iphones[key] = new IphoneFlyWeight(model,screen,memory)
return iphones[key]
}
}
);
#备忘录模式
备忘录模式就是将上次操作的数据保存下来,一旦撤销到上次状态就把上次的数据覆盖回来。
示例
Vuex的浏览器"时光旅行"功能。
#中介者模式
中介者模式是封装的一层调度器,用来处理多对多的映射关系。例如MVC的controller
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!