迭代器模式(Iterator)
在不暴露对象内部结构的同时,可以顺序地访问聚合对象内部的元素。
迭代器:解决处理多个元素不同处理的问题,简化遍历操作
比如我们拥有一系列的DOM元素,而需求是处理其中的一部分元素,而不是全部。一般方法我们会去遍历所有DOM元素,然后判断指定元素实现我们指定的方法,这么做就冗余遍历了我们不需要处理的元素,而迭代器模式很好的解决了这一问题。
迭代器可以顺序地访问一个聚合对象内部的元素的,它可以简化遍历操作,就像银行里的点钞机,有了它降低了我们点钞成本,安全而可靠。
/**
* 获取DOM元素
* @param {string} id DOMID
* @returns DOM元素
*/
function $(id) {
return document.getElementById(id);
}
/**
* 迭代器
* @param {string} containerId 父DOM元素ID
* @param {string} tagName 子DOM元素名称
* @returns 对象,内存定义有针对元素的操作
*/
const Iterator = function (containerId, tagName) {
const container = $(containerId);
const tags = container.getElementsByTagName(tagName);
const len = tags.length;
let index = 0;
return {
/**
* 获取第一个DOM元素
* @returns DOM元素
*/
first() {
index = 0;
return tags[index];
},
/**
* 获取最后一个DOM元素
* @returns DOM元素
*/
last() {
index = len - 1;
return tags[index];
},
/**
* 获取当前DOM元素
* @param {number} i 索引
* @returns DOM元素
*/
get(i) {
// 如果num大于等于0再获取正向获取;当i>=len时i继续从0开始索引;小于0时,逆向获取
index = i >= 0 ? (i % len) : Math.abs(len + i % len);
return tags[index];
},
/**
* 获取前一个DOM元素
* @returns DOM元素
*/
pre() {
return this.get(--index);
},
/**
* 获取后一个DOM元素
* @returns DOM元素
*/
next() {
return this.get(++index);
},
/**
* 对每一个元素执行某一个方法
* @param {Function} fn 回调函数-this指向当前元素
* @param {...any} args 传入回调的参数
*/
dealEach(fn, ...args) {
[...tags].forEach(tag => {
fn.apply(tag, args);
});
},
/**
* 对某一个元素执行某一个方法
* @param {*} i 索引
* @param {*} fn 回调函数-this指向当前元素
* @param {...any} args 传入回调的参数
*/
dealItem(i, fn, ...args) {
fn.apply(this.get(i), args);
},
/**
* 对 所有元素 以及 对 某个或某些元素 的处理
* @param {number|number[]} i 索引
* @param {Function} allFn 对每一个元素执行某一个方法的回调
* @param {Function} iFn 对某一个元素执行某一个方法的回调
*/
exclusive(i, allFn, iFn) {
this.dealEach(allFn);
if (Array.isArray(i)) {
i.forEach(j => this.dealItem(j, iFn));
} else {
this.dealItem(i, iFn);
}
}
}
};
<ol id="container1">
<li>Lee</li>
<li>Tom</li>
<li>Lucy</li>
<li>张三</li>
<li>李四</li>
<li>王五</li>
<li>赵六</li>
<li>哈哈</li>
<li>呵呵</li>
<li>嘿嘿</li>
<li>嘻嘻</li>
</ol>
const it1 = Iterator('container1', 'li');
console.log(it1);
console.log(it1.first()); // <li>Lee</li>
console.log(it1.last()); // <li>嘻嘻</li>
console.log(it1.get(-2)); // <li>嘿嘿</li>
console.log(it1.pre()); // <li>呵呵</li>
console.log(it1.next()); // <li>嘿嘿</li>
it1.dealEach(function (color) {
this.style.color = color;
}, '#ff088f');
it1.dealItem(7, function (text, color) {
this.innerText += text;
this.style.color = color;
}, '-疯子', 'blue');
<ol id="container2">
<li>Tom</li>
<li>张三</li>
<li>李四</li>
<li>王五</li>
<li>赵六</li>
<li>哈哈</li>
<li>呵呵</li>
<li>嘿嘿</li>
<li>嘻嘻</li>
</ol>
const it2 = Iterator('container2', 'li');
console.log(it2);
it2.exclusive([-1, 3, 5, 12], function () {
this.innerText += ' - [全部元素的处理]';
this.style.backgroundColor = 'red';
}, function () {
this.innerText += ' -【指定元素的处理】';
this.style.backgroundColor = 'green';
});
数组迭代器
/**
* 仿forEach
* @param {any[]} arr 数组
* @param {Function} fn 回调
*/
const eachArray = function (arr, fn) {
for (let i = 0; i < arr.length; i++) {
if (fn.call(arr[i], arr[i], i) === false) break;
}
};
eachArray(['Lee', 'Tom', 'Lucy'], function (item, i) {
console.log(item, i); // 'Lee' --> 0 | 'Tom' --> 1 | 'Lucy' --> 2
// if(item === 'Tom') return false; // 'Lee' --> 0 | 'Tom' --> 1
});
对象迭代器
/**
* 仿forEach
* @param {object} obj 对象
* @param {Function} fn 回调
*/
const eachObject = function (obj, fn) {
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
if (fn.call(obj[key], key, obj[key]) === false) break;
}
}
};
eachObject({
name: 'Lee', age: 18, desc: '呜呜~~~'}, function (key, value) {
/**
* name ---> Lee
* age ---> 18
* desc ---> 呜呜~~~
*/
console.log(key, value);
});
同步变量迭代器
解决a.b.c.d
报错问题
Uncaught TypeError: Cannot read properties of undefined (reading 'c')
// 同步变量
let A = {
code: 200,
data: {
name: 'Lee',
age: 18,
info: {
desc: '描述一下呗',
lang: 'zh-cn'
}
},
msg: 'ok'
};
/**
* 同步变量迭代取值器
* @param {string} key key字符串
*/
const AGetter = function (key) {
if (!A) return false;
let result = A;
const keys = key.split('.');
for (let i = 0; i < keys.length; i++) {
let k = keys[i];
result = result[k] || undefined;
if (result === undefined) return undefined;
}
return result;
};
// undefined
AGetter('data.name.age.xxx');
// '描述一下呗'
AGetter('data.info.desc');
// {name: 'Lee', age: 18, info: {desc: '描述一下呗', lang: 'zh-cn'}}
AGetter('data');
/**
* 同步变量迭代赋值器
* @param {string} key key字符串
* @param {any} val 值
*/
const ASetter = function (key, val) {
if (!A) return false;
let result = A;
const keys = key.split('.');
let len = keys.length;
for (let i = 0; i < len - 1; i++) {
let k = keys[i];
result[k] = result[k] || {
};
result = result[k];
}
return result[keys[len - 1]] = val;
};
ASetter('data.aaa.bbb.ccc', 'ABC');
/**
* {
* "code":200,
* "data":{
* "name":"Lee",
* "age":18,
* "info":{
* "desc":"描述一下呗",
* "lang":"zh-cn"
* },
* "aaa":{
* "bbb":{
* "ccc":"ABC"
* }
* }
* },
* "msg":"ok"
* }
*/
console.log(A);
分支循环嵌套问题
处理图像
/**
* 获取DOM元素
* @param {string} id DOMID
* @returns DOM元素
*/
function $(id) {
return document.getElementById(id);
}
// 获取canvas
const canvas = $('canvas');
const ctx = canvas.getContext('2d');
// 定义图片
const image = new Image();
image.src = './00.png';
const scale = 0.3;
const [w, h] = [image.width * scale, image.height * scale];
// [image.width, image.height] = [w, h];
// document.body.append(image);
// 配置canvas宽高
[canvas.width, canvas.height] = [w * 3, h];
// 图片加载完成
image.onload = function () {
ctx.drawImage(image, 0, 0, w, h);
dealImage(ctx, 'gray', 0, 0, w, h, 255);
dealImage(ctx, 'red', w, 0, w, h, 255);
};
将循环遍历抽象出来作为一个迭代器存在,每次循环都执行传入迭代器中的某一固定算法,而对于特效算法我们可以设置在策略对象中实现,通过策略模式与迭代器模式的综合运用即可解决上述分支判断问题。代码如下:
/**
* 绘制特效图片
* @param {CanvasRenderingContext2D} ctx 绘制对象
* @param {string} effect 特效标识
* @param {number} x 位置x
* @param {number} y 位置y
* @param {number} w 宽度
* @param {number} h 高度
* @param {number} opacity 透明度
*/
const dealImage = function (ctx, effect, x, y, w, h, opacity) {
const imageData = ctx.getImageData(x, y, w, h);
let {
data } = imageData;
// 状态模式封装算法
const Deal = (function () {
const Action = {
// 默认方法
default(i) {
return this.gray(i);
},
// 红色特效
red(i) {
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = opacity;
},
// 平均灰度特效
gray(i) {
// 将红、绿、蓝色取平均值
data[i] = data[i + 1] = parseInt(data[i + 2] = (data[i] + data[i + 1] + data[i + 2]) / 3);
data[i + 3] = opacity;
}
};
return function (state) {
return Action[state] || Action[state]();
}
}());
// 迭代器处理数据
function eachData(fn) {
for (let i = 0; i < data.length; i += 4) {
// 处理一组像素数据
fn(i);
}
}
// 处理数据
eachData(Deal(effect));
ctx.putImageData(imageData, x + w, 0);
};
特点
通过
迭代器
我们可以顺序地访问
一个聚合对象中的每一个元素
。在开发中,
迭代器极大简化了代码中的循环语句,使代码结构清晰紧凑,然而这些简化了的循环语句实质上隐形地移到了迭代器中
。用
迭代器
去处理一个对象
时,我们只需提供处理的方法
,而不必去关心对象的内部结构
,这也解决
了对象的使用者与对象内部结构之间的耦合
。
迭代器
的存在也为我们提供了操作对象的一个统一接口
。