看客雅正 發表於 2020-2-10 09:02:00

21种JavaScript设计模式最新记录(含图和示例)

<p>  最近观看了《<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">Javascript设计模式系统讲解与应用</span></span>》教程,对设计模式有了新的认识,特在此做些记录。</p>
<h1>一、UML</h1>
<p>  文中会涉及众多的UML类图,在开篇需要做点基础概念的认识。以下面的图为例,图片和说明均来源于《<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">大话设计模式</span></span>》一书。</p>
<p><img style="display: block; margin: 0 auto" src="https://img2018.cnblogs.com/blog/211606/201912/211606-20191227161046855-778014437.gif" alt=""></p>
<p>  (1)矩形框,它代表一个类。类图分三层,第一层显示类的名称,如果是抽象类,则用斜体显示。第二层是类的特性,通常就是字段和属性。第三层是类的操作,通常是方法或行为。前面的符号,+ 表示public,- 表示private,# 表示protected。</p>
<p>  (2)矩形框的顶端包含&lt;&lt;interface&gt;&gt;就表示一个接口。第一行是接口名称,第二行是接口方法。</p>
<p>  (3)继承关系用空心三角形 + 实线来表示的。</p>
<p>  (4)实现接口用空心三角形 + 虚线来表示。</p>
<p>  (5)当一个类知道另一个类时,可以用关联(Association),关联关系用实线箭头来表示。</p>
<p>  (6)聚合(Aggregation)表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。聚合关系用空心的菱形 + 实线箭头来表示。</p>
<p>  (7)组合(Composition)是一种强的拥有关系,体现了严格的部分和整体的关系,组合关系用实心的菱形 + 实线箭头来表示。</p>
<p>  (8)依赖(Dependency)关系,用虚线箭头来表示。</p>
<h1>二、五大原则(SOLID)</h1>
<p>  作者认为设计模式得分成两部分,第一是设计(即设计原则),第二才是模式。五大原则是设计模式的基础,SOLID是五大原则的首字母简写,作者在介绍每个模式的时候,都会提到是否符合这五大原则。</p>
<p>  (1)S-单一职责原则,一个程序只做一件事,如果功能过于复杂,那么就拆分,保持独立。</p>
<p>  (2)O-开放封闭原则,对扩展开发,对修改封闭,增加需求就扩展新代码,而非修改已有代码。修改已有代码不但需要测试,成本高昂,而且多人开发很容易发生冲突。</p>
<p>  (3)L-里氏替换原则,子类能覆盖父类,父类能出现的地方子类就能出现。</p>
<p>  (4)I-接口分离原则,保持接口的单一独立,避免胖接口。</p>
<p>  (5)D-依赖倒置原则,面向接口编程,依赖于抽象而不依赖于具体,使用方只关注接口而不关注具体类的实现。</p>
<p>  在JavaScript中,SO体现较多;而LID体现较少,因为JavaScript的语法限制,例如弱类型、没有接口等。如果用Promise来说明SO,那么就是:</p>
<p>  (1)S:每个then中的逻辑只做好一件事。</p>
<p>  (2)O:如果新增需求,那么就扩展then。</p>
<p>  说句题外话,TypeScript是一门强类型面向对象语言,在它问世后,就可以完完整整的应用这五大原则,不会有什么限制。前段时间写过几篇TypeScript的简单教程,有兴趣的可以<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">参考</span></span>。</p>
<h1>三、21种设计模式</h1>
<p><span style="font-size: 18px"><strong>1)工厂模式</strong></span></p>
<p>  将new操作单独封装。遇到new时,就要考虑是否该使用工厂模式。例如购买汉堡直接点餐,不用自己制作,而商店要封装做汉堡的操作,然后卖给客户。下面是一个简单示例。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Creator {
create(name) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Product(name);
}
}
let creator </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Creator();
let p </span>= creator.create("p");</pre>
</div>
<p>  使用场景包括jQuery的$("div")、React.createElement()和Vue的异步组件。</p>
<p><span style="font-size: 18px"><strong>2)单例模式</strong></span></p>
<p>  系统中被唯一使用,一个类只有一个实例,例如登录框、购物车。单例模式需要用到private修饰符,但ES6中没有,可用闭包模拟,如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class SingleObject {
login() {
    console.log(</span>"login..."<span style="color: rgba(0, 0, 0, 1)">);
}
}
SingleObject.getInstance </span>= (<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
let instance;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">instance) {
      instance </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SingleObject();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> instance;
};
})();
let obj1 </span>=<span style="color: rgba(0, 0, 0, 1)"> SingleObject.getInstance();
obj1.login();</span></pre>
</div>
<p>  使用场景包括jQuery中的$(只有一个)、模拟登录框和Redux中的Store。</p>
<p><span style="font-size: 18px"><strong>3)适配器模式</strong></span></p>
<p>  旧接口格式和使用者不兼容,中间加一个适配转换接口。以转换插头为例,如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Adaptee {
specificRequest() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> "德国标准"<span style="color: rgba(0, 0, 0, 1)">;
}
}
class Target {
constructor() {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.adaptee = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Adaptee();
}
request() {
    let info </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.adaptee.specificRequest();
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> `${info}--转换--<span style="color: rgba(0, 0, 0, 1)">中国标准`;
}
}
let target </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Target();
target.request();</span></pre>
</div>
<p>  使用场景包括封装旧接口、Vue的计算属性。</p>
<p><span style="font-size: 18px"><strong>4)装饰器模式</strong></span></p>
<p>  为对象添加新功能,不改变其原有的结构和功能。与适配器模式不同,之前的接口仍然可以使用。装饰器模式类似于生活中的手机壳,不仅保护了手机,还不影响音量键、扬声器等部分。</p>
<p>  使用场景包括<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">ES7装饰器</span></span>、<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">core-decorator</span></span>,下面通过装饰器语法演示调用方法时自动打日志。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> log(target, name, descriptor) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> oldValue =<span style="color: rgba(0, 0, 0, 1)"> descriptor.value;
descriptor.value </span>= <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
    console.log(`Calling ${name} </span><span style="color: rgba(0, 0, 255, 1)">with</span><span style="color: rgba(0, 0, 0, 1)">`, arguments);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> oldValue.apply(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, arguments);
};
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> descriptor;
}
class Math {
@log
add(a, b) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> a +<span style="color: rgba(0, 0, 0, 1)"> b;
}
}
const math </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Math();
math.add(</span>2, 4);</pre>
</div>
<p>  装饰器的原理如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@decorator
class A {}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">等同于</span>
<span style="color: rgba(0, 0, 0, 1)">class A {}
A </span>= decorator(A) || A;</pre>
</div>
<p><span style="font-size: 18px"><strong>5)代理模式</strong></span></p>
<p>  当使用者无权访问目标对象时,可在中间加代理,通过代理做授权和控制,类似于明星的经纪人。</p>
<p>  使用场景包括DOM的事件委托、jQuery的$.proxy、<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">ES6的proxy</span></span>。下面通过代理语法模拟明星和经纪人。</p>
<div class="cnblogs_code">
<pre>let star =<span style="color: rgba(0, 0, 0, 1)"> {
name: </span>"张XX",                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 明星</span>
age: 25<span style="color: rgba(0, 0, 0, 1)">,
phone: </span>"13800138000"<span style="color: rgba(0, 0, 0, 1)">
};
let agent </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Proxy(star, {   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 经纪人</span>
get: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(target, key) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (key === "phone") {      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回经纪人自己的手机号</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> "18012345678"<span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (key === "price") {      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 明星不报价,经纪人报价</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> 120000<span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> target;
},
set: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(target, key, val) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (key === "customPrice"<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (val &lt; 100000<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error("价格太低"<span style="color: rgba(0, 0, 0, 1)">);
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      target </span>=<span style="color: rgba(0, 0, 0, 1)"> val;
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}
});</span></pre>
</div>
<p>  代理模式提供一模一样的接口,而适配器模式提供不同的接口。代理模式直接针对原有功能,不过需要经过限制或阉割;装饰器模式能扩展功能,而原有功能不变且可直接使用。</p>
<p><span style="font-size: 18px"><strong>6)外观模式</strong></span></p>
<p>  为子系统中的一组接口提供一个高层接口,使用者使用这个高层接口。下面是外观模式的示意图,去医院看病通常是左边的情况,但如果有接待员,那么就会是右边的情况,由他去挂号、门诊、取药等。</p>
<p><img style="display: block; margin: 0 auto" src="https://img2018.cnblogs.com/blog/211606/201912/211606-20191227162755835-146076550.jpg" alt="" width="600"></p>
<p>  外观模式的UML类图是下面这样。</p>
<p><img style="display: block; margin: 0 auto" src="https://img2018.cnblogs.com/blog/211606/201912/211606-20191227162910502-1309765284.jpg" alt=""></p>
<p>  使用场景包括同一个函数可接收不同组合的参数,如下所示,在jQuery中提供了很多这类方法。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> bindEvent(elem, type, selector, fn) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (fn == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    fn </span>=<span style="color: rgba(0, 0, 0, 1)"> selector;
    selector </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调用可以有两种</span>
bindEvent(elem, "click", "#div"<span style="color: rgba(0, 0, 0, 1)">, fn);
bindEvent(elem, </span>"click", fn);</pre>
</div>
<p>  注意,外观模式虽然好用,但是不符合单一职责和开放封闭原则,需要谨慎使用。</p>
<p><span style="font-size: 18px"><strong>7)观察者模式</strong></span></p>
<p>  观察者模式是一种发布订阅机制,当对象间存在一对多(也可以一对一)的关系时,可使用观察者模式。在使用观察者模式后,当一个对象被修改时,就会自动通知它的依赖对象。</p>
<p>  生活中的点咖啡也是一种观察者模式,当点好后,就找位子坐下,等叫号或者等服务员送过来。下面是个简单的示例。</p>
<div class="cnblogs_code">
<pre>class Subject {                      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主题,接收状态变化,触发每个观察者</span>
<span style="color: rgba(0, 0, 0, 1)">constructor() {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state = 0<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.observers =<span style="color: rgba(0, 0, 0, 1)"> [];
}
getState() {                     </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取状态</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state;
}
setState(state) {                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">修改状态</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> state;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.notifyAllObservers();
}
attach(observer) {               </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">添加观察者</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.observers.push(observer);
}
notifyAllObservers() {             </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通知所有观察者</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.observers.forEach(observer =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      observer.update();
    });
}
}
class Observer {                     </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 观察者,等待被触发</span>
<span style="color: rgba(0, 0, 0, 1)">constructor(name, subject) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.subject =<span style="color: rgba(0, 0, 0, 1)"> subject;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.subject.attach(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}
update() {
    console.log(`${</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name} update, state: ${<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.subject.getState()}`);
}
}

