安装node.js
安装
验证
|
1 2 3 4 |
$ node -v v16.14.2 $ npm -v 8.7.0 |
其他设置1
镜像
- 找到
npm配置文件:Windows:C:\Users\<用户名>\.npmrcMac/Linux:~/.npmrc
- 用文本编辑器打开
.npmrc,添加以下内容:
|
1 2 |
electron_mirror="https://npmmirror.com/mirrors/electron/" registry=https://registry.npmmirror.com/ |
- 保存文件并重启终端
- 清理缓存并重试
- 可能需要
|
1 2 3 4 5 6 7 8 |
# 强制清理 npm 缓存 npm cache clean --force # 删除 node_modules 和锁定文件 rm -rf node_modules package-lock.json # 重新安装 Electron npm install --save-dev electron |
- 通过以下命令检查配置是否生效:
electron_mirror=https://npmmirror.com/mirrors/electron/表示配置成功
|
1 |
npm config list | grep electron_mirror |
代理
- 若使用
VPN或企业代理,需在.npmrc中添加代理设置:
|
1 2 |
proxy=http://your-proxy:port https-proxy=http://your-proxy:port |
创建应用程序
初始化 npm 项目
|
1 2 |
mkdir my-electron-app && cd my-electron-app npm init |
将 Electron 安装为项目的 devDependencies
- 仅在开发环境需要的额外依赖
- 输出如下:
|
1 2 3 4 5 6 7 |
d:\codes\electron\my-electron-app>npm install --save-dev electron npm warn deprecated boolean@3.2.0: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. added 70 packages in 4m 17 packages are looking for funding run `npm fund` for details |
运行 Electron 应用
main.js
|
1 |
console.log('Hello from Electron 👋') |
package.json- 要执行这个脚本,需要在
package.json的scripts字段中添加一个start命令,内容为electron . - 这个命令会告诉
Electron在当前目录下寻找主脚本,并以开发模式运行它
- 要执行这个脚本,需要在
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "name": "my-electron-app", "version": "1.0.0", "description": "Hello World!", "main": "main.js", "scripts": { "start": "electron .", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Jane Doe", "license": "MIT", "devDependencies": { "electron": "23.1.3" } } |
- 终端执行:
|
1 |
npm run start |
|
1 2 3 4 5 6 |
d:\codes\electron\my-electron-app>npm run start > my-electron-app@1.0.0 start > electron . Hello from Electron 馃憢 |
将网页装载到 BrowserWindow
- 在
Electron中,每个窗口展示一个页面,后者可以来自本地的HTML,也可以来自远程URL- 在项目的根目录中创建一个
index.html文件,并写入下面的内容:
- 在项目的根目录中创建一个
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" /> <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'" /> <title>Hello from Electron renderer!</title> </head> <body> <h1>Hello from Electron renderer!</h1> <p>👋</p> </body> </html> |
修改main.js
- 第一行中,我们使用
CommonJS语法导入了两个Electron模块:app,这个模块控制着您应用程序的事件生命周期BrowserWindow,这个模块创建和管理app的窗口
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
console.log('Hello from Electron 👋') const {app, BrowserWindow} = require('electron') const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600 }) win.loadFile('index.html') } app.whenReady().then(() => { createWindow() }) |
Electron的许多核心模块都是Node.js的事件触发器,遵循Node.js的异步事件驱动架构app模块就是其中一个- 在
Electron中,只有在app模块的ready事件(event)触发后才能创建BrowserWindows实例 - 可以通过使用
app.whenReady()API来监听此事件,并在其成功后调用createWindow()方法
|
1 2 3 |
app.whenReady().then(() => { createWindow() }) |
关于页面
- 应用中的每个页面都在一个单独的进程中运行,我们称这些进程为 渲染器 (
renderer) - 渲染进程使用与常规
Web开发相同的JavaScript API和工具
管理应用的窗口生命周期
概述
- 应用窗口在不同操作系统中的行为也不同
Electron允许您自行实现这些行为来遵循操作系统的规范,而不是采用默认的强制执行- 可以通过监听
app和BrowserWindow模组的事件,自行实现基础的应用窗口规范
针对特定平台
- 通过检查
Node.js的process.platform变量,您可以针对特定平台运行特定代码
关闭所有窗口时退出应用 (Windows & Linux)
- 在
Windows和Linux上,我们通常希望在关闭一个应用的所有窗口后让它退出 - 可以监听
app模块的window-all-closed事件,并在判断用户不使用macOS后调用app.quit()来退出您的应用程序
|
1 2 3 |
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }) |
没有窗口打开则打开一个窗口 (macOS)
- 与前二者相比,即使没有打开任何窗口,
macOS应用通常也会继续运行- 在没有窗口可用时调用
app会打开一个新窗口 - 可以监听
app模组的activate事件,如果没有任何打开(open)的BrowserWindow,调用您已有的createWindow()方法新建一个
- 在没有窗口可用时调用
|
1 2 3 4 5 6 7 |
app.whenReady().then(() => { createWindow() app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) |
VSCode里面调试
- 在根目录新建一个
.vscode文件夹,然后在其中新建一个launch.json配置文件并填写如下内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{ "version": "0.2.0", "compounds": [ { "name": "Main + renderer", "configurations": ["Main", "Renderer"], "stopAll": true } ], "configurations": [ { "name": "Renderer", "port": 9222, "request": "attach", "type": "chrome", "webRoot": "${workspaceFolder}" }, { "name": "Main", "type": "node", "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", "windows": { "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" }, "args": [".", "--remote-debugging-port=9222"], "outputCapture": "std", "console": "integratedTerminal" } ] } |
DevTools调试
Node.js 的异步事件驱动架构
概述
Node.js的异步事件驱动架构是其高效处理高并发的核心机制,结合了非阻塞I/O、事件循环(Event Loop)和事件驱动的编程模型
V8
- 负责解析和执行
JavaScript代码,将代码编译为机器指令 - 与底层系统直接交互,提升性能
libuv 库
- 跨平台异步
I/O库,提供事件循环、线程池和文件系统操作的非阻塞实现 - 管理事件循环的
6个阶段(timers、poll、check等),协调异步任务调度
事件循环(Event Loop)
- 单线程模型:
- 主线程仅负责事件调度,避免多线程上下文切换的开销
- 非阻塞
I/O:- 通过回调函数或
Promise处理耗时操作(如文件读写、网络请求),主线程无需等待I/O完成
- 通过回调函数或
事件循环的详细阶段
- 概述
- 事件循环按顺序执行以下阶段,每个阶段处理特定类型的任务
Timers阶段- 执行
setTimeout和setInterval的回调
- 执行
Pending I/O Callbacks- 处理上一轮未完成的
I/O回调(如TCP错误)
- 处理上一轮未完成的
Idle/Prepare- 系统内部使用
Poll阶段- 核心阶段:检索新的
I/O事件(如文件读取完成、HTTP请求到达),执行关联的回调 - 若队列为空,则阻塞等待新事件,或跳转到
Check阶段
- 核心阶段:检索新的
Check阶段- 执行
setImmediate的回调
- 执行
Close Callbacks- 处理关闭事件(如
socket.on('close'))
- 处理关闭事件(如
异步任务的分类与优先级
- 宏任务(
Macrotask)- 包括
setTimeout、setInterval、I/O操作、setImmediate - 每个事件循环阶段执行一个宏任务
- 包括
- 微任务(
Microtask)- 包括
Promise.then、process.nextTick process.nextTick在事件循环各阶段之间立即执行(优先级最高)Promise回调在宏任务结束后执行
- 包括
事件驱动模型的核心机制
EventEmitter类- 实现发布-订阅模式,通过
.on()注册事件监听器,.emit()触发事件
- 实现发布-订阅模式,通过
|
1 2 3 4 5 |
const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const emitter = new MyEmitter(); emitter.on('event', () => console.log('Event triggered!')); emitter.emit('event'); // 触发事件 |
非阻塞 I/O 的工作流程
- 主线程发起
I/O请求后,立即继续执行后续代码 I/O完成后,libuv将回调推入事件队列,事件循环在Poll阶段执行回调
单线程与事件循环
Node.js主线程运行V8引擎解析JavaScript,同步代码直接执行,异步任务(如I/O、定时器)通过libuv提交到事件循环队列- 主线程通过 事件循环(
Event Loop) 调度任务,避免阻塞
- 主线程通过 事件循环(
- 与
libuv的协作- 任务分发:
Node.js主线程将异步任务(如fs.readFile)委托给libuv,后者通过线程池或系统内核的异步接口(如epoll)处理 - 回调触发:任务完成后,
libuv将回调推入事件队列,主线程在轮询到对应阶段时执行回调
- 任务分发:
问题
libuv是否维护自己的事件队列?- 是的,
libuv维护独立的事件队列,但它的队列结构是分阶段的 libuv的事件循环分为6个阶段(如Timers阶段、Poll阶段、Check阶段等),每个阶段对应一个特定类型的事件队列
Timers队列:存放setTimeout/setInterval的回调
I/O队列:存放已完成的I/O操作(如文件读写、网络请求)的回调
Check队列:存放setImmediate的回调- 当异步任务(如文件
I/O)完成时,libuv的线程池会将回调推入对应阶段的队列,等待事件循环轮询到该阶段时执行
- 是的,
- 主线程是否有自己的事件队列?
- 主线程没有独立的事件队列,但
Node.js主线程的事件循环与libuv的事件队列紧密绑定
具体来说:
主线程的事件循环直接管理libuv的事件队列
当主线程空闲时,会通过事件循环(uv_run)依次轮询libuv的6个阶段队列,并执行其中的回调 - 微任务队列:主线程还有两个特殊的中间队列(不属于
libuv):
nextTick队列:存放process.nextTick的回调,优先级最高
Microtask队列:存放Promise的回调,优先级次之
这两个队列会在每个libuv阶段切换时优先清空
- 主线程没有独立的事件队列,但
- 主线程有没有任务队列?
nextTick 队列和Microtask 队列属于Node.js主线程自身的任务队列
- 回调的执行者是
libuv还是主线程?- 回调由主线程执行,但
libuv负责调度: libuv线程池:处理耗时任务(如文件I/O、DNS解析),完成后将回调推入对应阶段的队列- 主线程的事件循环:在轮询到对应阶段时,从
libuv队列中取出回调并执行
- 回调由主线程执行,但
- 主程序处理任务的优先级?
- 同步代码
nextTick队列Microtask队列libuv阶段队列(如Timers、I/O)
总结(记忆)
node.js主线程运行着一个事件循环,还有两个自己的任务队列nextTick和Microtaskv8引擎负责解析执行JavaScript代码,同步代码直接在主线程执行,异步代码交给libuv处理libuv维护了6个阶段队列,这6个队列存放相应任务的对应的回调node.js主线程在空闲时候,按优先级取出这些队列中的回调(nextTick,Microtask,libuv阶段队列)并执行
事件触发器
EventEmitter 类
Node.js的events模块提供了EventEmitter类,所有能触发事件的对象均是该类的实例- 通过
on()方法注册监听器(订阅事件),emit()方法触发事件(发布事件)
|
1 2 3 4 5 6 7 |
const EventEmitter = require('events'); const emitter = new EventEmitter(); // 订阅事件 emitter.on('fire', () => console.log('点火')); // 触发事件 emitter.emit('fire'); // 输出:点火 |
事件驱动的异步模型
- 异步模型见上述
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 【Javascript】赋值解构,日期时间,JSON04/05
- ♥ 【Javascript】try...catch,try…catch…finally,自定义Error,扩展Error04/13
- ♥ 【Javascript】全局对象,函数对象,new_function,setTimeout,setInterval04/06
- ♥ 【Javascript】递归,链表,Rest,Spread,作用域,闭包,词法环境,Var04/05
- ♥ 【Javascript】原型继承,F.prototype,原生的原型,原型方法,“Very plain”对象04/12
- ♥ 【Javascript】对象引用复制05/18