## :-: 设计模式的“前端化” -- 策略模式
### 什么是设计模式:
设计模式 = 解决方案
解决软件设计中普遍存在(反复出现)的各种问题, 不必做重复劳动
### 设计模式的核心思想——封装变化
但在实际开发中,不发生变化的代码可以说是不存在的
我们能做的只有将这个变化造成的影响最小化
将变与不变分离,确保变化的部分灵活、不变的部分稳定。
## 一: 策略模式:重构小能手-拆分胖逻辑
举个 🌰 :
有一天,产品经理韩梅梅找到李雷,给李雷提了这么个需求:
马上大促要来了,我们本次大促要做**差异化询价**。啥是差异化询价?就是说同一个商品,我通过在后台给它设置不同的价格类型,可以让它展示不同的价格。具体的逻辑如下:
* 当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
* 当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
* 当价格类型为“返场价”时,满 200 - 50,不叠加
* 当价格类型为“尝鲜价”时,直接打 5 折
李雷扫了一眼 prd,立刻来了主意。他首先将四种价格做了标签化:
预售价 - pre
大促价 - onSale
返场价 - back
尝鲜价 - fresh
接下来李雷仔细研读了 prd 的内容,作为资深 if-else 侠,他三下五除二就写出一套功能完备的代码:
```
// 询价方法,接受价格标签和原价为入参
function askPrice(tag, originPrice) {
// 处理预热价
if(tag === 'pre') {
if(originPrice >= 100) {
return originPrice - 20
}
return originPrice \* 0.9
}
// 处理大促价
if(tag === 'onSale') {
if(originPrice >= 100) {
return originPrice - 30
}
return originPrice \* 0.8
}
// 处理返场价
if(tag === 'back') {
if(originPrice >= 200) {
return originPrice - 50
}
return originPrice
}
// 处理尝鲜价
if(tag === 'fresh') { return originPrice \* 0.5 } }
```
这么写代码会带来什么后果:
* 违背了“单一功能”原则 (比如说万一其中一行代码出了 Bug,那么整个询价逻辑都会崩坏)
* 违背了“开放封闭”原则 (只要加需求 就需要继续 if - else,对测试同学不太友好)
## 重构询价逻辑
1. 单一功能改造
prePrice - 处理预热价
onSalePrice - 处理大促价
backPrice - 处理返场价
freshPrice - 处理尝鲜价
askPrice - 分发询价逻辑
```
// 处理预热价
function prePrice(originPrice) {
if(originPrice >= 100) {
return originPrice - 20
}
return originPrice * 0.9
}
// 处理大促价
function onSalePrice(originPrice) {
if(originPrice >= 100) {
return originPrice - 30
}
return originPrice * 0.8
}
// 处理返场价
function backPrice(originPrice) { ... }
// 处理尝鲜价
function freshPrice(originPrice) { ... }
function askPrice(tag, originPrice) {
// 处理预热价
if(tag === 'pre') {
return prePrice(originPrice)
}
// 处理大促价
if(tag === 'onSale') {
return onSalePrice(originPrice)
}
// 处理返场价
if(tag === 'back') {
return backPrice(originPrice)
}
// 处理尝鲜价
if(tag === 'fresh') {
return freshPrice(originPrice)
}
}
```
问题: askPrice 的函数体,还是没有实现“对扩展开放,对修改封闭”,新需求来了,还要继续 if
2. 开放封闭改造
其实 if - else 就是为了确定一个映射关系
在 JS 中,既能够既帮我们明确映射关系,同时不破坏代码的灵活性的方法 -- **对象映射**!
```
// 定义一个询价处理器对象
const priceProcessor = {
pre(originPrice) {
if (originPrice >= 100) {
return originPrice - 20;
}
return originPrice \* 0.9;
},
onSale(originPrice) {
if (originPrice >= 100) {
return originPrice - 30;
}
return originPrice \* 0.8;
},
back(originPrice) { ... },
fresh(originPrice) { ... };
}
```
```
// 询价函数
function askPrice(tag, originPrice) {
return priceProcessor[tag](originPrice)
}
```
如此一来,askPrice 函数里的 if-else 大军彻底被消灭了。这时候如果需要一个新人价,只需要给 priceProcessor 新增一个映射关系。这样完成对扩展开放,对修改封闭了。
这,就是策略模式!
- 计算机网络
- content-type
- cookie相关
- TCP/IP 4层模型
- HTTP2和HTTP1的区别
- http状态码
- DNS 域名解析
- URI 和 URL
- request和response
- cdn的原理
- http2和 https的区别
- 轮询 & 长轮询
- websocket 和 http 协议的区别
- 三次握手四次挥手
- https是如何加密的
- 原码、反码和补码
- Socket
- css
- 如何触发 BFC
- css 动画
- postcss-px-to-viewport、postcss-plugin-px2rem
- css 预处理器
- PostCss
- 实现一个 0.5 px的边框
- 实现10px的字体
- 实现左侧宽度固定,右侧宽度自适应的布局
- calc 的原理
- 伪类和伪元素的区别
- 层叠上下文和层叠顺序
- css 中的 meta标签
- flex
- css 画斜线
- transform 合成层
- 如何画一个三角形、正方体
- js动画和css动画的差异
- 盒模型
- 选择器的优先级
- css 选择器的解析顺序
- h5适配方案
- 适配刘海屏
- 移动端 rem 的大小是怎么计算出来的
- rem+vw
- 动态 viewport 和 REM 适配
- 深入理解viewport
- 算法
- 时间复杂度
- 排序
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 数组扁平化
- 二叉树
- 深度优先搜索
- 前中后序遍历
- 广度优先搜索
- 判断两个二叉树是否相等
- 计算二叉树节点个数
- 二分法查找
- 字符串
- 压缩字符串
- 一个函数,传入url和一个对象,返回以query形式的url
- 双指针算法
- 双指针算法(合并有序数组,三数求和)
- 限制同时只能并发四个请求
- 输出环形数组
- 函数坷里化
- 动态规划 - 最优解问题
- 股票利润最大
- 股票利润最大 -1
- 下台阶问题
- 连续子数组最大和
- 栈和队列
- 用两个栈模拟一个队列
- 两数求和
- 打印数组第n大的数
- 字符串包含
- 每3秒打印一个helloworld,总共执行4次
- 斐波那契
- 打印斐波那契数列前n项
- 动态规划实现输出斐波那契数列前n项
- 最长连续子字符串
- 实现一个深拷贝
- 实现一个进制转换
- js
- 基础知识整理
- for in 和 for of的区别
- js 隐式转换
- canvas
- 如何使用 Math.random() 生成 n-m,不包含 n 但包含 m 的整数?
- encodeURIComponent & encodeURI
- 为什么用 setTimeout, 不用 setInterval
- MutationObserver
- 浏览器渲染过程
- cookie
- 滚动相关
- 函数
- js有函数重载吗
- 函数柯里化的实现
- 高阶函数
- arguments
- IIFE立即执行函数
- 手写系列
- 手写 map、filter、reduce
- call、apply、bind
- 数据类型
- 基本数据类型
- 判断数据类型的typeof
- null 和 undefined
- 字符串类型
- 数字类型
- 引用数据类型
- Set、Map
- 如何判断数组类型
- instanceof 原理
- 数组的创建
- Service Worker
- JS中==、===和Object.is()的区别
- 插入节点
- 前端缓存
- script 异步加载有什么问题
- xhr 请求
- fetch
- axios
- 页面输入 url 到页面渲染都发生了什么
- 包装对象
- 错误对象
- 拖放 api
- 原型
- js new的时候都干了什么
- 屏蔽属性
- 闭包
- 应用一 - 防抖和节流
- 应用二 - 单例模式
- 创建对象
- 判断对象里是否有某个属性
- 事件循环
- node中的事件循环
- 继承
- 原型和继承的关系
- 如何基于es5实现继承
- 原型链继承
- 盗用构造函数继承
- 组合继承(函数可以重用)
- 原型式继承
- 寄生式继承
- 寄生式组合继承
- es6继承
- 继承基础
- 事件
- 自定义事件
- 事件派发
- 写一个EventEmitter类,包括on()、off()、once()、emit()方法
- 作用域
- 执行上下文
- 为什么let、const 有暂时性的死区
- 变量提升和函数声明提前
- 作用域链
- 变量提升和函数提升
- 词法作用域
- Promise
- 以往的异步编程模式
- promise的状态
- async/await
- 手写 promise
- 手写 promise all
- 输出顺序
- 垃圾回收
- 内存泄漏
- 浏览器缓存策略
- 跨域的解决方案
- options请求返回 *的弊端
- cookie 跨域
- 同源策略
- nginx反向代理时,对域名的要求
- 深浅拷贝
- Object 构造函数是深拷贝还是浅拷贝
- 手写深拷贝
- this
- 箭头函数中的this指向
- this的四种绑定机制和作用域
- 迭代器和生成器
- 文件的操作
- 文件上传
- 文件下载
- js输出题
- 操作dom为啥耗时
- 生成器
- 图片懒加载
- 前端框架类
- vuex
- getter
- mutation 和 action 的区别
- 原理分析
- vue3
- 和 vue2 key的对比
- 响应式原理
- diff 算法
- 和vue2相比 性能优化在什么地方
- 新特性
- @vue/composition-api
- 动静结合diff算法
- vue2
- 添加环境变量
- 变化侦测
- vue defineReactive 源码分析
- $delete 的原理
- $set 的原理
- $watch 的原理
- Array 的变化侦测
- object的变化侦测
- 虚拟 DOM
- patch
- vue 性能优化技巧
- vnode
- 模版编译原理
- 解析器
- 实例方法与全局api的实现原理
- 全局API实现原理
- 事件相关实例方法
- 生命周期相关实例方法
- 自定义指令是如何生效的
- 过滤器原理
- 生命周期
- 初始化阶段
- 模版编译
- 挂载阶段
- 卸载阶段
- 父子组件生命周期执行过程
- 调试源代码
- keep-live
- 组件注册原理
- render函数
- computed 原理
- 响应式是如何实现的
- errorCaptured 错误处理
- vue中的事件
- Vue中的scoped的实现原理
- 插件系统
- sync 修饰符的原理
- v-model 实现原理
- vue-router
- vue-router基础知识
- vue 路由按需加载
- vue-router 核心源码解读
- vue-router 工作原理
- 源码调试
- react-router-dom
- 基础整理
- 简介
- react(17.0.3)
- 事件机制
- react 单项数据流原理
- react diff
- react key
- react fiber
- 组件
- 受控组件和非受控组件
- 函数组件
- hooks
- useReducer
- useCallback 和 useMemo的区别
- useRef、createRef的区别
- 类组件
- pureComponent
- setState原理
- 高阶组件
- 代码分割
- 组件之间的通讯
- react 基础
- React17更新了哪些内容
- vue和react的对比
- react 生命周期
- render阶段和commit阶段
- 数据发生变更时,分别触发哪些生命周期
- context - 跨层级组件数据传递
- Profiler API
- web component
- redux
- compose 函数
- 迭代器
- useSelector
- dva-core
- redux-saga
- 性能优化
- 目的
- 常用策略
- 图片的优化
- html 的优化
- css 的优化
- js 优化
- 加载策略优化
- 关键指标
- RAIL
- 性能定位与监控
- 性能优化原则
- 长列表渲染优化
- 首屏加载优化
- 设计模式
- 策略模式
- 装饰器模式 & 如何用装饰器开发
- 观察者模式
- 如何实现发布订阅模式
- 安全相关
- DDos
- SQL 注入
- 浏览器安全
- xss 攻击
- dom 型 xss是如何攻击的
- csrf 攻击
- 服务端渲染
- next
- nuxt
- 初始化
- git
- 一个仓库里怎么同时管理了多个单独发布的包
- 常用命令
- git reset 和 git revert 的区别
- git rebase的缺点
- typescript
- typescript 之 infer
- 高级类型
- 移动端 H5
- 1. 响应式页面开发
- vw 作为css长度单位
- 响应式背景图片
- vw px rem 之间的换算
- dpr 是什么
- 背景图保持宽高比
- 动效开发
- transform + transition
- hybrid
- jsbridge 原理
- 安卓微信唤醒app
- visibilitychange
- vconsole的实现原理
- cross-env原理
- 微信小程序
- mpx
- taro
- 微信小程序底层原理
- npm
- npm 脚本
- npx
- npm link
- node
- node 单线程的优点和缺点
- 如何理解node的事件驱动、无阻塞、单线程
- 洋葱模型
- node 基础知识
- buffer
- event
- 模块机制
- 如何设置环境变量
- 网络编程
- 构建TCP服务
- 构建UDP服务
- 构建HTTP服务
- 构建WebSocket服务
- 安全
- web应用
- 内存控制
- fs
- readFile 和 createReadStream 的区别
- util
- express
- serverLess
- 微前端
- 认识微前端
- 微前端原理
- qiankun
- html entry
- 路由原理
- 整体架构
- 框架、路由开发
- 模块
- 页面的渲染
- 模块之间的耦合
- 通用的组件库
- 模块之间的通信
- store
- 性能优化部分
- 缓存机制
- 防止重复打包基础模块
- 运行、工具
- 微前端应用跟业务组件有什么区别
- 视频直播
- RTC 和 RTMP
- m3u8和mp4
- webrtc 直播
- eslint
- eslint做代码检查
- nginx
- nginx 基础知识整理
- 使用背景
- V8引擎
- 渲染进程
- event loop 、js 引擎、渲染引擎的关系
- 基础知识
- linux
- 常用 linux 命令
- 前端工程化
- webpack
- webpack 打包原理
- webpack 如何分析内部依赖图
- webpack 5个核心概念
- webpack 打包静态资源
- 打包css到单独文件
- 使用devServer
- tree shaking
- 分割代码,按需加载
- commonChunkPlugin 提取第三方库和公共模块
- webpack优化静态资源
- webpack 与 gulp 对比
- webpack周边工具
- webpack中hash、chunkhash和contenthash三者的区别
- 实现一个自己的devServer
- babel
- 词法分析和语法分析
- AST
- 核心api
- 自动在 console.log 等 api 中插入文件名和行列号的参数
- gulp
- vite
- webComponents
- lit
- 基础
- 运维
- 弹性云、物理机
- k8s
- docker
- docker 基础知识