导航
[封装-设计模式01] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[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设计模式 - 代理,策略,单例
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
(一) 前置知识
(1) 一些单词
principle 原则 原理
responsibility 职责 责任
substitution 代替 置换
// Single Responsibility Principle 单一职责原则
abstract 抽象的
simple 简单的
show up 出现,露面
advanced 高级的,先进的
configuration 配置
// advanced configuration 高级配置
constraint 约束
peek 偷看
shape 形状
anonymous 匿名的
(2) Error
- new Error(message)
抛出Error实例对象后,整个程序就中断在错误发生的地方,不再往下执行
- error实例属性
- message 错误提示信息
- name 错误名称 ( 非标准属性 )
- stack 错误堆栈 ( 非标准属性 )
-
六种错误对象
- SyntaxError 语法错误
- ReferenceError ( 引用一个不存在的变量时发生的错误 ) 或者 ( 将一个值分配给无法分配的对象 )
- RangeError 一个值超出有效范围时发生的错误
- TypeError ( 变量或者参数 ) 不是 ( 预期类型 ) 时候发生的错误
- RUIError URI 相关函数的参数不正确时抛出的错误
- EvalError eval函数没有被正确执行时抛出的错误
- 第七种,是自定义错误对象
- 原理
- 1.就是普通的构造函数(具有name,message,stack等属性)
- 2.将构造函数的prototype属性指向 new Error() 生成的实例
- 3.将构造函数的prototype.contructor属性指向构造函数自己,防止引用出错
- 原理
-
throw语句
- throw语句的作用是:手动中断程序执行,并抛出错误,即手动抛出错误
- 注意:throw可以抛出任意类型的值,不一定是错误实例
try { // throw new Error('错误了') // ---------------------------- 注意:throw 可以抛出任何类型的值,不局限于错误对象实例 throw '错误了2' } catch(e) { // console.log(e.message) ----- 注意:这里对应的是try抛出的错误对象,错误对象具有message属性 // ---------------------------- 注意:catch() 的参数是try代码块中抛出的值,即也可以是任意类型 console.log(e); }
-
try...catch(e)结构
- try...catch主要作用:是对错误进行处理,选择是否往下执行
- catch(e) 接受的参数表示try代码块抛出的值,任意类型,因为throw可以抛出任意类型
- 注意:
catch捕获错误后,程序不会中断,会按正常流程继续执行下去
try { throw "出错了"; } catch (e) { console.log(111); } console.log(222); // 111 // 222 还是会正常执行
- try...catch还可以嵌套try...catch
-
try...finally结构
- try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句
如果在try...catch中有return语句,会在finally执行完毕后才会去执行return后面的语句
(二) 六大设计原则
-
单一职责原则
Single Responsibility Principle -
开放封闭原则
Open Closed Principle - 里氏替换原则 Liskov Substitution Principle
-
迪米特法则(最少知识原则)
Law of Demeter - 接口隔离原则 Interface Segregation Principle
- 依赖倒置原则 Dependence Inversion Principle
(2.1) 单一职责原则
- 关键词
- (
单一
) (职责
) - 如果我们有两个动机去改写一个方法,那么这个方法就具有两个职责
- (
- 单一职责原则
- 一个方法只做一件事
- 这样需求变迁,则只会影响到最小粒度的方法,其他方法不会受到影响
-
如何划分职责???
- 两个完全不一样的功能,不应放在一个类中
- 一个类中应该是 ( 一组相关性很高的函数,或者数据的封装 )
- 如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们
- 即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们
-
单一职责原则的优优缺点
- 优点
- 降低了单个类或者对象的 ( 复杂度 )
- 按照 ( 职责 ) 把类和对象分解成 ( 更小粒度 ),有利于 ( 代码复用 ) ( 单元测试 ) 等
- 当一个职责变化时,不会影响到其他职责
- 缺点
- 增加了代码复杂度
- 增加了对象之间相互联系的难度
- 优点
(2.2) 开放封闭原则
- 关键词
-
开放-封闭原则
,满足类,对象,模块,函数是 (可扩展的
),但是是 (不可修改的
) - 即当需要给程序添加功能或者修改功能时,可以尝试添加代码,但是不能去修改源代码
-
- 案例
- 用对象的 ( 多态 ) 来消除 ( 条件分支 )
- 将 ( 稳定不变的部分 ) 和 ( 容易变化的部分 ) 分开,则在系统演化迭代的过程中,只需要去修改经常变化的部分,变化的部分使用 ( 钩子函数 ) 和 ( 回调函数 )
- 优点
- 原来的代码不用修改
- 可以复用原来没有修改的代码
- 开放封闭原则总结
- 不管是具体的各种设计模式,还是更抽象的面向对象设计原则,比如单一职责原则、最少知识原则、依赖倒置原则等,都是为了让程序遵守开放-封闭原则而出现的
(2.3) 最少知识原则
- 概念
- 一个 (
对象,类,模块,函数,变量
) 等应该对其他对象有最少的了解,减少对象之间的耦合
- 一个 (
(2.4) 里氏替换原则
- 概念
- 所有引用 ( 基类 ) 的地方,必须能够使用其 ( 子类 ) 直接替换
- 特点
- 子类必须实现父类的所有方法,即继承父类所有的方法和属性
- 子类可以有自己的属性和方法 ( 重写或者添加新的属性 )
- 覆盖或者实现父类的方法时,入参可以被放大,输出可以被缩小
(2.5) 依赖倒置原则
- 模块间依赖通过抽象(比如:接口interface)发生,实现类(实现类可以去实现接口中的方法)之间不直接依赖
- ( 实现类 ) 依赖于 ( 接口或抽象类 )
- ( 接口或者抽象类 ) 不依赖于 ( 实现类 ) - 什么是抽象类类?
- 什么是抽象类类?
- 如果以一个类中没有足够的信息来描绘一个具体的对象,这样的类就是抽象类
- 抽象类不能实例化对象,所以抽象类必须被继承才能使用
(2.6) 接口隔离原则
- 客户端不应该依赖于它不需要的接口
- 建立单一的接口
(三) 工厂模式
(3.1) 简单工厂模式
在简单工厂模式中,是由工厂来创建产品的,即是老板也是服务员
abstract class Animal { // 抽象类不需要自己实现方法
constructor(public name: string) {
// this.name = name
}
}
class Cat extends Animal { } // 继承
class Dog extends Animal { } // 继承
// 简单工厂模式
// 通过传入 (类型) 在工厂类中根据传入的类型,分别调用给构造函数去生产对应的实例
class AnimalFactory {
static create(name: string) { // static 静态方法可以通过类本身去调用
switch (name) { // 根据传入的类型,调用不同的构造函数,创建不同的实例
case 'cat':
return new Cat('cat')
case 'dog':
return new Dog('dog')
default:
return new Error('出错了')
}
}
}
const dog = AnimalFactory.create('dog') // 生产dog
const cat = AnimalFactory.create('cat') // 生产cat
console.log(dog)
console.log(cat)
(3.2) 工厂方法模式
- 在简单工厂模式中,是由工厂来创建产品的,即是老板也是服务员
在工厂方法模式中,不再由工厂来创建产品,而是先创建具体的工厂,然后具体的工厂来创建产品
- 即由原来的老板做,变成了发命令让别人来做
- 即由工厂命令其他工厂来生产产品
abstract class Animal { // ----------------------------------------- 产品
// 抽象类不需要自己实现方法
constructor(public name: string) {
// this.name = name
}
}
class Cat extends Animal { } // ------------------------------------ 具体产品 cat
class Dog extends Animal { } // ------------------------------------ 具体产品 dog
abstract class AnimalFactory { // ---------------------------------- 具体类型的工厂抽象类
abstract createAnimal(): Animal
}
class CatFactory extends AnimalFactory { // ------------------------ 创建cat的工厂,实现类
// 实现类 实现 抽象类的方法
createAnimal() {
return new Cat('cat')
}
}
class DogFactory extends AnimalFactory { // ------------------------ 创建dog的工厂,实现类
// 实现类 实现 抽象类的方法
createAnimal() {
return new Dog('dog')
}
}
class Factory { // ------------------------------------------------- 工厂类
// --------------------------------------------------------------- 根据类型,让具体的工厂去生产产品
static create(name: string) { // static 静态方法可以通过类本身去调用
switch (name) { // 根据传入的类型,调用不同的构造函数,创建不同的实例
case 'cat':
return new CatFactory().createAnimal()
case 'dog':
return new DogFactory().createAnimal()
default:
return new Error('出错了')
}
}
}
const dog = Factory.create('dog')
const cat = Factory.create('cat')
console.log(dog)
console.log(cat)
(3.3) 抽象工厂模式
abstract class Cat {}
abstract class Dog {}
class ChinessCat extends Cat {} // 中国猫
class ChinessDog extends Dog {} // 中国狗
class EnglishCat extends Cat {} // 美国猫
class EnglishDog extends Dog {} // 美国狗
abstract class AnimalFactory { // ------------------------------- 抽象工厂类 => 抽象动物工厂
abstract createCat(): Cat
abstract createDog(): Dog
}
class ChineseAnimalFactory extends AnimalFactory { // ----------- 实现类 => 中国动物工厂
createCat() {
return new ChinessCat()
}
createDog() {
return new ChinessDog()
}
}
class EnglishAnimalFactory extends AnimalFactory { // ----------- 实现类 => 美国动物工厂
createCat() {
return new EnglishCat()
}
createDog() {
return new EnglishDog()
}
}
const chineseAnimal = new ChineseAnimalFactory() // ------------- 中国动物
const chineseCat = chineseAnimal.createCat() // ----------------- 中国猫
console.log(chineseCat)
(四) 适配器模式 adapter
- 适配器模式又称包装器模式,将一个类的接口转化为用户需要的另一个接口,解决类或对象之间接口不兼容问题
- 旧的接口和使用者不兼容
- 中间加一个适配器转换接口
- 总结
- 适配器模式就是为了解决 ( 适配两个以上接口不兼容的问题 ) 和 ( 外观模式 ) 的核心思路保持一致
- 适配器模式adapter,也被称作 ( 包装器模式wrapper )
- 本质上是在原有逻辑上再包装一层
- 类比 ( 高阶组件 )
1
// Rmb 是需要被适配的类
class Rmb {
output() {
return '100rmb'
}
}
abstract class Money {
abstract transform(): string
}
class MoneyAdaptor extends Money { // 适配器实现类
rmb: Rmb
constructor(rmb: Rmb) {
super()
this.rmb = rmb
}
transform() {
return this.rmb.output() + '转成美元'
}
}
const dollar = new MoneyAdaptor(new Rmb())
console.log(dollar.transform());
2
适配器模式在javascript中的运用
- 概念:适配器就是将不适配东西适配起来,比如:手机转接头,转换器等
- 案例:将 ( 小写字符串 ) 适配成 ( 大写字符串 )
---
class Lower {
getSize = () => "size";
}
class Adapter {
lower = new Lower().getSize();
getSize = () => this.lower.toUpperCase(); // 转成大写
}
const upper = new Adapter().getSize();
(4.1) 适配器模式有哪些运用
- axios中区分浏览器和node端时的adaptor函数
- vue中的computed计算属性
(4.2) 适配器模式应用案例 - axios
- axios中的adapter函数就会根据系统的类型去调用不同的请求方法,比如浏览器中使用XMLHttpRequest,而在node环境中使用http模块,从而抹平差异化
- axios源码中的adapter
1. var adapter = config.adapter || defaults.adapter;
2. adapter(config).then() // 不管是什么环境,浏览器或者node都返回一个promise
3. defaultes.adapter 如下
var defaults = {
adapter: getDefaultAdapter()
}
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr'); // 浏览器环境
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = require('./adapters/http'); // node环境
}
return adapter;
}
手写一个axios中的adapter函数
axios({
method: "GET",
url: "www.baidu.com",
}).then(
(value) => console.log(value),
(reason) => console.error(reason)
);
// 设计模式yuanze
// 1. 单一职责原则 => 一个函数只实现一个逻辑
// 2. 开放封闭原则 => 可扩展,不可修改
// 3. 最少知识原则 => 模块,函数,方法,变量都要尽量依赖其他类和对象
// 4. 里氏替换原则 => 基类可以用子类直接替换
// 5. 接口隔离原则
// 6. 依赖倒置原则 => 实现类 依赖 接口或者抽象类
// 浏览器端
function xhrAdaptor(config) {
const { method, url } = config;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("Content-Type", "application/json"); // setRequestHeader 必须是在 open之后 send之前
xhr.responseType = "json";
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
// readyState
// 0 UNSENT ------------- xhr实例已经被创建,open()方法未被调用
// 1 OPEND -------------- open()方法被调用,send()方法未被调用,setRequestHeader() 可以被调用
// 2 HEADERS_RECEIVED --- send()方法被调用,响应头和响应状态已经返回
// 3 LOADING ------------ 响应体(response entity body)正在下载中,此状态下通过 xhr.response 可能已经有了响应数据
// 4 DONE --------------- 整个数据传输过程已经完成,无论本次请求是成功还是失败
}
if (xhr.status === 200) {
// readyState === 4 && status === 200
return resolve(xhr.responseText);
} else {
return reject(new Error(xhr.statusText));
}
};
xhr.onerror = function () {
console.error("error");
};
// xhr.onload = function() {
// if (xhr.status > 200 && xhr.status < 300 || xhr.status === 304) {
// return resolve(xhr.responseText)
// }
// }
xhr.send();
});
}
// 服务器端
function httpAdaptor(config) {
const { method, url } = config
const http = requrie('http')
const {hostname, port, path} = url.parse(url)
return new Promise((resolve, reject) => {
// http模块
const options = {
method,
hostname,
port,
path
};
let req = http.request(options, function (response) {
let chunks = [];
response.on("data", function (chunk) {
chunks.push(chunk);
});
response.on("end", function () {
const result = Buffer.concat(chunks).toString();
return resolve(result);
});
});
req.on("error", function (error) {
return reject(error);
});
});
}
function getDefaultAdaptor() {
// ---------------------- 适配器模式,根据环境调用不同的方法
let adaptor;
if (typeof XMLHttpRequest !== "undefined") {
// ------ 浏览器环境
adaptor = xhrAdaptor;
}
if (typeof process !== "undefined") {
// ------------- node环境
adaptor = httpAdaptor;
}
return adaptor;
}
function axios(config) {
const adaptor = getDefaultAdaptor();
return adaptor(config);
// axios返回值就是一个promise对象,无论是浏览器端还是node端
}
(五) 装饰器模式
- 装饰器模式是为已有功能更多功能的一种方式,是一种结构型设计模式,是对原有类进行扩展
是一种依靠 ( 组合 ) 来实现 ( 类的功能扩展 ),并且支 ( 持多层嵌套 ) 的设计模式
- 如果直接添加逻辑会违反 ( 单一职责原则 ) 和 ( 开放封闭原则 )
- 常用的装饰器有 ( 类装饰器 ) ( 属性装饰器 ) ( 方法装饰器 ) ( 参数装饰器 )
abstract class Shape { // 形状抽象类
abstract draw(): void;
}
class Circle extends Shape { // 形状子类 圆
draw() {
console.log("绘制圆");
}
}
class Rectagle extends Shape { // 形状子类 矩形
draw() {
console.log("绘制矩形");
}
}
abstract class Color extends Shape { // Color 也继承 Shape
constructor(public shape: Shape) {
super();
}
abstract draw(): void;
}
class Red extends Color {
draw() {
this.shape.draw(); // 调用传入的参数实例的draw方法,在new Red()时传入的参数是new Circle()
console.log("绘制红色");
}
}
class Yellow extends Color {
draw() {
this.shape.draw();
console.log("绘制黄色");
}
}
const redCircle = new Red(new Circle());
redCircle.draw() // 调用实例上的draw方法
(5.1) 装饰器模式在前端中的运用
2021/09/22 更新
- 表单验证
- 在 ( 表单提交时,先做表单验证 ) 的工作
- 核心函数:submit submit.before validate
/**
* 借助装饰者模式,很容易衍生出 AOP 面向切面编程的概念
* - 场景:典型场景就是对表单的验证,我们将把表单输入逻辑校验的 validata 函数融入到 before 逻辑当中
* - 具体:
* 1. 在提交表单时,执行 ( submit ) 函数,因为在 ( Function.prototype.before ) 挂载了 ( before ) 函数,被所有实例函数所继承
* 2. 我们并不直接submit函数,而是调用 ( submit.before ) 从而在 sumbit 之前执行验证函数 ( validate ) 从而在执行sumbit之前做表单验证功能
*/
Function.prototype.before = function(beforefn) {
const self = this
return function() {
if (beforefn.apply(this, arguments) === false) return // 表示执行验证逻辑时,验证未通过
return self.apply(this, arguments) // this是调用before方法时所在的对象,是 submitBtn 在调用before,所以在了的 self 就是 submitBtn 函数
}
}
const validate = function() {
// 表单验证逻辑
}
const formSubmit = function() {
// 表达提交逻辑
ajax('http:// xxx.com/login', param)
}
submitBtn.onclick = function() {
formSubmit.before(validate)
}
资料
设计原则1 https://juejin.cn/post/6844904025565970439
设计原则2 https://juejin.cn/post/6847902225717854215
装饰器模式1 https://juejin.cn/post/6914235611545092109
装饰器模式2 https://juejin.cn/post/6881897639001751560
vscode=>codeRunner=>ts-node出现乱码解决方案如下
https://github.com/formulahendry/vscode-code-runner/issues/632