Javascript中常用的13种设计模式

B 订阅了 A 的消息: news

---- 接收消息from: 一条消息a { name: ‘news’,

param: ‘一条消息a’,

publisher: ‘A’,

time: 1603011782573 }

---- 接收消息from: 一条消息b { name: ‘news’, param: ‘一条消息b’, publisher: ‘A’ }

B 收到了 A 发布的消息 news 一条消息b

订阅的消息 { news:

{ name: ‘news’,

publisher: ‘A’,

subscriber: ‘B’,

callback: [Function],

time: 1603011782575 } }

无人订阅你的消息: news 一条消息c

发布历史 [ { name: ‘news’,

param: ‘一条消息a’,

publisher: ‘A’,

time: 1603011782573 },

{ name: ‘news’,

param: ‘一条消息b’,

publisher: ‘A’,

time: 1603011782577 },

{ name: ‘news’,

param: ‘一条消息c’,

publisher: ‘A’,

time: 1603011782578 } ]

*/

使用适配器类

  • 代码

/**

  • 代理类,屏蔽重复设置发布者、订阅者

*/

class Factory {

constructor(username, type) {

this.username = username;

this.type = type;

this._event = new Event();

this._event.registration(username, type || ‘all’);

}

// 发布

publish(param) {

return this._event.publish(Object.assign({}, param, {publisher: this.username}))

}

// 订阅

subscribe(param, callback) {

return this._event.subscribe(Object.assign({}, param, {subscriber: this.username}), callback);

}

// 取消订阅

unsubscribe(param) {

return this._event.unsubscribe(Object.assign({}, param, {subscriber: this.username}));

}

// 获取历史发布消息

getPublishHistory(name) {

return this._event.getPublishHistory(this.username, name);

}

// 获取订阅的消息列表

getSubscribeMsg() {

return this._event.getSubscribeMsg(this.username);

}

}

  • 使用

// 使用适配器封装

const publisherA = ‘A’;

const subscriberB = ‘B’;

const publisher = new Factory(publisherA, ‘publisher’);

const subscriber = new Factory(subscriberB, ‘subscriber’);

const name = ‘新闻’;

publisher.publish({name, param: ‘this is news 1’});

subscriber.subscribe({name, publisher: publisherA, receiveHistoryMsg: true}, (param) => {

console.log(---- get news from ${publisherA}:, param);

});

console.log(‘订阅的消息’, subscriber.getSubscribeMsg());

publisher.publish({name, param: ‘this is news 2’});

publisher.publish({name, param: ‘this is news of newspaper’});

console.log(‘发布历史’, publisher.getPublishHistory(name));

/*

发布者 A 注册消息: 新闻 this is news 1

发布者未注册: A

订阅的消息 { ‘新闻’:

{ name: ‘新闻’,

publisher: ‘A’,

subscriber: ‘B’,

callback: [Function],

time: 1603012329816 } }

无人订阅你的消息: 新闻 this is news 2

无人订阅你的消息: 新闻 this is news of newspaper

发布历史 [ { name: ‘新闻’,

param: ‘this is news 1’,

publisher: ‘A’,

time: 1603012329813 },

{ name: ‘新闻’,

param: ‘this is news 2’,

publisher: ‘A’,

time: 1603012329819 },

{ name: ‘新闻’,

param: ‘this is news of newspaper’,

publisher: ‘A’,

time: 1603012329819 } ]

*/

中介者模式(Mediator Pattern)

==================================================================================

中介者模式的作用就是解除对象与对象之间的紧耦合关系。

介绍


增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

中介者模式是迎合最少知识原则(迪米特法则)的一种实现。是指一个对象应 该尽可能少地了解另外的对象(类似不和陌生人说话)。

举例说明


用一个小游戏说明中介者模式的用处。

**游戏规则:**两组选手进行对战,其中一个玩家死亡的时候游戏便结束, 同时通知它的对手胜利。

普通实现

var players = [];

//接着我们再来编写Hero这个函数;代码如下:

