前端设计模式学习记录

设计模式介绍

概念

  • 设计模式是我们在解决问题的时候针对特定的问题给出的简洁而优化的处理方案
  • 在JS设计模式中,最核心的思想:封装变化
  • 将变与不变分离,确保变化的部分灵活、不变的部分稳定

注意:下面文章介绍的设计模式,并非全部设计模式、准确性也不保证,只是我个人理解

设计模式分类

构造器模式

简单例子就是

// 平常哦我们创建两个对象,可能会这样写
const obj1 = {
    name: "xx",
    age: 15
}

const obj2 = {
    name: "yyy",
    age: 16
}

// 利用构造器设计模式
function CreateObj(name, age){
    this.name = name;
    this.age = age;
    this.say = function(){
        console.log(`${this.anme}-${this.age}`)
    }
}

const obj1 = new CreateObj("xxx", 15)
const obj2 = new CreateObj("yyy", 16)

优点:减少创建对象的冗余代码

缺点:如果构造器有方法,则每次new 都会创建方法,会消耗内存

解决: 可以将方法放到原型上、现在比较流行的就是使用es6 class 代替

class Employee {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }

    sayHello() {
       console.log(`姓名:${this.name},年龄:${this.age}`);
    }
}

工厂模式

由一个工厂对象决定创建某一种产品对象类的实例,主要用来创建同一类对象

class User {
      constructor(role, pages) {
        this.role = name;
        this.pages = pages;
      }

      static userFactory() {
        switch (role) {
          case 'admin':
            return new User("admin", ["dashboard", "users", "pages"]);
          case 'editor':
            return new User("editor", ["dashboard", "pages"]);
          default:
            return new User("user", ["dashboard"]);
        }
      }
    }

优点: 只需要一个正确的参数,就可以获取到你所需要的对象,而无需知道创建的具体细节。

缺点:函数内包含了所有对象的创建和判断逻辑的代码,每增加新的构造函数还需要修改判断逻辑代码,当我们的对象不是上面3个而是是个或更多时,这个函数会成为一个庞大的超级函数,使得难以维护,所以简单工厂模式只能作用于创建数量较少,对象的创建逻辑不复杂时使用

抽象工厂模式

抽象工厂模式并不直接生成实例,而是用于对产品类的创建

// 基础父类
    class User {
      constructor(name, role, pages) {
        this.name = name;
        this.role = role;
        this.pages = pages;
      }

      welcome() {
        console.log(`Welcome, ${this.role}!`);
      }

      // 抽象方法
      dataShow() {
        throw new Error("抽象方法需被实现");
      }
    }

    // superAdmin类
    class SuperAdmin extends User {
      constructor(name) {
        super(name, "superAdmin", ["dashboard", "users", "pages"]);
      }

      dataShow() {
        console.log("superAdmin", this.pages);
      }
    }

    // admin类
    class Admin extends User {
      constructor(name) {
        super(name, "admin", ["dashboard", "pages"]);
      }

      dataShow() {
        console.log("superAdmin", this.pages);
      }
    }

    // editor类
    class Editor extends User {
      constructor(name) {
        super(name, "editor", ["dashboard"]);
      }

      dataShow() {
        console.log("superAdmin", this.pages);
      }
    }

    // 获取类
    function getUserClass(role) {
      switch (role) {
        case 'superAdmin':
          return SuperAdmin;
        case 'admin':
          return Admin;
        case 'editor':
          return Editor;
        default:
          throw new Error("参数错误");;
      }
    }

    let UserClass = getUserClass("superAdmin")
    console.log(new UserClass("mrx").dataShow());

抽象工厂要是的一个类,工厂要的是具体的对象实例,需要注意两者的区别 

建造者模式

建造者模式属于创建模型的一种,提供一种创建复杂对象的方式。它将一个复杂的对象构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步的创建一个复杂对象,它允许用户通过指定复杂的对象的类型和内容就可以构建它们,用户不需要指定内部具体构建细节

