第26章 模块

本章内容
 理解模块模式
 凑合的模块系统
 使用前 ES6 模块加载器
 使用 ES6 模块
现代 JavaScript 开发毋庸置疑会遇到代码量大和广泛使用第三方库的问题。解决这个问题的方案通常需要把代码拆分成很多部分,然后再通过某种方式将它们连接起来。

文章目录

  • 1 理解模块模式
    • 1.1 模块标识符
    • 1.2 模块依赖
    • 1.3 模块加载
    • 1.4 入口
    • 1.5 异步依赖
    • 1.6 动态依赖
    • 1.7 静态分析
    • 1.8 循环依赖
  • 2 凑合的模块系统
  • 3 使用 ES6 之前的模块加载器
    • 3.1 CommonJS
    • 3.2 异步模块定义
    • 3.3 通用模块定义
    • 3.4 模块加载器终将没落
  • 4 使用 ES6 模块
    • 4.1 模块标签及定义
    • 4.2 模块加载
    • 4.3 模块行为
    • 4.4 模块导出
    • 4.5 模块导入
    • 4.6 模块转移导出
    • 4.7 工作者模块
    • 4.8 向后兼容

1 理解模块模式

将代码拆分成独立的块,然后再把这些块连接起来可以通过模块模式来实现。这种模式背后的思想很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。不同的实现和特性让这些基本的概念变得有点复杂,但这个基本的思想是所有 JavaScript模块系统的基础。

1.1 模块标识符

模块系统本质上是键/值实体,其中每个模块都有个可用于引用它的标识符。这个标识符在模拟模块的系统中可能是字符串,在原生实现的模块系统中可能是模块文件的实际路径。
原生浏览器模块标识符必须提供实际 JavaScript 文件的路径。除了文件路径,Node.js 还会搜索 node_modules 目录,用标识符去匹配包含 index.js 的目录。

1.2 模块依赖

模块系统的核心是管理依赖。本地模块向模块系统声明一组外部模块(依赖),这些外部模块对于当前模块正常运行是必需的。模块系统检视这些依赖,进而保证这些外部模块能够被加载并在本地模块运行时初始化所有依赖。

1.3 模块加载

在浏览器中,加载模块涉及几个步骤。加载模块涉及执行其中的代码,但必须是在所有依赖都加载并执行之后。如果浏览器没有收到依赖模块的代码,则必须发送请求并等待网络返回。收到模块代码之后,浏览器必须确定刚收到的模块是否也有依赖。然后递归地评估并加载所有依赖,直到所有依赖模块都加载完成。

1.4 入口

相互依赖的模块必须指定一个模块作为入口(entry point),这也是代码执行的起点。这是理所当然的,因为 JavaScript 是顺序执行的,并且是单线程的,所以代码必须有执行的起点。
在这里插入图片描述
下面的脚本请求顺序能够满足依赖图的要求:

<script src="moduleE.js"></script> 
<script src="moduleD.js"></script> 
<script src="moduleC.js"></script> 
<script src="moduleB.js"></script> 
<script src="moduleA.js"></script>

这个策略存在一些性能和复杂性问题。为一个应用程序而按顺序加载五个 JavaScript 文件并不理想,并且手动管理正确的加载顺序也颇为棘手。

1.5 异步依赖

因为 JavaScript 可以异步执行,所以如果能按需加载就好了。换句话说,可以让 JavaScript 通知模块系统在必要时加载新模块,并在模块加载完成后提供回调。在代码层面,可以通过下面的伪代码来实现:

// 在模块 A 里面
load('moduleB').then(function(moduleB) { 
 moduleB.doStuff(); 
});

模块 A 的代码使用了 moduleB 标识符向模块系统请求加载模块 B,并以模块 B 作为参数调用回调。模块 B 可能已加载完成,也可能必须重新请求和初始化,但这里的代码并不关心。这些事情都交给了模块加载器去负责。这样有几个好处,其中之一就是性能,因为在页面加载时只需同步加载一个文件。

1.6 动态依赖

有些模块系统要求开发者在模块开始列出所有依赖,而有些模块系统则允许开发者在程序结构中动态添加依赖。动态添加的依赖有别于模块开头列出的常规依赖,这些依赖必须在模块执行前加载完毕。下面是动态依赖加载的例子:

if (loadCondition) { 
 require('./moduleA'); 
}

