JS学习之JavaScript模块化规范进化论
前言 JavaScript 语言诞生至今,模块规范化之路曲曲折折。
前言
JavaScript 语言诞生至今,模块规范化之路曲曲折折。社区先后出现了各种解决方案,包括 AMD、CMD、CommonJS 等,而后 ECMA 组织在 JavaScript 语言标准层面,增加了模块功能(因为该功能是在 ES2015 版本引入的,所以在下文中将之称为 ES6 module)。
今天我们就来聊聊,为什么会出现这些不同的模块规范,它们在所处的历史节点解决了哪些问题?
何谓模块化?
或根据功能、或根据数据、或根据业务,将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来。
全局变量
演示项目
为了更好的理解各个模块规范,先增加一个简单的项目用于演示。
Window
在刀耕火种的前端原始社会,JS 文件之间的通信基本完全依靠window
对象(借助 HTML、CSS 或后端等情况除外)。
-
config.js
var api = 'https://github.com/ronffy'; var config = {api: api, }
-
utils.js
var utils = {request() {console.log(window.config.api);} }
-
main.js
window.utils.request();
-
HTML
<!-- index.html --> <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta ><title>【深度全面】JS模块规范进化论</title> </head><body><!-- 所有 script 标签必须保证顺序正确,否则会依赖报错 --><script src="./js/config.js"></script><script src="./js/utils.js"></script><script src="./js/main.js"></script> </body></html>
IIFE
浏览器环境下,在全局作用域声明的变量都是全局变量。全局变量存在命名冲突、占用内存无法被回收、代码可读性低等诸多问题。
这时,IIFE(匿名立即执行函数)出现了:
用 IIFE 重构 config.js:
(function (root) {var api = 'https://github.com/ronffy';var config = {api: api,};root.config = config;
}(window));
IIFE 的出现,使全局变量的声明数量得到了有效的控制。
命名空间
依靠window
对象承载数据的方式是 “不可靠” 的,如window.config.api
,如果window.config
不存在,则window.config.api
就会报错,所以为了避免这样的错误,代码里会大量的充斥var api = window.config && window.config.api;
这样的代码。
这时,namespace
登场了,简约版本的namespace
函数的实现(只为演示,不要用于生产):
function namespace(tpl, value) {return tpl.split('.').reduce((pre, curr, i) => {return (pre[curr] = i === tpl.split('.').length - 1? (value || pre[curr]): (pre[curr] || {}))}, window);
}
用namespace
设置window.app.a.b
的值:
namespace('app.a.b', 3); // window.app.a.b 值为 3
用namespace
获取window.app.a.b
的值:
var b = namespace('app.a.b'); // b 的值为 3
var d = namespace('app.a.c.d'); // d 的值为 undefined
app.a.c
值为undefined
,但因为使用了namespace
, 所以app.a.c.d
不会报错,变量d
的值为undefined
。
AMD
随着前端业务增重,代码越来越复杂,靠全局变量通信的方式开始捉襟见肘,前端急需一种更清晰、更简单的处理代码依赖的方式,将 JS 模块化的实现及规范陆续出现,其中被应用较广的模块规范有 AMD。
面对一种模块化方案,我们首先要了解的是:1. 如何导出接口;2. 如何导入接口。
AMD
异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。
本规范只定义了一个函数define
,它是全局变量。
/*** @param {string} id 模块名称* @param {string[]} dependencies 模块所依赖模块的数组* @param {function} factory 模块初始化要执行的函数或对象* @return {any} 模块导出的接口*/
function define(id?, dependencies?, factory): any
RequireJS
AMD 是一种异步模块规范,RequireJS 是 AMD 规范的实现。
接下来,我们用 RequireJS 重构上面的项目。
在原项目 js 文件夹下增加 require.js 文件:
-
define(function() {var api = 'https://github.com/ronffy';var config = {api: api,};return config;});
-
utils.js
define(['./config'], function(config) {var utils = {request() {console.log(config.api);}};return utils; });
-
require(['./utils'], function(utils) {utils.request();});
-
html
<!-- index.html --> <!-- ...省略其他 --> <body><script data-main="./js/main" src="./js/require.js"></script> </body> </html>
可以看到,使用 RequireJS 后,每个文件都可以作为一个模块来管理,通信方式也是以模块的形式,这样既可以清晰的管理模块依赖,又可以避免声明全局变量。
更多 AMD 介绍,请查看文档。
更多 RequireJS 介绍,请查看文档。
特别说明:
先有 RequireJS,后有 AMD 规范,随着 RequireJS 的推广和普及,AMD 规范才被创建出来。
CommonJS
前面说了, AMD 主要用于浏览器端,随着 node 诞生,服务器端的模块规范 CommonJS 被创建出来。
还是以上面介绍到的 config.js、utils.js、main.js 为例,看看 CommonJS 的写法:
-
config.js
var api = 'https://github.com/ronffy'; var config = {api: api, }; module.exports = config;
-
utils.js
var config = require('./config'); var utils = {request() {console.log(config.api);} }; module.exports = utils;
-
main.js
var utils = require('./utils'); utils.request(); console.log(global.api)
执行node main.js
,https://github.com/ronffy
被打印了出来。
在 main.js 中打印global.api
,打印结果是undefined
。node 用global
管理全局变量,与浏览器的window
类似。与浏览器不同的是,浏览器中顶层作用域是全局作用域,在顶层作用域中声明的变量都是全局变量,而 node 中顶层作用域不是全局作用域,所以在顶层作用域中声明的变量非全局变量。
module.exports 和 exports
我们在看 node 代码时,应该会发现,关于接口导出,有的地方使用module.exports
,而有的地方使用exports
,这两个有什么区别呢?
CommonJS 规范仅定义了exports
,但exports
存在一些问题(下面会说到),所以module.exports
被创造了出来,它被称为 CommonJS2 。
每一个文件都是一个模块,每个模块都有一个module
对象,这个module
对象的exports
属性用来导出接口,外部模块导入当前模块时,使用的也是module
对象,这些都是 node 基于 CommonJS2 规范做的处理。
// a.js
var s = 'i am ronffy'
module.exports = s;
console.log(module);
执行node a.js
,看看打印的module
对象:
{exports: 'i am ronffy',id: '.', // 模块idfilename: '/Users/apple/Desktop/a.js', // 文件路径名称loaded: false, // 模块是否加载完成parent: null, // 父级模块children: [], // 子级模块paths: [ /* ... */ ], // 执行 node a.js 后 node 搜索模块的路径
}
其他模块导入该模块时:
// b.js
var a = require('./a.js'); // a --> i am ronffy
当在 a.js 里这样写时:
// a.js
var s = 'i am ronffy'
exports = s;
a.js 模块的module.exports
是一个空对象。
// b.js
var a = require('./a.js'); // a --> {}
把module.exports
和exports
放到 “明面” 上来写,可能就更清楚了:
var module = {exports: {}
}
var exports = module.exports;
console.log(module.exports === exports); // truevar s = 'i am ronffy'
exports = s; // module.exports 不受影响
console.log(module.exports === exports); // false
模块初始化时,exports
和module.exports
指向同一块内存,exports
被重新赋值后,就切断了跟原内存地址的关系。
所以,exports
要这样使用:
// a.js
exports.s = 'i am ronffy';// b.js
var a = require('./a.js');
console.log(a.s); // i am ronffy
CommonJS 和 CommonJS2 经常被混淆概念,一般大家经常提到的 CommonJS 其实是指 CommonJS2,本文也是如此,不过不管怎样,大家知晓它们的区别和如何应用就好。
CommonJS 与 AMD
CommonJS 和 AMD 都是运行时加载,换言之:都是在运行时确定模块之间的依赖关系。
二者有何不同点:
- CommonJS 是服务器端模块规范,AMD 是浏览器端模块规范。
- CommonJS 加载模块是同步的,即执行
var a = require('./a.js');
时,在 a.js 文件加载完成后,才执行后面的代码。AMD 加载模块是异步的,所有依赖加载完成后以回调函数的形式执行代码。 - [如下代码]
fs
和chalk
都是模块,不同的是,fs
是 node 内置模块,chalk
是一个 npm 包。这两种情况在 CommonJS 中才有,AMD 不支持。
var fs = require('fs');
var chalk = require('chalk');
ES6 module
AMD 是在原有 JS 语法的基础上二次封装的一些方法来解决模块化的方案,ES6 module(在很多地方被简写为 ESM)是语言层面的规范,ES6 module 旨在为浏览器和服务器提供通用的模块解决方案。长远来看,未来无论是基于 JS 的 WEB 端,还是基于 node 的服务器端或桌面应用,模块规范都会统一使用 ES6 module。
兼容性
目前,无论是浏览器端还是 node ,都没有完全原生支持 ES6 module,如果使用 ES6 module ,可借助 babel等编译器。本文只讨论 ES6 module 语法,故不对 babel 或 typescript 等可编译 ES6 的方式展开讨论。
导出接口
CommonJS 中顶层作用域不是全局作用域,同样的,ES6 module 中,一个文件就是一个模块,文件的顶层作用域也不是全局作用域。导出接口使用export
关键字,导入接口使用import
关键字。
export
导出接口有以下方式:
-
方式 1
export const prefix = 'https://github.com'; export const api = `${prefix}/ronffy`;
-
方式 2
const prefix = 'https://github.com'; const api = `${prefix}/ronffy`; export {prefix,api, }
方式 1 和方式 2 只是写法不同,结果是一样的,都是把prefix
和api
分别导出。
-
方式 3(默认导出)
// foo.js export default function foo() {}// 等同于: function foo() {} export {foo as default }
export default
用来导出模块默认的接口,它等同于导出一个名为default
的接口。配合export
使用的as
关键字用来在导出接口时为接口重命名。
-
方式 4(先导入再导出简写)
export { api } from './config.js'; // 等同于: import { api } from './config.js'; export {api }
如果需要在一个模块中先导入一个接口,再导出,可以使用export ... from 'module'
这样的简便写法。
导入模块接口
ES6 module 使用import
导入模块接口。
导出接口的模块代码 1:
// config.js
const prefix = 'https://github.com';
const api = `${prefix}/ronffy`;
export {prefix,api,
}
接口已经导出,如何导入呢:
-
方式 1
import { api } from './config.js'; // or // 配合`import`使用的`as`关键字用来为导入的接口重命名。 import { api as myApi } from './config.js';
-
方式 2(整体导入)
import * as config from './config.js'; const api = config.api;
将 config.js 模块导出的所有接口都挂载在config
对象上。
-
方式 3(默认导出的导入)
// foo.js export const conut = 0; export default function myFoo() {} // index.js // 默认导入的接口此处刻意命名为cusFoo,旨在说明该命名可完全自定义。 import cusFoo, { count } from './foo.js';// 等同于: import { default as cusFoo, count } from './foo.js';
export default
导出的接口,可以使用import name from 'module'
导入。这种方式,使导入默认接口很便捷。
- 方式 4(整体加载)
这样会加载整个 config.js 模块,但未导入该模块的任何接口。
- 方式 5(动态加载模块)
上面介绍了 ES6 module 各种导入接口的方式,但有一种场景未被涵盖:动态加载模块。比如用户点击某个按钮后才弹出弹窗,弹窗里功能涉及的模块的代码量比较重,所以这些相关模块如果在页面初始化时就加载,实在浪费资源,import()
可以解决这个问题,从语言层面实现模块代码的按需加载。
ES6 module 在处理以上几种导入模块接口的方式时都是编译时处理,所以import
和export
命令只能用在模块的顶层,以下方式都会报错:
// 报错
if (/* ... */) {import { api } from './config.js';
}// 报错
function foo() {import { api } from './config.js';
}// 报错
const modulePath = './utils' + '/api.js';
import modulePath;
使用import()
实现按需加载:
function foo() {import('./config.js').then(({ api }) => {});
}const modulePath = './utils' + '/api.js';
import(modulePath);
CommonJS 和 ES6 module
CommonJS 和 AMD 是运行时加载,在运行时确定模块的依赖关系。
ES6 module 是在编译时(import()
是运行时加载)处理模块依赖关系,。
CommonJS
CommonJS 在导入模块时,会加载该模块,所谓 “CommonJS 是运行时加载”,正因代码在运行完成后生成module.exports
的缘故。当然,CommonJS 对模块做了缓存处理,某个模块即使被多次多处导入,也只加载一次。
// o.js
let num = 0;
function getNum() {return num;
}
function setNum(n) {num = n;
}
console.log('o init');
module.exports = {num,getNum,setNum,
}
// a.js
const o = require('./o.js');
o.setNum(1);
// b.js
const o = require('./o.js');
// 注意:此处只是演示,项目里不要这样修改模块
o.num = 2;
// main.js
const o = require('./o.js');require('./a.js');
console.log('a o.num:', o.num);require('./b.js');
console.log('b o.num:', o.num);
console.log('b o.getNum:', o.getNum());
命令行执行node main.js
,打印结果如下:
o init
模块即使被其他多个模块导入,也只会加载一次,并且在代码运行完成后将接口赋值到module.exports
属性上。a o.num: 0
模块在加载完成后,模块内部的变量变化不会反应到模块的module.exports
。b o.num: 2
对导入模块的直接修改会反应到该模块的module.exports
。b o.getNum: 1
模块在加载完成后即形成一个闭包。
ES6 module
// o.js
let num = 0;
function getNum() {return num;
}
function setNum(n) {num = n;
}
console.log('o init');
export {num,getNum,setNum,
}
// main.js
import { num, getNum, setNum } from './o.js';console.log('o.num:', num);
setNum(1);console.log('o.num:', num);
console.log('o.getNum:', getNum());
我们增加一个 index.js 用于在 node 端支持 ES6 module:
// index.js
require("@babel/register")({presets: ["@babel/preset-env"]
});module.exports = require('./main.js')
命令行执行npm install @babel/core @babel/register @babel/preset-env -D
安装 ES6 相关 npm 包。
命令行执行node index.js
,打印结果如下:
o init
模块即使被其他多个模块导入,也只会加载一次。o.num: 0
o.num: 1
编译时确定模块依赖的 ES6 module,通过import
导入的接口只是值的引用,所以num
才会有两次不同打印结果。o.getNum: 1
对于打印结果 3,知晓其结果,在项目中注意这一点就好。这块会涉及到 “Module Records(模块记录)”、“module instance(模快实例)” “linking(链接)” 等诸多概念和原理
ES6 module 是编译时加载(或叫做 “静态加载”),利用这一点,可以对代码做很多之前无法完成的优化:
- 在开发阶段就可以做导入和导出模块相关的代码检查。
- 结合 Webpack、Babel 等工具可以在打包阶段移除上下文中未引用的代码(dead-code),这种技术被称作 “tree shaking”,可以极大的减小代码体积、缩短程序运行时间、提升程序性能。
后记
大家在日常开发中都在使用 CommonJS 和 ES6 module,但很多人只知其然而不知其所以然,甚至很多人对 AMD、CMD、IIFE 等概览还比较陌生,希望通过本篇文章,大家对 JS 模块化之路能够有清晰完整的认识。
相关文章:

JS学习之JavaScript模块化规范进化论
前言 JavaScript 语言诞生至今,模块规范化之路曲曲折折。 前言 JavaScript 语言诞生至今,模块规范化之路曲曲折折。社区先后出现了各种解决方案,包括 AMD、CMD、CommonJS 等,而后 ECMA 组织在 JavaScript 语言标准层面࿰…...

亚博microros小车-原生ubuntu支持系列:7-脸部检测
背景知识 官网介绍: Face Mesh - mediapipe mpFaceMesh.FaceMesh() 类的参数有:self.staticMode, self.maxFaces, self.minDetectionCon, self.minTrackCon staticMode:是否将每帧图像作为静态图像处理。如果为 True,每帧都会进行人脸检测…...

第二届国赛铁三wp
第二届国赛 缺东西去我blog找👇 第二届长城杯/铁三 | DDLS BLOG web Safe_Proxy 源码题目 from flask import Flask, request, render_template_stringimport socketimport threadingimport htmlapp Flask(__name__)app.route(/, methods"GET"])de…...

缓存商品、购物车(day07)
缓存菜品 问题说明 问题说明:用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。 结果: 系统响应慢、用户体验差 实现思路 通过Redis来缓存菜品数据,减少数据库查询…...

4【编程语言的鄙视链原因解析】
在编程行业中,是存在鄙视链的,技术越好的圈子越不明显,技术越差的圈子越明显,很多时候为新人营造了错误的观点,我们来针对此类现象为新人们讲解原因 ①心里落差:比如你是学厨师的 你经过过年努力练…...