class Test1 {
      init() {
        console.log("Test1");
      }

      getData() {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve("Test1 getData");
          })
        })
      }

      render(data) {
        console.log(`${data}`);
      }
    }

    class Test2 {
      init() {
        console.log("Test2");
      }

      getData() {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve("Test2 getData");
          })
        })
      }

      render(data) {
        console.log(`${data}`);
      }
    }

    // 建造者
    class TestCreate {
      async create(builder) {
        builder.init();
        // 可以根据返回值做一些处理
        const res = await builder.getData();
        builder.render(res);
      }
    }

    const op = new TestCreate();
    op.create(new Test1());
    op.create(new Test2());

 建造者模式将一个复杂对象的构建层与其表示层分离,同样的构建过程可采用不同的表示,工厂模式主要是为了创建对象实例或者类(抽象工厂),关心的是最终产出(创建)的是什么,而不关心过程,而建造者模式关心的是创建这个对象的整个过程,甚至于创建对象的每一个细节。

单例模式

 保证一个类仅有一个实例,并提供一个访问它的全局访问点

主要解决一个全局使用的类频繁地创建和销毁,占用内容

class Singleton {
      constructor() {
        console.log("创建实例");
      }

      static getInstance() {
        if(!this.instance){
          this.instance = new Singleton()
          return this.instance
        }

        return this.instance
      }
    }

    const instance1 = Singleton.getInstance();
    const instance2 = Singleton.getInstance();
    // true 证明两次创建的对象是同一个
    console.log(instance1 === instance2);

装饰器模式

Function.prototype.before = function(beforeFn) {
      const _this = this;
      return function() {
        // beforeFn前置方法,也就是test.before(callback)里面这个callback
        beforeFn.apply(this, arguments);
        return _this.apply(this, arguments);
      }
    }

    Function.prototype.after = function(afterFn) {
      const _this = this;
      return function() {
        const result = _this.apply(this, arguments);
        afterFn.apply(this, arguments);
        return result;
      }
    }

    function test() {
      console.log('test');
    }

    // 给test增加前置后置方法、也可以只增加前置或者后置方法、或者不增加、就是可插拔的了
    const testCallback = test.before(()=>{
      console.log("before test");
    }).after(function() {
      console.log('after test');
    })

    testCallback();

打印结果

适配器模式

将一个类的接口转换成客户希望的另一个接口,适配器模式让那些不兼容的类可以一起工作

/*
      需求:我们是需要使用多个类型的地图,但是这些地图的接口并不相同,我们需要对这些地图进行适配,使其能够统一使用。
      然后我们定义一个renderMap 方法去统一使用这两个地图,但是由于两个地图的展示方法不一样,所以需要对另一个类做适配
    */
    class AMap {
      show() {
        console.log('显示A地图');
      }
    }

    class BMap {
      display() {
        console.log('显示B地图');
      }
    }

    // a地图的适配器
    class AMapAdapter extends AMap {
      constructor(map) {
        super()
      }
      display() {
        super.show();
      }
    }

    /*
      统一使用地图
    */
    function renderMap(map) {
      map.display();
    }
    renderMap(new AMapAdapter());
    renderMap(new BMap());

策略模式

策略模式,可以替换那种很多ifelse那种场景,可以在一定程度上较少复杂度,是代码看起来更优雅

 不使用策略模式的情况下的代码

 // 不使用策略模式
    // 假设我们有一个计算折扣的函数
    function calculateDiscount(price, discountType) {
      if (discountType === 'regular') {
        return price * 0.9;
      } else if (discountType === 'senior') {
        return price * 0.85;
      } else if (discountType === 'student') {
        return price * 0.95;
      } else {
        return price;
      }
    }

    console.log(calculateDiscount(100, 'regular')); // 90
    console.log(calculateDiscount(100, 'senior')); // 85
    console.log(calculateDiscount(100, 'student')); // 95
    console.log(calculateDiscount(100, 'other')); // 100

使用策略模式的代码