在这个模块中,是否加载 moduleA 是运行时确定的。加载 moduleA 时可能是阻塞的,也可能导致执行,且只有模块加载后才会继续。无论怎样,模块内部的代码在 moduleA 加载前都不能执行,因为moduleA 的存在是后续模块行为正确的关键。动态依赖可以支持更复杂的依赖关系,但代价是增加了对模块进行静态分析的难度。

1.7 静态分析

模块中包含的发送到浏览器的 JavaScript 代码经常会被静态分析,分析工具会检查代码结构并在不实际执行代码的情况下推断其行为。对静态分析友好的模块系统可以让模块打包系统更容易将代码处理为较少的文件。

更复杂的模块行为,例如动态依赖,会导致静态分析更困难。不同的模块系统和模块加载器具有不同层次的复杂度。至于模块的依赖,额外的复杂度会导致相关工具更难预测模块在执行时到底需要哪些依赖。

1.8 循环依赖

要构建一个没有循环依赖的 JavaScript 应用程序几乎是不可能的,因此包括 CommonJS、AMD 和ES6 在内的所有模块系统都支持循环依赖。在下面的模块代码中(其中使用了模块中立的伪代码),任何模块都可以作为入口模块,即使依赖图中存在循环依赖:

require('./moduleD'); 
require('./moduleB'); 
console.log('moduleA'); 
require('./moduleA'); 
require('./moduleC'); 
console.log('moduleB'); 
require('./moduleB'); 
require('./moduleD'); 
console.log('moduleC'); 
require('./moduleA'); 
require('./moduleC'); 
console.log('moduleD');

修改主模块中用到的模块会改变依赖加载顺序。如果 moduleA 最先加载,则会打印如下输出,这表示模块加载完成时的绝对顺序:

moduleB 
moduleC 
moduleD 
moduleA

2 凑合的模块系统

为按照模块模式提供必要的封装,ES6 之前的模块有时候会使用函数作用域和立即调用函数表达式(IIFE,Immediately Invoked Function Expression)将模块定义封装在匿名闭包中。模块定义是立即执行的,如下:

(function() { 
 // 私有 Foo 模块的代码
 console.log('bar'); 
})(); 
// bar

如果把这个模块的返回值赋给一个变量,那么实际上就为模块创建了命名空间:

var Foo = (function() { 
 console.log('bar'); 
})(); 
'bar'

为了暴露公共 API,模块 IIFE 会返回一个对象,其属性就是模块命名空间中的公共成员:

var Foo = (function() { 
 return { 
 bar: 'baz', 
 baz: function() { 
 console.log(this.bar); 
 } 
 }; 
})(); 
console.log(Foo.bar); // 'baz' 
Foo.baz(); // 'baz'

类似地,还有一种模式叫作“泄露模块模式”(revealing module pattern)。这种模式只返回一个对象,其属性是私有数据和成员的引用:

var Foo = (function() { 
 var bar = 'baz'; 
 var baz = function() { 
 console.log(bar); 
 }; 
 return { 
 bar: bar, 
 baz: baz 
 }; 
})(); 
console.log(Foo.bar); // 'baz' 
Foo.baz(); // 'baz'

在模块内部也可以定义模块,这样可以实现命名空间嵌套:

var Foo = (function() { 
 return { 
 bar: 'baz' 
 }; 
})(); 
Foo.baz = (function() { 
 return { 
 qux: function() { 
 console.log('baz'); 
 } 
 }; 
})(); 
console.log(Foo.bar); // 'baz' 
Foo.baz.qux(); // 'baz'

为了让模块正确使用外部的值,可以将它们作为参数传给 IIFE:

var globalBar = 'baz'; 
var Foo = (function(bar) { 
 return { 
 bar: bar, 
 baz: function() { 
 console.log(bar); 
 } 
 }; 
})(globalBar); 
console.log(Foo.bar); // 'baz' 
Foo.baz(); // 'baz

因为这里的模块实现其实就是在创建 JavaScript 对象的实例,所以完全可以在定义之后再扩展模块:

// 原始的 Foo 
var Foo = (function(bar) { 
 var bar = 'baz'; 
 return { 
 bar: bar 
 }; 
})();

// 扩展 Foo 
var Foo = (function(FooModule) { 
 FooModule.baz = function() { 
 console.log(FooModule.bar); 
 } 
 return FooModule; 
})(Foo); 
console.log(Foo.bar); // 'baz' 
Foo.baz(); // 'baz'

实际开发中并不建议手写模块系统,因为不够可靠。对这样的系统进行静态分析也是个问题。