let s </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Subject();
let o1 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Observer("o1"<span style="color: rgba(0, 0, 0, 1)">, s);
let o2 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Observer("o2"<span style="color: rgba(0, 0, 0, 1)">, s);
s.setState(</span>1<span style="color: rgba(0, 0, 0, 1)">);
s.setState(</span>2);</pre>
</div>
<p>  使用场景包括DOM事件绑定、<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">Promise</span></span>、jQuery callbacks和Node.js自定义事件。</p>
<p><span style="font-size: 18px"><strong>8)迭代器模式</strong></span></p>
<p>  访问一个有序集合(不包括对象),使用者无需知道集合的内部结构。下面是一个模拟的迭代器(Iterator),包含next()和hasNext()方法。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Iterator {
constructor(conatiner) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.list =<span style="color: rgba(0, 0, 0, 1)"> conatiner.list;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.index = 0<span style="color: rgba(0, 0, 0, 1)">;
}
next() {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.hasNext()) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.list[<span style="color: rgba(0, 0, 255, 1)">this</span>.index++<span style="color: rgba(0, 0, 0, 1)">];
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
}
hasNext() {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.index &gt;= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.list.length) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
class Container {
constructor(list) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.list =<span style="color: rgba(0, 0, 0, 1)"> list;
}
getIterator() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Iterator(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}
}

