likes
comments
collection
share

前端设计模式知识体系(四): 迭代器、原型、过滤器、职责链、外观、计算属性、路由、解释器与依赖注入模式在前几篇文章中,我

作者站长头像
站长
· 阅读数 53

前端设计模式知识体系(四): 迭代器模式、原型模式、过滤器模式、职责链模式、外观模式、计算属性模式、路由模式、解释器模式、依赖注入模式

在前几篇文章中,我们深入探讨了前端开发中的几种设计模式,为大家展示了它们在代码优化和问题解决中的实际应用。接下来,我们将进一步了解更多设计模式的实践应用,介绍包括迭代器模式、原型模式、过滤器模式等在内的更多设计模式。这些模式将帮助我们更好地管理复杂业务逻辑,提高代码的灵活性和可维护性。一起学习如何运用这些设计模式提升开发效率吧!

迭代器模式 (Iterator Pattern)

提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴漏该对象的内部表示, 在 JavaScript 中, 可以使用迭代器模式来操作数组或类数组对象。在迭代器模式中,集合对象包含一个方法, 用于返回一个迭代器,该迭代器可以按顺序访问该集合中的元素。迭代器提供了一个通用的接口, 使得可以使用相同的方法遍历不同类型的集合对象。

前端迭代器常用于处理数据集合的遍历,如在列表渲染、分页、数据流处理等场景中。

JavaScript 提供了大量实现迭代器模式的方法,如 forEach()、map()、filter() reduce() 、for-of、some() 、every()、Object.keys() 和 Object.values() 等,帮助我们高效地处理和遍历数据集合。

实现一个可迭代对象

// 定义一个 Collection 类,用于存储数字并实现迭代器模式
class Collection {
  // 私有属性,存储数字的数组
  private itemList = new Array<number>();

  // 添加数字到集合中
  add(item: number) {
    this.itemList.push(item);
  }

  // 实现可迭代接口,定义 Symbol.iterator 方法
  [Symbol.iterator]() {
    let i = 0; // 迭代器的当前索引
    const itemList = this.itemList; // 保存对 itemList 的引用

    // 返回一个迭代器对象
    return {
      next() {
        // 如果当前索引小于数组长度,返回当前项
        if (i < itemList.length) {
          return { value: itemList[i++], done: false };
        } else {
          // 否则,表示迭代完成
          return { done: true };
        }
      },
    };
  }
}

// 创建一个 Collection 实例
const c = new Collection();

// 向集合中添加元素
c.add(1);
c.add(2);
c.add(4);
c.add(6);
c.add(8);

// 使用 for...of 循环遍历集合中的元素
for (let i of c) {
  console.log(i); // 打印出集合中的每一个元素
}

// 也可以手动调用迭代器的 next 方法来逐个获取元素
// const it = c[Symbol.iterator]();
// console.log(it.next()); // { value: 1, done: false }
// console.log(it.next()); // { value: 2, done: false }
// console.log(it.next()); // { value: 4, done: false }
// console.log(it.next()); // { value: 6, done: false }
// console.log(it.next()); // { value: 8, done: false }
// console.log(it.next()); // { done: true }

Symbol.iterator 是一个内置的符号 (ES6+),用于定义对象的迭代器,使其可以使用 for...of 循环或其他迭代机制进行遍历。

在 JavaScript 中,迭代器分为同步迭代器异步迭代器两种类型。同步迭代器使用 Symbol.iterator 方法来逐步返回集合中的值,而异步迭代器则通过 Symbol.asyncIterator 方法处理异步数据流,这对于处理异步操作(如网络请求)中的数据尤为重要。同步迭代器适用于常规的同步数据遍历,而异步迭代器则允许在异步操作中进行逐步迭代,确保数据处理的高效性和响应性。

// 定义一个对象并实现 Symbol.asyncIterator 方法
const asyncIterableObject = {
  // 实现 Symbol.asyncIterator 方法
  [Symbol.asyncIterator]() {
    let count = 1; // 初始化计数器

    // 返回一个异步迭代器对象
    return {
      async next() {
        // 模拟异步延迟
        await new Promise((resolve) => setTimeout(resolve, 1000)); // 延迟 1 秒

        if (count <= 5) {
          // 返回一个值和 done 状态
          return { value: count++, done: false };
        } else {
          // 迭代完成
          return { done: true };
        }
      },
    };
  },
};

