Vue-Router源码实现详解
1.Hash模式
- hash就是url中#后面的部分
- hash改变时,页面不会从新加载,会触发hashchange事件,去监听hash改变,而且也会被记录到浏览器历史记录中
- vue-router的hash模式,主要是通过hashchange事件,根据hash值找到对应的组件去进行渲染(源码里会先判断浏览器支不支持popstate事件,如果支持,则是通过监听popstate事件,如果不支持,则监听hashchange事件)
hash模式页面跳转不刷新
根据http协议所示,url中hash改变请求是不会发送到服务端的,不管怎么location跳转,或者url上直接加上hash值回车,他都一样,请求是不会发送到服务端。
但是我们的系统里又引入了vue-router,其中hashchange这个事件监听到了hash的变化,从而触发了组件的更新,也就绘制出了相应的页面
2.History模式
- 通过history.pushstate去修改页面的地址
- 当history改变时,会触发popstate事件,所以可以通过监听popstate事件获取路由地址
- 根据当前路由地址找到对应组件渲染
history模式,切换路由时页面刷新
看一下正确的history模式下,首页刷新到显示的整体流程:
1.将这个完整的url发到服务器nginx2.ngix需要配置用这个uri在指给前端index.html(因为根本没有任何一个服务器提供了这个url路由,如果直接访问的话就是404,所以就要指回给前端,让前端自己去根据path来显示)
location / {root /usr/share/nginx/html/store;//项目存放的地址index index.html index.htm;try_files $uri $uri/ /index.html;//history模式下,需要配置它
}所以try_files $uri $uri/的意思就是,比如http://test.com/example先去查找单个文件example,如果example不存在,则去查找同名的文件目录/example/,如果再不存在,将进行重定向index.html(只有最后一个参数可以引起一个内部重定向)凡是404的路由,都会被重定向到index.html,这样就显示正确了3.此时nginx将这个请求指回了前端的index.html,index.html中开始加载js,js中已有vue-router的代码,vue-router自动触发了popstate这个事件,在这个事件回调中,绘制了这个path下对应的页面组件
3.实现vue-router
VueRouter需要做以下这些事情
- 实现VueRouter根据不同模式进行不同处理
- 根据传入的路由配置,生成对应的路由映射
- init函数监听hashchange或
popState
事件,浏览器记录改变时重新渲染router-view
组件 - 实现install静态方法
- 给Vue实例挂载router实例
- 注册全局组件和,
router-view
组件通过当前url找到对应组件进行渲染,并且url改变时,重新渲染组件,router-link则渲染为a标签 - 使用
Object.defineProperty
在Vue的原型上定义$router
和$route
属性
代码实现
首先在创建文件 router/index.js,router/my-router.js
在index中我就不做多讲解了,和平常vue-router一样的配置,只是不需要vue-router,我们自己实现
一下都会有详细的注释,每一项的作用
import Vue from 'vue'
import VueRouter from './my-router'; //实现router文件
import HomeView from '../views/HomeView.vue' //home文件
import about from '../views/AboutView.vue' //about文件Vue.use(VueRouter) //注意! 这是我们自己实现的文件,只是名字叫vuerouterconst routes = [ //这是我们的路由表{path: '/home',name: 'home',component: HomeView},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: about}
]const router = new VueRouter({ //创建实例mode: 'history', //模式 hash historyroutes //路由表给实例传过去
})export default router //最后到处router
接下来就是正式实现router
首先在my-router最顶部,声明一个变量
Vue
并将其初始化为null
//my-router.js
let Vue = null; //保存Vue的构造函数,在插件中要使用,保留在将来为Vue分配一个值,减少全局命名空间的污染
定义一个HistoryRoute的类,并在其构造函数中将
this.current
也初始化为null
用途:
- 状态管理:
this.current
用于存储当前路由的状态或信息,比如当前激活的路由路径、参数等。- 初始化:通过将其初始化为
null
,你可以在类的其他方法中根据需要更新this.current
的值,而不会受到未初始化属性的影响。- 灵活性:将
this.current
初始化为null
提供了灵活性,允许你在类的生命周期中的任何时刻为其分配一个具体的值。
//my-router.js
let Vue = null; //保存Vue的构造函数,在插件中要使用,保留在将来为Vue分配一个值,减少全局命名空间的污染
class HistoryRoute {constructor() {this.current = null;}
}
定义一个HistoryRoute的类
构造函数:
constructor(options)
:接收一个options
对象,该对象包含两个属性:mode
和routes
。mode
指定路由模式(hash
或history
),routes
是一个路由配置数组,每个路由配置对象包含path
和component
属性。this.changeMap
:将routes
数组转换为一个对象(Map),以path
为键,component
为值,方便后续根据路径快速查找对应的组件。Vue.util.defineReactive(this, "history", new HistoryRoute());
和this.history = new HistoryRoute();
:这里设置了history
属性,后者覆盖了前者。HistoryRoute
类用于管理当前路由状态。
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash"; //默认是hashthis.routes = options.routes || []; //默认为空// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}
}
添加init 方法:
- 根据
mode
的不同,为window
添加相应的事件监听器,以监听路由变化(hashchange
或popstate
事件),并更新history.current
属性。- 在页面加载时(
load
事件),也根据当前URL设置history.current
。
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash";this.routes = options.routes || [];// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();this.init();}init() {// 如果是hash模式if (this.mode === "hash") {location.hash ? void 0 : (location.hash = "/");window.addEventListener("load", () => {this.history.current = location.hash.slice(1);});window.addEventListener("hashchange", () => {console.log(location.hash.slice(1))this.history.current = location.hash.slice(1);});}// 如果是history模式if (this.mode === "history") {location.pathname ? void 0 : (location.pathname = "/");window.addEventListener("load", () => {console.log(location.pathname)this.history.current = location.pathname;});window.addEventListener("popstate", () => {console.log(location.pathname)this.history.current = location.pathname;});}}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}
Vue Router作为一个Vue插件,需要创建instll方法
- 设置Vue实例:
Vue = v;
:将传入的Vue实例赋值给全局变量Vue
,以便后续使用。- 全局混入:
- 使用
Vue.mixin
在Vue的生命周期钩子beforeCreate
中注入代码,用于处理路由相关的初始化。- 在根组件中,保存
_router
和_root
属性,分别指向VueRouter实例和根组件自身。- 对于非根组件,通过
$parent
找到根组件,从而访问到_router
和_root
。
VueRouter.install = (v) => {Vue = v;// vue-router还自带了两个组件,分别是router-link和router-view 在Vue.use(VueRouter)的时候加载的 所以我们要写在install里面// 新增代码Vue.mixin({beforeCreate() {// 如果是根组件if (this.$options && this.$options.router) {console.log(this.$options)// 将根组件挂载到_root上this._root = this;this._router = this.$options.router;// 拦截router-linkthis._router.mode === "history" && document.addEventListener("click", (e) => {if (e.target.className === "router-link-to") {// 阻止默认跳转事件e.preventDefault();// 手动改变url路径console.log(e.target.getAttribute("href"))history.pushState(null, "", e.target.getAttribute("href"));// 为current赋值url路径this._router.history.current = location.pathname;}});} else {// 如果是子组件// 将根组件挂载到子组件的_root上this._root = this.$parent && this.$parent._root;console.log(this._root);}},});
};
- 使用
Object.defineProperty
在Vue的原型上定义$router
和$route
属性,以便在任何Vue组件中通过this.$router
和this.$route
访问到VueRouter实例和当前路由信息。- 在install方法里面写就行
// 定义$routerObject.defineProperty(Vue.prototype, "$router", {get() {console.log(this);return this._root._router;},});// 定义$routeObject.defineProperty(Vue.prototype, "$route", {get() {return this._root._router.history.current;},});
定义全局组件(install方法里面添加)
router-link
:一个用于导航的<a>
标签组件,根据路由模式(hash
或history
)自动添加#
或正常路径。点击时,如果是history
模式,会阻止默认跳转行为,改为手动更新URL和路由状态。router-view
:一个用于渲染当前路由对应组件的占位符组件。它根据history.current
和routesMap
找到对应的组件,并使用Vue的render
函数渲染。
Vue.component("router-link", {props: {to: String,},render(h) {const mode = this._root._router.mode;let to = mode === "hash" ? "#" + this.to : this.to;return h("a",{attrs: {href: to,},// 新增代码class: "router-link-to",},this.$slots.default);},});Vue.component("router-view", {render(h) {const current = this._root._router.history.current;const routesMap = this._root._router.routesMap;return h(routesMap[current]);},});
完整代码
/** @Author: hukai huzhengen@gmail.com* @Date: 2023-06-08 17:49:08* @LastEditors: hukai huzhengen@gmail.com* @LastEditTime: 2024-10-24 11:08:00* @FilePath: \vue源码\vue-router-yuanma\src\router\my-router.js* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
let Vue = null;
class HistoryRoute {constructor() {this.current = null;}
}
// 因为router时new出来的 并且穿了一个对象 配置的路由由此可知router是一个class类
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash";this.routes = options.routes || [];// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();this.init();}init() {// 如果是hash模式if (this.mode === "hash") {location.hash ? void 0 : (location.hash = "/");window.addEventListener("load", () => {this.history.current = location.hash.slice(1);});window.addEventListener("hashchange", () => {console.log(location.hash.slice(1))this.history.current = location.hash.slice(1);});}// 如果是history模式if (this.mode === "history") {location.pathname ? void 0 : (location.pathname = "/");window.addEventListener("load", () => {console.log(location.pathname)this.history.current = location.pathname;});window.addEventListener("popstate", () => {console.log(location.pathname)this.history.current = location.pathname;});}}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}
}// 通过Vue.use 知道里面有一个install方法 并且第一个参数是Vue实例
VueRouter.install = (v) => {Vue = v;// vue-router还自带了两个组件,分别是router-link和router-view 在Vue.use(VueRouter)的时候加载的 所以我们要写在install里面// 新增代码Vue.mixin({beforeCreate() {// 如果是根组件if (this.$options && this.$options.router) {console.log(this.$options)// 将根组件挂载到_root上this._root = this;this._router = this.$options.router;// 拦截router-linkthis._router.mode === "history" && document.addEventListener("click", (e) => {if (e.target.className === "router-link-to") {// 阻止默认跳转事件e.preventDefault();// 手动改变url路径console.log(e.target.getAttribute("href"))history.pushState(null, "", e.target.getAttribute("href"));// 为current赋值url路径this._router.history.current = location.pathname;}});} else {// 如果是子组件// 将根组件挂载到子组件的_root上this._root = this.$parent && this.$parent._root;console.log(this._root);}},});// 定义$routerObject.defineProperty(Vue.prototype, "$router", {get() {console.log(this);return this._root._router;},});// 定义$routeObject.defineProperty(Vue.prototype, "$route", {get() {return this._root._router.history.current;},});Vue.component("router-link", {props: {to: String,},render(h) {const mode = this._root._router.mode;let to = mode === "hash" ? "#" + this.to : this.to;return h("a",{attrs: {href: to,},// 新增代码class: "router-link-to",},this.$slots.default);},});Vue.component("router-view", {render(h) {const current = this._root._router.history.current;const routesMap = this._root._router.routesMap;return h(routesMap[current]);},});
};
export default VueRouter;
最后在main.js中注册一下就行
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'Vue.config.productionTip = false
new Vue({router,store,render: h => h(App)
}).$mount('#app')
APP.vue中
<template><div id="app"><nav><router-link to="/home">Home</router-link> |<router-link to="/about">About</router-link></nav><router-view/></div>
</template>
<script>
export default {name:"Router",mounted(){console.log(this.$router)}
}
</script>
<style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}nav {padding: 30px;
}nav a {font-weight: bold;color: #2c3e50;
}nav a.router-link-exact-active {color: #42b983;
}
</style>
总结
这段代码通过定义VueRouter类和install方法,实现了一个简化版的Vue Router。它允许开发者定义路由规则,并在Vue应用中通过和组件实现页面导航和组件渲染。尽管这个实现相对简单,但它展示了Vue Router的核心概念和工作原理。
相关文章:
Vue-Router源码实现详解
1.Hash模式 hash就是url中#后面的部分hash改变时,页面不会从新加载,会触发hashchange事件,去监听hash改变,而且也会被记录到浏览器历史记录中vue-router的hash模式,主要是通过hashchange事件,根据hash值找…...

程序员节日的日期是10月24日程序员日
程序员节日的日期是10月24日。 这一天被称为中国程序员日或1024程序员节,由博客园、CSDN等自发组织设立,旨在纪念程序员对科技世界的贡献。 程序员节日的由来和意义 1024程序员节的由来可以追溯到2010年,最初由网友提出设立一个…...
联邦学习中的数据异构性
在联邦学习(Federated Learning, FL)领域中, 异构数据(Heterogeneous Data) 是指不同客户端所持有的本地数据在特征分布、类别分布、数量等方面存在差异的数据。这种数据的异质性是联邦学习面临的一大挑战,…...
Python小程序 - 替换文件内容
1. 写入文件c:\a.txt 1)共写入10行 2)每行内容 0123456789 # 1 ls 0123456789 ln 10 with open("c:/a.txt", w,encodingUTF-8) as f:for i in range(ln):f.write(ls\n)######################################### 2 ln 10…...

k8s备份恢复(velero)
velero简介 velero官网: https://velero.io/ velero-github: https://github.com/vmware-tanzu/velero velero的特性 备份可以按集群资源的子集,按命名空间、资源类型标签选择器进行过滤,从而为备份和恢复的内容提供高度的灵活…...

LED户外屏:面对复杂环境的七大挑战
户外LED显示屏作为现代城市广告和信息传播的重要媒介,其应用范围越来越广泛。然而,与室内环境相比,户外环境的复杂多变对LED显示屏提出了更高的要求。本文将探讨户外LED显示屏在设计和应用过程中必须考虑的七个关键问题。 1. 高分辨率 户外LE…...

LabVIEW自动化流动返混实验系统
随着工业自动化的不断发展,连续流动反应器在化工、医药等领域中的应用日益广泛。传统的流动返混实验操作复杂,数据记录和处理不便,基于LabVIEW的全自动流动返混实验系统能自动测定多釜反应器、单釜反应器和管式反应器的停留时间分布ÿ…...

【性能优化】安卓性能优化之CPU优化
【性能优化】安卓性能优化之CPU优化 CPU优化及常用工具原理与文章参考常用ADB常用原理、监控手段原理监控手段多线程并发解决耗时UI相关 常见场景排查CPU占用过高常用系统/开源分析工具AndroidStudio ProfilerSystraceBtracePerfettoTraceView和 Profile ANR相关ANR原理及常见场…...

springboot二手图书交易系统-计算机设计毕业源码88413
目 录 摘要 1 绪论 1.1研究背景 1.2研究意义 1.3论文结构与章节安排 2 二手图书交易系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 二手…...

解决ElasticSearch启动成功却无法在浏览器访问问题
目录 前言: 问题复现 : 解决问题: 1、修改sysctl.conf文件 2、在sysctl.conf文件增加这段东西 3、 然后保存退出,输入以下命令使其生效 结语: 前言: 这篇文章是小白我今天突然启动es,发现e…...

稀土有色包芯线良好的导电性
稀土有色包芯线是一种结合了稀土元素和有色金属(如铜、铝、镁等)的特殊线材。以下是对稀土有色包芯线的详细介绍: 一、组成与结构 芯线:由稀土元素和有色金属组成的合金制成。稀土元素(如镧、铈、镁等)的添加量在一定范围内,以确保合金性能的…...

SIP 业务举例之 Call Forwarding Unconditional(无条件呼转)
目录 1. Call Forwarding Unconditional 简介 2. RFC5359 的 Call Forwarding Unconditional 信令流程 PS:Dialog 建立条件 Dialog 会话完全建立 3. Call Forwarding Unconditional 过程总结 博主wx:yuanlai45_csdn 博主qq:2777137742 想要 深入学习 5GC IMS 等通信知识…...

基于stm32的esp8266的WIFI控制风扇实验
实验案例WIFI控制风扇 项目需求 电脑通过esp8266模块远程遥控风扇。 项目框图 风扇模块封装 #include "sys.h" #include "fan.h"void fan_init(void) {GPIO_InitTypeDef gpio_initstruct;//打开时钟…...
java中的ScheduledExecutorService介绍和使用案例
ScheduledExecutorService 是 Java 并发包 java.util.concurrent 中的一个接口,它提供了一种机制,允许我们安排一个任务在给定的延迟后运行,或者定期地执行。 主要特点 单次调度:可以安排任务在一定的延迟后执行一次。周期性调度…...

4天涨粉14万!这个AI小众赛道粉丝涨疯了吧?保姆级教程免费教会你!
测一下你的搞钱灵敏度有多高,看下面两张截图,有没有发现什么异常值? 发现了吧? 第一张是10月17号截的,第二张是21号,4天时间粉丝从2.8万飙到16.6万,涨粉14万! 这个号我几天之前就发…...

RK3588 技术分享 | 在Android系统中使用NPU实现Yolov5分类检测
随着人工智能和大数据时代的到来,传统嵌入式处理器中的CPU和GPU逐渐无法满足日益增长的深度学习需求。为了应对这一挑战,在一些高端处理器中,NPU(神经网络处理单元)也被集成到了处理器里。NPU的出现不仅减轻了CPU和GPU…...
itext 转换word文档转pdf
itext 转换word文档转pdf <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version><scope>compile</scope></dependency> <dependency><groupId>org.a…...

WSL-默认root登录
WSL-默认root登录 使用管理员,打开powershell PS C:\WINDOWS\system32> wsl -l 适用于 Linux 的 Windows 子系统分发版: Ubuntu-22.04 (默认) PS C:\WINDOWS\system32> ubuntu2204.exe config --default-user root PS C:\WINDOWS\system32>修改之后&…...
ASIO网络调试助手之四:浅谈QTcpServer性能
网络上有些质疑Qt Network模块性能的声音,本文将从理论和压测两个方面对比ASIO tcp server和QTcpServer在Windows上的性能,最后给出结论。 一.理论 QTcpServer在Windows上的实现是基于select模型,源码位置:5.12.6\Src\qtbase\src\network\socket\qnativesocketengine_win…...
快手 日常实习一面面经
官网投递,三天约面 线上面试 (60mins): 1. 自我介绍 2. 问实习 介绍自己做的业务上一段实习的公司框架和开源的 RPC 框架的区别负责的业务与核心业务的依赖关系场景题 -> 设计高并发下的负载均衡 3. 项目拷打 手写 双检锁…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...