美团一面面经
第一个问题:介绍一下最近做的项目 第二个问题:我对你项目有个地方比较感兴趣啊。就是你用的那个二级缓存,你的吞吐量有多大啊,为什么需要使用二级缓存? 答: 在二级缓存策略下,笔记详情接口的吞…...

什么是报文的大端和小端,有没有什么记忆口诀?
在计算机科学中,**大端(Big-Endian)和小端(Little-Endian)**是两种不同的字节序(即多字节数据在内存中的存储顺序)。理解这两种字节序对于网络通信、文件格式解析以及跨平台编程等非常重要。 1…...

Spring中BeanFactory和ApplicationContext的区别
目录 一、功能范围 二、Bean的加载时机 三、国际化支持 四、事件发布 五、资源加载 六、使用场景说明 在Spring框架中,BeanFactory和ApplicationContext是两种常见的容器实现方式,它们在功能和使用场景上存在一些显著的差异。本文将详细解析这两种容…...

期货行业专题|基于超融合实现 IT 基础设施现代化与国产化转型实践合集
SmartX 期货行业重要进展 帮助近 60 家期货用户部署 730 超融合节点,含 230 信创节点。 深入 5 大应用场景: 核心生产资源池 主席灾备资源池 信创云资源池 云原生存储与容器资源池 分布式存储资源池 更多超融合金融核心生产业务场景实践…...

