这是我参与「第四届青训营 」笔记创作活动的的第9天
前端设计模式应用
大家好,这里是刚吃了两个大包子的Vic,今天给大家带来前端设计模式应用课程的相关笔记。
什么是设计模式
首先,我们来解释一下设计模式的相关概念。什么是设计模式呢?设计模式指的是软件设计中常见问题的解决方案模型,它们是历史经验的总结,与特定语言无关。
常见的设计模式一共有23种,其一共分为三大类:
- 创建型 -- 如何创建一个对象
- 结构型 -- 如何灵活地将对象组装成较大的结构
- 行为型 -- 负责对象间的高效通信和职责划分
浏览器中的设计模式
下面我们来看一下浏览器中的设计模式,浏览器中的设计模式有两种,分别为单例模式和发布订阅模式。
单例模式的设计模式属于创建型,它的特点是只有一个实例,并且提供一个全局唯一的访问对象。在我们的浏览器中对应的就是window
对象。这种设计模式的应用场景一般在缓存或全局状态管理等。
如图所示就是一个单例模式实现请求缓存的例子:
下面来解释一下这个代码,在第一段代码中,我们定义了一个Requset
类,在这个类中定义了一个静态属性instance
,同时还定义了一个私有的用于记录的缓存cache
、一个用于获取instance
实例的静态方法getInstance
(如果实例存在就返回实例,如果不存在就构造一个实例请求)、一个异步请求request
(如果缓存中存在URL对应的对象则返回该对象,否则请求URL的借口并将返回的内容存入缓存并返回)。
在第二段代码中,我们有两个测试函数,第一个函数中为没有缓存的情况下的速度,第二个函数中为有缓存情况下的速度。
由于上面的代码采用类的形式,看着会比较麻烦,下面给出一个函数式的代码,含义是一样的。
发布订阅模式属于行为型模式,它是一种订阅机制,可以在被订阅对象发生变化的时候通知订阅者。其应用的场景比较广泛,有系统架构之间的解耦、业务中像邮件订阅、上线订阅等的实现。
这是一段伪代码,代码中给一个按键绑定一个点击事件,这个点击事件触发了两个函数,第一个函数负责将消息发送给使用者,第二个函数负责登录。简单地说这就是一个上线订阅的伪代码。我们来看下面的一段代码:
在第一段代码中我们定义了一个类User
,在这个类中有三个属性分别为name
、status
、followers
,包含了两个函数,分别用于添加订阅与更改上线状态。第二段代码中就是这个User类的使用。
JavaScript中的设计模式
JavaScript中的设计模式比较典型的有三种,分别为原型模式、代理模式、迭代器模式。
原型模式属于创建型模式,其主要思想是复制已有对象来创建新的对象,这也是JS中对象创建的基本模式。
上面的代码比较简单,在这里就不做过多解释。
代理模式属于结构型模式,其可以自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理。一般来说在一些监控、代理工具、前端框架实现等应用场景中使用。
迭代器模式属于行为型模式,其可以在不暴露数据类型的情况下访问集合中的数据,其应用场景为数据结构中的多种数据类型,如列表、树等,提供了一些通用操作借口。
在这段代码中我们以哈希表和集合为例展示了不暴露数据类型情况下使用for...of...
访问其中的数据。
前端框架中的设计模式
前端框架中的代理模式分为两种,分别为代理模式、组合模式。
代理模式属于结构型模式,比较经典的案例就是前端框架中对DOM操作的代理,在不使用前端框架的情况下,当我们更新DOM属性后,视图会立刻更新。而在使用了前端框架的情况下,我们会先更新虚拟DOM,再通过diff算法对视图进行更新。
如图所示就是一个Vue框架下,使用生命周期函数钩子的更新前后对比。
组合模式属于结构型模式,此模式可以多个对象组合使用,也可以单个对象独立使用。比较典型的应用场景就是DOM、前端组件、文件目录等。
如图所示就是一个react的组件结构。
练习题
使用组件模式实现一个文件夹结构
// 文件夹及其方法
const Folder = function (name) {
this.name = name
this.parent = null
this.files = []
}
Folder.prototype.add = function (file) {
file.parent = this
this.files.push(file)
}
Folder.prototype.scan = function () {
console.log('开始对文件夹 ' + this.name + ' 进行扫描')
for (let i = 0, file, files = this.files; file = files[i++];) {
file.scan()
}
}
Folder.prototype.remove = function () {
if (!this.parent) {
return
}
for (let files = this.parent.files, len = files.length - 1; len >= 0; len--) {
let file = files[len]
if(file === this) {
files.splice(len, 1)
}
}
}
// 文件及其方法
const File = function () {
this.name = name
this.parent = null
}
File.prototype.add = function () {
throw new Error('这是一个文件,下面不允许添加文件')
}
File.prototype.remove = function () {
if(!this.parent) {
return
}
for (let files = this.parent.files, len = files.length - 1; len >= 0; len--) {
let file = files[len]
if (file === this) {
file.splice(len, 1)
}
}
}
File.prototype.scan = function () {
console.log('开始扫描 ' + this.name + '文件')
}
let folder1 = new Folder('一级目录')
let folder2 = new Folder('二级目录')
let folder3 = new Folder('三级目录')
let file1 = new File('文件1')
let file2 = new File('文件2')
let file3 = new File('文件3')
let file4 = new File('文件4')
folder1.add(file1)
folder1.add(folder2)
folder2.add(file2)
folder2.add(file3)
folder3.add(file4)
folder1.scan()
folder2.remove()
folder1.scan()