什么是设计模式?
所谓设计模式,是前辈们总结下来的,在软件设计、开发过程中,针对特定场景、特定问题的较优解决方案。
为什么需要设计模式?
实际上,不使用设计模式,照样可以进行需求开发。但是这造成的后果是:因设计缺陷、代码实现缺陷,给后期维护、开发、迭代带来了麻烦。
一、代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。引入代理模式,其实是为了实现单一职责的面向对象设计原则。单一职责其实就是指在一个类中(js中通常指对象和函数等),应仅有一个引起它变化的原因。这样会帮助程序设计具有良好的健壮和高内聚特性,从而当变化发生时,程序设计会尽量少的受到意外破坏。
- 用户无权访问目标对象
- 中间加上代理对象,通过代理进行授权和控制。
如何实现代理模式
代理对象内部含有对本体对象的引用,因而可以与调用本体的相关方法;同时,代理对象提供与本体对象相同的接口,方便在任何时刻代理本体对象。
例一
class Girl{
sayBrokeup(b,a){
console.log(b+','+a+'想要和你分手!');
}
}
class Sisters{
constructor(){
this.someone=new Girl();
}
sayBrokeup(b,a){
this.someone.sayBrokeup(b,a);
}
}
let julia=new Sisters();
julia.sayBrokeup('jack','rose');
运行结果:
由上述代码可以看出,女生确实有说分手的能力,但她不会自己亲口说出分手,而是让自己的闺蜜向男生转述分手!
例二
加入页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发。
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');
for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
alert(i+1);
}
}
</script>
相信部分小伙伴的写法可能和我上面的写法一样,循环,给每个li添加点击事件,我们看看有多少次的dom操作,我们来看看,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li,那么问题来了,如果有很多li怎么办?
Event对象给我们提供了一个属性叫target,它可以返回事件的目标节点,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom, 我们还要用nodeName来获取具体是的标签名,这个返回的是一个大写的标签名,我们还需要转成小写。
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
var ul = document.getElementById("ul");
ul.onclick = function (e) {
var e = e || window.event;
var target = e.target;
if (target.nodeName.toLowerCase() == 'li') {
alert(target.innerHTML);
}
}
</script>
这样就达到了事件代理的效果,将所有li的点击事件,全部代理给ul,而且只有点击li才会触发事件了,而且每次只需要执行一次dom操作,如果li数量很多 ,就可以大大的减少dom操作,大大提高了性能优化。
二、单例模式
单例就是保证一个类只有一个实例,实现方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
class SingleObject {
login() {
console.log('login...')
}
}
SingleObject.getInstance = (function () {
let instance
return function () {
if (!instance) {//判断实例存在与否
instance = new SingleObject();
}
return instance
}
})()
// 测试
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2)//true
任意一个网站,点击登录按钮,只会弹出有且仅有一个登录框,即使后面再点击登录按钮,也不会再弹出多一个弹框。这就是单例模式的应用场景
三、工厂模式
将new
操作单独封装,遇到new
时,就要考虑是否该使用工厂模式。
class Animal{
static getKind(name){
switch(name){
case 'dog':
return new Dog();
case 'cat':
return new Cat();
default:
throw new Error('没有这个动物');
}
}
};
class Cat{
constructor(){
this.name='mimi'
}
say(){
console.log('小猫'+this.name)
}
}
class Dog{
constructor(){
this.name='wangwang'
}
say(){
console.log('小狗'+this.name)
}
}
const a=Animal.getKind('dog')
a.say();
工厂模式的应用
工厂模式在源码中应用频繁,以 vue-router 中的源码为例:
export default class VueRouter {
constructor(options) {
// 路由模式
this.mode = mode
// 简单工厂
switch (mode) {
// history 方式
case 'history':
this.history = new HTML5History(this, options.base)
break
// hash 方式
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
// abstract 方式
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
// 初始化失败报错
default:
}
}
}
工厂模式的优缺点
工厂模式将 对象的创建和实现分离,这带来了优点:
-
良好的封装,代码结构清晰,访问者无需知道对象的创建流程,特别是创建比较复杂的情况下;
-
扩展性优良,通过工厂方法隔离了用户和创建流程隔离,符合开放封闭原则;
-
解耦了高层逻辑和底层产品类,符合最少知识原则,不需要的就不要去交流;
工厂模式的缺点:带来了额外的系统复杂度,增加了抽象性
四、观察者模式
观察者模式,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。
// 定义主题,即观察者观察的对象(目标)
class Subject {
constructor (name) {
this.name = name
this.observers = [] // 用来存储观察者
this.state = '雨天'
}
// 添加观察者
attach (observer) {
this.observers.push(observer)
}
// 一旦被观察的信息发生变化,则通知观察者
setState (newState) {
this.state = newState
this.observers.forEach(observer => {
observer.update(newState)
})
}
}
// 定义观察者
class Observer {
constructor (name) {
this.name = name
}
// 定义观察者针对数据发生变化做出的响应
update (newState) {
console.log(this.name + '知道了' + newState)
}
}
const subject = new Subject('我是天气')
const observerOne = new Observer('我是播报员1')
const observerTwo = new Observer('我是播报员2')
subject.attach(observerOne)
subject.attach(observerTwo)
subject.setState('晴天')
五、发布订阅模式
发布/订阅模式,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
观察者模式和发布订阅模式的区别
- 角色角度来看,发布订阅模式需要三种角色,发布者、事件中心、订阅者。而观察者模式需要两种角色,目标和观察者,无事件中心负责通信。
- 从耦合度上来看,发布订阅模式是一个中心调度模式,订阅者和发布者是没有直接关联的,通过事件中心尽心关联,两者是解耦的。而观察者模式中的目标和观察者是直接关联的,耦合在一起(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者本身)