设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在思想和解决的问题。
我们的程序要对扩展开放,对修改关闭;当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
我们的模块只做一件事情,模块的职责越单一越好。如果功能过于复杂就拆分开,每个部分都保持独立。
如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
我们的上层模块不要依赖于具体的下层模块,应该依赖于抽象。降低耦合度。
接口要细化,功能要单一,一个接口不要调用太多方法,使其能力单一。
听起来和“单一职责原则”类似,但不同:单一职责原则主要关注于模块本身,接口隔离原则关注于接口。
我们尽量细化接口,每个接口做的事情尽量单一化。
尽量减少对象之间的交互。
如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求。
它主要关注于继承,它的意义是任何使用父类的地方都可以用子类去替换,就是说子类继承父类的时候,我们的子类必须完全保证继承父类的属性和方法,在子类中尽量不要重写父类得方法。
处理对象的创建,根据实际情况使用合适的方式创建对象
工厂模式是用来创建对象的常见设计模式,不暴露创建对象的具体逻辑,将逻辑进行封装,那么它就可以被称为工厂
应用场景
它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new,就应该考虑是否使用工厂模式,将new操作单独封装。
简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责
栗子:
初始:
class Dog { // 狗狗
constructor(name) { console.log(name) }
}
class Cat { // 小猫
constructor(name) { console.log(name) }
}
class Mouse { // 小老鼠
constructor(name) { console.log(name) }
}
new Dog('Spike')
new Cat('Tom')
new Mouse('Jerry')
工厂改善:
class Pet { // 创建宠物店
constructor(type, name) {
this.pet = ""
switch (type) {
case 'dog': this.pet = new Dog(name); break;
case 'cat': this.pet = new Cat(name); break;
case 'mouse': this.pet = new Mouse(name); break;
default: this.pet = '你还没有小宠物,快去买一只吧';
}
}
}
new Pet('dog', 'Spike')
new Pet('cat', 'Tom')
new Pet('mouse', 'Jerry')
简单工厂模式其实并不算是一种设计模式,更多的时候是一种编程习惯。
如果把class类改为function,是不是发觉很多时候我们都使用过类工厂模式了?
工厂方法模式是对简单工厂的进一步优化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。
改善上述栗子:
const Pet = (() => { // 宠物店升级
const pets = {
dog(name) { console.log(name) },
cat(name) { console.log(name) },
mouse(name) { console.log(name) },
duck(name) { // 我是新来的宠物小鸭子
console.log(name)
}
}
return class {
constructor(type, name) {
try { return pets[type](name) }
catch (error) { console.log('你还没有小宠物,快去买一只吧') }
}
}
})()
new Pet('dog', 'Spike')
new Pet('cat', 'Tom')
new Pet('duck', 'Duck')
如上,如果是简单工厂,新增一个宠物后,不仅要增加宠物名称,还要新增对应的宠物工厂。而在工厂方法模式中,只需要增加宠物名称就可以。
抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
栗子:
如果我们在开宠物店后,再增开水族馆呢?
class headPet { // 宠物总店
sellpet(name) { // 出售宠物
console.log('出售一只宠物', name)
}
desert(name) { // 遗弃宠物
console.log('遗弃一只宠物', name)
}
operation(name, type) {
switch (type) {
case 'sell': {
this.sellpet((name))
break
}
default: {
this.desert((name))
}
}
}
}
class normalPet extends headPet { // 普通宠物分店
constructor() {
super()
}
dog(name, type) {
this.operation(name, type)
}
cat(name, type) {
this.operation(name, type)
}
mouse(name, type) {
this.operation(name, type)
}
}
class fishPet extends headPet { // 水族馆分店
constructor() {
super()
}
tortoise(name, type) {
this.operation(name, type)
}
goldfish(name, type) {
this.operation(name, type)
}
}
function selectPet(shop) {
switch (shop) {
case 'normal': {
return new normalPet()
}
case 'fish': {
return new fishPet()
}
default: {
console.log('暂无此分店哦!')
}
}
}
const normal = selectPet('normal')
normal.dog('Spike', 'sell') // 出售一只狗狗
normal.cat('Tom', 'desert') // 遗弃一只病猫
normal.mouse('Jerry', 'sell') // 出售一只小老鼠
const fish = selectPet('fish')
fish.goldfish('Goldfish', 'desert') // 遗弃一条死鱼
fish.tortoise('Tortoise', 'sell') // 出售一只乌龟
保证全局只有一个对象的思想。(一个类仅有一个实例,这个实例在系统中唯一被使用,并提供一个访问它的全局访问点。)
应用场景:为了避免重复新建,避免多个对象存在相互干扰
栗子:
class Singleton{
constructor(name){
this.name = name;
}
getName(){
console.log(this.name);
}
}
// 此静态方法是获取本类实例的唯一访问点
Singleton.getInstance = (function() {
let instance
return function(name){
// 若实例不存在,则new一个新实例,否则返回已有实例
if (!instance) {
instance = new Singleton(name)
}
return instance
}
})()
// 比较两次实例化后对象的结果是实例相同
let A = Singleton.getInstance('A');
let B = Singleton.getInstance('B');
console.log(A===B); // true
console.log(A.getName()); // 'A'
console.log(B.getName()); // 'A'
也类似于vue全局Store和Router,他们都是通过单例模式实现的
ex:
let _Vue
function install(Vue) {
if (install.installed && _Vue === vue) return
install.installed = true
_Vue = vue
}
建造者模式是一种比较复杂使用频率较低的创建型设计模式。
是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
应用场景:当要创建单个、庞大的组合对象时
栗子:
先创建一个平平无奇的不用建造者的栗子:
// 最终类
class Car{
constructor(){
this.name = '';
this.number = '';
this.price = '';
this.engine = '';
}
//设置名字
setName(){
this.name = '宝马';
}
//设置车牌号
setNumber(){
this.number = '888888'
}
//设置价钱
setPrice(){
this.price = '100万'
}
//设置引擎
setEngine(){
this.engine = '最好的引擎'
}
//车的创建
getCar(){
this.setName();
this.setNumber();
this.setPrice();
this.setEngine();
}
}
//创建一个车:
let car = new Car();
car.getCar();
console.log(car)
如上,如果要创建一个车,包括的元素有:name,number,price,engine,而最终都要通过getCar来完成。如果创建车的元素较多,会使创建过程相互影响,相互耦合。
下面根据建造者模式优化以上栗子,进行解耦:
建造者模式包含如下角色:
Builder:建造者
Director:指挥者
Product:产品角色
//产品类:产品要素
class Car{
constructor(){
this.name = '';
this.number = '';
this.price = '';
this.engine = '';
}
}
//建造者类:各种工人完成相应的部分
class CarBuilder{
setName(){
this.name = '宝马';
}
setNumber(){
this.number = '888888';
}
setPrice(price){
this.price = '100万';
}
setEngine(engine){
this.engine = '最好的引擎';
}
getCar(){
var car = new Car();
car.name = this.name;
car.number = this.number;
car.price = this.price;
car.engine = this.engine;
return car;
}
}
//指挥官类:指挥工人完成各部分工作
class Director{
action(builder){
builder.setName();
builder.setNumber();
builder.setPrice();
builder.setEngine();
}
}
//使用方法:
let builder = new CarBuilder();
let director = new Director();
director.action(builder);
let car = builder.getCar();
console.log(car)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或者相似的新对象。
栗子:
class Dog {
constructor() {
this.name = "lili";
this.birthYear = 2015;
this.sex = "男";
this.presentYear = 2018;
}
getDiscription() {
return `狗狗叫${this.name},
性别${this.sex},
${this.presentYear}年,
${this.presentYear-this.birthYear}岁了`;
}
// 实现复制
clone() {
return Object.create(this);
}
}
// 使用
const dog = new Dog();
console.log(dog.getDiscription());
dog.presentYear = 2020;
const dog1 = dog.clone()
console.log(dog1.getDiscription());
console.log(dog == dog1);
如上,原型模式复制核心方法是Object.create().
一个简单的栗子:
let person = {
name:'hello',
age:30
}
let anotherPerson = Object.create(person);
console.log(anotherPerson.__proto__) //{name: "hello", age: 30}
通过识别系统中组件间的简单关系来简化系统的设计
把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API
应用场景:当完成一个操作时,需要操作多个子系统,不如提供一个更高级的
栗子:
let myEvent = {
// ...
stop: e => {
e.stopPropagation();
e.preventDefault();
}
};
通过写一个适配器,来代替替换
应用场景:面临接口不通用的问题,用新的接口去承接旧接口
栗子:
// 假如我们项目中换成了jQuey,我们不想全部去替换A方法,就用适配器的方法
A.c = function() {
return $.on.apply(this.arguments)
}
// A框架调用的方式
A.c()
适配器模式满足开放封闭原则,对扩展开放,对修改封闭,我们使用适配器的一个理念就是对于原有的代码不进行修改,添加适配器满足扩展的需求。
在vue-computed中的适配器用法:
original: {{message}}
computed: {{ reserveMessage}}
message: '1234',
computed: {
reserveMessage(){
return this.message.split('').reverse().join('')
}
}
适配器模式满足开放封闭原则,对扩展开放,对修改封闭,我们使用适配器的一个理念就是对于原有的代码不进行修改,添加适配器满足扩展的需求。
在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)。
应用场景:一个方法需要扩展,但是又不好去修改原有方法
栗子:
对现在项目改造,需要给 input 标签已经有的事件增加一些操作
var decorator = function(dom, fn) {
if ((typeof dom.onclick = 'function')) {
var _old = dom.onclick
dom.onclick = function() {
_old()
fn()
}
}
}
decorator(document.getElementById('dom1'), function() {
// 自己的操作
})
一种用于性能优化的模式,核心是运用共享技术来有效支持大量的细粒度对象。如果系统中创建了大量的类似对象,会导致内存消耗过高,通过享用模式处理重用类似对象,减少内存消耗的问题,达到性能优化方案。
应用场景:当代码中创建了大量类似对象和类似的代码块
栗子:
// 有一百种不同文字的弹窗,每种弹窗行为相同,但是文字和样式不同,我们没必要创建一百个弹窗对象
function Pop(){
}
// 保留同样的行为
Pop.prototype.action=function(){}
//显示
Pop.prototype.show=function(){}
// 提取出每个弹窗不同的部分作为一个外部数组
var popArr=[
{text:"window1",style:[400,400]}
{text:"window2",style:[400,200]}
...
]
var poper=new Pop()
for(var i=0;i<100;i++){
poper.show(popArr[i])
}
通过桥接代替耦合
应用场景:减少模块之间的耦合;提高复用性
栗子:
// 有三种形状,每种形状都有3种颜色
function rect(color) {
//矩形
showcolor(color)
}
function circle() {
// 圆形
showcolor(clor)
}
function delta(color) {
// 三角形
showcolor(clor)
}
new circle('red')
还有一个对比:
有一组菜单,上面每种选项都有不同的选中效果
普通做法:
function menuItem(word) {
this.word = ''
this.dom = document.createElement('div')
this.dom.innerHTML = this.word
}
var menu1 = new menuItem('menu1')
var menu2 = new menuItem('menu2')
var menu3 = new menuItem('menu3')
menu1.onmouseover = function() {
menu1.style.color = 'red'
}
menu2.onmouseover = function() {
menu1.style.color = 'green'
}
menu3.onmouseover = function() {
menu1.style.color = 'blue'
}
menu1.onmouseout = function() {
menu1.style.color = 'white'
}
menu2.onmouseout = function() {
menu1.style.color = 'white'
}
menu3.onmouseout = function() {
menu1.style.color = 'white'
}
桥接优化:
function menuItem(word, color) {
this.word = word
this.color = color
this.dom = document.createElement('div')
this.dom.innerHTML = this.word
document.getElementById('app').appendChild(this.dom)
}
menuItem.prototype.bind = function() {
var self = this
this.dom.onmouseover = function() {
console.log(self.color)
this.style.color = self.color.colorOver
}
this.dom.onmouseout = function() {
this.style.color = self.color.colorOut
}
}
function menuColor(colorover, colorout) {
this.colorOver = colorover
this.colorOut = colorout
}
var data = [
{ word: 'menu1', color: ['red', 'black'] },
{ word: 'menu2', color: ['green', 'black'] },
{ word: 'menu3', color: ['blue', 'black'] }
]
for (var i = 0; i < data.length; i++) {
new menuItem(
data[i].word,
new menuColor(data[i].color[0], data[i].color[1])
).bind()
}
这个模式跟我们的建造者模式很类似拆分再组合,建造者模式的核心是如何去构造对象;而桥接模式是如何简化代码,提高可复用性,一个关注的是功能一个关注的是创建,这是它们的区别。
用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
应用场景:当两个模块直接沟通会增加它们的耦合性时
手写一个发布订阅栗子:
// 被观察者
function Subject() {
this.observers = [];
}
Subject.prototype = {
// 订阅
subscribe: function (observer) {
this.observers.push(observer);
},
// 取消订阅
unsubscribe: function (observerToRemove) {
this.observers = this.observers.filter(observer => {
return observer !== observerToRemove;
})
},
// 事件触发
fire: function () {
this.observers.forEach(observer => {
observer.call();
});
}
}
常用观察者模式的表现:
on('name', fn)订阅消息,name: 订阅的消息名称, fn: 订阅的消息
emit('name', args)发布消息, name: 发布的消息名称, args: 发布的消息
return new Promise((resolve,reject) => {
uni.closeBluetoothAdapter({
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
});
})
document.body.addEventListener('click', function() {
console.log('hello world!');
});
document.body.click()
状态模式:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
策略模式:通过定义一系列的算法,并将每一个算法封装起来,可以使他们相互替换,并让算法可以在不影响到客户端的情况下变化
应用场景:当代码 if-else 分支过多时,可以优化if-else分支
策略模式栗子:
根据用户权限展示不同内容:
没有用策略模式:
function showPart1() {
console.log(1)
}
function showPart2() {
console.log(2)
}
function showPart3() {
console.log(3)
}
axios.get('xxx').then(res => {
if (res == 'boss') {
showPart1()
showPart2()
showPart3()
} else if (res == 'manner') {
showPart1()
showPart2()
} else if (res == 'staff') {
showPart3()
}
})
策略模式优化:
function showControl() {
this.status = ''
this.power = {
boss: function() {
showPart1()
showPart2()
showPart3()
},
manner: function() {
showPart1()
showPart2()
},
staff: function() {
showPart3()
}
}
}
showControl.prototype.show = function() {
var self = this
axios.get('xxx').then(res => {
self.status = res
self.power[self.status]()
})
}
new showControl().show()
状态模式栗子:
控制一个小球的前后左右移动:
没有用状态模式:
function moveLeft() {
console.log('left')
}
function moveRight() {
console.log('RigmoveRight')
}
function moveTop() {
console.log('Top')
}
function moveBottom() {
console.log('bomoveBottom')
}
function mover() {
if (arguments.length == 1) {
if (arguments[0] == 'left') {
moveLeft()
} else if (arguments[0] == 'right') {
moveRight()
} else if (arguments[0] == 'top') {
moveTop()
} else if (arguments[0] == 'bottom') {
moveBottom()
}
} else {
if (arguments[0] == 'left' && arguments[1] == 'top') {
moveLeft()
moveTop()
} else if (arguments[0] == 'right' && arguments[1] == 'bottom') {
moveRight()
moveBottom()
}
}
}
状态模式优化
function mover() {
this.status = []
this.actionHandle = {
left: moveLeft,
right: moveRight,
top: moveTop,
bottom: moveBottom
}
}
mover.prototype.run = function() {
this.status = Array.prototype.slice.call(arguments)
this.status.forEach(action => {
this.actionHandle[action]()
})
}
new mover().run('left', 'right')
策略模式封装的是行为,而状态模式封装的是变化。尽管这么说,但事实上策略模式与状态模式在很多情况下都是可以互相转化的,具体应该使用何种模式,就要尽心分析:能够看得出明显状态变化的,用状态模式;如果只是选择一个合适的具体执行方案,那么显然策略模式更为适合,毕竟状态模式由于牵涉到状态的变化和转移方向,是要比策略模式略微复杂的,这里的复杂并不是指代码难以理解,而是从设计模式的角度来说明类的结构。
使用多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象链成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
应用场景:把操作分隔成一系列模块,每个模块只处理自己的事情
栗子:
普通实现:
var order = function( orderType, pay, stock ){
if(orderType===1){ //判断是否是500定金用户
if(pay===true){ // 已支付定金
console.log('已经预付500,得到100优惠卷')
}else{ //没有支付定金,转为普通用户
if(stock > 0){ // 普通用户需要判断是否还有库存
console.log('普通购买,没有优惠卷')
}else{
console.log('手机库存不足')
}
}
}else if(orderType===2){ //判断是否是200定金用户
if(pay===true){ // 已支付定金
console.log('已经预付200,得到50优惠卷')
}else{ //没有支付定金,转为普通用户
if(stock > 0){ // 普通用户需要判断是否还有库存
console.log('普通购买,没有优惠卷')
}else{
console.log('手机库存不足')
}
}
}else if(orderType===3){ //判断是否是普通用户
if(stock > 0){ // 普通用户需要判断是否还有库存
console.log('普通购买,没有优惠卷')
}else{
console.log('手机库存不足')
}
}
}
// 500 元订单
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购, 得到100 优惠券' );
}else{
order200( orderType, pay, stock ); // 将请求传递给200 元订单
}
};
// 200 元订单
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购, 得到50 优惠券' );
}else{
orderNormal( orderType, pay, stock ); // 将请求传递给普通订单
}
};
// 普通购买订单
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
// 测试结果:
order500( 1 , true, 500); // 输出:500 元定金预购, 得到100 优惠券
order500( 1, false, 500 ); // 输出:普通购买, 无优惠券
order500( 2, true, 500 ); // 输出:200 元定金预购, 得到500 优惠券
order500( 3, false, 500 ); // 输出:普通购买, 无优惠券
order500( 3, false, 0 ); // 输出:手机库存不足
职责链模式再优化-职责链节点传递
(约定好,如果某个节点不能处理请求,则返回一个特定的字符串 'nextSuccessor’来表示该请求需要继续往后面传递)
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到100 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到50 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
Chain.prototype.passRequest = function(){
var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};
// 现在我们把3 个订单函数分别包装成职责链的节点:
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
//然后指定节点在职责链中的顺序:
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
//最后把请求传递给第一个节点:
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足
// 假如某天网站运营人员又想出了支持300 元定金购买
var order300 = function(){
// 具体实现略
};
chainOrder300= new Chain( order300 );
chainOrder500.setNextSuccessor( chainOrder300);
chainOrder300.setNextSuccessor( chainOrder200);
指一个执行某些特定的事情的指令
应用场景:调用的命令充满不确性
栗子:
// html-------------------
// js---------------------
const button1 = document.getElementById('button1');
const button2 = document.getElementById('button2');
const button3 = document.getElementById('button3');
const MenBar = {
refresh:function(){
console.log('刷新菜单目录')
}
}
const SubMenu = {
add:function(){
console.log('增加子菜单')
},
del:function(){
console.log('删除子菜单')
}
}
function setCommand(el,command){
el.onclick = function(){
command.execute()
}
}
class MenuBarCommand{
constructor(receiver,key){
this.receiver = receiver
this.key = key
}
execute(){
this.receiver[this.key]()
}
}
setCommand(button1,new MenuBarCommand(MenBar,'refresh'))
setCommand(button2,new MenuBarCommand(SubMenu,'add'))
setCommand(button3,new MenuBarCommand(SubMenu,'del'))
思想:
用户只管输入他要的命令,不用关心 api
命令和实现解耦,无论命令发生变动,还是实现发生变动,都不会彼此影响
不访问内部的情况下,方便的遍历数据
应用场景:当我们需要对某个对象进行操作,但是又不能暴露内部
回想一下ES6的迭代器Iterator:
它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
ES6栗子:
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
原生具备 Iterator 接口的数据结构如下:
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
比如,再实现一个 Range 类用于在某个数字区间进行迭代栗子:
function Range(start, end) {
return {
[Symbol.iterator]: function () {
return {
next() {
if (start < end) {
return { value: start++, done: false };
}
return { done: true, value: end };
}
}
}
}
}
for (num of Range(1, 5)) {
console.log(num);
}
// 输出:1, 2, 3, 4
手写一个forEach:
// 对数组和对象进行迭代
function Iterator(data) {
this.data = data
}
Iterator.prototype.dealEach = function(fn) {
if (this.data instanceof Array) {
for (var i = 0; i < this.data.length; i++) {
fn(this.data[i], i)
}
} else {
for (var item in this.data) {
fn(this.data[item], item)
}
}
}
function testConsole (data, index){
console.log(data, index)
}
const iter = new Iterator([1,2,3])
iter.dealEach(testConsole)
参考:
https://senior-frontend.pages.dev/jsadvanced/designpattern.htm
https://juejin.cn/post/6844904184257609735
https://www.cnblogs.com/yinhaiying/p/10782151.html
https://blog.csdn.net/m0_57307213/article/details/126990990
https://segmentfault.com/a/1190000038249041
https://blog.csdn.net/qq_40124555/article/details/126474930
https://www.cnblogs.com/zaisanshuiyifang/p/15601337.html
https://www.jb51.net/article/252965.htm