// 使用异步迭代器遍历异步可迭代对象的值
(async () => {
  for await (const value of asyncIterableObject) {
    console.log(value); // 打印每个值
  }
})();

原型模式 (Prototype Pattern)

在 JavaScript 中,原型模式利用构造函数和原型链机制,通过指定原型来创建对象,并通过拷贝原型创建新的实例,从而实现对象的继承和共享。

下面举个例子:

定义了一个包含属性和方法的原型对象 prototype,然后使用 Object.create(prototype) 创建了一个新对象 obj0。这个新对象 obj0 继承了 prototype 上的属性和方法,使得我们可以通过调用 obj0 的 add 方法来增加 value 的值。最终,通过三次调用 add 方法,obj0.value 的值从初始的 0 增加到了 3。这展示了如何利用原型链实现对象的继承和属性共享。

原型模式的具体实现

// 定义一个原型对象,包含属性和方法
const prototype = {
  value: 0,  // 属性 value,用于记录当前值
  add() {    // 方法 add,用于将 value 加 1
    this.value++;
  },
  sub() {    // 方法 sub,用于将 value 减 1
    this.value--;
  },
};

// 使用 Object.create() 方法创建一个新对象 obj0,指定该对象的原型是 prototype
const obj0 = Object.create(prototype);

// 调用 obj0 的 add 方法三次
obj0.add();
obj0.add();
obj0.add();

// 输出 obj0 的 value 属性值,应该是 3
console.log(obj0.value); // 3

过滤器模式 (Filter Pattern)

定义一个过滤器函数,该函数接收一个数据集合和一个过滤条件,并返回满足该条件的数据子集。

数组方法的 filter

JS 中过滤器模式的经典实现

let arr = [1,2,3,4]
arr.filter(i=> i>2).filter(i => i < 4)

职责链模式

通过将所有请求处理者连接成一条链,避免了请求发送者与多个请求处理者之间的直接耦合。在这种模式中,每个处理者对象都持有对下一个处理者的引用,请求沿着这条链传递,直到有一个处理者处理它为止。在 JavaScript 中,职责链模式通常通过使用函数对象或对象字面量实现,这种实现方式涉及构建一个处理对象的链表。

多用于 webserver 端, Koa 的洋葱模型(Onion Model)可以被视为职责链模式的一种实现。

前端设计模式知识体系(四): 迭代器、原型、过滤器、职责链、外观、计算属性、路由、解释器与依赖注入模式在前几篇文章中,我

求传递链:在洋葱模型中,每个中间件可以决定是否将请求传递给下一个中间件,这与职责链模式中请求沿链传递的概念类似。

中间件处理:每个中间件负责处理请求的特定部分,类似于职责链模式中每个处理者处理请求的某些方面。

链式调用:中间件以链式调用的方式执行,确保请求能够在链上继续传递,直到链上的所有中间件都处理完请求。

权限检查的职责链模式

class PermissionChecker {
  constructor(nextChecker = null) {
    this.nextChecker = nextChecker;
  }

  check(user, resource) {
    if (this.nextChecker) {
      return this.nextChecker.check(user, resource);
    }
    return true;
  }
}

// 具体权限检查器
class AdminPermissionChecker extends PermissionChecker {
  check(user, resource) {
    if (user.role !== 'admin') {
      return 'Admin role required.';
    }
    return super.check(user, resource);
  }
}

class ResourcePermissionChecker extends PermissionChecker {
  check(user, resource) {
    if (!user.permissions.includes(resource)) {
      return `No permission for resource: ${resource}`;
    }
    return super.check(user, resource);
  }
}

// 创建权限检查链
const permissionChain = new AdminPermissionChecker(
  new ResourcePermissionChecker(),
);

// 模拟用户和资源检查
const user = { role: 'admin', permissions: ['resource1'] };
const resource = 'resource2';
const checkResult = permissionChain.check(user, resource);
console.log(checkResult); // No permission for resource: resource2

外观模式(Facade Pattern)

外观模式(Facade Pattern)在前端开发中用于简化复杂的组件库使用,提供统一的接口。通过将多个模块的方法暴露在一个主入口文件(如 index.ts),外观模式让开发者只需关注简洁的接口,而无需深入了解模块的内部实现细节,从而提升开发效率和代码可维护性。

暂时无法在飞书文档外展示此内容

moduleA.js

export function featureA() {
  console.log('Feature A');
}

moduleB.js

export function featureB() {
  console.log('Feature B');
}

index.js

