JavaScript设计模式(二十四):点钞机-迭代器模式

简介: 点钞机-迭代器模式

迭代器模式(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);
            }
        }
    }
};

image.png

<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');

image.png

<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);

分支循环嵌套问题

处理图像

image.png

/**
 * 获取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);
};

特点

通过迭代器我们可以顺序地访问一个聚合对象中的每一个元素

在开发中,迭代器极大简化了代码中的循环语句,使代码结构清晰紧凑,然而这些简化了的循环语句实质上隐形地移到了迭代器中

迭代器处理一个对象时,我们只需提供处理的方法,而不必去关心对象的内部结构,这也解决对象的使用者与对象内部结构之间的耦合

迭代器的存在也为我们提供了操作对象的一个统一接口

目录
相关文章
|
29天前
|
设计模式 Java 开发者
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin的用户,推荐查看“简洁”系列教程。本文重点介绍迭代器模式,通过具体示例展示了如何在Kotlin中实现迭代器模式,包括使用Iterator、Iterable接口及重载iterator运算符的方法。
28 4
|
29天前
|
设计模式 Java Kotlin
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
27 2
|
30天前
|
设计模式 Java 开发者
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
本教程详细讲解了Kotlin中的迭代器模式,包括如何通过实现Iterator和Iterable接口以及重载iterator运算符来实现可遍历的自定义集合。示例展示了如何创建一个图书集类,并通过不同方式使其支持遍历操作,适合希望深入了解Kotlin迭代器模式的开发者。
29 3
|
8天前
|
设计模式 Java Kotlin
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
14 0
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
30 1
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
Kotlin教程笔记(54) - 改良设计模式 - 迭代器模式
26 1
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
30 3
|
2月前
|
设计模式 安全 Java
Java设计模式-迭代器模式(21)
Java设计模式-迭代器模式(21)
|
3月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
50 1
|
3月前
|
设计模式 JavaScript 前端开发
小白请看 JS大项目宝典:设计模式 教你如何追到心仪的女神
小白请看 JS大项目宝典:设计模式 教你如何追到心仪的女神