var players = []; // 定义一个数组 保存所有的玩家

function Hero(name,teamColor) {

this.friends = []; //保存队友列表

this.enemies = []; // 保存敌人列表

this.state = ‘live’; // 玩家状态

this.name = name; // 角色名字

this.teamColor = teamColor; // 队伍的颜色

}

Hero.prototype.win = function(){

console.log(“win:” + this.name);

};

Hero.prototype.lose = function(){

console.log(“lose:” + this.name);

};

Hero.prototype.die = function(){

// 所有队友死亡情况 默认都是活着的

var all_dead = true;

this.state = ‘dead’; // 设置玩家状态为死亡

for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) {

// 遍历,如果还有一个队友没有死亡的话,则游戏还未结束

if(this.friends[i].state !== ‘dead’) {

all_dead = false;

break;

}

}

if(all_dead) {

this.lose(); // 队友全部死亡,游戏结束

// 循环 通知所有的玩家 游戏失败

for(var j = 0,jlen = this.friends.length; j < jlen; j+=1) {

this.friends[j].lose();

}

// 通知所有敌人游戏胜利

for(var j = 0,jlen = this.enemies.length; j < jlen; j+=1) {

this.enemies[j].win();

}

}

}

// 定义一个工厂类来创建玩家

var heroFactory = function(name,teamColor) {

var newPlayer = new Hero(name,teamColor);

for(var i = 0,ilen = players.length; i < ilen; i+=1) {

// 如果是同一队的玩家

if(players[i].teamColor === newPlayer.teamColor) {

// 相互添加队友列表

players[i].friends.push(newPlayer);

newPlayer.friends.push(players[i]);

}else {

// 相互添加到敌人列表

players[i].enemies.push(newPlayer);

newPlayer.enemies.push(players[i]);

}

}

players.push(newPlayer);

return newPlayer;

};

// 红队

var p1 = heroFactory(“aa”,‘red’),

p2 = heroFactory(“bb”,‘red’),

p3 = heroFactory(“cc”,‘red’),

p4 = heroFactory(“dd”,‘red’);

// 蓝队

var p5 = heroFactory(“ee”,‘blue’),

p6 = heroFactory(“ff”,‘blue’),

p7 = heroFactory(“gg”,‘blue’),

p8 = heroFactory(“hh”,‘blue’);

// 让红队玩家全部死亡

p1.die();

p2.die();

p3.die();

p4.die();

// lose:dd lose:aa lose:bb lose:cc

// win:ee win:ff win:gg win:hh

复制代码

中介者模式实现

玩家与玩家之间的耦合代码解除,把所有的逻辑操作放在中介者对象里面进去处理,某个玩家的任何操作不需要去遍历去通知其他玩家,而只是需要给中介者发送一个消息即可,中介者接受到该消息后进行处理,处理完消息之后会把处理结果反馈给其他的玩家对象。

var players = []; // 定义一个数组 保存所有的玩家

function Hero(name,teamColor) {

this.state = ‘live’; // 玩家状态

this.name = name; // 角色名字

this.teamColor = teamColor; // 队伍的颜色

}

Hero.prototype.win = function(){

// 赢了

console.log(“win:” + this.name);

};

Hero.prototype.lose = function(){

// 输了

console.log(“lose:” + this.name);

};

// 死亡

Hero.prototype.die = function(){

this.state = ‘dead’;

// 给中介者发送消息,玩家死亡

playerDirector.ReceiveMessage(‘playerDead’,this);

}

// 移除玩家

Hero.prototype.remove = function(){

// 给中介者发送一个消息,移除一个玩家

playerDirector.ReceiveMessage(‘removePlayer’,this);

};

// 玩家换队

Hero.prototype.changeTeam = function(color) {

// 给中介者发送一个消息,玩家换队

playerDirector.ReceiveMessage(‘changeTeam’,this,color);

};

// 定义一个工厂类来创建玩家

