强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

03 - 基本使用

基本使用

3.1 REPL 交互模式

REPL(Read-Eval-Print Loop,读取-求值-打印 循环)是学习和调试 JavaScript 代码的最直接方式。

启动 REPL

# 启动交互式 REPL
./qjs

# 启动时加载标准库到全局
./qjs --std

启动后会看到:

QuickJS - Type "\h" for help
qjs >

REPL 命令

命令说明
\h显示帮助信息
\q退出 REPL
\x以十六进制显示结果
\d以十进制显示结果
\t以 typeof 显示结果

REPL 使用技巧

qjs > # 1. 多行输入
qjs > function greet(name) {
......     return `Hello, ${name}!`;
...... }
undefined
qjs > greet("QuickJS")
"Hello, QuickJS!"

qjs > # 2. 使用 Tab 补全
qjs > Math.P      # 按 Tab
Math.PI    Math.pow

qjs > # 3. 访问上一次结果(注意:QuickJS REPL 没有 _ 变量,需自己存储)
qjs > let last = 1 + 2
undefined
qjs > last * 2
6

qjs > # 4. 执行异步代码
qjs > let p = new Promise(resolve => resolve(42))
undefined
qjs > p.then(v => console.log(v))
42
[object Promise]

qjs > # 5. 加载模块(需要 --std 或手动 import)
qjs > let std = await import("std")
undefined
qjs > std.printf("formatted: %d\n", 42)
formatted: 42
14

3.2 脚本执行

基本脚本

// hello.js — 第一个 QuickJS 脚本
console.log("Hello from QuickJS!");
console.log("Arguments:", scriptArgs);

// 日期处理
const now = new Date();
console.log("Current time:", now.toISOString());

// 数组操作
const data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
const sorted = [...data].sort((a, b) => a - b);
console.log("Sorted:", sorted);
console.log("Sum:", data.reduce((a, b) => a + b, 0));
console.log("Average:", (data.reduce((a, b) => a + b, 0) / data.length).toFixed(2));
./qjs hello.js arg1 arg2

脚本模式 vs 模块模式

QuickJS 支持两种脚本加载模式:

# 脚本模式(默认)— 代码在全局作用域执行
./qjs --script script.js

# 模块模式 — 代码在模块作用域执行,支持 import/export
./qjs --module app.mjs
./qjs -m app.mjs

# 自动检测:.mjs 后缀或包含 import/export 语句时自动使用模块模式

脚本模式 vs 模块模式的区别:

特性脚本模式模块模式
作用域全局模块私有
this 指向全局对象undefined
import / export
var 声明挂载到全局模块私有
顶层 await
严格模式需手动启用默认启用

错误处理

// error_handling.js — 在 QuickJS 中处理错误
try {
    // 故意触发错误
    undefined_function();
} catch (e) {
    console.log("Caught error:", e.message);
    console.log("Stack trace:", e.stack);
}

// 捕获语法错误(需要在外部 try-catch 中处理 eval)
try {
    eval("function {invalid syntax");
} catch (e) {
    console.log("Syntax error caught:", e.message);
}

// 未捕获的 Promise 拒绝
// QuickJS 会打印警告
Promise.reject(new Error("unhandled rejection"));

3.3 模块系统

QuickJS 完整支持 ES Module(ESM),包括命名导出、默认导出、重导出等。

基本导入导出

// math_utils.js — 工具模块
// 命名导出
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

export const PI = 3.141592653589793;

// 默认导出
export default class Calculator {
    #history = [];

    calculate(expr) {
        const result = eval(expr);
        this.#history.push({ expr, result });
        return result;
    }