js

import { featureA } from './moduleA';
import { featureB } from './moduleB';

export default {
  featureA,
  featureB
};

index.js 作为外观层,将 moduleA 和 moduleB 的功能整合到一个统一的接口中。这样,其他模块在使用这些功能时,只需关注 index.js 提供的简洁接口,而无需直接操作各个模块的细节。

外观模式(Facade Pattern)在前端开发中用于提供一个统一的接口来简化对复杂系统的访问。通过创建一个外观对象,我们可以将多个模块的功能封装起来,暴露一个简洁的 API 供其他模块使用,从而简化代码的使用和维护。

常见应用场景

组件库:将组件库中的所有组件和工具整合到一个主入口文件中,用户只需引入这个主入口文件即可使用所有功能。

工具集:将多个工具函数或服务集成到一个工具对象中,方便统一管理和调用。

API 封装:对复杂的 API 接口进行封装,提供简洁的调用方式,隐藏实现细节。

通过统一接口简化系统复杂性、模块化功能降低耦合、集中管理接口提升可维护性和扩展性。

计算属性模式

计算属性模式通过定义 getter 方法,动态计算并返回对象属性的值,而无需存储结果,提升了数据的灵活性和实时性。

const rect = {
  length: 5,
  width: 10,
  get area() {
    return this.length * this.width;
  },
  get round() {
    return (this.length + this.width) * 2;
  },
};
console.log(rect.area);
console.log(rect.round);

路由模式 (Router Pattern)

路由模式通过定义路径与处理程序的映射关系,根据请求的路径动态分发到对应的处理逻辑,从而实现模块化的路由管理。

在前端应用中,路由模式常用于单页应用(SPA)中,根据URL的变化动态渲染不同的组件或页面,实现无刷新页面导航与视图切换。

// 定义路由对象
const routes = {
  "/": "Home Page",
  "/about": "About Page",
  "/contact": "Contact Page",
};

// 简单的路由函数,根据 hash 变化显示对应内容
function router() {
  const content = document.getElementById("content");
  const path = window.location.hash.slice(1); // 获取 URL 的 hash 部分
  content.innerHTML = routes[path] || "404 - Page Not Found"; // 显示对应页面内容或404
}

// 监听 hash 变化事件并执行路由
window.addEventListener("hashchange", router);

// 页面加载时初始化路由
window.addEventListener("load", router);
<ul>
  <li><a href="#/">Home</a></li>
  <li><a href="#/about">About</a></li>
  <li><a href="#/contact">Contact</a></li>
</ul>

<div id="content"></div>

当点击导航链接时,hash 会更新,并触发路由函数,页面内容根据当前的 URL 渲染对应的内容。

解释器模式

前端的解释器模式是一种用于解释特定语言或表达式的设计模式,常见于解析模板语言或命令行输入等场景,将复杂的语法转换为可执行的操作。

解释器模式在前端应用中用于将高级语言(如 JSX、ES6+ 语法)转换为浏览器能够理解和执行的低级语言(如 ES5),常见的工具包括 Babel 和各种自定义编译器。

依赖注入模式

依赖注入模式是一种设计模式,通过将对象的依赖关系从内部创建转移到外部注入,从而提高系统的灵活性和可测试性。

依赖注入模式常用于构建可测试、可扩展的应用程序,特别适用于需要管理复杂对象依赖关系的场景。

在前端开发中,依赖注入模式可以用于管理和注入组件的依赖,比如在 React 项目中,通过 Context API 提供全局状态或功能的共享,减少组件之间的耦合度。

// 祖先组件
export default {
  provide() {
    return {
      myData: 'Hello from provide!'
    };
  }
}

// 后代组件
export default {
  inject: ['myData'],
  created() {
    console.log(this.myData); // 输出: Hello from provide!
  }
}

结语

在本系列文章中,我们深入探讨了前端开发中的多种设计模式,包括迭代器模式、原型模式、过滤器模式、职责链模式、外观模式、计算属性模式、路由模式、解释器模式和依赖注入模式。通过这些模式的学习和应用,我们能够更好地组织代码,简化复杂业务逻辑,提高开发效率和代码的可维护性。希望这些设计模式的实践能为你的前端开发工作提供实用的指导和帮助,让你的项目更加高效、灵活与可扩展。前端设计模式的知识体系到此完结,感谢你的阅读与支持!

转载自:https://juejin.cn/post/7415725134596309004
评论
请登录