当前位置: 首页 > news >正文

JS 设计模式 - 怎么让你的代码提示一个档次

设计模式是我们在解决一些问题的时候 ,针对特定的问题给出的简介并且优化的处理方案

这篇文章说提及到的 JavaScript 设计模式将围绕着封装方法类来展开叙述

构造器模式

构造器模式本质就是我们平常在编码中常用的封装方法,重复利用构造函数

// 这是一个简单的构造器,我们创建实例的时候需要传递给他俩个参数,这俩个参数就是它实例的属性
// 这个构造器中还有一个方法,也就是它的实例所拥有的功能
function Constructors(user_name, user_age) {this.user_name = user_namethis.user_age = user_agethis.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_namethis.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_namethis.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 = rolethis.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 = rolethis.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 = namethis.role = rolethis.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_namethis.user_age = user_age
}let singleton = (function () {// 使用闭包,让 GC 不会回收该变量 那么该变量也就理所应当的成为了全局变量let instance = nullreturn 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_namethis.user_age = user_ageSingleton.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 = thisreturn function () {beforeFunc.apply(this, arguments)return _this.apply(this, arguments)}
}Function.prototype.after = function (afterFunc) {let _this = thisreturn 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]) returnthis.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 = 0return {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 = 0const 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 = 0const 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 = elethis.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 = folderthis.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 = 0return {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>

在这里插入图片描述


结束了

这么快就结尾了,我想说我们不要为了使用设计模式而去使用设计模式,有时候根本没必要,有时候会画蛇添足,如果使用设计模式可以给你带来代码的整洁可维护或者业务上的性能提升,那么在这个时候我们可以适当考虑使用设计模式,它很棒 😎

相关文章:

JS 设计模式 - 怎么让你的代码提示一个档次

设计模式是我们在解决一些问题的时候 &#xff0c;针对特定的问题给出的简介并且优化的处理方案 这篇文章说提及到的 JavaScript 设计模式将围绕着封装方法类来展开叙述 构造器模式 构造器模式本质就是我们平常在编码中常用的封装方法&#xff0c;重复利用构造函数 // 这是…...

遮挡贴图(Occlusion Map)和微表面贴图(Microsurface Map)

遮挡贴图&#xff08;Occlusion Map&#xff09; 在3D图形学中&#xff0c;遮挡&#xff08;Occlusion&#xff09;是指光被物体挡住。即便是在PBR中&#xff0c;环境光在某些应该被遮挡的地方&#xff0c;也会以古怪的方式被反射。遮挡贴图&#xff08;Occlusion Map&#xff…...

【Vue】基本交互指令

Vue el挂载点 <div id"app">{{message}} </div> <script>var app new Vue({el:"#app",data:{message:"hello"}}) </script>Vue实例的作用范围&#xff1a;管理el选项命中的元素及其内部的后代元素使用其他的选择器&a…...

MySQL 中的 distinct 和 group by 哪个效率更高?

结论先说大致的结论&#xff08;完整结论在文末&#xff09;&#xff1a;在语义相同&#xff0c;有索引的情况下group by和distinct都能使用索引&#xff0c;效率相同。在语义相同&#xff0c;无索引的情况下&#xff1a;distinct效率高于group by。原因是distinct 和 group by…...

Spring 框架源码(六) Bean的生命周期全流程源码解析

Spring框架作为Java王国的地基&#xff0c;我觉得它包含了很多精妙的设计&#xff0c;例如Bean工厂设计、Bean的生命周期、tx、aop、web、mvc等&#xff0c;最核心基本的Bean设计是Spring 的框架的灵魂&#xff0c;本文就Bean的生命周期全流程做源码程度上的解析&#xff0c;欢…...

运维服务商低成本提升服务质量解决方案

在信息化高速发展的今天&#xff0c;网络建设的重要性不言而喻&#xff0c;更多客户选择将运维服务外包或托管给运维服务商&#xff0c;市场需求愈大竞争压力愈大&#xff0c;想要脱颖而出势必要优化自身提高服务质量&#xff0c;最好是低成本、大提升&#xff0c;nVisual助力渠…...

Raft 一致性算法

Raft Raft提供了一种在计算系统集群中分布状态机的通用方法&#xff0c;确保集群中的每个节点都同意一系列相同的状态转换。 一个Raft集群包含若干个服务器节点&#xff0c;通常为5个&#xff0c;这允许整个系统容忍2个节点的失效。每个节点处于以下三种状态之一&#xff1a; …...

驱动程序开发:基于EC20 4G模块自动拨号联网的两种方式(GobiNet工具拨号和PPP工具拨号)

目录一、EC20 4G模块简介二、根据移远官方文档修改EC20 4G模组驱动  1、因为EC20 4G模组min-pice接口其实就是usb接口&#xff0c;因此需要修改Linux内核源码drivers/usb/serial/option.c文件&#xff0c;如下图&#xff1a;  2、根据USB协议的要求&#xff0c;需要在drive…...

Web自动化测试——常见问题篇

文章目录一、什么是自动化测试二、为啥进行自动化测试&#xff08;优点&#xff09;三、Webdriver 的工作原理四、显示等待和隐式等待的区别五、什么样的项目适合做自动化六、自动化测试的流程七、如何分析生成的自动化测试报告一、什么是自动化测试 所谓的自动化测试就是使用…...

快速实现Modbus TCP转BACnet IP协议的方案

一、需求背景 BACnet是用于智能楼宇自控系统的主流通信协议&#xff0c;可用在暖通空调系统&#xff08;HVAC&#xff0c;包括暖气、通风、空气调节&#xff09;&#xff0c;也可以用在照明控制、门禁系统、火警侦测系统及其相关的设备。楼宇中的受控设备都通过BACnet协议连接到…...

Unity CircleLayoutGroup 如何实现一个圆形自动布局组件

文章目录简介实现原理Editor 编辑器简介 Unity中提供了三种类型的自动布局组件&#xff0c;分别是Grid Layou Group、Horizontal Layout Group、Vertical Layout Group&#xff0c;本文自定义了一个圆形的自动布局组件Circle Layout Group&#xff0c;如图所示&#xff1a; Ra…...

springcloud+nacos+gateway案例

一、先搭建好springcloudnacos项目地址:https://javazhong.blog.csdn.net/article/details/128899999二、spring cloud gateway简述Spring Cloud Gateway 是Spring Cloud家族中的一款API网关。Gateway 建立在 Spring Webflux上&#xff0c;目标是提供一个简洁、高效的API网关&a…...

实习这么久,你知道Maven是如何从代码仓库中找到需要的依赖吗?

目录 碎碎念 Maven是如何找到代码仓库里需要的依赖的&#xff1f; 如何根据坐标在本地仓库中寻找所需要的依赖&#xff1f; 如何根据坐标在远程仓库中寻找所需要的依赖&#xff1f; Maven 如何使用 HTTP 或 HTTPS 协议从远程仓库中获取依赖项&#xff0c;请详细解释其原理…...

低代码/零代码的快速开发框架

目前国内主流的低代码开发平台有&#xff1a;宜搭、简道云、明道云、云程、氚云、伙伴云、道一云、JEPaaS、华炎魔方、搭搭云、JeecgBoot 、RuoYi等。这些平台各有优劣势&#xff0c;定位也不同&#xff0c;用户可以根据自己需求选择。 一、阿里云宜搭 宜搭是阿里巴巴集团在20…...

C# 中常见的设计模式

设计模式是一套被广泛应用于软件设计的最佳实践&#xff0c;它们可以帮助开发者解决特定的问题&#xff0c;提高代码的可重用性、可读性和可维护性。本文将介绍 C# 中常见的几种设计模式&#xff0c;并提供相应的示例代码。 工厂模式 工厂模式是一种创建型设计模式&#xff0c…...

promethues/servicemonitor

目录 1.promethues 能保证源源不断地采集/metrics 信息吗&#xff1f;每次都是最新的吗 2.部署servicemonitor 的作用是什么&#xff1f; 3.pod 部署采集数据直接上报promthues &#xff0c;不通过servicemonitor 可以吗&#xff1f; 4.你说的"此外&#xff0c;如果部署…...

postman使用简介

1、介绍 postman是一款功能强大的网页调试和模拟发送HTTP请求的Chrome插件&#xff0c;支持几乎所有类型的HTTP请求 2、下载及安装 官方文档&#xff1a;https://www.getpostman.com/docs/v6/ chrome插件&#xff1a;chrome浏览器应用商店直接搜索添加即可&#xff08;需墙&…...

@DS注解在事务中实现数据源的切换@DS在事务中失效【已解决】

在Springboot的application.yml中的配置&#xff1a; spring:datasource:url: jdbc:mysql://localhost:3306/test2?serverTimezoneUTC&useUnicodetrue&characterEncodingutf8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rootdynamic:primar…...

Java I/O之文件系统

一、全文概览 在学习文件系统之前&#xff0c;需要了解下Java在I/O上的发展史&#xff1a;在Java7之前&#xff0c;打开和读取文件需要编写特别笨拙的代码&#xff0c;涉及到很多的InputStream、OutputStream等组合起来使用&#xff0c;每次在使用时或许都需要查一下文档才能记…...

Mysql元数据获取方法(information_schema绕过方法)

前提&#xff1a;如果waf或其它过滤了information_schema关键字&#xff0c;那我们该如何获取元数据呢&#xff1f;能够代替information_schema的有&#xff1a;sys.schema_auto_increment_columnssys.schema_table_statistics_with_bufferx$schema_table_statistics_with_buff…...

Eclipse快捷键

* 1.补全代码的声明&#xff1a;alt /* 2.快速修复: ctrl 1 * 3.批量导包&#xff1a;ctrl shift o* 4.使用单行注释&#xff1a;ctrl /* 5.使用多行注释&#xff1a; ctrl shift / * 6.取消多行注释&#xff1a;ctrl shift \* 7.复制指定行的代码&#xff1a;ctrl a…...

java ssm自习室选座预约系统开发springmvc

人工管理显然已无法应对时代的变化&#xff0c;而自习室选座预约系统开发能很好地解决这一问题&#xff0c;既能提高人力物力&#xff0c;又能提高预约选座的知名度&#xff0c;取代人工管理是必然趋势。 本自习室选座预约系统开发以SSM作为框架&#xff0c;JSP技术&#xff0c…...

分享我从功能测试转型到测试开发的真实故事

由于这段时间我面试了很多家公司&#xff0c;也经历了之前公司的不愉快。所以我想写一篇文章来分享一下自己的面试体会。希望能对我在之后的工作或者面试中有一些帮助&#xff0c;也希望能帮助到正在找工作的你。 找工作 我们总是草率地进入一个自己不了解的公司工作&#xf…...

TypeScript快速入门———(二)TypeScript常用类型

文章目录概述1 类型注解2 常用基础类型概述3.原始类型4 数组类型5 类型别名6.函数类型7 对象类型8 接口9 元组10 类型推论11 类型断言12 字面量类型13 枚举14 any 类型15 typeof概述 TypeScript 是 JS 的超集&#xff0c;TS 提供了 JS 的所有功能&#xff0c;并且额外的增加了…...

Mac M1 使用Centos8➕VMware Fusion进行静态网络配置

大部分的流程网络上面都有当我们已经下载好mac m1版的Centos8链接: https://pan.baidu.com/s/1UTl4Lo-_c17s-PDj3dA6kA 提取码: 7xh2 和VMware Fusionhttps://www.vmware.com/cn/products/fusion.html之后就可以进行安装了在导入过后 记得将硬盘和内存都设置好了 记得在关机状态…...

RadGraph: Extracting Clinical Entities and Relations from Radiology Reports代码

文章来源&#xff1a;NeurIPS 文章类别&#xff1a;IE(Information Extraction) RadGraph主要基于dygie&#xff0c;主要文件为inference.py。 inference.py&#xff1a; 1、get_file_list(data_path) def get_file_list(path):file_list [item for item in glob.glob(f&q…...

13. OPenGL与QT界面元素交互控制图形渲染

1. 说明&#xff1a; 前面文章中讲到的 OPenGL 渲染都是在页面加载完成即立刻渲染的&#xff0c;如果向控制图形渲染的时间&#xff0c;可以在QT界面中添加一些元素来进行控制。此时需要用到OPenGL当中的makeCurrent(),update(),doneCurrent()函数。 效果展示&#xff1a; ope…...

高通平台开发系列讲解(USB篇)libuvc详解

文章目录 一、什么是UVC二、UVC拓扑结构三、libuvc的预览时序图沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍libuvc。 一、什么是UVC UVC,全称为:USB video(device) class。 UVC是微软与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标…...

ICC2:set_route_opt_target_endpoints

route_opt阶段通过指定endpoint/driver pin list的方式执行incremental优化。 set_route_opt_target_endpoints [-setup_endpoints file] [-setup_endpoints_collection pin_list] [-hold_endpoints file] [-hold_endpoints_collection pin_list] [-ldrc_objects fil…...

5、小程序面试题

1, 小程序页面有哪些生命周期函数onLoad: 页面加载onReady: 页面初次渲染完成onShow: 页面显示onHide: 页面隐藏onUnload: 页面卸载2, 一页小程序页面有哪些文件组成,分别有什么作用.wxml: 使用微信框架设计的一套组件构建页面结构.wxss: 用于设置页面样式, 和css基本一致.js :…...

做旅游网站课程设计报告/5g站长工具查询

笔者为了学习PHP&#xff0c;WIN7系统上装了XAMPP。 默认情况下&#xff0c;mysql的root密码为空。在命令行下&#xff0c;通过mysql -u root即可进入。笔者在mysql数据库下user表中更改了root密码&#xff0c;退出后&#xff0c;发现root用户使用新设置密码登录不了。root用户…...

php怎么做网站/国际大新闻最新消息

计算机组成原理A形成性考核作业二(参考答案)一、选择题&#xff1a;1&#xff0e;计算机硬件能直接识别和运行的只能是_______程序。A&#xff0e;机器语言   B&#xff0e;汇编语言   C&#xff0e;高级语言   D&#xff0e;VHDL答&#xff1a;A2&#xff0e;指令中用到…...

合肥网约车收入怎么样/seo优化策略

要监视原始的传感器数据&#xff0c;你需要实现两个通过SensorEventListener接口暴露的回调方法&#xff1a;onAccuracyChanged()和onSensorChanged()。 传感器数据的速度值&#xff0c;这些值如下&#xff1a; 1.SENSOR_DELAY_GAME &#xff1a; 如果利用传感器开发游戏&#…...

大连金州代做网站公众号/快速排名教程

马哲包括5大部分即唯物论&#xff0c;辩证法&#xff0c;认识论&#xff0c;历史唯物论&#xff0c;资本主义本质论。 其中辩证法又包括&#xff1a; 1.两大特征&#xff1a;&#xff08;1&#xff09;普遍联系&#xff08;2&#xff09;永恒发展。 2.三大规律&#xff1a;&…...

创新的网站建设/百度快速查询

问题现象 有个flink实时任务&#xff0c;读kafka和redis&#xff0c;中间有复杂的逻辑处理过程&#xff0c;最终结果写redis。flink实时任务运行一段时间后阻塞了&#xff0c;有时是几个小时后&#xff0c;有时是一两天后。 任务看起来正常是正常的&#xff0c;但kafka消费已…...

自考网站建设与管理资料/公司软文推广

原理 高斯滤波是对整幅图像进行加权平均的过程&#xff0c;每一个像素点的值&#xff0c;都由其本身和邻域内的其他像素值经过加权平均后得到。其滤波核的值由如下公式得到&#xff08;用当前点与中心点的欧式距离的平方代替下面的&#xff08; x2y2x^2y^2x2y2&#xff09;&am…...