// 基于上面的代码使用策略模式重写
    // 定义一个策略类
    const discountStrategies = {
      regular: (price) => price * 0.9,
      senior: (price) => price * 0.85,
      student: (price) => price * 0.95,
      other: (price) => price
    }

    // 定义一个计算折扣的函数
    function calculateDiscount(price, discountType) {
      return (discountStrategies[discountType] || discountStrategies.other)(price);
    }

    console.log(calculateDiscount(100, 'regular')); // 90
    console.log(calculateDiscount(100, 'senior')); // 85
    console.log(calculateDiscount(100, 'student')); // 95
    console.log(calculateDiscount(100, 'other')); // 100

观察者模式 

观察这模式包含观察目标和观察者两类对象

一个目标可以有任意数目与之相依赖的观察者

一旦观察目标的状态发生改变,所有的观察者都将得到通知

 当一个对象状态发生改变时,所有依赖它的对象都得到通知并被被动自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他兑现通知的问题

效果:红色圈圈的内容,更具绿色框框点击内容改变 ,下面是demo代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>观察者</title>
  <style>
    body {
      padding: 0;
      margin: 0;
      color: blueviolet;
    }
    .box {
      display: flex;
      width: 100vw;
      height: 100vh;
    }
    .left {
      width: 200px;
      background-color: #f0f0f0;
    }
    .right {
      background: orange;
      flex: 1;
    }
    ul>li {
      list-style: none;
      padding: 10px;
      cursor: pointer;
    }
    .header {
      background-color: pink;
    }
  </style>
</head>
<body>
  <div class="header">头部</div>
  <div class="box">
    <div class="left">
      <ul>
        <li>首页</li>
        <li>用户管理</li>
        <li>权限管理</li>
      </ul>
    </div>
    <div class="right">
      <div class="content">内容</div>
    </div>
  </div>
  <script>
    // 被观察目标类-这里左侧的内容当做被观察目标
    class Target {
      constructor() {
        this.observers = [];
      }

      // 增加观察者
      subscribe(observer) {
        this.observers.push(observer);
      }

      // 移除观察者
      unsubscribe(observer) {
        this.observers = this.observers.filter(item => item !== observer);
      }

      // 通知观察者
      notify(data) {
        this.observers.forEach(observer => observer.update(data));
      }
    }

    // 观察者类-右侧显示的内容当做观察者、还有头部也当做观察者
    class Observer {
      constructor(dom) {
        this.dom = dom;
      }

      // 更新
      update(data) {
        this.dom.innerText = data;
      }
    }

    // 实例化观察者
    const observer1 = new Observer(document.querySelector('.header'));
    const observer2 = new Observer(document.querySelector('.content'));

    // 实例化被观察目标
    const target = new Target();

    // 订阅
    target.subscribe(observer1);
    target.subscribe(observer2);

    // 监听ul点击事件
    const ul = document.querySelector('ul');
    ul.addEventListener('click', function(e) {
      console.log(e.target.tagName);
      if (e.target.tagName === 'LI') {
        const text = e.target.innerText;
        target.notify(text);
      }
    })
  </script>
</body>
</html>

优势:目标与观察者,功能耦合度降低,专注耦合度降低,专注自身工能逻辑,观察者被动光接收更新,时间解耦,实时接受目标这更新状态

缺点:观察者模式虽然实现了对象间依赖的低耦合度,单却不能对事件通知进行细分管控,如筛选通知,指定主题事件通知

发布订阅模式

观察者和目标要相互知道

发布者和订阅者不用相互知道,通过第三方实现调度,属于经过解耦的观察者模式