let container </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Container();
let iterator </span>=<span style="color: rgba(0, 0, 0, 1)"> container.getIterator();
</span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)"> (iterator.hasNext()) {
console.log(iterator.next());
}</span></pre>
</div>
<p>  使用场景包括jQuery的each、<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">ES6的Iterator</span></span>。ES6之所以引入Iterator有以下三个原因:</p>
<p>  (1)实现了迭代器模式。</p>
<p>  (2)ES6语法中,有序集合的数据类型已经有很多,例如Array、Map、Set、String、TypedArray、arguments和NodeList。</p>
<p>  (3)需要有一个统一的接口来遍历所有数据类型。</p>
<p>  注意,Object不是有序集合,可用Map替代。</p>
<p><span style="font-size: 18px"><strong>9)状态模式</strong></span></p>
<p>  一个对象会有状态变化,每次状态变化都会触发一个逻辑,而这个逻辑不能总是用if...else语句来控制。生活中的交通信号灯是一种状态模式,当颜色变化时,会有不同的结果,例如红灯禁止、绿灯通行。</p>
<p>  使用场景包括Promise原理、有限状态机。下面的示例使用了开源的<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">javascript-state-machine</span></span>。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> fsm = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StateMachine({
init: </span>"solid"<span style="color: rgba(0, 0, 0, 1)">,
transitions: [
    { name: </span>"melt", from: "solid", to: "liquid"<span style="color: rgba(0, 0, 0, 1)"> },
    { name: </span>"freeze", from: "liquid", to: "solid"<span style="color: rgba(0, 0, 0, 1)"> },
    { name: </span>"vaporize", from: "liquid", to: "gas"<span style="color: rgba(0, 0, 0, 1)"> },
    { name: </span>"condense", from: "gas", to: "liquid"<span style="color: rgba(0, 0, 0, 1)"> }
],
methods: {
    onMelt: </span><span style="color: rgba(0, 0, 255, 1)">function</span>() { console.log("I melted"<span style="color: rgba(0, 0, 0, 1)">); },
    onFreeze: </span><span style="color: rgba(0, 0, 255, 1)">function</span>() { console.log("I froze"<span style="color: rgba(0, 0, 0, 1)">); },
    onVaporize: </span><span style="color: rgba(0, 0, 255, 1)">function</span>() { console.log("I vaporized"<span style="color: rgba(0, 0, 0, 1)">); },
    onCondense: </span><span style="color: rgba(0, 0, 255, 1)">function</span>() { console.log("I condensed"<span style="color: rgba(0, 0, 0, 1)">); }
}
});</span></pre>
</div>
<p><span style="font-size: 18px"><strong>10)原型模式</strong></span></p>
<p>  克隆自己,生成一个新对象。Java默认有clone接口,不用自定义。而JavaScript中的<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">Object.create()</span></span>采用了原型模式的思想,如下所示。</p>
<div class="cnblogs_code">
<pre>const person =<span style="color: rgba(0, 0, 0, 1)"> {
isHuman: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
printIntroduction: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
    console.log(`My name is ${</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.name}`);
}
};
const me </span>=<span style="color: rgba(0, 0, 0, 1)"> Object.create(person);
me.name </span>= "Matthew"<span style="color: rgba(0, 0, 0, 1)">;
me.isHuman </span>= <span style="color: rgba(0, 0, 255, 1)">true</span>;</pre>
</div>
<p><span style="font-size: 18px"><strong>11)桥接模式</strong></span></p>
<p>  将抽象化与实现化两者解耦,使得它们可以独立变化。</p>
<p>  下图和分析均来源于《<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">C#设计模式(10)——桥接模式</span></span>》,在用桥接模式优化后,就能将形状和颜色通过继承生产的强耦合关系改成弱耦合的关联关系,这里采用了组合大于继承的思想。</p>
<p><img style="display: block; margin: 0 auto" src="https://img2018.cnblogs.com/blog/211606/201912/211606-20191230110119811-747826752.png" alt="" width="600"></p>
<p>  如果想添加一个五角星,只需要添加一个形状类的子类五角星接即可,不需要再去添加各种颜色的具体五角星了。如果想要一个蓝色五角星就将五角星和蓝色进行组合来获取。这样设计降低了形状和颜色的耦合,减少了具体子类的种类。</p>
<p><span style="font-size: 18px"><strong>12)组合模式</strong></span></p>
<p>  生成树形结构,表示“整体-部分”关系。让整体和部分都具有一致的操作方式。虚拟DOM中的vnode是这种形式(如下所示),但数据类型比较简单。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
tag: </span>"div"<span style="color: rgba(0, 0, 0, 1)">,
attr: {
    className: </span>"container"<span style="color: rgba(0, 0, 0, 1)">
},
children: [
    {
      tag: </span>"p"<span style="color: rgba(0, 0, 0, 1)">,
      attr: {},
      children: [</span>"123"<span style="color: rgba(0, 0, 0, 1)">]
    },
    {
      tag: </span>"p"<span style="color: rgba(0, 0, 0, 1)">,
      attr: {},
      children: [</span>"456"<span style="color: rgba(0, 0, 0, 1)">]
    }
]
}</span></pre>
</div>
<p><span style="font-size: 18px"><strong>13)享元模式</strong></span></p>
<p>  享元是个组合词,分为共享和元数据。享元模式关注共享内存,考虑内存而非效率。</p>
<p>  在JavaScript中不用刻意的考虑内存问题,因此没有享元模式的直接场景,但可以找到意义相关(即共享数据开销思想)的场景,例如事件委托。</p>
<p><span style="font-size: 18px"><strong>14)策略模式</strong></span></p>
<p>  不同策略分开处理,避免出现大量if...else或者switch语句,下面是个示例。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class User {
constructor(type) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.type =<span style="color: rgba(0, 0, 0, 1)"> type;
}
buy() {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.type == "ordinary"<span style="color: rgba(0, 0, 0, 1)">) {
      console.log(</span>"普通用户购买"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.type == "member"<span style="color: rgba(0, 0, 0, 1)">) {
      console.log(</span>"会员用户购买"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.type == "vip"<span style="color: rgba(0, 0, 0, 1)">) {
      console.log(</span>"VIP用户购买"<span style="color: rgba(0, 0, 0, 1)">);
    }
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">策略模式生成三个类,各自实现buy()方法</span>
<span style="color: rgba(0, 0, 0, 1)">class UserOrdinary {
buy() {
    console.log(</span>"普通用户购买"<span style="color: rgba(0, 0, 0, 1)">);
}
}
class UserMember {
buy() {
    console.log(</span>"会员用户购买"<span style="color: rgba(0, 0, 0, 1)">);
}
}
class UserVip {
buy() {
    console.log(</span>"VIP用户购买"<span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p><span style="font-size: 18px"><strong>15)模板方法模式</strong></span></p>
<p>  如果代码中有几步处理,可以用一个方法将它们封装在一个方法中(如下所示),在方法中可对顺序或特殊逻辑进行处理,对外统一输出,有效降低了耦合度。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Action {
handle() {
    handle1();
    handle2();
    handle3();
}
handle1() { }
handle2() { }
handle3() { }
}</span></pre>
</div>
<p><span style="font-size: 18px"><strong>16)职责链模式</strong></span></p>
<p>  一步操作可能分为多个职责角色来完成,然后把这些角色都分开,再用一个链串起来,并且将发起者和各个处理者进行隔离。例如请假审批,需要逐级审批,如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Action {
constructor(name) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.nextAction = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
}
setNextAction(action) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.nextAction =<span style="color: rgba(0, 0, 0, 1)"> action;
}
handle() {
    console.log(`${</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.name} 审批`);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.nextAction != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.nextAction.handle();
    }
}
}
let a1 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Action("组长"<span style="color: rgba(0, 0, 0, 1)">);
let a2 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Action("经理"<span style="color: rgba(0, 0, 0, 1)">);
let a3 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Action("总监"<span style="color: rgba(0, 0, 0, 1)">);
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();</span></pre>
</div>
<p>  职责链模式和业务结合较多,能联想到链式操作,例如jQuery的链式操作、Promise.then的链式操作。</p>
<p><span style="font-size: 18px"><strong>17)命令模式</strong></span></p>
<p>  执行命令时,发布者和执行者分开。中间加入命令对象,作为中转站。发布者相当于将军,他会让旗手或号手将命令传达给普通士兵,如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Receiver {
exec() {
    console.log(</span>"执行"<span style="color: rgba(0, 0, 0, 1)">);
}
}
class Command {
constructor(receiver) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.receiver =<span style="color: rgba(0, 0, 0, 1)"> receiver;
}
cmd() {
    console.log(</span>"触发命令"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.receiver.exec();
}
}
class Invoker {
constructor(command) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.command =<span style="color: rgba(0, 0, 0, 1)"> command;
}
invoke() {
    console.log(</span>"开始"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.command.cmd();
}
}
let solider </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Receiver();             <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">士兵</span>
let trumpeter = <span style="color: rgba(0, 0, 255, 1)">new</span> Command(solider);   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">小号手</span>
let general = <span style="color: rgba(0, 0, 255, 1)">new</span> Invoker(trumpeter);   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将军</span>
general.invoke();</pre>
</div>
<p>  使用场景包括网页富文本编辑器,由浏览器封装一个命令对象,如下所示。</p>
<div class="cnblogs_code">
<pre>document.execCommand("bold"<span style="color: rgba(0, 0, 0, 1)">);
document.execCommand(</span>"undo");</pre>
</div>
<p><span style="font-size: 18px"><strong>18)备忘录模式</strong></span></p>
<p>  随时记录一个对象的状态变化,随时可以恢复之前的某个状态,例如网页编辑器中的撤销功能。</p>
<p><span style="font-size: 18px"><strong>19)中介者模式</strong></span></p>
<p>  当有许多对象,并且对象之间会频繁的相互访问(如左图)时,就会变得很混乱,而对象之间的访问都由中介者代转(如右图),就会变得很有条理,不会出现牵一发动全身的情况。</p>
<p><img style="display: block; margin: 0 auto" src="https://img2018.cnblogs.com/blog/211606/201912/211606-20191227170208252-809461214.png" alt="" width="600"></p>
<p>  生活中的房屋中介就采用了这种模式,如下所示。</p>
<div class="cnblogs_code">
<pre>class Mediator {      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">中介者</span>
<span style="color: rgba(0, 0, 0, 1)">constructor(a, b) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.a =<span style="color: rgba(0, 0, 0, 1)"> a;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.b =<span style="color: rgba(0, 0, 0, 1)"> b;
}
setA() {
    let number </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.b.number;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.a.setNumber(number * 100<span style="color: rgba(0, 0, 0, 1)">);
}
setB() {
    let number </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.a.number;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.b.setNumber(number / 100<span style="color: rgba(0, 0, 0, 1)">);
}
}
class A {
constructor() {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.number = 0<span style="color: rgba(0, 0, 0, 1)">;
}
setNumber(num, m) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.number =<span style="color: rgba(0, 0, 0, 1)"> num;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (m) {
      m.setB();         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过中介者修改</span>
<span style="color: rgba(0, 0, 0, 1)">    }
}
}
class B {
constructor() {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.number = 0<span style="color: rgba(0, 0, 0, 1)">;
}
setNumber(num, m) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.number =<span style="color: rgba(0, 0, 0, 1)"> num;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (m) {
      m.setA();         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过中介者修改</span>
<span style="color: rgba(0, 0, 0, 1)">    }
}
}</span></pre>
</div>
<p><span style="font-size: 18px"><strong>20)访问者模式</strong></span></p>
<p>  将数据操作和数据结构进行分离。</p>
<p><span style="font-size: 18px"><strong>21)解释器模式</strong></span></p>
<p>  描述语言语法如何定义,如何解释和编译,例如Babel。</p><br><br>
来源:https://www.cnblogs.com/strick/p/12107950.html
頁: [1]
查看完整版本: 21种JavaScript设计模式最新记录(含图和示例)