AI新玩法:Flux.1图像生成结合内网穿透远程生图的解决方案
文章目录 前言1. 本地部署ComfyUI2. 下载 Flux.1 模型3. 下载CLIP模型4. 下载 VAE 模型5. 演示文生图6. 公网使用 Flux.1 大模型6.1 创建远程连接公网地址 7. 固定远程访问公网地址 前言 在这个AI技术日新月异的时代,图像生成模型已经成为了创意工作者和开发者手中…...

Jenkins-pipeline Jenkinsfile说明
一. 简介: Jenkinsfile 是一个文本文件,通常保存在项目的源代码仓库中,用于定义 Jenkins Pipeline 的行为。使用 Jenkinsfile 可以使 CI/CD 流程版本化,并且易于共享和审核。 二. 关于jenkinsfile: jenkins的pipeline…...

vue3中为什么引入setup,引入setup是为了解决什么问题,setup的执行时机是什么?返回值是什么
在 Vue 3 中,引入 setup 函数是为了提供一种更加简洁、灵活、逻辑分离和可维护的方式来组织组件的逻辑。setup 使得 Vue 3 在构建应用时,能够更加有效地支持组合式 API(Composition API),解决了 Vue 2 中一些组件逻辑组…...

Ubuntu 安装 docker 配置环境及其常用命令
Docker 安装与配置指南 本文介绍如何在 Ubuntu 系统上安装 Docker,解决权限问题,配置 Docker Compose,代理端口转发,容器内部代理问题等并进行相关的优化设置。参考官方文档:Docker 官方安装指南 一、安装 Docker 1…...