var heroFactory = function(name,teamColor) {

// 创建一个新的玩家对象

var newHero = new Hero(name,teamColor);

// 给中介者发送消息,新增玩家

playerDirector.ReceiveMessage(‘addPlayer’,newHero);

return newHero;

};

var playerDirector = (function(){

var players = {}, // 保存所有的玩家

operations = {}; // 中介者可以执行的操作

// 新增一个玩家操作

operations.addPlayer = function(player) {

// 获取玩家队友的颜色

var teamColor = player.teamColor;

// 如果该颜色的玩家还没有队伍的话,则新成立一个队伍

players[teamColor] = players[teamColor] || [];

// 添加玩家进队伍

players[teamColor].push(player);

};

// 移除一个玩家

operations.removePlayer = function(player){

// 获取队伍的颜色

var teamColor = player.teamColor,

// 获取该队伍的所有成员

teamPlayers = players[teamColor] || [];

// 遍历

for(var i = teamPlayers.length - 1; i>=0; i–) {

if(teamPlayers[i] === player) {

teamPlayers.splice(i,1);

}

}

};

// 玩家换队

operations.changeTeam = function(player,newTeamColor){

// 首先从原队伍中删除

operations.removePlayer(player);

// 然后改变队伍的颜色

player.teamColor = newTeamColor;

// 增加到队伍中

operations.addPlayer(player);

};

// 玩家死亡

operations.playerDead = function(player) {

var teamColor = player.teamColor,

// 玩家所在的队伍

teamPlayers = players[teamColor];

var all_dead = true;

//遍历

for(var i = 0,player; player = teamPlayers[i++]; ) {

if(player.state !== ‘dead’) {

all_dead = false;

break;

}

}

// 如果all_dead 为true的话 说明全部死亡

if(all_dead) {

for(var i = 0, player; player = teamPlayers[i++]; ) {

// 本队所有玩家lose

player.lose();

}

for(var color in players) {

if(color !== teamColor) {

// 说明这是另外一组队伍

// 获取该队伍的玩家

var teamPlayers = players[color];

for(var i = 0,player; player = teamPlayers[i++]; ) {

player.win(); // 遍历通知其他玩家win了

}

}

}

}

};

var ReceiveMessage = function(){

// arguments的第一个参数为消息名称 获取第一个参数

var message = Array.prototype.shift.call(arguments);

operations[message].apply(this,arguments);

};

return {

ReceiveMessage : ReceiveMessage

};

})();

// 红队

var p1 = heroFactory(“aa”,‘red’),

p2 = heroFactory(“bb”,‘red’),

p3 = heroFactory(“cc”,‘red’),

p4 = heroFactory(“dd”,‘red’);

// 蓝队

var p5 = heroFactory(“ee”,‘blue’),

p6 = heroFactory(“ff”,‘blue’),

p7 = heroFactory(“gg”,‘blue’),

p8 = heroFactory(“hh”,‘blue’);

// 让红队玩家全部死亡

p1.die();

p2.die();

p3.die();

p4.die();

// lose:aa lose:bb lose:cc lose:dd

// win:ee win:ff win:gg win:hh

复制代码

策略模式(Strategy Pattern)

=================================================================================

要实现某一个功能有多种方案可以选择,定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,这就是策略模式。

举例


  • 表单校验:执行校验规则校验规则配置分开;

  • 前端动画类:将渲染动画动画配置以及动画控制分开

策略模式演示:表单校验

// 校验方法&规则配置

var strategies = {

isNonEmpty: function( value, errorMsg ){ // 不为空

if ( value === ‘’ ){

return errorMsg ;

}

},

minLength: function( value, length, errorMsg ){ // 限制最小长度

if ( value.length < length ){

return errorMsg;

}

},

isMobile: function( value, errorMsg ){ // 手机号码格式

if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){

return errorMsg;

}

}

};

// 校验执行器

var Validator = function(){

this.cache = []; // 保存校验规则

};

