结构型设计模式
装饰器模式
核心是在不改变原 对象/方法 的基础上,通过对其进行包装拓展,使原有 对象/方法 可以满足更复杂的需求.
装饰器本质
装饰器模式本质上就是 函数的传参和调用,通过函数为已有 对象/方法 进行扩展,而不用修改原对象/方法,满足 开放封闭原则.
通过配置 babel
通过将 test.js
转为为 bable_test.js
用来查看装饰器的本质:
babel.config.json
{ "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] } 复制代码
test.js
// 定义装饰器 function decoratorTest(target) { console.log(target); } // 使用装饰器,装饰 Person 类 @decoratorTest class Person { say() {} eat() {} } 复制代码
执行 babel test.js --out-file babel_test.js 命令是生成 babel_test.js
"use strict"; var _class; function decoratorTest(target) { console.log(target); } let Person = decoratorTest(_class = class Person { say() {} eat() {} }) || _class; 复制代码
React 中的装饰器模式 —— HOC 高阶组件
高阶组件 是参数为 组件,返回值为新组件的 函数,在 React 中 HOC 通常用于复用组件公共逻辑.
// TodoList 组件 class TodoList extends React.Component {} // HOC 函数 function WrapContainer(Comp) { return ( <div style={{ border: "1px solid red", padding: 10 }}> <Comp title="todo" /> </div> ); } // HOC 装饰 TodoList 组件,为 TodoList 组件包裹红色边框 const newTodoList = WrapContainer(TodoList); 复制代码
适配器模式
适配器模式本质就是 让原本不兼容的功能能够生效,避免大规模修改代码,对外提供统一使用.
Axios 中的适配器
通过观察 Axios 的目录结构,很容就发现其使用了适配器模式:
其实 Axios
中的 adapters
主要目的是根据当前运行时环境,向外返回对应的适配器 adapter
,而这个适配器要做的其实就是兼容 web
浏览器环境和 node
环境的 http
请求,保证对外暴露的仍然是统一的 API
接口
代理模式
代理模式顾名思义就是 不能直接访问目标对象,需要通过代理器来实现访问,通常是为了提升性能、保证安全等.
事件代理
事件代理是很常见的性能优化手段之一,react
的事件机制也采用了事件代理的方式(篇幅有限可自行了解),这里演示简单的 JavaScript
事件代理:
<div id="container"> <p>this number is 1</p> <p>this number is 2</p> <p>this number is 3</p> <p>this number is 4</p> <p>this number is 5</p> </div> <script> const container = document.querySelector("#container"); container.addEventListener("click", function (e) { alert(e.target.textContent); }); </script> 复制代码
Vue 中的代理 Proxy
Vue.js 3
中通过 Proxy
实现了对数据的代理,任何读取、设置的操作都会被 代理对象 的 handlers
拦截到,从而实现 Vue
中的 track
和 trigger
行为型设计模式
策略模式
策略模式实际上就是定义一系列的算法,将单个功能封装起来,并且对扩展开放.
举个例子
假如我们需要为某个游乐场的门票价格做差异化询价,主要人员类型分为 儿童、成年人、老年人 三种,其对应的门票折扣为 8折、9折、8.5折
if-else
代码一把梭
缺点:无论哪种人员类型的折扣变动,都需要修改 finalPrice
函数,不符合对 对修改封闭
function finalPrice(type, price) { if (type === "child") { // do other thing return price * 0.8; } if (type === "adult") { // do other thing return price * 0.9; } if (type === "aged") { // do other thing return price * 0.85; } } 复制代码
单一功能封装
缺点:若人员类型增加妇女类型,仍然需要修改 finalPrice
函数,且不符合 对扩展开放
function childPrice(price) { // do other thing return price * 0.8; } function adultPrice(price) { // do other thing return price * 0.9; } function agedPrice(price) { // do other thing return price * 0.85; } function finalPrice(type, price) { if (type === "child") { return childPrice(price); } if (type === "adult") { return adultPrice(price); } if (type === "aged") { return agedPrice(price); } } 复制代码
创建映射关系
通过映射关系,很好的将 finalPrice
和 具体的计算逻辑进行分离,在需要扩展类型时,只需要修改 priceTypeMap
对象而不用修改对外暴露的 finalPrice
函数.
const priceTypeMap = { child: function (price) { // do other thing return price * 0.8; }, adult: function (price) { // do other thing return price * 0.9; }, aged: function (price) { // do other thing return price * 0.85; }, }; function finalPrice(type, price) { return priceTypeMap[type](price); }