QuickJS 初探
深入探讨 QuickJS 轻量级 JavaScript 引擎的特性和使用方法,包括基本使用、核心特性和实际应用场景。
QuickJS 是一个轻量级的 JavaScript 引擎,由 Fabrice Bellard 开发。本文将带你初探 QuickJS 的特性和使用方法。
什么是 QuickJS
QuickJS 是一个小型且可嵌入的 JavaScript 引擎,由 Fabrice Bellard(FFmpeg 和 QEMU 的创始人)开发,支持 ES2020 标准。它具有以下特点:
- 轻量级:核心库只有几百 KB,相比 V8 引擎的几十 MB 要小得多
- 快速:启动速度快,冷启动时间通常在毫秒级别
- 可嵌入:易于集成到 C/C++ 项目中,API 设计简洁
- 标准兼容:支持最新的 JavaScript 特性,包括 ES6 模块、async/await 等
- 内存友好:内存占用低,适合资源受限的环境
基本使用
安装 QuickJS
# 克隆仓库
git clone https://github.com/bellard/quickjs.git
cd quickjs
# 编译
make
简单的 JavaScript 执行
#include "quickjs.h"
int main() {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
// 执行 JavaScript 代码
const char *script = "console.log('Hello, QuickJS!');";
JSValue result = JS_Eval(ctx, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
核心特性
1. 模块系统
QuickJS 支持 ES6 模块:
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
// main.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
2. 异步支持
QuickJS 支持 Promise 和 async/await:
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
fetchData().then(data => {
console.log('Data:', data);
}).catch(error => {
console.error('Error:', error);
});
3. 与 C 的互操作
QuickJS 提供了丰富的 C API 用于与 JavaScript 交互:
// 注册 C 函数到 JavaScript
JSValue js_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
int a, b;
if (JS_ToInt32(ctx, &a, argv[0]) || JS_ToInt32(ctx, &b, argv[1]))
return JS_EXCEPTION;
return JS_NewInt32(ctx, a + b);
}
// 在 JavaScript 中使用
JS_SetPropertyStr(ctx, global_obj, "add", JS_NewCFunction(ctx, js_add, "add", 2));
QuickJS vs V8:为什么选择 QuickJS
性能对比
特性 | QuickJS | V8 |
---|---|---|
二进制大小 | ~600KB | ~20MB+ |
内存占用 | 1-10MB | 50-200MB+ |
启动时间 | <10ms | 100-500ms |
适用场景 | 嵌入式、移动端 | 服务器、桌面应用 |
QuickJS 的优势
- 极小的内存占用:QuickJS 运行时通常只需要几 MB 内存,而 V8 需要几十 MB
- 快速启动:冷启动时间在毫秒级别,适合需要频繁启动的场景
- 更好的资源控制:可以精确控制内存使用和垃圾回收
- 简化的 API:C API 更加简洁,学习成本低
V8 的优势
- 更高的执行性能:在长时间运行的场景下,V8 的 JIT 编译能提供更好的性能
- 更丰富的生态系统:Node.js 生态系统的支持
- 更好的调试工具:Chrome DevTools 集成
实际应用场景
1. 移动端应用
滴滴 Hummer
滴滴的 Hummer 跨平台框架使用 QuickJS 作为 JavaScript 引擎:
// Hummer 中的 JavaScript 代码示例
import { View, Text, Button } from '@hummer/hummer-core';
class MyComponent extends View {
constructor() {
super();
this.text = new Text();
this.text.text = 'Hello Hummer!';
this.appendChild(this.text);
}
}
Hummer 选择 QuickJS 的原因:
- 包体积优化:相比 V8,QuickJS 能显著减少 App 包大小
- 内存效率:在移动设备上内存占用更少
- 快速启动:提升 App 启动速度
阿里 Weex 2.0
Weex 2.0 也采用了 QuickJS 引擎:
// Weex 2.0 组件示例
export default {
data() {
return {
message: 'Hello Weex 2.0!'
}
},
methods: {
handleClick() {
this.message = 'QuickJS is awesome!';
}
}
}
2. 嵌入式设备
QuickJS 的轻量级特性使其非常适合嵌入式应用:
// 在资源受限的设备上运行 JavaScript
void run_script_on_device(const char *script) {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
// 设置内存限制
JS_SetMemoryLimit(rt, 1024 * 1024); // 1MB
JSValue result = JS_Eval(ctx, script, strlen(script), "<device>", JS_EVAL_TYPE_GLOBAL);
JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
3. 配置文件处理
使用 QuickJS 处理 JSON 配置文件:
// config.js
const config = {
server: {
host: 'localhost',
port: 8080
},
database: {
url: 'mongodb://localhost:27017',
name: 'myapp'
}
};
export default config;
4. 游戏脚本引擎
QuickJS 也被用于游戏中的脚本系统:
// 游戏脚本引擎示例
typedef struct {
JSRuntime *rt;
JSContext *ctx;
JSValue game_objects;
} GameScriptEngine;
void init_game_script_engine(GameScriptEngine *engine) {
engine->rt = JS_NewRuntime();
engine->ctx = JS_NewContext(engine->rt);
// 注册游戏 API
register_game_api(engine->ctx);
// 加载游戏脚本
load_game_scripts(engine);
}
性能优化
1. 内存管理
// 及时释放 JavaScript 值
JSValue value = JS_NewString(ctx, "Hello");
// 使用完毕后释放
JS_FreeValue(ctx, value);
2. 预编译脚本
// 预编译脚本以提高性能
JSValue script = JS_Eval(ctx, source_code, strlen(source_code), "<script>", JS_EVAL_TYPE_MODULE);
// 保存编译后的脚本供重复使用
最佳实践和注意事项
1. 内存管理最佳实践
// 正确的内存管理方式
void safe_js_execution() {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
// 设置内存限制
JS_SetMemoryLimit(rt, 5 * 1024 * 1024); // 5MB
// 执行脚本
JSValue result = JS_Eval(ctx, script, strlen(script), "<script>", JS_EVAL_TYPE_GLOBAL);
// 检查执行结果
if (JS_IsException(result)) {
JSValue exception = JS_GetException(ctx);
const char *error = JS_ToCString(ctx, &exception);
printf("Error: %s\n", error);
JS_FreeCString(ctx, error);
JS_FreeValue(ctx, exception);
}
// 清理资源
JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
2. 错误处理
// 完善的错误处理机制
JSValue execute_with_error_handling(JSContext *ctx, const char *script) {
JSValue result = JS_Eval(ctx, script, strlen(script), "<script>", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(result)) {
JSValue exception = JS_GetException(ctx);
JSValue stack = JS_GetPropertyStr(ctx, exception, "stack");
const char *error_msg = JS_ToCString(ctx, &exception);
const char *stack_trace = JS_ToCString(ctx, &stack);
printf("JavaScript Error: %s\n", error_msg);
printf("Stack Trace: %s\n", stack_trace);
JS_FreeCString(ctx, error_msg);
JS_FreeCString(ctx, stack_trace);
JS_FreeValue(ctx, stack);
JS_FreeValue(ctx, exception);
}
return result;
}
3. 性能优化技巧
// 预编译和缓存脚本
typedef struct {
JSValue compiled_script;
char *script_hash;
} CachedScript;
CachedScript *cache_script(JSContext *ctx, const char *script) {
CachedScript *cached = malloc(sizeof(CachedScript));
// 编译脚本
cached->compiled_script = JS_Eval(ctx, script, strlen(script), "<script>", JS_EVAL_TYPE_MODULE);
// 生成脚本哈希(简化示例)
cached->script_hash = strdup(script);
return cached;
}
QuickJS 事件循环机制
1. 事件循环基础
QuickJS 的事件循环机制相对简单,主要基于 Promise 和微任务队列:
// QuickJS 中的事件循环示例
console.log('1. 同步代码开始');
Promise.resolve().then(() => {
console.log('3. 微任务 1');
});
setTimeout(() => {
console.log('4. 宏任务');
}, 0);
Promise.resolve().then(() => {
console.log('3. 微任务 2');
});
console.log('2. 同步代码结束');
// 输出顺序:
// 1. 同步代码开始
// 2. 同步代码结束
// 3. 微任务 1
// 3. 微任务 2
// 4. 宏任务
2. 自定义事件循环
QuickJS 允许你实现自定义的事件循环:
#include "quickjs.h"
#include <sys/epoll.h>
#include <unistd.h>
typedef struct {
int epoll_fd;
JSContext *ctx;
int running;
} EventLoop;
// 事件循环结构
EventLoop* create_event_loop(JSContext *ctx) {
EventLoop *loop = malloc(sizeof(EventLoop));
loop->epoll_fd = epoll_create1(0);
loop->ctx = ctx;
loop->running = 1;
return loop;
}
// 运行事件循环
void run_event_loop(EventLoop *loop) {
struct epoll_event events[10];
while (loop->running) {
// 处理微任务队列
JSContext *ctx = loop->ctx;
JSValue pending = JS_GetPendingException(ctx);
if (!JS_IsUndefined(pending)) {
// 处理异常
JS_FreeValue(ctx, pending);
}
// 处理 I/O 事件
int nfds = epoll_wait(loop->epoll_fd, events, 10, 100);
for (int i = 0; i < nfds; i++) {
// 处理事件
handle_io_event(events[i]);
}
// 执行微任务
execute_microtasks(ctx);
}
}
// 执行微任务
void execute_microtasks(JSContext *ctx) {
// QuickJS 内部会自动处理 Promise 微任务
// 这里可以添加自定义的微任务处理逻辑
}
3. 异步操作处理
// 注册异步操作到事件循环
typedef struct {
int fd;
JSValue callback;
EventLoop *loop;
} AsyncOperation;
// 创建异步读取操作
JSValue create_async_read(JSContext *ctx, int fd, JSValue callback) {
AsyncOperation *op = malloc(sizeof(AsyncOperation));
op->fd = fd;
op->callback = JS_DupValue(ctx, callback);
// 注册到事件循环
register_async_operation(ctx, op);
return JS_UNDEFINED;
}
// 异步操作完成后的回调
void async_read_complete(AsyncOperation *op, const char *data) {
JSContext *ctx = op->loop->ctx;
// 调用 JavaScript 回调函数
JSValue args[1];
args[0] = JS_NewString(ctx, data);
JSValue result = JS_Call(ctx, op->callback, JS_UNDEFINED, 1, args);
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, args[0]);
}
QuickJS 垃圾回收机制
1. 垃圾回收器类型
QuickJS 使用标记-清除(Mark-and-Sweep)垃圾回收算法:
// 垃圾回收器配置
void configure_gc(JSRuntime *rt) {
// 设置内存限制
JS_SetMemoryLimit(rt, 10 * 1024 * 1024); // 10MB
// 设置垃圾回收阈值
JS_SetGCThreshold(rt, 1024 * 1024); // 1MB
// 启用增量垃圾回收
JS_SetGCObjectCountLimit(rt, 10000);
}
2. 手动垃圾回收控制
// 手动触发垃圾回收
void manual_gc(JSRuntime *rt) {
// 强制垃圾回收
JS_RunGC(rt);
// 获取内存使用情况
size_t memory_usage = JS_GetMemoryUsage(rt);
printf("Memory usage: %zu bytes\n", memory_usage);
}
// 监控垃圾回收
void monitor_gc(JSRuntime *rt) {
// 设置垃圾回收回调
JS_SetGCCallback(rt, gc_callback, NULL);
}
// 垃圾回收回调函数
void gc_callback(JSRuntime *rt, JS_MarkFunc *mark_func, void *opaque) {
printf("Garbage collection triggered\n");
// 可以在这里添加自定义的清理逻辑
cleanup_custom_resources();
}
3. 内存泄漏检测
// 内存泄漏检测工具
typedef struct {
JSValue *tracked_values;
size_t count;
size_t capacity;
} MemoryTracker;
MemoryTracker* create_memory_tracker() {
MemoryTracker *tracker = malloc(sizeof(MemoryTracker));
tracker->capacity = 1000;
tracker->count = 0;
tracker->tracked_values = malloc(sizeof(JSValue) * tracker->capacity);
return tracker;
}
// 跟踪 JavaScript 值
void track_value(MemoryTracker *tracker, JSContext *ctx, JSValue value) {
if (tracker->count >= tracker->capacity) {
tracker->capacity *= 2;
tracker->tracked_values = realloc(tracker->tracked_values,
sizeof(JSValue) * tracker->capacity);
}
tracker->tracked_values[tracker->count++] = JS_DupValue(ctx, value);
}
// 检查内存泄漏
void check_memory_leaks(MemoryTracker *tracker, JSContext *ctx) {
printf("Tracking %zu JavaScript values\n", tracker->count);
for (size_t i = 0; i < tracker->count; i++) {
JSValue value = tracker->tracked_values[i];
// 检查值是否仍然有效
if (!JS_IsUndefined(value) && !JS_IsNull(value)) {
printf("Potential memory leak detected at index %zu\n", i);
}
}
}
4. 优化垃圾回收性能
// 优化垃圾回收的代码模式
void optimized_js_usage(JSContext *ctx) {
// 1. 及时释放不再使用的值
JSValue temp_value = JS_NewString(ctx, "temporary");
// 使用完毕后立即释放
JS_FreeValue(ctx, temp_value);
// 2. 避免创建大量临时对象
for (int i = 0; i < 1000; i++) {
// 不好的做法:每次都创建新对象
// JSValue obj = JS_NewObject(ctx);
// 好的做法:重用对象
static JSValue reusable_obj = JS_UNDEFINED;
if (JS_IsUndefined(reusable_obj)) {
reusable_obj = JS_NewObject(ctx);
}
// 使用 reusable_obj
}
// 3. 使用对象池
ObjectPool *pool = create_object_pool(ctx);
JSValue obj = get_object_from_pool(pool);
// 使用对象
return_object_to_pool(pool, obj);
}
// 对象池实现
typedef struct {
JSValue *objects;
size_t count;
size_t capacity;
} ObjectPool;
ObjectPool* create_object_pool(JSContext *ctx) {
ObjectPool *pool = malloc(sizeof(ObjectPool));
pool->capacity = 100;
pool->count = 0;
pool->objects = malloc(sizeof(JSValue) * pool->capacity);
return pool;
}
JSValue get_object_from_pool(ObjectPool *pool) {
if (pool->count > 0) {
return pool->objects[--pool->count];
}
return JS_NewObject(NULL); // 创建新对象
}
void return_object_to_pool(ObjectPool *pool, JSValue obj) {
if (pool->count < pool->capacity) {
pool->objects[pool->count++] = obj;
} else {
JS_FreeValue(NULL, obj); // 释放对象
}
}
总结
QuickJS 作为一个现代化的 JavaScript 引擎,在保持轻量级的同时提供了强大的功能。它特别适合需要嵌入 JavaScript 运行时的 C/C++ 项目,以及资源受限的嵌入式应用。
选择 QuickJS 的场景
- 移动端应用:需要优化包大小和内存占用
- 嵌入式设备:资源受限的环境
- 脚本引擎:需要快速启动和频繁执行
- 配置文件处理:轻量级的 JSON/JavaScript 处理
不适用 QuickJS 的场景
- 长时间运行的服务器应用:V8 的 JIT 编译性能更好
- 需要复杂调试工具的场景:V8 的调试工具更丰富
- 依赖 Node.js 生态的项目:V8 生态系统更完善
通过本文的介绍,你应该对 QuickJS 有了深入的了解。从滴滴 Hummer 到阿里 Weex 2.0,QuickJS 已经在多个知名项目中证明了其价值。接下来可以尝试在实际项目中应用它,探索更多高级特性。