Validator.prototype.add = function( dom, rule, errorMsg ){

var ary = rule.split( ‘:’ ); // 把strategy 和参数分开

this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入cache

var strategy = ary.shift(); // 用户挑选的strategy

ary.unshift( dom.value ); // 把input 的value 添加进参数列表

ary.push( errorMsg ); // 把errorMsg 添加进参数列表

return strategies[ strategy ].apply( dom, ary );

});

};

Validator.prototype.start = function(){

for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){

var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息

if ( msg ){ // 如果有确切的返回值,说明校验没有通过

return msg;

}

}

};

// 控制器

var validataFunc = function(){

var validator = new Validator(); // 创建一个validator 对象

/添加一些校验规则*/

validator.add( registerForm.userName, ‘isNonEmpty’, ‘用户名不能为空’ );

validator.add( registerForm.password, ‘minLength:6’, ‘密码长度不能少于6 位’ );

validator.add( registerForm.phoneNumber, ‘isMobile’, ‘手机号码格式不正确’ );

var errorMsg = validator.start(); // 获得校验结果

return errorMsg; // 返回校验结果

}

// 程序入口

var registerForm = document.getElementById( ‘registerForm’ );

registerForm.onsubmit = function(){

var errorMsg = validataFunc(); // 如果errorMsg 有确切的返回值,说明未通过校验

if ( errorMsg ){

alert ( errorMsg );

return false; // 阻止表单提交

}

};

复制代码

优点


策略模式是一种常用且有效的设计模式,总结一下策略模式的一些优点:

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。

  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。

  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。

  • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

缺点


策略模式也有一些缺点,但这些缺点并不严重:

  • 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。

  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选 择飞机、火车、自行车等方案的细节。此时 strategy 要向客户暴露它的所有实现,这是违反最少 知识原则的。

总结


不仅是算法,业务规则指向的目标一致,并且可以被替换使用,就也可以用策略模式来封装它们。

“在函数作为一等对象的语言中,策略模式是隐形的。 strategy 就是值为函数的变量。”

相对传统面向对象语言的方式实现策略模式,使用 JavaScript 语言的策略模式,策略类往往被函数所代替,这时策略模式就 成为一种“隐形”的模式。

代理模式(Proxy Pattern)

==============================================================================

为其他对象提供一种代理以控制对这个对象的访问。

常用的代理模式变种有以下几种:

  • 保护代理

  • 虚拟代理

  • 缓存代理

1. 保护代理


保护代理用于控制不同权限的对象对目标对象的访问

  • 举例

class Car {

drive() {

return “driving”;

};

}

class CarProxy {

constructor(driver) {

this.driver = driver;

}

drive() {

// 保护代理,仅18岁才能开车

return (this.driver.age < 18) ? “too young to drive” : new Car().drive();

};

}

复制代码

2. 虚拟代理


虚拟代理可应用于:图片懒加载惰性加载合并http请求

  • 举例:图片懒加载
图片懒加载

复制代码

3. 缓存代理


缓存代理可应用于:缓存ajax异步请求数据计算乘积

  • 举例:缓存ajax请求数据

const getData = (function() {

const cache = {};

return function(url) {

if (cache[url]) {

return Promise.resolve(cache[url]);

}

return $.ajax.get(url).then((res) => {

cache[url] = res;

return res;

}).catch(err => console.error(err))

}

})();

getData(‘/getData’); // 发起http请求

getData(‘/getData’); // 返回缓存数据

复制代码

总结


代理模式包括许多小分类,在 JavaScript 开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。

迭代器模式(Iterator Pattern)

==================================================================================

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象 5 的内部表示。

迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

例如 Javascript中的forEachmapsome等;

迭代器可分为一下两种:

  • 内部迭代器

  • 外部迭代器

内部迭代器


内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。

  • 例如:

function each(ary, callback) {

for (let i = 0; i < ary.length; i++){

callback.call(ary[i], i, ary[i]);

}

}

each([1, 2, 3], (i, n) => alert([i, n]));

复制代码

总结: 内部迭代器调用方式简答,但它的适用面相对较窄。

外部迭代器


必须显式地请求迭代下一个元素。

外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。