自动化01
测试用例的万能公式:功能测试界面测试性能测试易用性测试安全性测试兼容性测试 自动化的主要目的就是用来进行回归测试 新产品--第一个版本 (具备丰富的功能),将产品的整体进行测试,人工创造一个自动化测试用例,在n个版本的时候…...

音频入门(二):音频数据增强
本文介绍了一些常见的音频数据增强方法,并给出了代码实现。 目录 一、简介 二、代码 1. 安装必要的库 2. 代码 3. 各函数的介绍 4. 使用方法 参考: 一、简介 音频数据增强是机器学习和深度学习领域中用于改善模型性能和泛化能力的技术。 使用数据…...

MySQL管理事务处理
目录 1、事务处理是什么 2、控制事务处理 (1)事务的开始和结束 (2)回滚事务 (3)使用COMMIT (4)使用保留点 (5)结合存储过程的完整事务例子 3、小结 …...

MySQL数值型函数详解
简介 本文主要讲解MySQL数值型函数,包括:ROUND、RAND、ABS、MOD、TRUNCATE、CEIL、CEILING、FLOOR、POW、POWER、SQRT、LOG、LOG2、LOG10、SIGN、PI。 本文所有示例中,双横杠左边为执行的SQL语句,右边为执行语句的返回值。 ROU…...

