1、工厂模式
假设有一份很复杂的代码需要用户去调用,但是用户并不关心这些复杂的代码,只需要你提供给我一个接口去调用,用户只负责传递需要的参数,至于这些参数怎么使用,内部有什么逻辑是不关心的,只需要你最后返回我一个实例。这个构造过程就是工厂。
工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。
class Man {
constructor(name) {
this.name = name
}
alertName() {
alert(this.name)
}
}
class Factory {
static create(name) {
return new Man(name)
}
}
Factory.create('yck').alertName()
在vue源码中可以看到
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// 逻辑处理...
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
调用 createComponent
传入参数就能创建一个组件实例,但是创建这个实例是很复杂的一个过程,工厂帮助我们隐藏了这个复杂的过程,只需要一句代码调用就能实现功能
2、单利模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式很常用,比如全局缓存、全局状态管理等等这些只需要一个对象,就可以使用单例模式。
假设要设置一个管理员,多次调用也仅设置一次,我们可以使用闭包缓存一个内部变量来实现这个单例
function SetManager(name) {
this.manager = name;
}
SetManager.prototype.getName = function() {
console.log(this.manager);
};
function getSingleton(fn) {
var instance = null;
return function() {
if (!instance) {
instance = fn.apply(this, arguments);
}
return instance;
}
}
// 获取单例
var managerSingleton = getSingleton(function(name) {
var manager = new SetManager(name);
return manager;
});
managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a
在 Vuex
源码中,你也可以看到单例模式的使用,虽然它的实现方式不大一样,通过一个外部变量来控制只安装一次 Vuex
let Vue // bind on install
export function install (_Vue) {
if (Vue && _Vue === Vue) {
// 如果发现 Vue 有值,就不重新创建实例了
return
}
Vue = _Vue
applyMixin(Vue)
}
3、发布-订阅模式
发布-订阅模式也叫做观察者模式。通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。在现实生活中,也有很多类似场景,比如我需要在购物网站上购买一个产品,但是发现该产品目前处于缺货状态,这时候我可以点击有货通知的按钮,让网站在产品有货的时候通过短信通知我。
在实际代码中其实发布-订阅模式也很常见,比如我们点击一个按钮触发了点击事件就是使用了该模式
<ul id="ul"></ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
在 Vue
中,如何实现响应式也是使用了该模式。对于需要实现响应式的对象来说,在 get
的时候会进行依赖收集,当改变了对象的属性时,就会触发派发更新。
比如小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,导致公司C很不耐烦。
一种解决办法是 AB直接把联系方式留给C,有结果的话C自然会通知AB
// 观察者
var observer = {
// 订阅集合
subscribes: [],
// 订阅
subscribe: function(type, fn) {
if (!this.subscribes[type]) {
this.subscribes[type] = [];
}
// 收集订阅者的处理
typeof fn === 'function' && this.subscribes[type].push(fn);
},
// 发布 可能会携带一些信息发布出去
publish: function() {
var type = [].shift.call(arguments),
fns = this.subscribes[type];
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
// 挨个处理调用
for (var i = 0; i < fns.length; ++i) {
fns[i].apply(this, arguments);
}
},
// 删除订阅
remove: function(type, fn) {
// 删除全部
if (typeof type === 'undefined') {
this.subscribes = [];
return;
}
var fns = this.subscribes[type];
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
if (typeof fn === 'undefined') {
fns.length = 0;
return;
}
// 挨个处理删除
for (var i = 0; i < fns.length; ++i) {
if (fns[i] === fn) {
fns.splice(i, 1);
}
}
}
};
// 订阅岗位列表
function jobListForA(jobs) {
console.log('A', jobs);
}
function jobListForB(jobs) {
console.log('B', jobs);
}
// A订阅了笔试成绩
observer.subscribe('job', jobListForA);
// B订阅了笔试成绩
observer.subscribe('job', jobListForB);
// A订阅了笔试成绩
observer.subscribe('examinationA', function(score) {
console.log(score);
});
// B订阅了笔试成绩
observer.subscribe('examinationB', function(score) {
console.log(score);
});
// A订阅了面试结果
observer.subscribe('interviewA', function(result) {
console.log(result);
});
observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', '备用'); // 备用
observer.publish('job', ['前端', '后端', '测试']); // 输出A和B的岗位
// B取消订阅了笔试成绩
observer.remove('examinationB');
// A都取消订阅了岗位
observer.remove('job', jobListForA);
observer.publish('examinationB', 80); // 没有可匹配的订阅,无输出
observer.publish('job', ['前端', '后端', '测试']); // 输出B的岗位
观察者模式和发布订阅模型区别
观察者模式中,被观察者(可理解为发布者)与观察者(可理解为订阅者),这两者之间是直接关联、互相依赖的。
而发布-订阅模式中,发布者与订阅者是不直接关联的,它们之间多了一个事件通道,通过这个事件通道把发布者和订阅者关联起来。
观察者模式中,被观察者发布通知,所有观察者都会收到通知。
发布-订阅模式中,发布者发布通知,只有特定类型的订阅者会收到通知。
观察者模式中,被观察者发出状态更新通知后,观察者调用自身内部的更新方法。
发布-订阅模式中,订阅者的更新是通过事件通道进行细节处理和响应更新的。
具体可以查看这个地址 https://juejin.cn/post/6844903842501378055
4、代理模式
为一个对象提供一个代用品或占位符,以便控制对它的访问
代理模式主要有三种:保护代理(保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子)、虚拟代理(防抖节流)、缓存代理
js事件代理
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
5、装饰模式
以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
装饰模式不需要改变已有的接口,作用是给对象添加功能。
以下是如何实现装饰模式的例子,使用了 ES7 中的装饰器语法
function readonly(target, key, descriptor) {
descriptor.writable = false
return descriptor
}
class Test {
@readonly
name = 'yck'
}
let t = new Test()
t.yck = '111' // 不可修改
在 React 中,装饰模式其实随处可见
import { connect } from 'react-redux'
class MyComponent extends React.Component {
// ...
}
export default connect(mapStateToProps)(MyComponent)
6、适配器模式
适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。
以下是如何实现适配器模式的例子
class Plug {
getName() {
return '港版插头'
}
}
class Target {
constructor() {
this.plug = new Plug()
}
getName() {
return this.plug.getName() + ' 适配器转二脚插头'
}
}
let target = new Target()
target.getName() // 港版插头 适配器转二脚插头
在 Vue 中,我们其实经常使用到适配器模式。比如父组件传递给子组件一个时间戳属性,组件内部需要将时间戳转为正常的日期显示,一般会使用 computed 来做转换这件事情,这个过程就使用到了适配器模式