创建模式
单例模式
证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式。 很好理解,比如说一个windows系统, 只有一个任务管理器,在开机的时候创建,后面无论怎么访问都是这个任务管理器。
abap
class SingleDog {
show (){
console.log('ur a single dog');
}
static getInstance() {
if (!SingleDog.instance) {
SingleDog.instance = new SingleDog()
}
return SingleDog.instance
}
}
const s1 = SingleDog.getInstance()
const s2 = SingleDog.getInstance()
s1 === s2
缺点:1. 违反了单一职责原则,又创建又使用。 2. 不易于扩展,没有抽象层
vuex 使用单例模式
一般情况下,一个实例vue对象中只有一个store,vuex作为vue应用下的全局状态机,在生成vue实例中初始化,所以符合单例模式。
abap
export function install (_Vue) {
// 判断传入的Vue实例对象是否已经被install过Vuex插件(是否有了唯一的 store)
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
// 若没有,则为这个Vue实例对象install一个唯一的Vuex
Vue = _Vue
// 将Vuex的初始化逻辑写进Vue的钩子函数里
applyMixin(Vue)
}
vuex 并没有在class 中做单例限制,而是在install里面限制。因此也可以说vuex没有严格符合单例模式,但一般项目中就只有一个vue实例对象。
结构模式
代理模式
代理模式,式如其名------在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。
比如科学上网
事件代理
abap
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件代理</title>
</head>
<body>
<div id="father">
<a href="#">链接1号</a>
<a href="#">链接2号</a>
<a href="#">链接3号</a>
</div>
</body>
</html>
如果不适用代理,只能用for循环逐个添加。 而使用代理模式,则是通过监听父元素来实现监听所有子元素。(父元素 代理 子元素)
因为元素的事件监听有冒泡特性。(先捕获,再冒泡)
abap
// 获取父元素
const father = document.getElementById('father')
// 给父元素安装一次监听函数
father.addEventListener('click', function(e) {
// 识别是否是目标子元素
if(e.target.tagName === 'A') {
// 以下是监听函数的函数体
e.preventDefault()
alert(`我是${e.target.innerText}`)
}
} )
缓存代理
目标: 用空间换时间 方法: 缓存计算的中间值,或者缓存已经计算过的值
算法题中, 递归经常使用这种方法提高效率。
abap
function fibonacci(n) {
if (n === 1 || n === 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
function getAllFibo(arr) {
const map = {};
const result = [];
arr.forEach((item) => {
let calcFibo;
if (map[item]) {
calcFibo = map[item]; // 代理表现,通过访问map 代替访问 fabonacci函数
} else {
calcFibo = fibonacci(item);
map[item] = calcFibo;
}
result.push(calcFibo);
});
return result;
}
getAllFibo([1, 2, 3, 4, 5, 6, 3, 3, 3, 3]);
保护代理
使用Proxy构造函数,通过proxy对象来先对目标对象进行代理 如果是不可访问的对象直接返回特定信息实现保护。
abap
const protectedObj = {
name: 'jimmy',
age: 18,
}
const visitedObj = new Proxy(protectedObj, {
get(target, property, receiver) {
if (property === 'age') {
return 'fku';
} else {
return Reflect.get(target, property, receiver)
}
}
})
visitedObj.name; // jimmy
visitedObj.age; // fku
vue3 中使用的代理
vue3 的响应式数据就是使用 代理实现的。 为什么响应式的数据会触发页面更新,而普通数据不触发更新? 因为声明响应式数据的时候会经过一层代理,修改了set
方法,每次触发修改时,会记录一层更新事件,在下一次更新时统一更新。
abap
class RefImpl {
constructor(value, __v_isShallow) { // 值,是否浅层ref
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value); // 判断是否为浅层ref,否则调用toReactive,方法在下面
}
get value() { // getter方法 获取value值
trackRefValue(this);
return this._value;
}
set value(newVal) { // setter方法 设置value值
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal); // 在value值更新时进行判断是否为浅层ref,否则调用toReactive
triggerRefValue(this, newVal);
}
}
}
为什么模板中不用.value
而 js 代码中要写上呢,因为模板中使用了 proxy 自动 脱.value
abap
export function proxyRefs(object) {
return new Proxy(object, {
// 代理的思想,如果是ref 则取ref.value
get(target, key, recevier) {
let r = Reflect.get(target, key, recevier)
return r.__v_isRef ? r.value : r
},
// 设置的时候如果是ref,则给ref.value赋值
set(target, key, value, recevier) {
let oldValue = target[key]
if (oldValue.__v_isRef) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, recevier)
}
},
})
}
行为模式
策略模式 - 状态模式
业务上用得最多的, 比如说 根据不同的状态展示不同的样式 就可以实现一个 statusMap
函数来实现
解决大量的if-else
代码 有利于代码遵循开闭原则
abap
function a(){} // do a
function b(){} // do b
function c(){} // do c
function badDoSomething(param) {
if (param === 'a') {
a()
} else if (param === 'b') {
b()
} else if (param === 'c') {
c()
}
}
function goodDoSomething(param) {
const map = {
a: a,
b: b,
c: c
}
map[param]();
}
策略模式和状态模式确实是相似的,它们都封装行为、都通过委托来实现行为分发。 但策略模式中的行为函数是"潇洒"的行为函数,它们不依赖调用主体、互相平行、各自为政,井水不犯河水。而状态模式中的行为函数,首先是和状态主体之间存在着关联,由状态主体把它们串在一起;另一方面,正因为关联着同样的一个(或一类)主体,所以不同状态对应的行为函数可能并不会特别割裂。
观察者模式 - 发布订阅模式
定义: 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
区别,观察者模式一般是发布者和订阅者可以互相接触, 而发布订阅模式 发布者和订阅者互相不直接交流,中间有一层事件中心。 (实现起来的核心思想是一样的)
abap
class Publisher { // 目标对象
constructor() {
this.observers = [] // 观察此对象的观察者们
}
add(observer) {
this.observers.push(observer);
}
del(observer) {
if (this.observers.includes(observer)) {
this.observers.splice(this.observers.findIndex(item => item === observer), 1)
}
}
notify() {
this.observers.forEach((observer) => {
observer.update(this);
})
}
}
class Observer { // 观察者
update() {
console.log('do something');
}
}
事件总线 发布订阅模式
所有事件的发布/订阅操作,必须经由事件中心,禁止一切"私下交易"!
abap
class EventEmitter {
constructor() {
this.handlers = new Map();
}
on(name, cb) {
if (!this.handlers.has(name)) {
this.handlers.set(name, []);
}
this.handlers.get(name).push(cb);
}
emit(name, ...args) {
if (this.handlers.has(name)) {
const cbs = this.handlers.get(name);
cbs.forEach(cb => cb(...args))
}
}
off(name, cb) {
if (this.handlers.has(name)) {
const cbs = this.handlers.get(name);
const idx = cbs.findIndex(item => item === cb);
cbs.splice(idx, 1)
}
}
}
vue 的响应式原理、watch
在 Vue 中,每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新------这是一个典型的观察者模式。
每个响应式对象也相当一个 publisher,每次修改时会触发set()方法,通知所有的observer()更新。
- publisher: vue组件, 响应式数据, watch方法的第一个入参
- observer: watcher对象。watcher 接收到新的数据后,会去更新视图。
发布者实现 vue2
abap
class Dep {
constructor() {
// 初始化订阅队列
this.subs = []
}
// 增加订阅者
addSub(sub) {
this.subs.push(sub)
}
// 通知订阅者(是不是所有的代码都似曾相识?)
notify() {
this.subs.forEach((sub)=>{
sub.update()
})
}
}
订阅者实现:
abap
// observe方法遍历并包装对象属性
function observe(target) {
// 若target是一个对象,则遍历它
if(target && typeof target === 'object') {
Object.keys(target).forEach((key)=> {
// defineReactive方法会给目标属性装上"监听器"
defineReactive(target, key, target[key])
})
}
}
// 定义defineReactive方法
function defineReactive(target, key, val) {
// 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
observe(val)
// 为当前属性安装监听器
Object.defineProperty(target, key, {
// 可枚举
enumerable: true,
// 不可配置
configurable: false,
get: function () {
return val;
},
// 监听器函数
set: function (value) {
console.log(`${target}属性的${key}属性从${val}值变成了了${value}`)
val = value
}
});
}
迭代器模式
定义: 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
比如说Array.prototype.forEach
只能在原型链在数组的聚合对象上使用,一些类数组就无法访问这个方法。
ES6中迭代器的实现
ES6约定,任何数据结构只要具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历------准确地说,是被for...of...循环和迭代器的next方法遍历。 事实上,for...of...的背后正是对next方法的反复调用。
abap
const arr = [1, 2, 3]
// 通过调用iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()
// 对迭代器对象执行next,就能逐个访问集合的成员
iterator.next()
iterator.next()
iterator.next()
因此,只要是配置了[Symbol.iterator] 的结构对象,都能使用for of 来循环。