3 使用 ES6 之前的模块加载器

在 ES6 原生支持模块之前,使用模块的 JavaScript 代码本质上是希望使用默认没有的语言特性。因此,必须按照符合某种规范的模块语法来编写代码,另外还需要单独的模块工具把这些模块语法与JavaScript 运行时连接起来。这里的模块语法和连接方式有不同的表现形式,通常需要在浏览器中额外加载库或者在构建时完成预处理。

3.1 CommonJS

CommonJS 规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组织,但也可用于定义在浏览器中使用的模块依赖。CommonJS 模块语法不能在浏览器中直接运行。

注意 一般认为,Node.js的模块系统使用了CommonJS规范,实际上并不完全正确。Node.js使用了轻微修改版本的 CommonJS,因为 Node.js 主要在服务器环境下使用,所以不需要考虑网络延迟问题。考虑到一致性,本节使用 Node.js 风格的模块定义语法。

CommonJS 模块定义需要使用 require()指定依赖,而使用 exports 对象定义自己的公共 API。下面的代码展示了简单的模块定义:

var moduleB = require('./moduleB'); 
module.exports = { 
 stuff: moduleB.doStuff(); 
};

moduleA 通过使用模块定义的相对路径来指定自己对 moduleB 的依赖。什么是“模块定义”,以及如何将字符串解析为模块,完全取决于模块系统的实现。比如在 Node.js 中,模块标识符可能指向文件,也可能指向包含 index.js 文件的目录。

无论一个模块在 require()中被引用多少次,模块永远是单例。在下面的例子中,moduleA 只会被打印一次。这是因为无论请求多少次,moduleA 只会被加载一次。

console.log('moduleA'); 
var a1 = require('./moduleA'); 
var a2 = require('./moduleA'); 
console.log(a1 === a2); // true

如果有模块想使用这个接口,可以像下面这样导入它:

var moduleA = require('./moduleA'); 
console.log(moduleA.stuff);

module.exports 对象非常灵活,有多种使用方式。如果只想导出一个实体,可以直接给 module. exports 赋值:

module.exports = 'foo';

这样,整个模块就导出一个字符串,可以像下面这样使用:

var moduleA = require('./moduleB'); 
console.log(moduleB); // 'foo'

导出多个值:

module.exports = { 
 a: 'A', 
 b: 'B' 
};

模块的一个主要用途是托管类定义:

class A {} 
module.exports = A; 
var A = require('./moduleA'); 
var a = new A();

也可以将类实例作为导出值:

class A {} 
module.exports = new A();

没有封装的 CommonJS 代码在浏览器中执行会创建全局变量。常见的解决方案是提前把模块文件打包好,把全局属性转换为原生JavaScript 结构,将模块代码封装在函数闭包中,最终只提供一个文件。为了以正确的顺序打包模块,需要事先生成全面的依赖图。

3.2 异步模块定义

CommonJS 以服务器端为目标环境,能够一次性把所有模块都加载到内存,而异步模块定义(AMD,Asynchronous Module Definition)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。

AMD 的一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖它们的模块。

AMD 模块实现的核心是用函数包装模块定义。这样可以防止声明全局变量,并允许加载器库控制何时加载模块。包装模块的函数是全局define 的参数,它是由 AMD 加载器库的实现定义的。

// ID 为'moduleA'的模块定义。moduleA 依赖 moduleB,
// moduleB 会异步加载
define('moduleA', ['moduleB'], function(moduleB) { 
 return { 
 stuff: moduleB.doStuff(); 
 }; 
});

3.3 通用模块定义

为了统一 CommonJS 和 AMD 生态系统,通用模块定义(UMD,Universal Module Definition)规范应运而生。UMD 可用于创建这两个系统都可以使用的模块代码。本质上,UMD 定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式(IIFE)中。虽然这种组合并不完美,但在很多场景下足以实现两个生态的共存。

(function (root, factory) { 
 if (typeof define === 'function' && define.amd) { 
 // AMD。注册为匿名模块
 define(['moduleB'], factory); 
 } else if (typeof module === 'object' && module.exports) { 
 // Node。不支持严格 CommonJS 
 // 但可以在 Node 这样支持 module.exports 的
 // 类 CommonJS 环境下使用
 module.exports = factory(require(' moduleB ')); 
 } else { 
 // 浏览器全局上下文(root 是 window)
 root.returnExports = factory(root. moduleB); 
 } 
}(this, function (moduleB) { 
 // 以某种方式使用 moduleB 
 // 将返回值作为模块的导出
 // 这个例子返回了一个对象
 // 但是模块也可以返回函数作为导出值
 return {}; 
}));