    getHistory() {
        return [...this.#history];
    }
}
// app.js — 主模块
// 默认导入
import Calculator from "./math_utils.js";

// 命名导入
import { add, multiply, PI } from "./math_utils.js";

// 别名导入
import { add as addition } from "./math_utils.js";

// 全部导入
import * as math from "./math_utils.js";

// 使用
const calc = new Calculator();
console.log(calc.calculate("2 + 3"));     // 5
console.log(add(10, 20));                  // 30
console.log(multiply(PI, 2));             // 6.28318...
console.log(addition(5, 7));              // 12
console.log(math.PI);                     // 3.14159...
# 以模块模式运行
./qjs -m app.js

重导出

// index.js — 模块聚合
export { add, multiply } from "./math_utils.js";
export { default as Calculator } from "./math_utils.js";

// 重导出全部
export * from "./math_utils.js";

3.4 标准模块

QuickJS 提供了两个内置模块:stdos,以及一个 os 子模块用于 Worker。

std 模块

import * as std from "std";

// 文件操作
const content = std.loadFile("data.txt");
if (content !== null) {
    print("File content:", content);
} else {
    print("Failed to read file, errno:", std.errno);
}

// 使用 FILE* API 进行更精细的文件操作
const file = std.open("output.txt", "w");
if (file) {
    file.puts("Line 1\n");
    file.puts("Line 2\n");
    file.printf("Formatted: %d, %s\n", 42, "hello");
    file.close();
}

// 标准流
std.out.puts("stdout output\n");
std.err.puts("stderr output\n");

// 临时文件
const tmpName = "/tmp/quickjs_test.txt";
std.saveFile(tmpName, "temporary data");

// 环境变量
print("HOME:", std.getenv("HOME"));

// 评估文件内容
const result = std.evalScript("1 + 2");
print("Eval result:", result);

// 获取最后一个错误信息
print("Error string:", std.strerror(std.errno));

os 模块

import * as os from "os";

// 系统信息
console.log("Platform:", os.platform);   // "linux", "darwin", "win32"
console.log("Architecture:", os.arch);   // "x64", "arm", "arm64"

// 时间
console.log("Timestamp (ms):", Date.now());
console.log("High-res timer:", os.now());  // 毫秒级高精度时间

// 定时器
const timer = os.setTimeout(() => {
    print("Timer fired after 1 second!");
}, 1000);

// 清除定时器
// os.clearTimeout(timer);

// 周期定时器
let count = 0;
const interval = os.setInterval(() => {
    count++;
    print(`Interval tick ${count}`);
    if (count >= 3) {
        os.clearInterval(interval);
        print("Interval cleared");
    }
}, 500);

// 进程相关
print("PID:", os.pid);

// 信号处理(Unix)
if (os.platform !== "win32") {
    os.signal(os.SIGINT, () => {
        print("Caught SIGINT, exiting...");
        std.exit(0);
    });
}

// 文件系统操作
os.mkdir("/tmp/quickjs_test_dir", 0o755);
os.rename("/tmp/quickjs_test_dir", "/tmp/quickjs_renamed");
os.remove("/tmp/quickjs_renamed");

// 获取文件状态
const stat = os.stat("/etc/hostname");
if (stat) {
    print("File size:", stat.size);
    print("Is directory:", !!(stat.mode & os.S_IFDIR));
}

// 读取目录
const entries = os.readdir("/tmp");
print("Entries:", entries);

// 睡眠(阻塞)
print("Sleeping 100ms...");
os.sleep(100);
print("Awake!");

综合示例:文件处理管道

// file_pipeline.js — 使用 std 和 os 处理文件
import * as std from "std";
import * as os from "os";

function processLogFile(inputPath, outputPath) {
    const input = std.loadFile(inputPath);
    if (input === null) {
        print(`Error: Cannot read ${inputPath}`);
        return false;
    }

    const lines = input.split("\n");
    const errors = lines
        .filter(line => line.includes("ERROR"))
        .map(line => {
            const timestamp = line.substring(0, 24);
            const message = line.substring(line.indexOf("ERROR") + 6);
            return `[${timestamp}] ${message.trim()}`;
        });

    const output = errors.join("\n");
    const result = std.saveFile(outputPath, output);

    if (result < 0) {
        print(`Error: Cannot write ${outputPath}`);
        return false;
    }

    print(`Processed ${lines.length} lines, found ${errors.length} errors`);
    return true;
}

// 主流程
const startTime = os.now();
processLogFile("app.log", "errors.txt");
const elapsed = os.now() - startTime;
print(`Completed in ${elapsed}ms`);

3.5 字节码编译与执行

字节码(Bytecode)编译可以将 JavaScript 源码预编译为二进制格式,提高加载速度并隐藏源码。

使用 qjsc 编译

# 编译为字节码文件
./qjsc -o myapp.qjsc myapp.js

# 编译为模块字节码
./qjsc -o mymodule.qjsc -m mymodule.js

# 执行字节码
./qjs myapp.qjsc

# 生成 C 嵌入文件
./qjsc -c -o bytecode.h -m myapp.js

在代码中编译字节码

// compile_bytecode.js — 使用 std 模块编译字节码
import * as std from "std";

// 注意:在 QuickJS 中,字节码编译主要通过 qjsc 命令行工具完成
// 这里展示的是使用 std.evalScript 执行预编译的字节码

// 保存字节码的典型流程:
// 1. 使用 qjsc -c -o bytecode.h script.js 生成字节数组
// 2. 在 C 程序中 #include "bytecode.h"
// 3. 使用 JS_ReadObject() 加载字节码
// 4. 使用 JS_EvalFunction() 执行

print("Bytecode compilation is primarily done via qjsc CLI tool.");
print("See Chapter 04 for C API bytecode loading.");

字节码的优势与限制

方面说明
加载速度跳过解析阶段,直接加载字节码
文件大小字节码通常比源码小 30-50%
源码保护字节码不包含原始源码(但可反编译)
版本绑定字节码与 QuickJS 版本绑定,升级后需重新编译
可读性二进制格式,不可直接阅读

3.6 脚本参数传递

通过 scriptArgs 获取参数

// args_demo.js
console.log("Script name:", scriptArgs[0]);
console.log("All arguments:", scriptArgs.slice(1));

// 简单的参数解析器
function parseArgs(args) {
    const result = { files: [], options: {} };

    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if (arg.startsWith("--")) {
            const [key, value] = arg.slice(2).split("=");
            result.options[key] = value || true;
        } else if (arg.startsWith("-")) {
            result.options[arg.slice(1)] = true;
        } else {
            result.files.push(arg);
        }
    }

