设计模式是我们在解决一些问题的时候 ,针对特定的问题给出的简介并且优化的处理方案
这篇文章说提及到的 JavaScript 设计模式将围绕着封装方法类来展开叙述
构造器模式
构造器模式本质就是我们平常在编码中常用的封装方法,重复利用构造函数
// 这是一个简单的构造器,我们创建实例的时候需要传递给他俩个参数,这俩个参数就是它实例的属性
// 这个构造器中还有一个方法,也就是它的实例所拥有的功能
function Constructors(user_name, user_age) {
this.user_name = user_name
this.user_age = user_age
this.clickFunc = () => {
console.log(`${this.user_name}---${this.user_age}`)
}
}
// 我们创建了 Constructors 的实例
const Constructor_01 = new Constructors("brave-airPig", 22)
const Constructor_02 = new Constructors("airPig", 21)
// 我们使用通过 Constructors 构造器产生的实例的方法
Constructor_01.clickFunc()
Constructor_02.clickFunc()
// brave-airPig---22
// airPig---21
看似没有问题的代码,如果我们仔细观察会发现 clickFunc 方法在每次创建实例的时候创建这样一个相同的方法,属实是没有必要的 😑,我们有没有一种方法可以只创建一次该方法,便可以让所有实例共享呢?
原型模式
这次我们在构造器函数的原型上挂载该方法,这样就不需要每次创建实例都创建一个方法去浪费内存了,这里提到了一个关键字 prototype
,这是 JavaScript 语言中对象的原型,我们直接挂载到其原型上,这样就可以完美的解决这个问题了
function Constructors(user_name, user_age) {
this.user_name = user_name
this.user_age = user_age
}
Constructors.prototype.clickFunc = function () {
console.log(`${this.user_name}--${this.user_age}`)
}
const Constructor_01 = new Constructors("brave-airPig", 22)
Constructor_01.clickFunc()
// brave-airPig--22
ES6类的写法:
使用类的书写好像更好看,逻辑更清楚一些,类中的方法本身就是在其原型上的,所以也不需要进行单独挂载了
class Constructors {
constructor(user_name, user_age) {
this.user_name = user_name
this.user_age = user_age
}
clickFunc() {
console.log(`${this.user_name}---${this.user_age}`)
}
}
const Constructor_01 = new Constructors("brave-airPig", 22)
const Constructor_02 = new Constructors("airPig", 21)
Constructor_01.clickFunc()
Constructor_02.clickFunc()
在构造器与原型俩种模式的选择中,我们的代码不知不觉就被优化了,性能也相应提升了不少
工厂模式
工厂模式是由一个工厂对象决定创建某一种产品对象类的实例,主要用来创建同一类对象,也就是工厂的流水线,生产出来的都是一摸一样的物品
我们只需要一个正确的参数即可获取到所需对象,我们不需要知道创建的具体细节,但是在函数内部包含了所有的创建逻辑以及代码,每增加新的构造函数还需要修改判断逻辑代码,当我们对象很多的时候,这个工厂将会很庞大,不好维护,所以简单的工厂模式只能作用于创建的对象数量少,对象的创建逻辑不复杂的时候使用
比如下面这个例子,假如我们的系统有很多种客户角色,每一种客户所能使用的功能和看到的内容是不一样的,就像用户只能在系统中执行 func1
这个功能,而超级管理员则可以在该系统中使用 4 个功能或者说是权限,具体实现的话我们不用担心,我们可以将这个功能数组进行循环创建 Tabbar ,这样的话,没有权限压根就看不到该 Tabbar,也就无法点击以及使用其中的功能了
function UserFactory(role) {
function User(role, pages) {
this.role = role
this.pages = pages
}
switch (role) {
case "superadmin":
return new User("超级管理员", ["fun1", "fun2", "fun3", "fun4"])
case "admin":
return new User("管理员", ["fun1", "fun2", "fun3"])
case "loweradmin":
return new User("低级管理员", ["fun1", "fun2"])
case "users":
return new User("用户", ["fun1"])
default:
throw new Error("参数错误!")
}
}
console.log(UserFactory("users"))
console.log(UserFactory("superadmin"))
// User { role: '用户', pages: [ 'fun1' ] }
// User { role: '超级管理员', pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ] }
这样我们就可以根据对应的身份去做对应的权限分配了 🦄
ES6写法:
class User {
constructor(role, pages) {
this.role = role
this.pages = pages
}
static UserFactory(role) {
switch (role) {
case "superadmin":
return new User("超级管理员", ["fun1", "fun2", "fun3", "fun4"])
case "admin":
return new User("管理员", ["fun1", "fun2", "fun3"])
case "loweradmin":
return new User("低级管理员", ["fun1", "fun2"])
case "users":
return new User("用户", ["fun1"])
default:
throw new Error("参数错误!")
}
}
}
console.log(User.UserFactory("users"))
console.log(User.UserFactory("superadmin"))
// User { role: '用户', pages: [ 'fun1' ] }
// User { role: '超级管理员', pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ] }
抽象工厂模式
这也是针对工厂模式无法应对多对象多复杂对象而出的一种模式,抽象工厂模式并不是直接生成实例,而是对于产品的一个分类的创建
// 抽象父类
class User {
constructor(name, role, pages) {
this.name = name
this.role = role
this.pages = pages
}
//登录功能
login() {
console.log(`尊敬的${this.name},欢迎回来`)
}
// 指定用户权限对应显示的数据
dataShow() {
throw new Error("请实现抽象方法")
}
}
// 超级管理员子类继承 User 父类
class SupeRadmin extends User {
constructor(name) {
super(name, "superadmin", ["fun1", "fun2", "fun3", "fun4"])
}
dataShow() {
console.log("超级管理员可视数据")
}
addTest() {
console.log("超级管理员独有的方法")
}
}
// 管理员子类继承 User 父类
class Admin extends User {
constructor(name) {
super(name, "admin", ["fun1", "fun2", "fun3"])
}
dataShow() {
console.log("管理员可视数据")
}
}
// 低级管理员子类继承 User 父类
class LowerRadmin extends User {
constructor(name) {
super(name, "loweradmin", ["fun1", "fun2"])
}
dataShow() {
console.log("低级管理员可视数据")
}
}
// 普通用户子类继承 User 父类
class Users extends User {
constructor(name) {
super(name, "users", ["fun1"])
}
dataShow() {
console.log("用户可视数据")
}
usersTest() {
console.log("用户特有方法")
}
}
// 构造方法,通过这个鉴权方法来判定我们应该实例化那个子类或者说实例,然后展现对应权限的数据以及实现相应的功能
function getAbstractUserFactory(role) {
switch (role) {
case "superadmin":
return new SupeRadmin("超级管理员")
case "admin":
return new Admin("管理员")
case "loweradmin":
return new LowerRadmin("低级管理员")
case "users":
return new Users("用户")
default:
throw new Error("参数错误!")
}
}
console.log(getAbstractUserFactory("superadmin"))
getAbstractUserFactory("users").login()
getAbstractUserFactory("loweradmin").login()
getAbstractUserFactory("superadmin").addTest()
// SupeRadmin {
// name: '超级管理员',
// role: 'superadmin',
// pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ]
// }
// 尊敬的用户,欢迎回来
// 尊敬的低级管理员,欢迎回来
// 超级管理员独有的方法
建造者模式
建造者模式是属于创建型模式的一种,提供了一种创建复杂对象的方式,它将一个复杂的对象的构建与它的表示分离开来,使得同样的构建过程可以创建不同的表示
构建者模式是通过一步一步的创建一个复杂对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户无需指定内部的具体构造细节
// 模拟navbar选项卡的选项与数据从初始化到渲染到视图的简单生命周期
class Navbar {
// 初始化
init() {
console.log("初始化")
}
getData() {
// 模拟ajax异步请求数据
return new Promise((resolve) => {
setTimeout(() => {
resolve()
console.log("请求数据成功")
}, 1500)
})
}
// 将数据渲染到视图
render() {
console.log("渲染")
}
}
class List {
// 初始化
init() {
console.log("初始化")
}
getData() {
// 模拟ajax异步请求数据
return new Promise((resolve) => {
setTimeout(() => {
resolve()
console.log("请求数据成功")
}, 1500)
})
}
// 将数据渲染到视图
render() {
console.log("渲染")
}
}
const navbar = new Navbar()
const list = new List()
// 建造者
class Operator {
async startBuild(build) {
await build.init()
await build.getData()
await build.render()
}
}
const op = new Operator()
op.startBuild(navbar)
op.startBuild(list)
// 初始化
// 初始化
// 请求数据成功
// 渲染
// 请求数据成功
// 渲染
单例模式
单例模式保证一个类仅有一个实例,并且提供一个访问它的全局访问点,解决全局使用的类频繁的创建和销毁,占用内存
ES5构建方法(闭包):
function User(user_name, user_age) {
this.user_name = user_name
this.user_age = user_age
}
let singleton = (function () {
// 使用闭包,让 GC 不会回收该变量 那么该变量也就理所应当的成为了全局变量
let instance = null
return function (user_name, user_age) {
if (!instance) instance = new User(user_name, user_age)
return instance
}
})()
console.log(singleton("airpig", 22))
console.log(singleton("pig", 21))
// User { user_name: 'airpig', user_age: 22 }
// User { user_name: 'airpig', user_age: 22 }
ES6 实现:
class Singleton {
constructor(user_name, user_age) {
if (!Singleton.instance) {
this.user_name = user_name
this.user_age = user_age
Singleton.instance = this
}
return Singleton.instance
}
}
console.log(new Singleton("airpig", 22))
console.log(new Singleton("pig", 21))
// Singleton { user_name: 'airpig', user_age: 22 }
// Singleton { user_name: 'airpig', user_age: 22 }
装饰器模式
装饰器模式能够很好的对已有功能进行拓展,也就是 ”锦上添花“ 的意思
它不会更改原有代码、对其他的业务产生印象、方便我们在较少的改动之下对软件功能进行拓展
Function.prototype.before = function (beforeFunc) {
let _this = this
return function () {
beforeFunc.apply(this, arguments)
return _this.apply(this, arguments)
}
}
Function.prototype.after = function (afterFunc) {
let _this = this
return function () {
const fn = _this.apply(this, arguments)
afterFunc.apply(this, arguments)
return fn
}
}
const testFunc = () => {
console.log("这是一个测试方法")
return "测试方法"
}
const addTest = testFunc
.before(() => {
console.log("测试方法之前执行")
})
.after(() => {
console.log("测试方法之后执行")
})
console.log(testFunc())
console.log("--------------")
console.log(addTest())
// 这是一个测试方法
// 测试方法
// --------------
// 测试方法之前执行
// 这是一个测试方法
// 测试方法之后执行
// 测试方法
适配器模式
适配器不会去改变实现层、实现层不属于它的职责范围,它干涉了抽象过程,外部接口的适配能够让同一个方法适用于多种系统,也就是说它会将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作
// 腾讯地图
class TencentMap {
TencentMapShow() {
console.log("显示腾讯地图")
}
}
class BaiduMap {
BaiduMapShow() {
console.log("显示百度地图")
}
}
// 适配器
class TencentMapAdapter extends TencentMap {
constructor() {
super()
}
// 渲染
render() {
this.TencentMapShow()
}
}
class BaiduMapAdapter extends BaiduMap {
constructor() {
super()
}
// 渲染
render() {
this.BaiduMapShow()
}
}
// 使用者
const renderMap = (mapStyle) => mapStyle.render()
// 调用地图
let userP = "IQQO"
if (!userP === "IQQO") renderMap(new TencentMapAdapter())
else renderMap(new BaiduMapAdapter())
策略模式
策略模式定义了一系列算法,并将每一个算法封装起来。使他们可以相互的替换,并且算法的变化不会影响使用算法的客户,策略模式属于对象行为模式,它通过对算法进行封装把使用算法的责任和算法的实现分隔开来,并委派给不同的对象对这些算法进行管理
该模式主要用来解决多种算法相似的情况下的优化,使用 if...else
所带来的复杂和难以维护使用这种模式即可以解决,算法可以自由进行切换,同时可避免多重 if...else
判断,并具有良好的扩展性
let policy = {
A: (multiple) => multiple * 100,
B: (multiple) => multiple * 10,
C: (multiple) => multiple * 2,
}
const numPolicy = (lv, multiple) => policy[lv](multiple)
console.log(numPolicy("C", 200))
console.log(numPolicy("B", 50))
console.log(numPolicy("A", 60))
// 400
// 500
// 6000
代理模式
代理模式是为其他对象提供一种代理以控制对这个对象的访问,使得代理对象控制具体对象的引用,代理几乎可以是任何对象
// 名为 airPig 的员工跳槽记
let user = {
name: "airPig",
deposit: 2500,
}
let proxy = new Proxy(user, {
// 访问对象将执行 get 方法 -- 模拟去新公司面试
get(target, key) {
if (key === "deposit")
console.log("新公司问你之前工资多少 -- 你回答了:" + target[key])
if (key === "name")
console.log("新公司问你名字 -- 你回答了:" + target[key])
return target[key]
},
// 修改对象属性 执行 set 方法 -- 修改你的工资
set(target, key, value) {
if (key === "deposit") {
console.log("将要修改你的工资")
if (value > target["deposit"])
console.log("修改成功 -- 现在工资为:" + value)
else throw new Error("跳槽不能把工资往小修改!!!")
}
},
})
// proxy.name 新公司问你名字 -- 你回答了:airPig
// proxy.deposit 新公司问你之前工资多少 -- 你回答了:2500
// proxy.deposit = 1500 Error: 不能往小修改
// proxy.deposit = 24000
// 已被修改
// 修改成功 -- 现在工资为:24000
观察者模式
观察者模式包含观察目标和观察者俩类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生了变化,那么所有的观察者也将得到通知
当一个对象的状态发生变化的时候,所有的依赖与它的对象都将得到通知并被自动更新、解决了主题对象与观察者之间功能的耦合,即一个对象状态该改变,通知其他对象
这个模式的有点在于目标者与观察者功能的耦合度降低,可以专注自身功能逻辑,观察者将被动接受更新,在时间上解耦,实时目标者更新状态,不过没有一种模式是完美无缺的,什么场景都合适的,它的缺点是虽然实现了对象之间的依赖关系解耦合,但是却不能对事件通知进行细分管理,如筛选通知、指定主题事件通知等等
class Subject {
constructor() {
this.observer = []
}
// 添加观察者
addObserver = (observer) => this.observer.push(observer)
// 删除观察者
removeObserver = (observer) =>
(this.observer = this.observer.filter((item) => item !== observer))
// 通知观察者更新
notify = () => this.observer.forEach((item) => item.update())
}
class Observer {
constructor(name) {
this.name = name
}
update = () => console.log("update -- 观察者更新了", this.name)
}
const subject = new Subject()
const observer1 = new Observer("观察者1号")
const observer2 = new Observer("观察者2号")
const observer3 = new Observer("观察者3号")
const observer4 = new Observer("观察者4号")
subject.addObserver(observer1)
subject.addObserver(observer2)
subject.addObserver(observer3)
subject.addObserver(observer4)
subject.notify()
console.log("-------------")
subject.removeObserver(observer1)
subject.notify()
// update -- 观察者更新了 观察者1号
// update -- 观察者更新了 观察者2号
// update -- 观察者更新了 观察者3号
// update -- 观察者更新了 观察者4号
// -------------
// update -- 观察者更新了 观察者2号
// update -- 观察者更新了 观察者3号
// update -- 观察者更新了 观察者4号
发布订阅者模式
发布订阅者模式与观察者模式很是相似,不过它们却有着本质的区别
观察者和目标者需要相互知晓
发布者和订阅者不需要相互知晓,它们是通过第三方来实现调度的,属于经过解耦合的观察者模式
const SUBSCRIBER = {
message: {},
// 发布订阅消息
publish(type, data) {
if (!this.message[type]) return
this.message[type].forEach((item) => item(data))
},
// 添加订阅者
subscribe(type, fn) {
if (!this.message[type]) this.message[type] = [fn]
else this.message[type].push(fn)
},
// 删除订阅者
unsubscribe(type, fn) {
if (!this.message[type]) return
// 没有指定删除订阅者就删除全部订阅者
if (!fn) this.message[type] && (this.message[type].length = 0)
else this.message[type] = this.message[type].filter((item) => item !== fn)
},
}
let test_01 = (data) => console.log("test_01", data)
let test_02 = (data) => console.log("test_02", data)
let test_03 = (data) => console.log("test_03", data)
let test_04 = (data) => console.log("test_04", data)
SUBSCRIBER.subscribe("A", test_01)
SUBSCRIBER.subscribe("B", test_02)
SUBSCRIBER.subscribe("A", test_03)
SUBSCRIBER.subscribe("C", test_04)
// 发新闻
SUBSCRIBER.publish("A", "专属订阅A的早报")
SUBSCRIBER.publish("B", "这是给B订阅者的晚报")
// 取消订阅
SUBSCRIBER.unsubscribe("C", test_04)
SUBSCRIBER.publish("C", "发给C订阅者的报纸")
// 删除所有订阅
SUBSCRIBER.unsubscribe("A")
SUBSCRIBER.publish("A", "专属订阅A的早报")
// test_01 专属订阅A的早报
// test_03 专属订阅A的早报
// test_02 这是给B订阅者的晚报
模块模式
模块化模式最初被定义为在传统的软件工程中为类提供私有和公共封装的一种方法,能够使一个单独的对象拥有公共或者私有的方法和变量,从而屏蔽来自全局作用域的特殊部分,这可以减少我们的函数名与在页面中其他脚本或者作用域内定义的函数名冲突的可能性,不过在ES6提出块级作用域的时候就解决了这个问题了
并且在 ES6 中的模块化导入导出也是很好的支持了这个模块化模式,或者我们使用 ES6 类的时候也可以使用 #
来声明一个私有变量
那么 ES6 之前我们都是怎么解决的呢?没错,就是闭包,闭包会使函数作用域一直存在,其中的私有变量也不会出现被 CG
var testMoudle = (function () {
var count = 0
return {
increment: function () {
return ++count
},
reset: function () {
count = 0
},
decrement() {
return --count
},
}
})()
console.log(testMoudle.increment())
console.log(testMoudle.decrement())
testMoudle.reset()
console.log(testMoudle.decrement())
console.log(count)
// 1
// 0
// - 1
// count is not defined
ES6 模块化导入导出:
let count = 0
const increase = () => ++count
const decrease = () => --count
const reset = () => {
count = 0
}
export default {
increase,
decrease,
reset,
}
// 只导出三个方法,变量该脚本私有
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="module">
import MyModule from "./module_export.js"
console.log(MyModule)
</script>
</body>
</html>
第二种导出导入方法:
let count = 0
const increase = () => ++count
const decrease = () => --count
const reset = () => {
count = 0
}
export { increase, decrease, reset }
// 只导出三个方法,变量该脚本私有
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="module">
import { increase, decrease, reset } from "./module_export.js"
console.log(increase(), decrease())
</script>
</body>
</html>
桥接模式
桥接模式就是将抽象部分与它的实现部分进行分离,使它们都可以独立的变化,一个类存在俩个或者多个独立变化的维度,并且这俩个维度都需要进行扩展,有助于独立的管理各组成部分,缺点是每使用一个桥接元素都要增加一次函数的调用,性能降低,提高系统复杂程度
// 模态框的动作
class Toast {
constructor(ele, animation) {
this.ele = ele
this.animation = animation
}
show() {
this.animation.show(this.ele)
}
hide() {
this.animation.hide(this.ele)
}
}
const ANIMATIONS = {
bounce: {
show: (ele) => console.log(`${ele}元素弹跳显示`),
hide: (ele) => console.log(`${ele}元素弹跳隐藏`),
},
silde: {
show: (ele) => console.log(`${ele}元素滑动显示`),
hide: (ele) => console.log(`${ele}元素滑动隐藏`),
},
}
let toast = new Toast("#box", ANIMATIONS.silde)
toast.show()
toast.hide()
let toast2 = new Toast(".div", ANIMATIONS.bounce)
toast2.show()
toast2.hide()
// #box元素滑动显示
// #box元素滑动隐藏
// .div元素弹跳显示
// .div元素弹跳隐藏
组合模式
组合模式就是在对象间形成一个树形结构,组合模式中基本对象和组合对象被一致对待,我们不需要去关心对象有多少层,调用的时候只需要在根部进行一个调用,它模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样去处理复杂元素从而使得客户程序与复杂元素的内部结构进行一个解耦
const FOLDER = function (folder) {
this.folder = folder
this.list = []
}
FOLDER.prototype.addFunc = function (res) {
this.list.push(res)
}
FOLDER.prototype.scanFunc = function () {
console.log(`扫描 ${this.folder} 文件`)
for (let i = 0; i < this.list.length; i++) {
this.list[i].scanFunc()
}
}
const FILE = function (file) {
this.file = file
}
FILE.prototype.scanFunc = function () {
console.log(`开始扫描${this.file}文件`)
}
const Folder = new FOLDER("root")
let folder_02 = new FOLDER("src")
let folder_03 = new FOLDER("components")
let folder_04 = new FOLDER("public")
let folder_05 = new FOLDER("images")
let file_01 = new FILE("test_01.js")
let file_02 = new FILE("test_02.js")
let file_03 = new FILE("test_01.jsx")
let file_04 = new FILE("test_01.html")
let file_05 = new FILE("test_01.jpg")
Folder.addFunc(folder_02)
Folder.addFunc(folder_03)
Folder.addFunc(folder_04)
Folder.addFunc(folder_05)
folder_02.addFunc(file_01)
folder_02.addFunc(file_02)
folder_03.addFunc(file_03)
folder_04.addFunc(file_04)
folder_05.addFunc(file_05)
Folder.scanFunc()
// 扫描 root 文件
// 扫描 src 文件
// 开始扫描test_01.js文件
// 开始扫描test_02.js文件
// 扫描 components 文件
// 开始扫描test_01.jsx文件
// 扫描 public 文件
// 开始扫描test_01.html文件
// 扫描 images 文件
// 开始扫描test_01.jpg文件
命令模式
有时我们需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,需要一种松耦合的方式来设计程序,使得发送者和接受者能够消除彼此之间的耦合
- 发布者:发出命令,调用命令对象,不知道如何执行与谁执行
- 接收者:提供对应接口处理请求,不知道谁发起的请求
- 命令对象:接受命令。调用接收者对应的接口处理发布者的请求
// 接收者
class Receiver {
execute() {
console.log("接收者执行请求")
}
}
// 命令对象
class Command {
constructor(receiver) {
this.receiver = receiver
}
execute() {
console.log("命令对象 --> 接收者 --> 对应接口执行")
this.receiver.execute()
}
}
// 发布者
class Invoker {
constructor(command) {
this.command = command
}
invoker() {
console.log("发布者发布请求")
this.command.execute()
}
}
let store_house = new Receiver()
let order = new Command(store_house)
let client = new Invoker(order)
client.invoker()
// 发布者发布请求
// 命令对象 --> 接收者 --> 对应接口执行
// 接收者执行请求
模板方法模式
模板方法模式由俩部分组成,第一部分是抽象父类,第二部分是具体实现子类,通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序,子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类方法,它是一种典型的通过封装变化提高系统扩展性的设计模式,运用了模板方法模式的程度中,子类方法种类和执行顺序都是不变的,但是子类的方法具体实现则是可变的,父类只是一个模板,子类可以添加不同的功能
let Container = function (param) {
this.render = function (list) {
console.log("渲染列表", list)
}
let getData =
param.getData ||
function () {
throw new Error("必须传递getData方法")
}
let func = new Function()
func.prototype.init = async function () {
let list = await getData()
render(list)
}
return func
}
let Nowplaying = Container({
getData: () => [1, 2, 3],
})
let nowplaying = new Nowplaying()
nowplaying.init()
let Commimgsoon = Container({
getData: () => [4, 5, 6],
})
let commingsoon = new Commimgsoon()
commingsoon.init()
// 渲染列表 [ 1, 2, 3 ]
// 渲染列表 [ 4, 5, 6 ]
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按照顺序访问其中的每个元素
// 统一接口
let MyEach = function (arr, callback) {
for (let i = 0; i < arr.length; i++) {
callback(i, arr[i])
}
}
// 外部调用
MyEach([1, 2, 3, 4, 5, 6, 7, 8, 9], (index, value) => {
console.log(index, value)
})
// 0 1
// 1 2
// 2 3
// 3 4
// 4 5
// 5 6
// 6 7
// 7 8
// 8 9
ES6中,数组等等支持了迭代器,如果我们想让对象使用迭代器的话:
let obj = {
0: "test_01",
1: "test_02",
2: "test_03",
3: "test_04",
4: "test_05",
5: "test_06",
6: "test_07",
7: "test_08",
8: "test_09",
length: 9,
[Symbol.iterator]: Array.prototype[Symbol.iterator],
}
for (let item of obj) {
console.log(item)
}
// test_01
// test_02
// test_03
// test_04
// test_05
// test_06
// test_07
// test_08
// test_09
上面我们的对象使用的是数组的迭代器,让我们继续手写一个精巧的迭代器:
let obj = {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
[Symbol.iterator]: function () {
let index = 0
return {
next: () => {
if (index < this.list.length) {
return { value: this.list[index++], done: false }
} else {
return { value: undefined, done: true }
}
},
}
},
}
let a = obj[Symbol.iterator]()
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: 5, done: false }
// { value: 6, done: false }
// { value: 7, done: false }
// { value: 8, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }
// { value: undefined, done: true }
// { value: undefined, done: true }
职责链模式
使多个对象都有机会处理请求,从而避免了请求的发送者与多个接收者直接的耦合关系,将这些接受连接成一条链,顺着这条链传递该请求,知道找到能处理该请求的对象
这种模式符合单一原则,使每个方法都只有一个职责;符合开放封闭原则,在需求增加时可以很方便的扩充新责任,使用时也无需知晓谁是真正的处理方法,减少了大量的 if
以及 switch
循环判断语句
这种模式的缺点是团队成员需要对责任链存在共识,也就是说这个模式写上别人不容易看懂,排查错误也是不容易的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="text" placeholder="输入符合条件的密码" />
<button>确认</button>
<script>
const ipt = document.querySelector("input")
const btn = document.querySelector("button")
btn.addEventListener("click", () => {
checks.check()
})
function checkEmpty() {
if (!ipt.value.trim()) {
console.log("不能为空")
return
}
return "next"
}
function checkNumber() {
if (Number.isNaN(+ipt.value)) {
console.log("只能为数字")
return
}
return "next"
}
function checkLength() {
if (ipt.value.length < 8) {
console.log("必须大于 8 位")
return
}
return "next"
}
///
class Chain {
constructor(func) {
this.checkRule = func || (() => "next")
this.nextRule = null
}
addRule(nextRule) {
this.nextRule = new Chain(nextRule)
return this.nextRule
}
end() {
this.nextRule = {
check: () => "end",
}
}
check() {
this.checkRule() == "next" ? this.nextRule.check() : null
}
}
const checks = new Chain()
checks.addRule(checkEmpty).addRule(checkNumber).addRule(checkLength).end()
</script>
</body>
</html>
结束了
这么快就结尾了,我想说我们不要为了使用设计模式而去使用设计模式,有时候根本没必要,有时候会画蛇添足,如果使用设计模式可以给你带来代码的整洁可维护或者业务上的性能提升,那么在这个时候我们可以适当考虑使用设计模式,它很棒 😎