此模式有支持严格 CommonJS 和浏览器全局上下文的变体。不应该期望手写这个包装函数,它应该由构建工具自动生成。开发者只需专注于模块的内由容,而不必关心这些样板代码。

3.4 模块加载器终将没落

随着 ECMAScript 6 模块规范得到越来越广泛的支持,本节展示的模式最终会走向没落。尽管如此,为了了解为什么选择设计决策,了解 ES6 模块规范的由来仍是非常有用的。

4 使用 ES6 模块

ES6 最大的一个改进就是引入了模块规范。这个规范全方位简化了之前出现的模块加载器,原生浏览器支持意味着加载器及其他预处理都不再必要。从很多方面看,ES6 模块系统是集 AMD 和 CommonJS之大成者。

4.1 模块标签及定义

ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带有 type="module"属性的script标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中,也可以作为外部文件引入:

<script type="module"> 
 // 模块代码
</script> 
<script type="module" src="path/to/myModule.js"></script>

解析到<script type="module">标签后会立即下载模块文件,但执行会延迟到文档解析完成。<script type="module">在页面中出现的顺序就是它们执行的顺序。

<!-- 第二个执行 --> 
<script type="module"></script> 
<!-- 第三个执行 --> 
<script type="module"></script> 
<!-- 第一个执行 --> 
<script></script>

也可以给模块标签添加 async 属性。这样影响就是双重的:不仅模块执行顺序不再与script标签在页面中的顺序绑定,模块也不会等待文档完成解析才执行。不过,入口模块仍必须等待其依赖加载完成。

<script type="module">标签关联的 ES6 模块被认为是模块图中的入口模块。一个页面上有多少个入口模块没有限制,重复加载同一个模块也没有限制。同一个模块无论在一个页面中被加载多少次,也不管它是如何加载的,实际上都只会加载一次,如下面的代码所示:

<!-- moduleA 在这个页面上只会被加载一次 --> 
<script type="module"> 
 import './moduleA.js' 
<script> 
<script type="module"> 
 import './moduleA.js' 
<script> 
<script type="module" src="./moduleA.js"></script> 
<script type="module" src="./moduleA.js"></script>

4.2 模块加载

ECMAScript 6 模块的独特之处在于,既可以通过浏览器原生加载,也可以与第三方加载器和构建工具一起加载。

完全支持 ECMAScript 6 模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它们的内容,确定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。

4.3 模块行为

ECMAScript 6 模块借用了 CommonJS 和 AMD 的很多优秀特性。下面简单列举一些。
 模块代码只在加载后执行。
 模块只能加载一次。
 模块是单例。
 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。
 模块可以请求加载其他模块。
 支持循环依赖。

ES6 模块系统也增加了一些新行为。
 ES6 模块默认在严格模式下执行。
 ES6 模块不共享全局命名空间。
 模块顶级 this 的值是 undefined(常规脚本中是 window)。
 模块中的 var 声明不会添加到 window 对象。
 ES6 模块是异步加载和执行的。

4.4 模块导出

ES6 模块支持两种导出:命名导出和默认导出。不同的导出方式对应不同的导入方式。
export 关键字用于声明一个值为命名导出。导出语句必须在模块顶级,不能嵌套在某个块中:

// 允许
export ... 
// 不允许
if (condition) { 
 export ... 
}

导出时也可以提供别名,别名必须在 export 子句的大括号语法中指定。因此,声明值、导出值和为导出值提供别名不能在一行完成。在下面的例子中,导入这个模块的外部模块可以使用 myFoo 访问导出的值:

const foo = 'foo'; 
export { foo as myFoo };

默认导出(default export)就好像模块与被导出的值是一回事。默认导出使用 default 关键字将一个值声明为默认导出,每个模块只能有一个默认导出。重复的默认导出会导致 SyntaxError。

const foo = 'foo'; 
export default foo;
// 行内默认导出中不能出现变量声明
export default const foo = 'bar'; 
// 只有标识符可以出现在 export 子句中
export { 123 as foo } 
// 别名只能在 export 子句中出现
export const foo = 'foo' as myFoo;

4.5 模块导入

import 必须出现在模块的顶级

// 允许
import { foo } from './fooModule.js'; 
console.log(foo); // 'foo'