var Iterator = function( obj ){

var current = 0;

var next = function(){

current += 1;

};

var isDone = function(){

return current >= obj.length;

};

var getCurrItem = function(){

return obj[ current ];

};

return {

next: next,

isDone: isDone,

getCurrItem: getCurrItem

}

};

复制代码

总结: 外部迭代器虽然调用方式相对复杂,但它的适用面更广,也能满足更多变的需求。内部迭代器和外部迭代器在实际生产中没有优劣之分,究竟使用哪个要根据需求场景而定。

迭代类数组对象和字面量对象


迭代器模式不仅可以迭代数组,还可以迭代一些类数组的对象。

无论是内部迭代器还是外部迭代器,只要被迭代的聚合对象拥有 length 属性而且可以用下标访问,那它就可以被迭代。

例如:arguments{'0': 'a', '1': 'b'}

// 迭代器支持类数组和对象的遍历

function each(obj, callback) {

var value, i = 0, length = obj.length, isArray = isArraylike( obj );

if ( isArray ) {

for ( ; i < length; i++ ) {

value = callback.call( obj[ i ], i, obj[ i ] );

if ( value === false ) {

break;

}

}

} else {

// 迭代object 对象

for ( i in obj ) {

value = callback.call( obj[ i ], i, obj[ i ] );

if ( value === false ) {

break;

}

}

}

return obj;

};

复制代码

倒序迭代器


从尾到头的遍历数组。

中止迭代器


在遍历过程中,如果满足某种条件可以终止迭代。

function each(ary, callback){

for (let i = 0; i < ary.length; i++){

// callback 的执行结果返回false,提前终止迭代

if (callback(i, ary[i]) === false ){

break;

}

}

};

复制代码

迭代器模式的应用举例


  • nodejs的express框架的中间件思想,处理请求用到的next()方法就是迭代器模式。

  • ECMAScript 6 的 Iterator(遍历器)和 Generator异步编程中使用了next()

适配器模式(Adapter Pattern)

=================================================================================

适配器模式的作用是解决两个接口/方法间的接口不兼容的问题。

作为两个不兼容的接口之间的桥梁,就是新增一个包装类,对新的接口进行包装以适应旧代码的调用,避免修改接口和调用代码。

示例


// 1. 方法适配 *******************************

const A = {

show() {

console.log(‘visible’);

}

}

const B = {

display() {

console.log(‘visible’);

}

}

// 不使用适配器

A.show();

B.display();

// 使用适配器

const C = {

show() {

B.display();

}

}

A.show();

C.show();

// 2. 接口适配 *******************************

const data1 = {name: ‘alan’};

const data2 = {username: ‘tom’};

function sayName(param) {

console.log(param.name);

}

function adapter(param) {

return {name: param.username}

}

sayName(data1);

sayName(adapter(data2));

复制代码

相似模式之间的差异


有一些模式跟适配器模式的 结构非常相似,比如装饰者模式、代理模式和外观模式。

这几种模式都属于“包 装模式”,都是由一个对象来包装另一个对象。区别它们的关键仍然是模式的意图。

  • 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实 现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够 使它们协同作用。

  • 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象 增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理 模式是为了控制对对象的访问,通常也只包装一次。

  • 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外 观模式最显著的特点是定义了一个新的接口。

总结


适配器模式是作为一个中间的桥梁,使原本有差异的接口变成需要的标准,能够满足现有接口的需要,而不需要去修改现有的源码。

命令模式(Command Pattern)

================================================================================

命令模式将调用者和执行者之间分开,通过命令来映射各种操作,从而达到松耦合的目的。

命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品。JavaScript 作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了 JavaScript 语言之中。

应用场景


有时候需要向某些对象发送请求,但是并不知道请求的接收 者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

命令模式的调用者只需要下达命令,不需要知道命令被执行的具体细节。从而使调用者专注于自己的主流程。

特点


  • 松耦合:将请求调用者和请求接收者松耦合

  • 生命周期

  • 支持撤销

  • 支持排队

