导航
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
(一) 前置知识
- 设计模式一直都没敢写,主要是个人觉得理解不难,主要胆怯在运用于生产项目
(1) 一些单词
pattern: 模式 // design pattern: 设计模式
strategy: 策略,战略
performance: 性能,绩效
salary: 工资,薪水
bonus: 奖金
singleton: 单身,单例模式
(2) Storage
- 概念
- 两个对象部署了 ( Storage接口 ),分别是 ( window.localStorage ) 和 ( window.sesstionStorage )
- 方法
-
Storage.setItem(key, value)
key 必须是符串,非字符串会被转成字符串
value 也必须是字符串,非字符串会被转成字符串
如果key已经存在,则新的value覆盖旧的value
如果存储空间已满,该方法会抛错
Storage.setItem('name', 'woow_wu7') 等价于 Storage.name = 'woow_wu7'
-
Storage.getItem(key)
- key不存在,则返回 null
-
Storage.removeItem(key)
- 清除key对应的value
-
Storage.clear()
- 清除Storage中的所有数据
- 返回值是undefined
-
Storage.key(number)
- 接受一个整数作为参数,返回该位置对应的键值
-
Storage.setItem(key, value)
-
事件
window.addEventListener('storage', onStorageChange)
- 该监听函数的event对象
- StorageEvent.key: 发生变动的键名
- StorageEvent.newValue: 新的键值,字符串
- Storage.oleValue: 旧的键值,字符串
- Storage.storageArea: 返回整个对象
- Storage.url: 表示原始触发 storage 事件的 ( 网页地址 )
-
特别注意
该事件不在导致数据变化的当前页面触发,而是在 ( 同一个域名 ) 的其他窗口触发
- 所以:如果浏览器只有一个窗口,可能看不到该事件触发
- 所以:可以实现多窗口通信
image
(3) 对象的命令空间
var namespace = {
a: () => { console.log('a') },
b: () => { console.log('b') },
}
(二) JS设计模式
(1) 代理模式
(1) 代理模式的定义
- 代理模式:
指给某一个对象提供一个 ( 代理对象 ),并由 ( 代理对象 ) 来控制 ( 原对象的引用 ),代理模式类似 ( 中介 )
(2) 为什么要使用代理模式?
- 中介隔离
- ( 客户对象 ) === ( 代理对象 ) === ( 委托对象 )
- 一些情况下,客户对象不想或者不能直接引用一个委托对象,则代理对象可以充当中介作用
- 开闭原则,增加功能
- ( 代理类 ) 除了是 ( 客户类 ) 和 ( 委托类 ) 的中介之外,还可以给代理类增加额外的功能来扩展委托类的功能,这样我们就可以直接修改代理类而不是直接修改委托类,符合代码设计的开闭原则
- ( 代理类 ) 主要负责委托类的 ( 预处理消息, 过滤消息, 转发消息给委托类,对返回结果的处理 )
- ( 代理类 ) 本身不提供服务,而是通过调用委托类相关的方法,来提供特定的服务,真正的业务功能还是由委托类来实现
(3) 实战
(3-1) 代理ajax请求 - 增加缓存功能
- 作用
- 通过 代理函数proxyRequest() 调用 cache() 使得 request() 方法做了一层缓存
- 缓存每次请求的参数,如果参数一样,就直接返回之前参数对应的 Map 中的缓存的 value
- 原理
利用 ( 代理模式 ) 给原由的函数添加新的逻辑但又不影响原函数,相当于用 ( 函数组合 ) 来实现 ( 复用逻辑和添加逻辑 )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// Map
// 1.实例属性 mapInstance.size
// 2.原型方法 mapInstance.get() set() has() delete() clear()
const mapInstance = new Map()
// request 发送请求,返回一个promise实例
const request = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(`data: ${params}`)
}, 2000)
})
}
// cache 缓存 ( 请求参数 )和 ( 请求结果 promise )
const cache = (params) => {
if (mapInstance.has(params)) { // Map.prototype.has
return mapInstance.get(params) // Map.prototype.get
}
mapInstance.set(params, request(params))
return request(params)
}
// proxyRequest 代理请求
const proxyRequest = (params) => {
return cache(params)
}
// 当参数一样时,是从Map中返回的缓存值
proxyRequest('11').then(data1 => {
proxyRequest('11').then(data2 => console.log('data1 === data2 ? 这里返回true ', data1 === data2))
})
</script>
</body>
</html>
(3-2) 缓存代理 - 处理缓存过期时间 - class版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
class Storage {
constructor({ type, expires }) {
this.type = type // 类型
this.expires = expires // 过期时间
this.storageType = {
local: 'localStorage',
session: 'sessionStorage'
}
}
// 取
// 代理原生 getItem() 方法,根据缓存过期时间,判断数据是否过期
getItem(key) {
const now = +new Date()
const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
if (now > setTimes + this.expires) {
window[this.storageType[this.type]].removeItem(key) // 如果过期,清除该 Storage
return null
}
return value
}
// 存
// 代理原生的 setItem() 方法,添加缓存时间
setItem(key, value) {
window[this.storageType[this.type]].setItem(key, JSON.stringify({
value,
setTimes: +new Date()
}))
}
}
const localStorage = new Storage({type: 'local', expires: 1000})
localStorage.setItem('name', 'woow_wu7')
console.log('未过期', localStorage.getItem('name'))
setTimeout(() => {
console.log('过期', localStorage.getItem('name'))
}, 2000)
</script>
</body>
</html>
2021/05/03 优化
(3-2) 优化缓存代理 - 处理缓存过期时间 - class版
<!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>
class Storage {
constructor(type, expires) {
this.storage = window[type];
this.expires = expires;
}
// 存
// 代理原生的 setItem() 方法,添加缓存过期时间
setItem = (key, value) => {
this.storage.setItem(
key,
JSON.stringify({
value,
setTime: +new Date(), // --------------------- ( 存 ) 的时间戳
})
);
};
// 取
// 代理原生的 getItem() 方法,根据传入的过期时间,来判断数据是否过期
getItem = (key) => {
const now = +new Date(); // --------------------- ( 取 ) 的时间戳
const { value, setTime } = JSON.parse(this.storage.getItem(key));
if (now - setTime > this.expires) {
this.storage.removeItem(key); // -------------- 过期删除
}
return this.storage.getItem(key);
};
}
const localStorageInstance = new Storage("localStorage", 5000);
localStorageInstance.setItem("name", "woow_wu7");
console.log("未过期", localStorageInstance.getItem("name"));
setTimeout(() => {
console.log("过期", localStorageInstance.getItem("name"));
}, 6000);
</script>
</body>
</html>
(3-3) 缓存代理 - 处理缓存过期时间 - function版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Storage(type, expires) {
this.type = type
this.expires = expires
this.storageType = {
local: 'localStorage',
sesstion: 'sesstionStorage'
}
}
Storage.prototype.getItem = function(key) {
const now = +new Date()
const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
if (now - setTimes > this.expires) {
window[this.storageType[this.type]].removeItem(key)
return null
}
return value
}
Storage.prototype.setItem = function(key, value) {
window[this.storageType[this.type]].setItem(key, JSON.stringify({
value,
setTimes: +new Date()
}))
}
const storage = new Storage('local', 1000)
storage.setItem('name', 'woow_wu7')
console.log('未过期', storage.getItem('name'))
setTimeout(() => {
console.log('过期', storage.getItem('name'))
}, 2000)
</script>
</body>
</html>
(2) 策略模式
(1) 概念
- 定义一系列算法,把它们一个个封装起来,并使它们可以 ( 相互替换 )
- 将 ( 不变的部分 ) 和 ( 变化的部分 ) 隔开
- (
策略模式
) 的主要目的就是将 (算法的使用
) 和 (算法的实现
) 分离开来
(2) 组成 - 策略模式组要包含 ( 策略类 ) 和 ( 环境类 )
-
策略类
- 封装了具体的算法,并负责具体的计算过程
-
环境类
context- 环境类context接受客户的请求,随后把请求 ( 委托 ) 给某一个策略类
环境类context中要维持对某个策略类的引用
(3) 特点
-
避免多重选择语句出现:策略模式利用 (
组合,委托,多态
) 等技术和思想,可以有效的避免 (多重条件选择语句if...else
) 的情况 - 符合开放封闭原则:将算法封装在独立strategy策略中,使得它们容易切换,理解,扩展
- 算法可复用:可以复用在系统其他地方,从而避免冗余重复的工作
(4) 缺点
- 必须了解所有策略:必须了解各个策略的不同点,才能实现一个特定的策略
(5) 实战
(5-1) 计算奖金
- S绩效 - 4倍工资
- A绩效 - 3倍工资
- B绩效 - 2倍工资
bonus: 奖金
salary: 工资
performance: 绩效,性能
strategy: 策略
strategy pattern 策略模式
(1) 未经过任何优化的写法
- 缺点
- 具有很多if...else语句
- 缺乏扩展性:如要添加C绩效,则需要修改内部的函数实现,违反开放封闭原则
- 复用性差
function getBonus(performance, salary) { // bonus奖金 performance绩效 salary奖金
if ( performance === 'S') return 4 * salary;
if ( performance === 'A') return 3 * salary;
if ( performance === 'B') return 2 * salary;
}
getBonus('S', 1000) // 输出:4000
---------------
(2) 使用 - 函数组合 - 重构代码
function getS (salary) { return 4 * salary }
function getA (salary) { return 3 * salary }
function getB (salary) { return 2 * salary }
function getBonus(performance, salary) {
if ( performance === 'S') return getS(salary)
if ( performance === 'A') return getA(salary)
if ( performance === 'B') return getB(salary)
}
getBonus('S', 1000) // 输出:4000
---------------
(3) 使用 - 策略模式strategyPattern - 重构代码
- 优点
- 消除了大量if...else语句
- 所有计算奖金bonus的逻辑都不在getBonus函数即环境类context中,而是分布在各个策略对象strategy中
- context环境类不负责计算,只是负责将请求委托给strategy策略类
const strategy = {
S: (salary) => 4 * salary,
A: (salary) => 3 * salary,
B: (salary) => 2 * salary,
}
const getBonus = (performance, salary) => strategy[performance](salary)
getBonus('S', 1000) // 输出:4000
(5-2) 表单验证
- 未优化的代码如下
(1) 未优化的代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="javascript:void(0)" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button>表单提交</button>
</form>
<script>
const form = document.querySelector('form')
form.onsubmit = () => {
// form.username可以访问到 name="username" 的 input元素节点
if (form.username.value === '') {
console.log('用户名不能为空')
return false
}
if (form.password.value.length < 6) {
console.log('密码长度不能少于6位')
return false
}
}
</script>
</body>
</html>
-
使用策略模式 - 优化表达验证
- 第一步:将验证逻辑封装到 ( 策略对象strategy中 )
- 第二步:将用户的请求通过 ( 环境对象context ) 委托给策略对象strategy来处理
(2) 使用策略模式 - 优化表达验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="javascript:void(0)" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button>表单提交</button>
</form>
<script>
// 策略对象 strategy
// 注意:
// 1. 这里每个方法可以能有 ( 两个 ) 或者 ( 三个参数 )
// 2. 很巧妙的利用了
// - "isEmpty".split(';') => ["isEmpth"].shift() => [].unshift(value) => [value].push(err) => [value, err]
// - "minLength:6".split(";") => ["minLength", "6"].shift() => ['6'].unshift(value) => [value, '6'].push(err) => [value, '6', err]
// - 最后就是两种情况: [value, err] 或 [value, minLength, err]
// ------------------------------------ strategys
const strategys = {
notEmpty: (value, err) => { if (value === '') return err },
minLength: (value, minLength, err) => { if (value.length < minLength) return err },
}
// 环境类 context
// ------------------------------------ Validator
class Validator {
constructor() {
this.rules = [] // 规则数组,用于存放所有的规则函数
}
add = (dom, rule, err) => {
const ruleToArr = rule.split(':') // ['isEmpty'] 或 ['minLength', 6]
this.rules.push(() => { // 每次没push一个匿名函数到this.rules中
const strategy = ruleToArr.shift() // 'isEmpty' 或 'minLength'
ruleToArr.unshift(dom.value) // 头部添加
ruleToArr.push(err) // 尾部添加
return strategys[strategy].apply(null, ruleToArr) // ruleToArr = [value, err] 或 [value, minLength, err]
})
}
start = () => {
for(let i = 0; i < this.rules.length; i++) { // 遍历执行每一个函数
let err = this.rules[i]();
if (err) { return err }
}
}
}
// ------------------------------------ form元素节点
const form = document.querySelector('form')
form.onsubmit = (e) => {
e.preventDefault()
const validator = new Validator()
validator.add(form.username, 'notEmpty', '用户名不能为空') // add
validator.add(form.password, 'minLength:6', '密码不能少于6位') // add
const err = validator.start() // start
if (err) {
console.log('err', err)
return false
}
}
</script>
</body>
</html>
(3) 单例模式 singleton
(1) 定义
- 保证一个类只有一个实例,并提供一个访问它的全局访问点
- 优点
- ( 创建对象 ) 和 ( 管理单例 ) 被分布在不同的方法中
(2) 应用场景
- 全局变量
- 连接数据库,防止多次连接或断开
- 全局状态管理redux vuex
- 登陆框,form表单,loading层等
(3) 实现
(3-1) 闭包实现一个单例
- 缺点:需要调用 getInstance 函数创建对象
function Singleton(name) { // 构造函数, singleton: 单身, 注意:箭头函数不能作为构造函数,arguments,yeild命令
this.name = name
}
Singleton.getInstance = (() => {
let instance = null // 闭包变量
return (name) => {
if (!instance) {
instance = new Singleton(name)
}
return instance
}
})(); // IIFE立即调用的函数表达式,注意小括号和中括号开头的前一条语句需要加分号,或者小括号中括号前加分号
const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B') // 这一次传参没用了
console.log('a === b', a === b) // true
(3-2) 函数实现一个单例
- 缺点:需要调用 getInstance 函数创建对象
function Singleton(name) {
this.name = name
}
Singleton.getInstance = function(name) {
// 注意:
// 1. 这里的 this 指向的是 Singleton ,而不是 Singleton生成的实例
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B')
console.log('a === b', a === b)
console.log('a', a)
console.log('b', b)
console.log('Singleton.instance', Singleton.instance) // instance直接是Singleton的属性
(3-3) 透明的单例模式
- 优点:解决12中的需要调用 createInstance 函数来生成对象的缺点,这里直接通过new调用
- 缺点:不符合单一职责原则,这个对象其实负责了两个功能:单例和创建对象
var Singleton = (function() {
var instance = null
return function(name) {
if (instance) return instance
this.name = name
return instance = this // 返回实例对象,instance仅仅是为了做判断
}
})(); // IIFE返回一个构造函数
const a = new Singleton('A')
const b = new Singleton('B')
console.log('a === b', a === b)
console.log('a', a)
(3-4) es6实现单例
class Book {
constructor(name) {
if (!Book.intance) {
this.name = name
Book.intance = this
}
return Book.intance
}
}
const english = new Book('english')
const chinese = new Book('chinese')
console.log('english === english', english === english)
console.log('english', english)
console.log('chinese', chinese)
(4) 实战 - 登陆框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="login">登陆</button>
<script>
function init() {
const button = document.getElementById('login')
button.addEventListener('click', showModal, false)
}
init() // 绑定监听
var singleton = (() => {
var instance = null
return function(fn) {
if (!instance) {
instance = fn.call(null)
}
return instance
}
})() // IIFE单例子模式
function createModal() {
const div = document.createElement('div')
div.innerHTML = '登陆框'
div.style.setProperty('width', '300px')
div.style.setProperty('height', '300px')
div.style.setProperty('border', '1px solid black')
document.body.appendChild(div)
return div
}
function showModal() {
singleton(createModal)
}
</script>
</body>
</html>
网友评论