直接修改导出的值是不可能的,但可以修改导出对象的属性。同样,也不能给导出的集合添加或删除导出的属性。要修改导出的值,必须使用有内部变量和属性访问权限的导出方法。

import foo, * as Foo './foo.js'; 
foo = 'foo'; // 错误
Foo.foo = 'foo'; // 错误
foo.bar = 'bar'; // 允许

命名导出和默认导出的区别也反映在它们的导入上。命名导出可以使用*批量获取并赋值给保存导出集合的别名,而无须列出每个标识符:

const foo = 'foo', bar = 'bar', baz = 'baz'; 
export { foo, bar, baz } 
import * as Foo from './foo.js'; 
console.log(Foo.foo); // foo 
console.log(Foo.bar); // bar 
console.log(Foo.baz); // baz

4.6 模块转移导出

模块导入的值可以直接通过管道转移到导出。此时,也可以将默认导出转换为命名导出,或者相反。如果想把一个模块的所有命名导出集中在一块,可以像下面这样在 bar.js 中使用*导出:

export * from './foo.js';

4.7 工作者模块

ECMAScript 6 模块与 Worker 实例完全兼容。在实例化时,可以给工作者传入一个指向模块文件的路径,与传入常规脚本文件一样。Worker 构造函数接收第二个参数,用于说明传入的是模块文件。
下面是两种类型的 Worker 的实例化行为:

// 第二个参数默认为{ type: 'classic' } 
const scriptWorker = new Worker('scriptWorker.js'); 
const moduleWorker = new Worker('moduleWorker.js', { type: 'module' });

4.8 向后兼容

// 支持模块的浏览器会执行这段脚本
// 不支持模块的浏览器不会执行这段脚本
<script type="module" src="module.js"></script> 
// 支持模块的浏览器不会执行这段脚本
// 不支持模块的浏览器会执行这段脚本
<script nomodule src="script.js"></script>

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

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

相关文章

MySQL基础-----函数

目录 前言 一、字符串函数 演示 案例 二、数值函数 演示 案例 三、日期函数 演示 案例 四、流程函数 演示 案例 前言 本期我们就开始MySQL中函数的学习。函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中 已经…

Linux之线程概念

目录 一、细粒度划分 1、堆区细粒度划分 2、物理内存和可执行程序细粒度划分 3、虚拟地址到物理地址的转化 二、线程的概念 1、基本概念 2、线程的优点 3、线程的缺点 4、线程异常 5、线程用途 三、Linux下的进程和线程 一、细粒度划分 1、堆区细粒度划分 在语言…

php安装kafka

我的开发环境是php7.3 ,先来部署两个php扩展&#xff0c;php7.3目录下放librdkafka.dll,ext/php_rdkafka.dll&#xff0c;php.ini增加,[rdkafka] extension php_rdkafka.dll php7.3对应的扩展包链接&#xff1a;PECL :: Package :: rdkafka 看自己php版本对应在这里找PECL :: …

antd vue 选择控件的使用

Ant Design Vue-------Select 选择器 今天就讲讲Ant Design Vue下的控件----select 下拉框 结合项目中的需求&#xff0c;讲一下该控件如何配置&#xff0c;需求&#xff1a; &#xff08;1&#xff09;设置控件的宽度和高度 &#xff08;2&#xff09;绑定数据源 &#x…

IT人才职业发展路径

IT人才的职业发展路径通常是多样化的&#xff0c;因为IT领域涵盖了广泛的技术和职能角色。以下是一个典型的IT人才职业发展路径的梳理&#xff0c;但具体情况会根据个人兴趣、技能、经验和行业需求而有所不同&#xff1a; 入门级岗位&#xff1a; 技术支持工程师&#xff1a;提…

CVE-2024-25600 WordPress Bricks Builder RCE-漏洞分析研究

本次代码审计项目为PHP语言&#xff0c;我将继续以漏洞挖掘者的视角来分析漏洞的产生&#xff0c;调用与利用..... 前方高能&#xff0c;小伙伴们要真正仔细看咯..... 漏洞简介 CVE-2024-25600 是一个严重的&#xff08;CVSS 评分 9.8&#xff09;远程代码执行 (RCE) 漏洞&am…

SpringBoot初步学习

SpringBoot 今日目标&#xff1a; 掌握基于SpringBoot框架的程序开发步骤 熟练使用SpringBoot配置信息修改服务器配置 基于SpringBoot的完成SSM整合项目开发 1. SpringBoot简介 SpringBoot 其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。 1.1 SpringBoot快速…