举例


命令模式实现向左、向右、及撤销操作

命令模式

命令模式

向左

向右

撤销

history:

复制代码

组合模式(Composite Pattern)

==================================================================================

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

当我们想执行一个宏大的任务时,这个任务可以被细分为多层结构,组合模式可以让我们只发布一次执行命令,就能完成整个复杂的任务,屏蔽层级关系差异化问题

注意


  1. 组合模式不是父子关系,它们能够合作的关键是拥有相同的接口;

  2. 对叶对象操作的一致性,要对每个目标对象都实行同样的操作;

  3. 可用中介者模式处理双向映射关系,例如一个子节点同时在不同的父节点中存在(组织架构);

  4. 可用职责链模式提高组合模式性能,通过设置链条避免每次都遍历整个树;

应用场景


组合模式应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。

  • 命令分发: 只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放-封闭原则

  • 统一处理: 统一对待树中的所有对象,忽略组合对象和叶对象的区别;

比如: 文件目录显示,多级目录呈现等树形结构数据的操作。

举例:文件系统操作


// 文件夹

var Folder = function( name ){

this.name = name;

this.parent = null; //增加this.parent 属性

this.files = [];

};

Folder.prototype.add = function( file ){

file.parent = this; //设置父对象

this.files.push( file );

};

Folder.prototype.scan = function(){

console.log( '开始扫描文件夹: ’ + this.name );

for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){

file.scan();

}

};

Folder.prototype.remove = function(){

if ( !this.parent ){ //根节点或者树外的游离节点

return;

}

for ( var files = this.parent.files, l = files.length - 1; l >=0; l-- ){

var file = files[ l ];

if ( file === this ){

files.splice( l, 1 );

}

}

};

// 文件

var File = function( name ){

this.name = name;

this.parent = null;

};

File.prototype.add = function(){

throw new Error( ‘不能添加在文件下面’ );

};