    return result;
}

const args = parseArgs(scriptArgs.slice(1));
console.log("Parsed:", JSON.stringify(args, null, 2));
./qjs args_demo.js --output=result.txt -v file1.js file2.js

输出:

Script name: args_demo.js
All arguments: ["--output=result.txt", "-v", "file1.js", "file2.js"]
Parsed: {
  "files": ["file1.js", "file2.js"],
  "options": {
    "output": "result.txt",
    "v": true
  }
}

3.7 定时器与事件循环

QuickJS 提供了基本的定时器功能,但没有内置完整的事件循环(Event Loop)

定时器使用

// timers.js
import * as os from "os";

print("Start");

os.setTimeout(() => {
    print("Timeout 1: 100ms later");
}, 100);

os.setTimeout(() => {
    print("Timeout 2: 50ms later");
}, 50);

// 注意:QuickJS 没有自动事件循环
// 定时器回调不会自动执行,需要手动驱动循环
// 在 qjs 中,exit 后的定时器可能不会执行

print("End (synchronous)");

重要提示: QuickJS 的 setTimeout / setInterval 是由 quickjs-libc.c 提供的,并且在 qjs 命令行工具中,它会自动运行一个简单的事件循环直到所有定时器完成。但在嵌入式 C 代码中,你需要自己管理事件循环。


3.8 调试技巧

控制台输出

// 调试输出方法
console.log("Standard log");
console.warn("Warning message");
console.error("Error message");

// 格式化输出(QuickJS 支持简单格式化)
console.log("Number: %d, String: %s", 42, "hello");

// 打印对象
const obj = { name: "test", values: [1, 2, 3] };
console.log("Object:", JSON.stringify(obj, null, 2));

// 打印调用栈
function debugHere() {
    try { throw new Error(); } catch(e) {
        console.log("Stack:", e.stack);
    }
}
debugHere();

性能测量

// perf_test.js
function measureTime(label, fn, iterations = 1000) {
    const start = Date.now();
    for (let i = 0; i < iterations; i++) {
        fn();
    }
    const elapsed = Date.now() - start;
    console.log(`${label}: ${elapsed}ms (${iterations} iterations, ${(elapsed/iterations).toFixed(3)}ms each)`);
}

measureTime("Array.push", () => {
    const arr = [];
    for (let i = 0; i < 1000; i++) arr.push(i);
});

measureTime("String concat", () => {
    let s = "";
    for (let i = 0; i < 1000; i++) s += "a";
});

measureTime("Array.join", () => {
    const parts = [];
    for (let i = 0; i < 1000; i++) parts.push("a");
    parts.join("");
});

3.9 常见陷阱与注意事项

全局变量污染

// ❌ 不好:变量泄漏到全局
function bad() {
    myVar = 42;  // 缺少 let/const/var
}

// ✅ 好:使用严格模式或模块
"use strict";
function good() {
    const myVar = 42;
}

数值精度

// QuickJS 使用双精度浮点数,与所有 JS 引擎相同
console.log(0.1 + 0.2);          // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false

// 使用 BigInt 处理大整数
const big = 9007199254740993n;    // 超过 Number.MAX_SAFE_INTEGER
console.log(big);                 // 9007199254740993n

// 使用 BigDecimal 处理精确十进制
const price = 1.10d;
console.log(price * 3d);          // 3.30d

同步阻塞

// 注意:os.sleep() 会阻塞整个线程
// 在嵌入式环境中,这可能会阻止其他任务执行
import * as os from "os";

// ❌ 不好:长时间阻塞
os.sleep(10000); // 10 秒

// ✅ 好:使用短时间的 sleep 或事件驱动
for (let i = 0; i < 100; i++) {
    os.sleep(100); // 每次只休眠 100ms
    // 可以在这里检查中断条件
}

3.10 本章小结

要点说明
REPL交互式 JavaScript 执行环境,使用 \h 查看帮助
脚本模式默认模式,代码在全局作用域执行
模块模式使用 -m 启用,支持 import/export
std 模块文件操作、环境变量、标准流
os 模块系统信息、定时器、文件系统、信号
字节码使用 qjsc 预编译,提高加载速度
调试使用 console.log 和 Date.now() 测量性能

扩展阅读