下面是简单使用案例 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>发布订阅者模式</title>
</head>
<body>
  <script>
    class PublishSubscriber {
      constructor() {
        // 存放事件及其对应的回调函数
        this.subscribers = {};
      }

      // 订阅
      subscribe(type, callback) {
        if (!this.subscribers[type]) {
          this.subscribers[type] = [callback];
        }else{
          this.subscribers[type].push(callback);
        }
      }

      // 取消订阅
      unsubscribe(type, callback) {
        if (!this.subscribers[type]) {
          return;
        }

        // 取消订阅所有事件
        if (!callback) {
          this.subscribers[type] = [];
          return;
        }

        // 取消订阅单个事件
        this.subscribers[type] = this.subscribers[type].filter(item => item !== callback);
      }

      // 发布
      publish(type, data) {
        if (!this.subscribers[type]) {
          return;
        }
        this.subscribers[type].forEach(callback => callback(data))
      }
    }

    // 创建发布订阅者实例
    const publishSubscriber = new PublishSubscriber();

    function subscribe1(data) {
      console.log('subscribe1', data);
    }

    function subscribe2(data) {
      console.log('subscribe2', data);
    }

    function subscribe3(data) {
      console.log('subscribe3', data);
    }

    // 订阅事件
    publishSubscriber.subscribe('event1', subscribe1);
    publishSubscriber.subscribe('event1', subscribe2);
    publishSubscriber.subscribe('event2', subscribe3);

    // 发布事件
    publishSubscriber.publish('event1', 'Hello World'); // 输出 subscribe1 Hello World 和 subscribe2 Hello World
    publishSubscriber.publish('event2', 'Hello PublishSubscriber'); // 输出 subscribe3 Hello PublishSubscriber

  </script>
</body>
</html>

桥接模式

桥接模式:将抽象部分与它的实现部分分离,使他们都可以独立地变化

使用场景:一个类存在两个或多个独立变化的维度,且这两个维度都需要进行扩展

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>桥接模式</title>
</head>
<body>
  <script>
    // 定义抽象类,将抽象与具体的实现隔离开来
    class AbstractClass {
      constructor(animation) {
        this.animation = animation;
      }

      onShow() {
        this.animation.onShow()
      }

      onHide() {
        this.animation.onHide()
      }
    }

    const animations = {
      slide: {
        onShow() {
          console.log('Slide animation on show');
        },
        onHide() {
          console.log('Slide animation on hide');
        }
      },
      bounce: {
        onShow() {
          console.log('bounce animation on show');
        },
        onHide() {
          console.log('bounce animation on hide');
        }
      },
      fade: {
        onShow() {
          console.log('fade animation on show');
        },
        onHide() {
          console.log('fade animation on hide');
        }
      }
    }

    // slide-桥接单元
    const abstractClass = new AbstractClass(animations.slide);
    abstractClass.onShow();
    abstractClass.onHide();

    // bounce-桥接单元
    const abstractClass2 = new AbstractClass(animations.bounce);
    abstractClass2.onShow();
    abstractClass2.onHide();

    // fade-桥接单元
    const abstractClass3 = new AbstractClass(animations.fade);
    abstractClass3.onShow();
    abstractClass3.onHide();

  </script>
</body>
</html>

优点:把抽象与实现隔离开,有助于独立地管理各组成部分

缺点:每使用一个桥接元素都要增加一次函数调用,这对应用程序的性能会有些=一些负面影响---提高系统的复杂程度 

模板方法模式

模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类,通常在抽象父类中封装子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序,子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>模板模式</title>
</head>
<body>
  <script>
    // 抽象模板
    class TemplateClass {
      constructor(params){
        this.params = params;
      }

      // 确定好执行顺序
      init(){
        this.getData();
        this.render();
      }

      getData(){
        if(!this.params?.getData){
          throw new Error("必须实现getData方法");
          return
        }

        this.params.getData();
      }

      // 渲染
      render(){
        console.log("渲染那方法");
      }
    };

    // 传入参数
    const params = {
      getData(){
        console.log("获取数据6666");
      }
    }

    // 实例化
    const instance = new TemplateClass(params);
    instance.init();

    // 实例化
    const instance2 = new TemplateClass({
      getData(){
        console.log("获取数据222");
      }
    });
    instance2.init();

  </script>
</body>
</html>