ROS从入门到精通4-2:Docker安装ROS、可视化仿真与终端复用

目录 0 专栏介绍1 Docker安装ROS2 Docker可视化仿真2.1 显示配置2.2 启动容器 3 终端复用工具3.1 session操作3.2 window操作3.3 pane操作3.4 其他操作 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0c;掌握ROS底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS…

大数据开发-Hadoop之YARN介绍以及实战

文章目录 YARN基本介绍YARN的结构分析YARN中的调度器实际案例&#xff1a;YARN多资源队列的配置和使用 YARN基本介绍 实现Hadoop集群的资源共享不仅支持MapReduce&#xff0c;还支持Spark&#xff0c;Flink等计算 YARN的结构分析 主要复制集群资源的管理和调度&#xff0c;支…

【论文阅读】单词级文本攻击TAAD2.2

TAAD2.2论文概览 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 结果d. 论文及代码 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…

javaWebssh水利综合信息管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh水利综合信息管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCA…

BJFU|操作系统考试复习纲要(思维导图版)

纲要涵盖五个章节&#xff0c;每节一图。红色框部分为必考重点&#xff0c;建议认真复习。

数据结构与算法-归并排序

引言 在计算机科学的广阔领域中&#xff0c;数据结构与算法犹如两大基石&#xff0c;支撑着软件系统高效运行。本文将深度剖析一种基于分治策略的排序算法——归并排序&#xff0c;并探讨其原理、实现步骤以及优缺点&#xff0c;以期帮助读者深入理解这一高效的排序方法。 一、…

用开发CesiumJS模拟飞机飞行应用(一,基本功能)

本部分向您展示如何构建您的第一个 Cesium 应用程序&#xff0c;以可视化模拟从旧金山到哥本哈根的真实航班&#xff0c;并使用 FlightRadar24收集的雷达数据。您将学习如何&#xff1a; 在网络上设置并部署您的 Cesium 应用程序。 添加全球 3D 建筑物、地形和图像的基础图层。…

MySQL 学习笔记(基础篇 Day2)

「写在前面」 本文为黑马程序员 MySQL 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. MySQL 学习笔记&#xff08;基础篇 Day1&#xff09; 目录 3 函数 3.1 字符串函数 3…

PostgreSQL开发与实战(6.2)体系结构2

作者&#xff1a;太阳 二、逻辑架构 graph TD A[database] -->B(schema) B -->C[表] B -->D[视图] B -->E[触发器] C -->F[索引] tablespace 三、内存结构 Postgres内存结构主要分为 共享内存 与 本地内存 两部分。共享内存为所有的 background process提供内…

VI-ORBSLAM2编译运行

ORB-SLAM2编译运行 源码地址电脑配置环境配置编译轨迹保存为tum格式运行结果Euroc数据集 源码地址 源码链接&#xff1a;https://github.com/jingpang/LearnVIORB 电脑配置 Ubuntu 18.04 ROS Melodic GTSAM 4.0.2 CERES 1.14.0 pcl1.8vtk8.2.0opencv3.2.0 环境配置 之前…

简易版手淘视频播放器开发心路历程

需求背景 简单描述一下这个功能&#xff1a;在一个走马灯组件里面第一屏是一个视频&#xff0c;第二屏第三屏是图片&#xff0c;点击播放视频&#xff0c;播放过程中滚动窗口&#xff0c;视频 fixed 在窗口顶部&#xff0c;回到顶部&#xff0c;视频还原&#xff0c;两个窗口视…

Aigtek:功率放大器的选型技巧有哪些

功率放大器在电子设备中扮演着重要的角色&#xff0c;它能够将输入信号放大到所需要的功率水平。在选择功率放大器时&#xff0c;我们需要考虑多个因素&#xff0c;包括功率需求、频率响应、失真和稳定性等。本文将介绍功率放大器选型的一些技巧&#xff0c;帮助您找到适合的功…

基于OpenCV的图形分析辨认05(补充)

目录 一、前言 二、实验内容 三、实验过程 一、前言 编程语言&#xff1a;Python&#xff0c;编程软件&#xff1a;vscode或pycharm&#xff0c;必备的第三方库&#xff1a;OpenCV&#xff0c;numpy&#xff0c;matplotlib&#xff0c;os等等。 关于OpenCV&#xff0c;num…