HTML5 Web Worker之性能优化
描述
由于 JavaScript 是单线程的,当执行比较耗时的任务时,就会阻塞主线程并导致页面无法响应,这就是 Web Workers 发挥作用的地方。它允许在一个单独的线程(称为工作线程)中执行耗时的任务。这使得 JavaScript 代码可以在后台执行,而不会阻塞主线程并导致页面无响应。
Web Worker 是一个作为后台线程运行的脚本,具有自己的引擎实例和事件循环。它与主执行线程并行运行,并且不会阻塞事件循环。
主线程(或工作线程本身)可以启动任意数量的工作线程。生成 worker 脚本:
-
主线程(或另一个工作线程)向新工作线程发送一条消息,其中包含所有必要的数据。
-
工作线程中的事件处理程序执行并开始处理数据。
-
完成(或失败)时,工作线程将一条带有计算结果的消息发送回主线程。
-
主线程中的事件处理程序执行、解析传入结果并运行必要的操作(例如显示值)。
重要性
Web Workers 为开发人员提供了在 Web 上实现多线程的方式,这对于构建高性能的 Web 应用至关重要。通过将耗时的任务在后台独立于主线程中执行,Web Workers 提高了网页的整体响应性,并使用户体验更加流畅。以下是 Web Workers 在 Web 多线程中的重要性和好处:
通过资源利用率
通过允许耗时任务在后台执行,Web Workers 更有效地利用系统资源,实现更快速和高效的数据处理,并提高整体性能。这对于涉及大量数据处理或图像操作的 Web 应用尤为重要,因为 Web Workers 可以在不影响用户界面的情况下执行这些任务。
增加稳定性和可靠性
通过将耗时任务隔离到单独的 worker 线程中,Web Workers 帮助防止在主线程上执行大量代码时发生崩溃和错误。这使得开发人员更容易编写稳定可靠的 Web 应用,减少用户的烦恼和数据丢失的可能性。
增强安全性
Web Workers 在与主线程分离的隔离环境中运行,这有助于提高 Web 应用的安全性。此隔离防止恶意代码访问或修改主线程或其他 Web Workers 中的数据,降低数据泄露或其他安全漏洞的风险。
更好的资源利用率
Web Workers 可以通过将耗时计算放到后台,使主线程用于处理用户输入和其他任务来帮助提高资源利用率。这有助于提高系统的整体性能并减少崩溃或错误的可能性。此外,通过利用多个 CPU 核心,Web Workers 可以更有效地利用系统资源,实现更快速和高效的数据处理。
Web Workers 还能够实现更好的负载平衡和扩展应用。通过允许任务在多个 worker 线程之间并行执行,Web Workers 可以帮助将工作负荷均匀分配到多个核心或处理器上,实现更快速和高效的数据处理。这对于经历高流量或需求的应用尤为重要,因为 Web Workers 可以帮助确保应用可以处理增加的负载而不影响性能。
Web Workers 客户端使用
使用 JavaScript 创建 Web Worker 的步骤如下:
-
创建一个新的 JavaScript 文件,其中包含要在工作线程中运行的代码(耗时任务)。该文件不应包含对 DOM 的引用,因为在工作线程中无法访问 DOM。
-
在主 JavaScript 文件中,使用
Worker
构造函数创建一个新的worker
对象。此构造函数接收一个参数,即在步骤 1 中创建的 JavaScript 文件的 URL。
const worker = new Worker('worker.js');
-
向
worker
对象添加事件侦听器以处理主线程和工作线程之间发送的消息。onmessage
用于处理从工作线程发送来的消息,postMessage
用于向工作线程发送消息。
worker.onmessage = function(event) {console.log('Worker: ' + event.data);
};worker.postMessage('Hello, worker!');
-
在 Web Worker 的 JavaScript 文件中,使用
self
对象的onmessage
属性添加一个事件监听器来处理从主线程发出的消息。可以使用event.data
属性访问发送的消息数据。
self.onmessage = function(event) {console.log('Main: ' + event.data);self.postMessage('Hello, Main!');
};
接下来就运行应用并测试 Worker。可以在控制台看到以下信息,表示主线程和 Worker 线程之间发送和接收了消息。
Main:Hello worker!
Worker:Hello Main!
我们可以使用terminate()
函数来终止一个工作线程,或者通过调用self
上的close()
函数使其自行终止。
// 从应用中终止一个工作线程
worker.terminate();
// 让一个工作线程自行终止
self.close();
可以使用importScripts()
函数将库或文件导入到工作线程中,该函数可以接受多个文件。以下示例将script1.js
和script2.js
加载到工作线程 worker.js
中:
importScripts('script1.js','script2');
可以使用 onerror
函数来处理工作线程抛出的错误:
worker.onerror = function(err) {console.log("遇到错误")
}
Web Workers 服务端应用
服务器端 JavaScript 运行时也支持 Web Worker:
-
Node.js 在版本 10 中实现了类似 Web Worker 的功能,称为 Worker thread(工作进程)。
-
Deno 复制了 Web Worker API,因此语法与浏览器代码完全相同。它还提供了兼容模式,可以填充 Node.js API,以便可以使用该运行时的工作线程语法。
-
Bun 将支持浏览器和 Node.js 的 Web Worker API。
基本使用
要在 Node.js 中使用 Web Worker,主脚本必须定义一个 Worker
对象,其中包含相对于项目根目录的 Web Worker 脚本的名称。第二个参数定义了一个对象,其中包含一个workerData
属性,该属性包含要发送的数据:
const worker = new Worker('./worker.js', {workerData: { a: 1, b: 2, c: 3 }
});
与浏览器中的 Web Worker 不同, 它在启动时无需运行worker.postMessage()
。如果需要的话,可以调用该方法并稍后发送更多数据,它会触发parentPort.on('message')
事件处理程序:
parentPort.on('message', e => {console.log(e);
});
一旦工作线程完成处理,它会使用以下方法将结果数据发送回主线程:
parentPort.postMessage(result);
这将在在主脚本中触发 message
事件,主线程接收到 worker 返回的结果:
worker.on('message', result => {console.log( result );
});
在发送完消息后,worker 就会终止。这也会触发一个exit
事件,如果希望运行清理或其他函数,可以利用这个事件:
worker.on('exit', code => {
//...
});
除此之外,还支持其他事件处理:
-
messageerror
:当 worker 收到无法反序列化的数据时触发。 -
online
:当 worker 开始执行时触发。 -
error
:当 worker 脚本中发生 JavaScript 错误时触发。
在服务端,一个单独的 Node.js 脚本文件可以同时包含主线程和工作线程的代码。脚本必须使用isMainThread
检查自身是否在主线程上运行,然后将自身作为工作线程进行调用(可以在 ES 模块中使用import.meta.url
作为文件引用,或者在 CommonJS 中使用__filename
)。
import { Worker, isMainThread, workerData, parentPort } from "node:worker_threads";if (isMainThread) {// 主线程const worker = new Worker(import.meta.url, {workerData: { a: 1, b: 2, c: 3 }});worker.on('message', msg => {});worker.on('exit', code => {});
}
else {// 工作线程const result = runSomeProcess( workerData );parentPort.postMessage(result);
}
这种方式更快,并且对于小型、自包含的单脚本项目来说是一个选择。如果是大型项目,将 worker 脚本文件分开会更容易维护。
数据通信
主线程和工作线程之间的通信涉及到了数据序列化。可以使用表示固定长度原始二进制数据的SharedArrayBuffer
对象在线程之间共享数据。以下是一个示例,主线程定义了从 0 到 99 的 100 个数字元素,并将其发送给工作线程:
// main.js
import { Worker } from "node:worker_threads";constbuffer = new SharedArrayBuffer(100 * Int32Array.BYTES_PER_ELEMENT),value = new Int32Array(buffer);value.forEach((v,i) => value[i] = i);const worker = new Worker('./worker.js');worker.postMessage({ value });
工作线程可以接收 value
对象:
// worker.js
import { parentPort } from 'node:worker_threads';parentPort.on('message', value => {value[0] = 100;
});
主线程或工作线程都可以更改值数组中的元素,数据将在两个线程之间保持一致。这可能会提高性能,但有一些缺点:
-
只能共享整数数据。
-
可能仍需要通知另一个线程更改。
-
存在两个线程可能同时更新同一值并且失去同步的风险。
Node.js 子进程
在 Node.js 中,除了使用工作线程外,还可以使用子进程来实现类似的功能。子进程用于启动其他应用、传递数据并接收结果。它们与工作线程类似,但通常效率较低,进程开销较大。
子进程和工作线程的选择取决于具体的应用场景。如果只需要在 Node.js 中执行其他任务或命令,子进程是一种更好的选择。但如果需要在 Node.js 中进行复杂的计算或处理任务,Web Worker 可能更适合。
Web Workers 应用场景
Web Workers 在实际应用中有许多常见且有用的应用场景。
处理 CPU 密集任务
假设有一个应用需要执行大量的 CPU 密集型计算。如果在主线程中执行这些计算,用户界面可能会变得无响应,用户体验将受到影响。为了避免这种情况,可以使用 Web Worker 在后台执行这些计算。
在主线程中:
// 创建一个新的 Web Worker
const worker = new Worker('worker.js');// 定义一个函数来处理来自Web Worker的消息
worker.onmessage = function(event) {const result = event.data;console.log(result);
};// 向Web Worker发送一个消息,以启动计算
worker.postMessage({ num: 1000000 });
在 worker.js 中:
// 定义一个函数来执行计算
function compute(num) {let sum = 0;for (let i = 0; i < num; i++) {sum += i;}return sum;
}// 定义一个函数来处理来自主线程的消息
onmessage = function(event) {const num = event.data.num;const result = compute(num);postMessage(result);
};
在这个例子中,创建了一个新的 Web Worker,并定义了一个函数来处理来自 Web Worker 的消息。然后,向 Web Worker 发送一条消息,并提供一个参数(num
),指定要执行计算的迭代次数。Web Worker 接收到这条消息后,在后台执行计算。当计算完成后,Web Worker 向主线程发送一条包含结果的消息。主线程收到这个消息后,将结果记录到控制台中。
在上面的例子中,向 Web Worker 的compute()
函数传递了数字 1000000。这意味着compute
函数将需要将从 0 到一百万的所有数字相加。这涉及大量的额外操作,可能需要很长时间才能完成,特别是如果代码在较慢的计算机上运行或在浏览器标签中同时处理其他任务。
通过将这个任务分配给 Web Worker,应用的主线程可以继续平稳运行,而不会被计算密集型的任务阻塞。这使得用户界面保持响应,并确保其他任务(如用户输入或动画)可以在没有延迟的情况下处理。
处理网络请求
假设有一个应用需要发起大量的网络请求。如果在主线程中执行这些请求,可能会导致用户界面无响应,用户体验差。为了避免这个问题,可以利用 Web Worker 在后台处理这些请求。通过这样做,主线程可以同时执行其他任务,而 Web Worker 负责处理网络请求,从而提高性能和改善用户体验。
在主线程中:
// 创建一个新的 Web Worker
const worker = new Worker('worker.js');// 定义一个函数来处理来自Web Worker的消息
worker.onmessage = function(event) {const response = event.data;console.log(response);
};// 向Web Worker发送一个消息,以启动计算
worker.postMessage({ urls: ['https://api.example.com/foo', 'https://api.example.com/bar'] });
在 worker.js 中:
// 定义一个函数来执行网络请求
function request(url) {return fetch(url).then(response => response.json());
}// 定义一个函数来处理来自主线程的消息
onmessage = async function(event) {const urls = event.data.urls;const results = await Promise.all(urls.map(request));postMessage(results);
};
在这个例子中,创建一个新的 Web Worker 并定义一个函数来处理来自该 Worker 的消息。然后,向 Worker 发送一个包含一组 URL 请求的消息。Worker 接收到这个消息后,在后台使用 fetch API 执行请求。当所有请求完成后,Worker 向主线程发送包含结果的消息。主线程接收到这个消息后,将结果记录到控制台中。
并行处理
假设应用需要执行大量独立计算。 如果在主线程中依次执行这些计算,用户界面将变得无响应,用户体验将受到影响。 为了避免这种情况,可以实例化多个 Web Worker 来并行执行计算。
在主线程中:
// 创建三个新的 Web Worker
const worker1 = new Worker('worker.js');
const worker2 = new Worker('worker.js');
const worker3 = new Worker('worker.js');// 定义三个处理来自 worker 的消息的函数
worker1.onmessage = handleWorkerMessage;
worker2.onmessage = handleWorkerMessage;
worker3.onmessage = handleWorkerMessage;function handleWorkerMessage(event) {const result = event.data;console.log(result);
}// 将任务分配给不同的 worker 对象,并发送消息启动计算
worker1.postMessage({ num: 1000000 });
worker2.postMessage({ num: 2000000 });
worker3.postMessage({ num: 3000000 });
在 worker.js 中:
// 定义一个函数来执行单个计算
function compute(num) {let sum = 0;for (let i = 0; i < num; i++) {sum += i;
}return sum;
}// 定义一个函数来处理来自主线程的消息
onmessage = function(event) {const result = compute(event.data.num);postMessage(result);
};
在这个例子中,创建三个新的 Web Worker 并定义一个函数来处理来自该 Worker 的消息。然后,向三个 Worker 分别发送一个要计算的数字消息。Worker 接收到这个消息后执行计算。当计算完成后,Worker 向主线程发送包含结果的消息。主线程接收到这个消息后,将结果记录到控制台中。
Web Workers 使用限制
Web Worker 是一个提高 Web 应用性能和响应能力的强大工具,但它们也有一些限制和注意事项。
浏览器支持
目前所有主流浏览器、Node.js、Deno 和 Bun 都支持 Web Workers。
对 DOM 的访问受到限制
Web Worker 在单独的线程中运行,无法直接访问主线程中的 DOM 或其他全局对象。这意味着不能直接从 Web Worker 中操作 DOM,也不能访问像window
或document
这样的全局对象。
为了解决这个限制,可以使用postMessage
方法与主线程进行通信,间接地更新 DOM 或访问全局对象。例如,使用postMessage
将数据发送到主线程,然后根据接收到的消息来更新 DOM 或全局对象。
另外,还有一些库可以帮助解决这个问题。例如,WorkerDOM[1] 库允许在 Web Worker 中运行 DOM,从而加快页面的渲染速度并提高性能。
现代桌面浏览器支持共享工作线程,即在不同窗口、iframes 或工作线程中可被多个脚本访问的单个脚本,它们通过独立的端口进行通信。但是,大多数移动浏览器不支持共享工作线程,所以对于大多数 Web 项目来说,它们并不实用。
通信开销大
Web Worker 使用postMessage
方法与主线程进行通信,这可能会引入通信开销。通信开销指的是在两个或多个计算系统之间建立和维护通信所需的时间和资源量,比如在 Web 应用中,Web Worker 与主线程之间的通信。这可能导致消息处理延迟,潜在地减慢应用程序的速度。为了最小化这种开销,应该只在线程之间发送必要的数据,避免发送大量数据或频繁发送消息。
调试工具有限
与在主线程中调试代码相比,调试 Web Worker 可能更具挑战性,因为可用的调试工具较少。为了简化调试过程,可以使用控制台 API 在 Worker 线程中记录消息,并使用浏览器开发者工具检查线程之间发送的消息。
代码复杂度
使用 Web Worker 可能会增加代码的复杂性,因为需要管理线程之间的通信,并确保数据正确传递。这可能会使编写、调试和维护代码更加困难,因此应该仔细考虑是否有必要在应用中使用 Web Worker。
Web Workers 实践
上面提到了在使用 Web Workers 时,可能会出现的一些潜在问题。下面就来看看如何缓解这些问题。
worker.on('message', result => {console.log( result );
});
消息批处理
消息批处理涉及将多个消息组合成一个批处理消息,这比单独发送个别消息更有效。这种方法减少了主线程和 Web Worker 之间往返的数量,它有助于最小化通信开销并提高应用的整体性能。
为了实现消息批量处理,可以使用队列来累积消息,并在队列达到一定阈值或经过设定时间后将消息批量发送。下面来在 Web Worker 中简单实现消息的批处理:
// 创建一个消息队列累积消息
const messageQueue = [];// 创建一个将消息添加到队列的函数
function addToQueue(message) {messageQueue.push(message);// 检查队列是否达到阈值大小if (messageQueue.length >= 10) {// 如果是,请将批处理消息发送到主线程postMessage(messageQueue);// 清除消息队列messageQueue.length = 0;}
}// 将消息添加到队列中
addToQueue({type: 'log', message: 'Hello, world!'});// 再添加另一条消息到队列中
addToQueue({type: 'error', message: 'An error occurred.'});
在这个例子中, 创建了一个消息队列,用于累积需要发送到主线程的消息。每当使用addToQueue
函数将消息添加到队列时,检查队列是否已达到阈值大小(10)。如果是,则使用postMessage
方法将批处理消息发送到主线程。然后,清除消息队列,以准备进行下一次批处理。
通过以这种方式批处理消息,可以减少主线程和 Web Worker 之间发送的消息总数,从而提高应用性能。
避免同步方法
同步方法是阻塞其他代码执行的 JavaScript 函数或操作。同步方法可以阻塞主线程,导致应变得无响应。为了避免这种情况,应尽量避免在 Web Worker 中使用同步方法。而应该使用setTimeout()
或setInterval()
等异步方法来执行长时间运行的计算。
// 在Web Worker中
self.addEventListener('message', (event) => {if (event.data.action === 'start') {// 使用setTimeout来异步执行计算setTimeout(() => {const result = doSomeComputation(event.data.data);// 将结果发送回主线程self.postMessage({ action: 'result', data: result });}, 0);}
});
注意内存使用情况
Web Workers 有自己的内存空间,这个空间根据用户的设备和浏览器设置可能是有限的。为了避免内存问题,应该注意你的 Web Worker 代码使用的内存量,并避免不必要地创建大对象。例如:
// 在Web Worker中
self.addEventListener('message', (event) => {if (event.data.action === 'start') {// 使用for循环处理一个数据数组const data = event.data.data;const result = [];for (let i = 0; i < data.length; i++) {// 处理数组中的每个项,并将结果添加到结果数组中const itemResult = processItem(data[i]);result.push(itemResult);}// 将结果发送回主线程self.postMessage({ action: 'result', data: result });}
});
在这段代码中,Web Worker 处理一个数据数组,并使用postMessage
方法将结果发送回主线程。然而,用于处理数据的 for 循环可能耗时较长。
导致这个问题的原因是代码一次性处理了整个数据数组,这意味着所有的数据都必须同时加载到内存中。如果数据集非常大,这可能导致 Web Worker 消耗大量的内存,甚至超过浏览器为 Web Worker 分配的内存限制。
为了缓解这个问题,可以考虑使用内置的 JavaScript 方法,如forEach
或reduce
,它们可以逐项处理数据,避免一次性加载整个数组到内存中。
浏览器兼容性
大多数现代浏览器都支持 Web Worker,但某些较旧的浏览器可能不支持它们。 为了确保与各种浏览器的兼容性,应该在不同的浏览器和版本中测试 Web Worker 代码。 还可以使用功能检测来检查 Web Worker 是否受支持,然后再在代码中使用它们,如下所示:
if (typeof Worker !== 'undefined') {const worker = new Worker('worker.js');
} else {console.log('Web Workers are not supported in this browser.');
}
这段代码会检查当前浏览器是否支持 Web Workers,并在支持时创建一个新的 Web Worker。如果 Web Workers 不受支持,则该代码记录一条消息到控制台,表示该浏览器不支持 Web Workers。
总结
Web Workers 是现代 Web 开发的一个基本特性,它允许开发人员将 CPU 密集型任务放到单独的线程中执行,从而提高应用的性能和响应能力。然而,在处理 Web Workers 时需要记住一些重要的限制和注意事项,例如无法访问 DOM 和数据类型之间传递的限制等。为了避免这些潜在问题,可以采用上面提到的策略,如使用异步方法并注意卸载的任务的复杂性。在未来,使用 Web Workers 进行多线程似乎仍然是提高 Web 应用程序性能和响应能力的重要技术。
相关文章:

HTML5 Web Worker之性能优化
描述 由于 JavaScript 是单线程的,当执行比较耗时的任务时,就会阻塞主线程并导致页面无法响应,这就是 Web Workers 发挥作用的地方。它允许在一个单独的线程(称为工作线程)中执行耗时的任务。这使得 JavaScript 代码可…...

应对恶意IP攻击的有效方法
在当今数字化时代,网络攻击已经成为了互联网安全的重大挑战之一。恶意IP攻击是网络安全领域中的一种常见威胁,它可能导致数据泄露、服务中断、系统瘫痪等严重后果。因此,有效地应对恶意IP攻击至关重要。IP数据云将深入探讨如何应对恶意IP攻击…...

如何使用“Docker registry创建本地仓库,在服务器之间进行文件push和pull”?
1.1、在服务器1,运行registry docker run -d -p 5000:5000 -v ${PWD}/registry:/var/lib/registry --restart always --name registry registry:2.7.11.2、编辑/etc/docker/daemon.json 文件, 192.168.xxx.xxx 换成你自己 registry 服务的地址 sudo na…...

Rocky Linux - Primavera P6 EPPM 安装及分享
引言 继上一期发布的Redhat Linux版环境发布之后,近日我又制作了基于Rocky Enterprise Linux 的P6虚拟机环境,同样里面包含了全套P6 最新版应用服务 此虚拟机仅用于演示、培训和测试目的。如您在生产环境中使用此虚拟机,请先与Oracle Primav…...
API 管理调研
当前大部分团队内 API 管理都是依赖 Postman,postman最大的问题是共享问题,如果我要使用另外一个人已经调试好的 API 非常麻烦。因此,能实现协作的 API 管理将极大提升效率。 Yapi https://github.com/YMFE/yapi https://hellosean1025.gi…...

在centOS服务器安装docker,并使用docker配置nacos
遇到安装慢的情况可以优先选择阿里镜像 安装docker 更新yum版本 yum update安装所需软件包 yum install -y yum-utils device-mapper-persistent-data lvm2添加Docker仓库 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.rep…...
JVM运行时数据区概述以及分别存放的内容
JVM的运行时数据区是JVM在执行Java程序时用于存储数据和状态信息的内存区域。它分为多个部分,每个部分都有其特定的作用和存放的内容。 1. 方法区(Method Area) 作用:方法区是所有线程共享的内存区域,用于存放已被虚…...
数据体系规范化
基础是标准化、规范化 建立数据仓库,面向主题的、集成的、相对稳定的、反映历史变化的数据集合,以支持管理决策decision making 大数据:大量volumn、多样variety、快速velocity、价值密度低value、准确性veracity、可视化visualization、合法性validity 多源数据、多样数…...

从政府工作报告探计算机行业发展
从政府工作报告中,我们可以深入洞察计算机行业在未来一年的发展趋势和政策导向。报告中明确提出了数字经济创新发展的重要性,以及制造业数字化转型、服务业数字化、智慧城市和数字乡村建设等关键任务,这些都为计算机行业提供了广阔的发展空间…...

【软件工具】网络性能测试工具 Iperf
Iperf 是一款专业的开源网络性能测试工具,它被广泛用于测量网络带宽、延迟、抖动和数据包丢失等网络性能指标,支持 TCP 和 UDP 等,可用于点对点或客户端-服务器等模式的网络测试。 软件获取 官方下载地址:https://iperf.fr/iper…...

Day32:安全开发-JavaEE应用Servlet路由技术JDBCMybatis数据库生命周期
目录 JavaEE-HTTP-Servlet&路由&周期 JavaEE-数据库-JDBC&Mybatis&库 思维导图 Java知识点: 功能:数据库操作,文件操作,序列化数据,身份验证,框架开发,第三方库使用等. 框架…...
C语言下使用SQL语言
需头文件:#include<sqlite.h>---需下载 1.sqlite3_open int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); 功能: 打开数据库文件(…...
Gitea相关漏洞
Go代码审计:Gitea远程命令执行漏洞链_新闻中心-网盾网络安全培训学校 Vulhub靶场gitea-1.4远程代码执行漏洞复现_gitea 漏洞-CSDN博客...

基于深度学习的图像去雨去雾
基于深度学习的图像去雨去雾 文末附有源码下载地址 b站视频地址: https://www.bilibili.com/video/BV1Jr421p7cT/ 基于深度学习的图像去雨去雾,使用的网络为unet, 网络代码: import torch import torch.nn as nn from torchsumm…...
使用JS的for循环实现九九乘法表
九九乘法表是我们在学习基础数学时经常会遇到的一个概念。在编程中,使用循环结构来实现九九乘法表是一个很好的练习。下面,我将详细介绍如何使用JavaScript的for循环来实现九九乘法表。 首先,我们需要理解for循环的基本结构。在JavaScript中…...

Leetcode 70 爬楼梯
文章目录 1. 题目描述2. 我的尝试 1. 题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n 2 输出:2 解释:有两种方法可以爬到…...

基于SpringBoot+MYSQL+Vue的校园管理系统
目录 1、前言介绍 2、主要技术 3、系统流程分析 3.1、操作流程 3.2、添加信息流程 3.3、删除信息流程 4、系统设计 4.1 系统体系结构 4.2开发流程设计 4.3 数据库设计原则 4.4 数据表 5、运行截图(部分) 5.1管理员功能模块 5.2用户功能模块 5.3院校管理员功能模块…...
Oracle P6 负浮时和必须完成日期
前言 学习过计划的人大都有这有这样的经历,即无论是Microsoft Project 亦或是P6见过负浮时那么 Primavera P6 计划中的负浮时是从何而来的呢,那么本文可能会有所帮助。 首先,当活动的最晚日期早于最早日期时,就会出现负浮时。 换…...

【C++】STL--String
这一节主要总结string类的常见接口,以及完成了string类的模拟实现。 目录 标准库的String类 string类常见接口 string类对象的常见构造 string析构函数:~string string类对象的容量操作 string类对象的访问及遍历操作 string类对象的修改操作 s…...
深入理解与使用go之中间件--实现
深入理解与使用go之--中间件实现 目录 引子 中间件 定义 原理 简单实现 多个中间件 优化 扩展 gin框架实现 引子 我们在做web开发的时候,经常会遇到下面一些需求: 统计耗时:想程序内部统计某个路由的请求耗时 预处理:接口需要登录鉴权后才能继续进行 错误捕获:当…...

移动端研发技术的进化历程
移动端研发技术 移动端研发技术主要分为原生开发和跨平台开发。本章主要介绍一下移动开发技术的过去、当下和未来,一步一步介绍移动技术的进化历程。 原生开发 原生应用程序是指某一个移动平台(比如iOS或Android)所特有的应用,使…...

ChromeDriver 122 版本为例 国内下载地址及安装教程
ChromeDriver 国内下载地址 https://chromedriver.com/download 靠谱 千千万万别下载错了 先确认 Chrome 浏览器版本 以 win64 版本为例 那我们下载这一个啊,不要下载错了 下载地址贴在这哈 https://storage.googleapis.com/chrome-for-testing-public/122.0.…...

【数据结构】双向链表及LRU缓存的实现
目录 前言 1. 在原有的自定义链表类 Linked 的基础上,添加新的 “节点添加”方法 addNode(Node node) 测试用例 测试结果 2. 在自定义链表类的基础上,使用双重循环“强力” 判断两个节点是否发生相交 测试用例 测试结果 3. 在自定义链表类的基础上…...
2、计划任务不显示UI的问题
计划任务不显示UI的问题 1、原因: 在windows7以上系统,使用system权限启动的进程默认是没有ui界面,这是windows系统出于安全考虑的限制。防止用户在高权限下误删重要的文件。 2、解决方案: 1、降权 解决的办法:降…...
学C还是学C++?
计算机专业学生,大一上学期学习了C语言,下学期学校要学C,请问我寒假继续深入学习C还是提前学C,大佬们有什么建议吗?(个人感觉C学的不是很明白,链表文件什么的还不是很懂…) 这个并没有一个统一的…...
Springboot参数分组校验
Springboot参数分组校验 文章目录 Springboot参数分组校验简介代码准备单个或多个参数的校验非 JSON 格式的对象参数校验JSON 格式的对象参数校验Service 层校验项目地址 简介 Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate …...

无缝集成 MongoDB Relational Migrator,Tapdata 提供关系型到 MongoDB 实时迁移优化方案
在去年的 MongoDB 用户大会纽约站上,MongoDB 正式宣布全面推出新工具 MongoDB Relational Migrator(MongoDB RM),用以简化应用程序迁移和转换——即从传统关系型数据模型到现代的文档数据模型,助力组织快速提升运营效率…...
【C++】每日一题 146 LRU缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 …...
CentOS搭建NAS服务器并使用
CentOS搭建NAS服务器并使用 文章目录 前言一、配置NAS服务器安装 NFS 服务:启动 NFS 服务:使 NFS 服务在系统启动时自动启动: 二、挂载服务器三、常见错误以及解决方案1、mount.nfs: No route to host2、mount.nfs: access denied by server …...

爬虫入门到精通_框架篇16(Scrapy框架基本使用)_名人名言的抓取
1 目标站点分析 抓取网站:http://quotes.toscrape.com/ 主要显示了一些名人名言,以及作者、标签等等信息: 点击next,page变为2: 2 流程框架 抓取第一页:请求第一页的URL并得到源代码,进行下…...