JS十大设计模式(2/2)

本文详细介绍了适配器模式、装饰器模式、代理模式、外观模式、迭代器模式等六种常用的设计模式,包括每种模式的核心思想及具体实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

六、适配器模式

什么是适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

适配器的别名是包装器(wrapper),这是一个相对简单的模式。在程序开发中有许多这样的场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。
这时候有两种解决办法:

第一种是修改原来的接口实现,但如果原来的模块很复杂,或者我们拿到的模块是一段别人编写的经过压缩的代码,修改原接口就显得太不现实了。
第二种办法是创建一个适配器,将原接口转换为客户希望的另一个接口,客户只需要和适配器打交道。

核心思想
解决两个已有接口之间不匹配的问题

具体实现

// 渲染数据,格式限制为数组了
function renderData(data) {
  data.forEach(function (item) {
    console.log(item);
  });
}

// 对非数组的进行转换适配
function arrayAdapter(data) {
  if (typeof data !== "object") {
    return [];
  }

  if (Object.prototype.toString.call(data) === "[object Array]") {
    return data;
  }

  var temp = [];

  for (var item in data) {
    if (data.hasOwnProperty(item)) {
      temp.push(data[item]);
    }
  }

  return temp;
}

var data = {
  0: "A",
  1: "B",
  2: "C",
};

renderData(arrayAdapter(data)); // A B C

七、装饰器模式

什么是装饰器模式

以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责

核心思想
是为对象动态加入行为,经过多重包装,可以形成一条装饰链

具体实现
最简单的装饰者,就是重写对象的属性

var A = {
  score: 10,
};
A.score = "分数:" + A.score;
// 也可以使用传统面向对象的方法来实现装饰,添加技能:
function Person() {}

Person.prototype.skill = function () {
  console.log("数学");
};

// 装饰器,还会音乐
function MusicDecorator(person) {
  this.person = person;
}

MusicDecorator.prototype.skill = function () {
  this.person.skill();
  console.log("音乐");
};

// 装饰器,还会跑步
function RunDecorator(person) {
  this.person = person;
}

RunDecorator.prototype.skill = function () {
  this.person.skill();
  console.log("跑步");
};

var person = new Person();

// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);

person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步

也可 我们也可以使用更通用的装饰函数:

// 装饰器,在当前函数执行前先执行另一个函数
function decoratorBefore(fn, beforeFn) {
  return function () {
    var ret = beforeFn.apply(this, arguments);

    // 在前一个函数中判断,不需要执行当前函数
    if (ret !== false) {
      fn.apply(this, arguments);
    }
  };
}

function skill() {
  console.log("数学");
}

function skillMusic() {
  console.log("音乐");
}

function skillRun() {
  console.log("跑步");
}

var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);

skillDecorator(); // 跑步 音乐 数学

八、代理模式

什么是代理模式 ?

当客户不方便直接访问一个 对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。
替身对象对请求做出一些处理之后,再把请求转交给本体对象代理和本体的接口具有一致性,本体定义了关键功能,而代理是提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。

核心思想
为一个对象提供一个代用品或占位符,以便控制对它的访问

具体实现
代理模式主要有三种:保护代理、虚拟代理、缓存代理

保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子:

// 主体,发送消息
function sendMsg(msg) {
  console.log(msg);
}
// 代理,对消息进行过滤
function proxySendMsg(msg) {
  // 无消息则直接返回
  if (typeof msg === "undefined") {
    console.log("deny");
    return;
  }
  // 有消息则进行过滤
  msg = ("" + msg).replace(/泥\s*煤/g, "");

  sendMsg(msg);
}
sendMsg("泥煤呀泥 煤呀"); // 泥煤呀泥 煤呀
proxySendMsg("泥煤呀泥 煤"); // 呀
proxySendMsg(); // deny

它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这属于护代理的形式。有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式。

虚拟代理在控制对主体的访问时,加入了一些额外的操作,如在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现:

// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
function debounce(fn, delay) {
  delay = delay || 200;

  var timer = null;

  return function () {
    var arg = arguments;

    // 每次操作时,清除上次的定时器
    clearTimeout(timer);
    timer = null;

    // 定义新的定时器,一段时间后进行操作
    timer = setTimeout(function () {
      fn.apply(this, arg);
    }, delay);
  };
}

var count = 0;

// 主体
function scrollHandle(e) {
  console.log(e.type, ++count); // scroll
}

// 代理
var proxyScrollHandle = (function () {
  return debounce(scrollHandle, 500);
})();

window.onscroll = proxyScrollHandle;
// 缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率。来个栗子——缓存加法操作:
// 主体
function add() {
  var arg = [].slice.call(arguments);

  return arg.reduce(function (a, b) {
    return a + b;
  });
}

// 代理
var proxyAdd = (function () {
  var cache = [];

  return function () {
    var arg = [].slice.call(arguments).join(",");

    // 如果有,则直接从缓存返回
    if (cache[arg]) {
      return cache[arg];
    } else {
      var ret = add.apply(this, arguments);
      return ret;
    }
  };
})();

console.log(
  add(1, 2, 3, 4),
  add(1, 2, 3, 4),

  proxyAdd(10, 20, 30, 40),
  proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

九、外观模式

什么是外观模式

为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用

核心思想
可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统

具体实现
外观模式在 JS 中,可以认为是一组函数的集合



// 三个处理函数
function start() {
  console.log("start");
}

function doing() {
  console.log("doing");
}

function end() {
  console.log("end");
}

// 外观函数,将一些处理统一起来,方便调用
function execute() {
  start();
  doing();
  end();
}

// 调用init开始执行
function init() {
  // 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
  execute();
}

init(); // start doing end

十、迭代器模式

什么是迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

核心思想
在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素

具体实现
JS 中数组的 map forEach 已经内置了迭代器

[1, 2, 3].forEach(function (item, index, arr) {
  console.log(item, index, arr);
});

不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码,我们可以封装一下:

function each(obj, cb) {
  var value;

  if (Array.isArray(obj)) {
    for (var i = 0; i < obj.length; ++i) {
      value = cb.call(obj[i], i, obj[i]);

      if (value === false) {
        break;
      }
    }
  } else {
    for (var i in obj) {
      value = cb.call(obj[i], i, obj[i]);

      if (value === false) {
        break;
      }
    }
  }
}

each([1, 2, 3], function (index, value) {
  console.log(index, value);
});

each({ a: 1, b: 2 }, function (index, value) {
  console.log(index, value);
});

// 0 1
// 1 2
// 2 3
// a 1
// b 2

再来看一个例子——强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句,虽然例子不太好,但在其他负责的分支判断情况下,也是值得考虑的:

function getManager() {
  var year = new Date().getFullYear();
  if (year <= 2000) {
    console.log("A");
  } else if (year >= 2100) {
    console.log("C");
  } else {
    console.log("B");
  }
}
getManager(); // B
将每个条件语句拆分出逻辑函数,放入迭代器中迭代

function year2000() {
  var year = new Date().getFullYear();

  if (year <= 2000) {
    console.log("A");
  }

  return false;
}
function year2100() {
  var year = new Date().getFullYear();

  if (year >= 2100) {
    console.log("C");
  }

  return false;
}
function year() {
  var year = new Date().getFullYear();

  if (year > 2000 && year < 2100) {
    console.log("B");
  }
  return false;
}
function iteratorYear() {
  for (var i = 0; i < arguments.length; ++i) {
    var ret = arguments[i]();

    if (ret !== false) {
      return ret;
    }
  }
}
var manager = iteratorYear(year2000, year2100, year); // B
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名字还没想好☜

祝福你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值