File.prototype.scan = function(){

console.log( '开始扫描文件: ’ + this.name );

};

File.prototype.remove = function(){

if ( !this.parent ){ //根节点或者树外的游离节点

return;

}

for ( var files = this.parent.files, l = files.length - 1; l >=0; l-- ){

var file = files[ l ];

if ( file === this ){

files.splice( l, 1 );

}

}

};

var folder = new Folder( ‘学习资料’ );

var folder1 = new Folder( ‘JavaScript’ );

var file1 = new Folder ( ‘深入浅出Node.js’ );

folder1.add( new File( ‘JavaScript 高级程序设计’ ) );

folder.add( folder1 );

folder.add( file1 );

folder1.remove(); //移除文件夹

folder.scan();

复制代码

总结


组合模式可以让我们使用树形方式创 建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上。在大多数情况下,我们都 可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们。

模板方法模式(Template Method)

==================================================================================

模板方法模式是一种通过封装变化提高系统扩展性的设计模式。

在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把 这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这 部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改 动抽象父类以及其他子类,这也是符合开放-封闭原则的。

应用场景


假如我们有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为。如果相同和不同的行为都混合在各个子类的实现中,说明这些相同的行为会在各个子类中重复出现。 但实际上,相同的行为可以被搬移到另外一个单一的地方,模板方法模式就是为解决这个问题而生的。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。这也很好地体现了泛化的思想。

组成


模板方法模式由两部分结构组成:

  • 抽象父类: 通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行方式(比如执行顺序、条件执行等)。

  • 具体的实现子类: 子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

示例


模板方法模式通常通过继承来实现,在 JavaScript 开发中用到继承的场景其实并不是很多,很多时候我们都喜欢用 mix-in 的方式给对象扩展属性。

抽象类定义执行方法的方式(比如执行顺序、条件执行等),它的子类可以按需要重写被执行的方法,核心是抽象类。

class Tax {

calc(value) {

if (value >= 1000)

value = this.overThousand(value);

return this.complementaryFee(value);

}

complementaryFee(value) {

return value + 10;

}

}

class Tax1 extends Tax {

constructor() {

super();

}

overThousand(value) {

return value * 1.1;

}

}

复制代码

享元模式(Flyweight Pattern)

==================================================================================

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。

享元模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

适应场景


解决的问题: 在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

使用注意: 使用了享元模式之后,我们需要分别多维护一个 factory 对象和一个 manager 对象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。

因此我们可以在以下场景中使用享元模式:

  • 一个程序中使用了大量的相似对象。

  • 由于使用了大量对象,造成很大的内存开销。

  • 对象的大多数状态都可以变为外部状态。

  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

内部状态与外部状态


享元模式要求将对象的属性划分为内部状态外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量,那么如何划分内部状态和外部状态呢?

  • 内部状态存储于对象内部。

  • 内部状态可以被一些对象共享。

  • 内部状态独立于具体的场景,通常不会改变。

  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态 可以从对象身上剥离出来,并储存在外部。

剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整 的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系 统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。

使用享元模式的关键是如何区别内部状态外部状态

举例


  • 代码关键点:用 HashMap 对象池存储这些对象。

// 享元模式,对象池缓存对象

class colorFactory {

constructor(name) {

this.colors = {};

}

create(name) {

let color = this.colors[name];

if (color) return color;

this.colors[name] = new Color(name);

return this.colors[name];

}

};

复制代码

优缺点


优点: 大大减少对象的创建,降低系统的内存,使效率提高。

缺点: 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

模职责链模式 (Chain of Responsibility Pattern)

===================================================================================================

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推,直到有一个对象处理它为止。

这种类型的设计模式属于行为型模式

适用场景


职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

  3. 可动态指定一组对象处理请求。

应用示例


JS 中的事件冒泡 express、koa中间件洋葱模型

  • 实现简易koa中间件【职责链模式】

class Middleware {

constructor() {

this.middlewares = [];

}

use(fn) {

if(typeof fn !== ‘function’) {

throw new Error('Middleware must be function, but get ’ + typeof fn);

}

this.middlewares.push(fn);

return this;

}

compose() {

const middlewares = this.middlewares;

return dispatch(0);

function dispatch(index) {

const middleware = middlewares[index];

if (!middleware) {return;}

try{

const ctx = {};

const result = middleware(ctx, dispatch.bind(null, index + 1));

return Promise.resolve(result);

} catch(err) {

return Promise.reject(err);

}

}

}

}

const middleware = new Middleware();

middleware.use(async (ctx, next) => {

console.log(1);

await next();

console.log(2);

});

middleware.use(async (ctx, next) => {

console.log(3);

await next();

console.log(4);

});

middleware.compose();// 1 3 4 2

复制代码

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推,直到有一个对象处理它为止。

这种类型的设计模式属于行为型模式

适用场景


职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

  3. 可动态指定一组对象处理请求。

应用示例


JS 中的事件冒泡 express、koa中间件洋葱模型

  • 实现简易koa中间件【职责链模式】

class Middleware {

constructor() {

this.middlewares = [];

}

use(fn) {

if(typeof fn !== ‘function’) {

throw new Error('Middleware must be function, but get ’ + typeof fn);

}

this.middlewares.push(fn);

return this;

}

compose() {

const middlewares = this.middlewares;

return dispatch(0);

function dispatch(index) {

const middleware = middlewares[index];

if (!middleware) {return;}

try{

const ctx = {};

const result = middleware(ctx, dispatch.bind(null, index + 1));

return Promise.resolve(result);

} catch(err) {

return Promise.reject(err);

}

}

}

}

const middleware = new Middleware();

middleware.use(async (ctx, next) => {

console.log(1);

await next();

console.log(2);

});

middleware.use(async (ctx, next) => {

console.log(3);

await next();

console.log(4);

});

middleware.compose();// 1 3 4 2

复制代码

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Q0zth3Dk-1713501413875)]

[外链图片转存中…(img-srcD7em8-1713501413876)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-wIbUBwrX-1713501413876)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-kL4cGfnH-1713501413876)]

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值