1、设计的原则
1.1 单一职责原则
每个类只需要负责自己的那部分,类的复杂度就会降低。
1.2 开闭原则
对扩展开放,对修改关闭
1.3 里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象。
换句话说父类的对象替换成子类来使用,而程序执行效果不会改变。
1.4 迪米特法则
迪米特法则(最少知识原则),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
1.5 接口隔离原则
多个特定的客户端接口要好于一个通用性的总接口。
尽可能将接口单独抽离,需要的时候将其使用。
1.6 依赖倒置原则
- 上层模块不应该依赖底层模块,它们都应该依赖于抽象。
- 抽象不应该依赖于细节,细节应该依赖于抽象
2、什么是设计模式
设计模式是软件开发人员在软件开发过程中面临的一些具有代表性问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
3、常见的设计模式
3.1 单例模式
3.1.1 通过静态属性创建
class Person {
// 定义静态属性,用于保存实例地址
static instance = null
constructor (name) {
// 如果创建过,则返回原来的
if (Person.instance) {
return Person.instance
}
// 将实例保存起来
Person.instance = this
this.name = name
}
}
3.1.2 通过函数创建
// 通用的单例函数
function getSingle (fn) {
let instance
return function(...args) {
if (!instance) {
instance = new fn(...args)
}
return instance
}
}
class Person {
constructor(name, age){
this.name = name
this.age = age
}
}
const SinglePerson = getSingle(Person)
const newPerson = new SinglePerson("梁又文", 22)
3.1.3 应用场景
window、document、store、jQuery、对话框等。
3.2 工厂模式
数据 =》 加工 =》 返回需要的内容。
封装具体实例创建逻辑和过程,外部只需要根据不同条件返回不同的实例。
function Factory (type) {
switch (type) {
case '实例1':
return new ShiLi1()
break
case '实例2':
return new ShiLi2()
break
default:
console.log('没有匹配')
break
}
}
3.3 装饰者模式
Decorator 扩展一些额外信息(把功能增强)
3.3.1 普通的装饰效果
class Person {
constructor(name){
this.name = name
}
sayHi(){
console.log("你好呀")
}
}
Function.prototype.Decorator = function(fn) {
// 谁调用我,我就执行谁
this()
// 并且我还要执行传进来的函数
fn()
}
const lyw = new Person("梁又文")
lyw.sayHi.Decorator(()=>console.log("我又加了个功能"))
3.3.2 装饰者链
Function.prototype.Decorator = function(fn) {
let _this = this
return function() {
_this()
fn()
}
}
const test1 = ()=>console.log("我是1")
const test2 = ()=>console.log("我是2")
lyw.say.Decorator(test1).Decorator(test2)()
3.4 观察者模式(EventHub)
观察者模式又叫自定义事件。
定义一个对象与其他对象之间的一种依赖关系,当对象发生某种变化的时候,依赖它的其它对象都会得到更新。解耦 、延迟执行、一对多的依赖关系。
class Event {
constructor () {
// 保存事件
this.handles = {}
}
// 添加事件,监听、观察
addEvent (eventName, fn) {
if (typeof this.handles[eventName] === 'undefined') {
this.handles[eventName] = []
}
this.handles[eventName].push(fn)
}
// 触发
trigger (eventName) {
this.handles[eventName].forEach(v => {
v()
})
}
// 移除事件
removeEvent (eventName, fn) {
if (!(eventName in this.handles)) return
for (let i = 0; i < this.handles[eventName].length; i++) {
if (this.handles[eventName][i] === fn) {
this.handles[eventName].splice(i, 1)
break
}
}
}
}
3.5 代理模式
为其他对象提供一种代理以控制对这个对象的访问,类似于生活中的中介。
3.5.1 示例1
找个中间商代理买房,中间人可以拿走20块钱的中介费用
const obj1 = {
payMoney(money) {
console.log('支付了', money)
},
}
const proxyObj = {
proxyPayMoney(isProxy, money) {
if (isProxy) {
obj1.payMoney(money - 20)
} else {
obj1.payMoney(money)
}
},
}
let realMoney = 100
obj1.payMoney(realMoney)
proxyObj.proxyPayMoney(true, realMoney)
proxyObj.proxyPayMoney(false, realMoney)
3.5.2 Proxy代理
每次读取或者写入数据的时候,通过newObj进行代理,可以进行额外的操作
const obj2 = {
name: '梁又文',
}
const newObj2 = new Proxy(obj2, {
get(target, keyName) {
console.log('读取属性时触发', keyName)
console.log('>>>>>>>>>>>>>>>>>>>')
return target[keyName]
},
set(target, keyName, value) {
console.log('设置属性时触发')
console.log(keyName, value)
console.log('>>>>>>>>>>>>>>>>>>>')
target[keyName] = value
},
})
console.log(newObj2.name)
newObj2.name = '憨憨'
console.log(newObj2.name)
3.5.3 加载图片加载动画
通过代理,实现在加载前显示加载动画,并且不改变原有功能
// 创建图片
class CreateImg{
constructor(){
this.img = document.createElement("img");
document.body.appendChild(this.img);
}
setSrc(src){
this.img.src = src;
}
}
const src = '需要加载的图片地址'
// 代理图片
function ProxyImg(src){
// 创建图片
let myImg = new CreateImg();
// 创建加载图片
let loadImg = new Image();
// 先设置加载到 loadImg 中
loadImg.src = src;
// 将目前显示改为加载的图片
myImg.setSrc("./loading.jpg");
// 等待 loadImg 的图片加载结束后,设置给myImg
loadImg.onload = function(){
myImg.setSrc(this.src);
}
}
// 执行代码
ProxyImg(src)
3.6 适配器模式
两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
3.6.1 axios中服务端与浏览器的适配
function getDefaultAdapter() {
let adapter
if(typeof XMLHttpRequest !== 'undefined') {
adapter = '浏览器'
}else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = '服务端'
}
return adapter
}
3.6.2 对响应数据进行加工(适配)
// 模拟后端返回的数据
function getUsers() {
return [
{ name: 'zhangsan', age: 20 },
{ name: 'lisi', age: 25 },
{ name: 'wangwu', age: 23 },
]
}
function adaptor(users) {
const arr = []
for (let i = 0; i < users.length; i++) {
const obj = { [users[i].name]: users[i].age }
arr.push(obj)
}
return arr
}
const newArr = adaptor(getUsers())
console.log(newArr)
3.7 混入模式
混入模式(mixins),虽然可以进行混合合并。有两个明显的缺点:1. 原型污染 2. 不知道混入来源
class Student1 {
constructor(name) {
this.name = name
}
playGame() {
console.log('打游戏')
}
}
class Student2 {
goSchool() {
console.log('上学')
}
backHome() {
console.log('回家了')
}
}
function mixin(receivingClass, givingClass) {
if (typeof arguments[2] !== 'undefined') {
for (let i = 2; i < arguments.length; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]
}
}
}
mixin(Student1, Student2, 'goSchool', 'backHome')
const lyw = new Student1('梁又文')
lyw.playGame()
lyw.goSchool()
lyw.backHome()