54.DataGrid数据框图 C#例子 WPF例子
首先是绑定一个属性,属性名称无所谓。到时候看属性设置的啥,可能要改。 <DataGrid ItemsSource"{Binding Index_instance}"/> 然后创建INotifyPropertyChanged的类,并把相关固定的代码粘贴上去。 然后把这个目录类建好&am…...

总结6..
背包问题的解决过程 在解决问题之前,为描述方便,首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同…...

复位信号的同步与释放(同步复位、异步复位、异步复位同步释放)
文章目录 背景前言一、复位信号的同步与释放1.1 同步复位1.1.1 综述1.1.2 优缺点 1.2 recovery time和removal time1.3 异步复位1.3.1 综述1.3.2 优缺点 1.4 同步复位 与 异步复位1.5 异步复位、同步释放1.5.1 总述1.5.2 机理1.5.3 复位网络 二、思考与补充2.1 复…...

Gartner发布2025年网络治理、风险与合规战略路线图
新型网络风险和合规义务,日益成为网络治理、风险与合规实践面临的问题。安全和风险管理领导者可以参考本文,实现从被动、专注于合规的方法到主动、进一步自动化方法的转型。 主要发现 不断变化的监管环境和不断扩大的攻击面,使企业机构难以实…...

基于STM32的智能空气质量监测与净化系统设计
目录 引言系统设计 硬件设计软件设计 系统功能模块 空气质量检测模块自动净化模块数据显示与用户交互模块远程监控与数据上传模块 控制算法 空气质量检测与判断算法净化设备控制算法数据记录与远程反馈算法 代码实现 空气质量检测与显示代码自动净化与调节代码数据上传与远程控…...

人工智能之数学基础:线性代数中的线性相关和线性无关
本文重点 在线性代数的广阔领域中,线性相关与线性无关是两个核心概念,它们对于理解向量空间、矩阵运算、线性方程组以及人工智能等问题具有至关重要的作用。 定义与直观理解 当存在一组不全为0的数x1,x2,...,xn使得上式成立的时候,那么此时我们可以说向量组a1,a2...,an…...

08 工欲善其事必先利其器—常用类
1 字符串相关 1.1 String 所属包:java.lang 代表不可变的字符序列 注意:Java中,String是一个final类 1)创建字符串方式 String a "hello"; // 开辟内存空间 String b new String("hello"); String d…...

Redis实战-初识Redis
初识Redis 1、Redis简介2、 Redis数据结构简介3、 Redis命令3.1 字符串3.2 列表3.3 集合3.4 散列3.5 有序集合3.6 发布与订阅3.7 其他命令3.7.1 排序3.7.2 过期时间 如有侵权,请联系~ 如有错误,也欢迎批评指正~ 本篇文章大部分是来…...

spring boot中实现手动分页
手动分页 UserMapper.xml <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace"cn.m…...

【优选算法】5----有效三角形个数
又是一篇算法题,今天早上刚做的热乎的~ 其实我是想写博客但不知道写些什么(就水一下啦) -------------------------------------begin----------------------------------------- 题目解析: 这道题的题目算是最近几道算法题里面题目最短的&a…...

C++打字模拟
改进于 文宇炽筱_潜水 c版的打字效果_c自动打字-CSDN博客https://blog.csdn.net/2401_84159494/article/details/141023898?ops_request_misc%257B%2522request%255Fid%2522%253A%25227f97863ddc9d1b2ae9526f45765b1744%2522%252C%2522scm%2522%253A%252220140713.1301023…...

最新版pycharm如何配置conda环境
首先在conda prompt里创建虚拟环境,比如 conda create --prefix E:/projects/myenv python3.8然后激活 conda activate E:/projects/myenv往里面安装点自己的包,比如 conda install pytorch1.7.1 torchvision0.8.2 -c pytorch打开pycharm 注意&#x…...

UML-对象图(Object Diagram)
一、定义 UML对象图用于描述系统中对象的状态和相互关系,是类图的一个实例化版本,主要展示了类图中定义的关系在特定时间点的实际体现。它帮助开发者在设计阶段理解对象之间的实际关系、属性值和状态,从而支持系统设计的准确性与有效性。 二、组成要素 UML对象图主要由以…...