总结:以上便是部分设计模式,个人感觉前端,可能会用到多一点的,其他设计模式感觉用得极少,当然,也可能是我接触的业务不复杂。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/648546.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 Ollama框架 下载和使用 Llama3 AI大模型的完整指南

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年5月24日20点59分 &#x1f004;️文章质量&#xff1a;96分 目录 &#x1f4a5;Ollama介绍 主要特点 主要优点 应…

流量控制的艺术:深入探索分布式限流策略与实践

前言 ​ 当资源成为瓶颈时&#xff0c;服务框架需要对消费者做限流&#xff0c;启动流控保护机制。流量控制有多种策略&#xff0c;比较常用的有&#xff1a;针对访问速率的静态流控、针对资源占用的动态流控、针对消费者并发连接数的连接控制和针对并行访问数的并发控制。 常…

【全开源】排队叫号系统源码(FastAdmin+GatewayWorker)

一款基于FastAdminGatewayWorker开发的多项目多场景排队叫号系统&#xff0c;支持大屏幕投屏&#xff0c;语音播报叫号&#xff0c;可用于餐厅排队取餐、美甲店排队取号、排队领取、排队就诊、排队办理业务等诸多场景&#xff0c;助你轻松应对各种排队取号叫号场景。 ​打造高…

【C语言】大小端字节序存储

引子 不知道你是否像我一样好奇过一个问题&#xff1a;为什么每当我们在调试查看内存窗口时&#xff0c;&#xff08;以int类型为例&#xff09;4个字节内容存储的顺序好像是倒着的。 比如下面这张图&#xff0c;十进制数2077转换为十六进制是0x81d&#xff0c;四个字节分别是…

Activiti7_使用

Activiti7_使用 一、Activiti7二、绘制工作流三、通过代码部署流程&#xff0c;再对流程进行实例化&#xff0c;完整运行一遍流程即可四、在springbooot中使用 一、Activiti7 为了实现后端的咨询流转功能&#xff0c;学习Activiti7&#xff0c;记录下使用的过程及遇到的问题 二…

【赠书第24期】Java项目开发实战(微视频版)

文章目录 前言 1 项目选择与需求分析 1.1 项目选择 1.2 需求分析 2 系统设计 2.1 系统架构设计 2.2 数据库设计 2.3 接口设计 3 编码实现 3.1 环境搭建 3.2 编码规范 3.3 编码实现 4 测试与部署 4.1 单元测试 4.2 系统测试 4.3 部署与上线 5 总结与展望 6 推…

行为神经科学研究利器 | FiPhA:光纤光度分析开源平台

摘要 意义&#xff1a;光纤光度法(FP)是现代行为神经科学中广泛使用的技术&#xff0c;利用基因编码荧光传感器监测清醒行为动物的神经活动和神经递质。然而&#xff0c;分析光度数据既费力又耗时。 目的&#xff1a;本研究提出了一种通用的FP分析应用——光纤光度分析(FiPhA…

n7. 图

1.定义 图的每个结构之间有着某种关系。 六度空间理论等&#xff1b; 1.1引子&#xff1a; 最短路径问题 1.2什么是图 表示一种多对多的关系 线性关系表示的是一对一的关系&#xff0c;树表示的是一对多的关系。其实线性表和树其实都可以认为是图的一种特殊的情况。包含&am…

一种改进的形态学滤波算法-以心电信号的基线校正和噪声抑制为例(MATLAB环境)

信号在釆集和传输过程中难免受到噪声源的干扰&#xff0c;反映非线性动力学行为的特征信息有可能被噪声所掩盖。尤其是在混沌振动信号噪声抑制方面&#xff0c;因为混沌信号的高度非线性及宽频特性&#xff0c;噪声和混沌信号往往具有重叠的带宽。传统的时域及频域降噪方法效果…

【设计模式】JAVA Design Patterns——Data Access Object(数据访问对象模式)

&#x1f50d;目的 对象为某种类型的数据库或其他持久性机制提供了抽象接口。 &#x1f50d;解释 真实世界例子 有一组客户数据需要持久化到数据库中。 我们需要整个额外的增删改查操作以便操作客户数据。 通俗描述 DAO是我们通过基本持久性机制提供的接口。 维基百科 在计算机…

第七届数字峰会即将举行!正宇软件助力数字赋能社会发展!

据人民日报消息&#xff0c;由国家发展改革委、国家数据局、国家网信办、科技部、国务院国资委、福建省人民政府共同主办&#xff0c;福州市人民政府和相关单位承办的“第七届数字中国建设峰会”&#xff0c;将于5月24日至25日在福建省福州市举行&#xff0c;主题是“释放数据要…

事务报错没有显示回滚导致DDL阻塞引发的问题

在业务开发过程中&#xff0c;显示的开启事务并且在事务处理过程中对不同的情况进行显示的COMMIT或ROLLBACK&#xff0c;这是一个完整数据库事务处理的闭环过程。 这种在应用开发逻辑层面去handle的事务执行的结果&#xff0c;既确保了事务操作的数据完整性&#xff0c;又遵循了…

C++:单例模型、强制类型转换

目录 特殊类的设计不能被拷贝的类实现一个类&#xff0c;只能在堆上实例化的对象实现一个类&#xff0c;只能在栈上实例化的对象不能被继承的类 单例模式饿汉模式懒汉模式饿汉模式与懒汉模式的对比饿汉优缺点懒汉优缺点懒汉模式简化版本&#xff08;C11&#xff09; 单例释放问…

速看!打造专属数字化能力模型的七大关键!

在数字化浪潮中&#xff0c;企业如何打造适应自身发展的数字化能力模型&#xff1f;这是许多企业面临的重要课题。今天&#xff0c;通过众多企业使用蚓链数字化生态解决方案实践总结&#xff0c;为大家分享至关重要的七大经验&#xff0c;助你开启数字化转型之旅&#xff01; 1…

栈和队列OJ题详解

一.有效的括号&#xff1a; 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 首先拿到这个题目&#xff0c;我的第一个思路是利用双指针来走&#xff0c;看看是不是匹配的 但是这种情况就把双指针的这个思路直接pass了&#xff0c;明明是匹配的括号&#xff0c;用双指…

protobuf学习

学习了下protobuf这个工具&#xff0c;可以用来序列化数据结构&#xff0c;而且效率很高&#xff0c;数据可以压缩的更小。 记录下&#xff0c;我这里主要在C#里使用&#xff0c;从NuGet程序包安装以下两个 安装好后可以在该程序目录找到 packages\Google.Protobuf.Tools.3.26.…

【计算机毕业设计】安卓054基于Android校园助手

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

离线强化学习基础知识之offline MBRL和MFRL

1 离线强化学习介绍 离线强化学习&#xff08;也称为批量强化学习或完全脱策略强化学习&#xff09;仅依赖于先前收集的数据集&#xff0c;无需进一步交互。它提供了一种利用先前收集的数据集的方法以自动学习决策策略。 离线强化学习可以被定义为 data-driven 形式的强化学习…

一篇文章讲透排序算法之堆排序

1.前言 在学习这篇文章之前&#xff0c;请大家先学习堆这一数据结构中堆的概念&#xff0c;向下调整算法&#xff0c;向下调整建堆。 有关堆的实现方式请参考&#xff1a;堆的实现 堆排序就是利用堆里面学习过的知识点进行排序&#xff0c;如何进行排序呢&#xff1f; 2.堆…

拓扑排序(概念 + 模板 + 例题)

概念 : 拓扑排序只有有向图有 &#xff0c; 可以判断图中是否有环 ; Kahn(卡恩)算法 过程 : 模板 : vector<int> a[N] , res ; int d[N] ; // 存放每个结点的入度 int n , x ;bool toposort() {queue<int> q;for(int i 1; i < n; i) if(d[i] 0) q.push…