本地安装 QuickJS 与 入门示例

简介

QuickJS 是一个小巧、易嵌入的 JavaScript 引擎,由 Fabrice Bellard 大神编写。它的目标是高度兼容 ECMAScript 标准,并具备快速启动、低内存占用的特性,适用于嵌入式环境或需要快速初始化脚本执行的场景。

它的主要特点包括:

  • 极小体积,便于嵌入:整个引擎由几个 C 文件构成,无需依赖其他库,在 x86 上一个最小 "hello world" 程序仅约 210 KiB。
  • 快速解释器,启动极快:可在台式机单核下,2 分钟内跑完 ECMAScript 测试套件的 77,000 个测试用例。
  • 高度标准兼容:几乎完整支持 ES2023 标准,并部分实现 ES2024 新增特性。
  • 无需外部依赖的独立可执行文件:可将 JS 源码直接编译成可执行文件。
  • 引用计数 GC + 循环回收:使用引用计数进行垃圾回收,同时支持循环引用的清理。
  • 命令行交互器带语法高亮,并可在 JS 中进行扩展。
  • 内建标准库小巧实用:提供对 libc 和底层 OS 功能的封装。

安装

安装 CMake

QuickJS 使用 CMake 作为构建系统,所以需要先安装它。

推荐通过 Homebrew 安装:

bash 复制代码
brew install cmake

或前往官网下载适配平台的二进制包:

安装 QuickJS

从 GitHub 获取 QuickJS 源码:

bash 复制代码
git clone https://github.com/bellard/quickjs

进入项目目录,使用 make 进行编译安装:

bash 复制代码
cd quickjs
sudo make install /usr/local

注意: 如果你本地 CMake 版本较新,可能在编译过程中会看到一些警告信息,直接忽略即可。

命令行工具

QuickJS 提供两个命令行工具:

  • qjs: JavaScript 解释器,可用于交互或执行 .js 文件;
  • qjsc: 编译器,将 .js 编译为原生可执行文件(无需解释器支持)。

交互式命令行

构建完成后,直接执行:

bash 复制代码
qjs

进入一个交互式 JavaScript Shell,语法高亮且可即时执行 JS 表达式。

命令行选项:
选项/参数 中文解释
-h/ --help 显示帮助信息,列出所有支持的选项。
-e EXPR/ --eval EXPR 直接执行指定的 JavaScript 表达式(EXPR),无需文件输入。
-i/ --interactive 进入交互模式(默认情况下,若提供文件参数则不会自动进入交互模式)。
-m/ --module 强制将文件作为 ES6 模块加载(默认根据扩展名或 import关键字自动判断)。
--script 强制将文件作为 ES6 脚本加载(默认自动判断)。
-I file/ --include file 在运行主文件前,先加载并执行指定的附加文件(file)。
--std 即使脚本非模块模式,也强制启用 stdos内置模块的支持。
-d/ --dump 输出内存使用统计信息(用于调试或性能分析)。
-q/ --quit 仅初始化解释器后立即退出(不执行任何脚本,用于测试或环境检查)。

编译器使用(qjsc)

编写一个简单脚本:

js 复制代码
// hello.js
console.log('Hello QuickJS');

将其编译成可执行程序:

bash 复制代码
qjsc -o hello hello.js

运行:

bash 复制代码
./hello
# 输出:Hello QuickJS

命令行选项

选项/参数 中文解释
-c 仅生成包含字节码的 C 文件(默认输出可执行文件)。
-e 生成包含 main()函数和字节码的 C 文件(默认输出可执行文件)。
-o output 指定输出文件名(默认为 out.ca.out)。
-N cname 设置生成数据(如字节码数组)的 C 变量名称。
-m 强制按 ES6 模块编译(默认根据 .mjs扩展名或 import关键字自动判断)。
-D module_name 编译动态加载模块及其依赖(需配合 importos.Worker使用)。
-M module_name[,cname] 为外部 C 模块添加初始化代码(参考 c_module示例)。
-x 生成字节序交换的输出(仅用于交叉编译)。
-flto 启用链接时优化(编译较慢但生成更小更快的可执行文件)。
-fno-[feature] 禁用指定语言特性以生成更小的可执行文件(如 -fno-eval禁用 eval功能)。

示例:文件内容搜索

下面是一个使用 QuickJS 编写的文件搜索工具,功能类似 grep,用于查找文件中包含特定文本的行。

示例源码

js 复制代码
import * as os from 'os'
import * as std from 'std'

"use strict";

const colors = {
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  reset: '\x1b[0m'
}

function isFile(mode) {
  return mode & os.S_IFMT === os.S_IFREG;
}

/**
 * 简单的文件搜索函数
 * @param {*} filename 
 * @param {*} searchText 
 * @returns 
 */
function searchInFile(filename, searchText) {
  try {
    const [fileObj, err] = os.stat(filename);
    if (isFile(fileObj?.mode) || err !== 0) {
      console.log(`${colors.red}错误: ${filename} 不是文件${colors.reset}`)
      return
    }

    const file = std.open(filename, 'r');
    if (!file) throw new Error(`无法打开文件 ${filename}`);

    let lineNumber = 1;
    let found = false;

    let i = 0;
    while (true) {
      const line = file.getline();
      if (line === null) break;

      if (line.includes(searchText)) {
        found = true;
        console.log(`${colors.green}${filename}:${lineNumber}${colors.reset} ${highlightText(line, searchText)}`);
      }

      lineNumber++;
    }

    file.close();

    if (!found) {
      console.log(`${colors.yellow}未找到 "${searchText}"${colors.reset}`);
    }
  } catch (error) {
    console.log(`${colors.red}错误: ${error.message}${colors.reset}`);
  }
}

/**
 * 高亮匹配文本
 * @param {*} line 
 * @param {*} searchText 
 * @returns 
 */
function highlightText(line, searchText) {
  return line.replace(
    new RegExp(searchText, 'g'),
    `${colors.yellow}${searchText}${colors.reset}`
  )
}

/**
 * scriptArgs 是一个全局对象,用于提供命令行参数,第一个参数是脚本名称。
 *   类似 Node.js 中的 process.argv;
 */
if (typeof scriptArgs !== 'undefined' && scriptArgs.length >= 3) {
  const filename = scriptArgs[1];
  const searchText = scriptArgs[2];
  searchInFile(filename, searchText);
} else {
  console.log(`${colors.yellow}使用方法: ./file-search <文件名> <搜索文本>${colors.reset}`);
}

编译代码:

bash 复制代码
qjsc -o file-search file-search.js

运行脚本:

arduino 复制代码
./file-search file-search.js const

示例结果:

解析说明

scriptArgs

这是 QuickJS 提供的全局变量,类似 Node.js 的 process.argv

js 复制代码
['脚本路径', '参数1', '参数2', ...]

在脚本中可以通过 scriptArgs[1] 获取用户传入的文件名。

os.stat

js 复制代码
const [statInfo, err] = os.stat(filename);

该方法返回一个数组:

  • 第一个元素是文件信息对象(包含 modesize 等);
  • 第二个是错误码,为 0 表示无错误。

mode 判断文件类型

由于 QuickJS 没有提供类似 isFile() 的 API,我们需要手动通过位运算判断:

js 复制代码
(mode & os.S_IFMT) === os.S_IFREG
  • S_IFMT:提取文件类型位的掩码;
  • S_IFREG:常量,表示"普通文件"。

QuickJS 中的 mode 属性常量,与 C/C++ 编程语言中定义在 POSIX(Unix/Linux 等类 Unix 系统)下的一个系统头文件 <sys/stat.h> 一致,主要用于 文件状态 的相关操作。

S_IFMT type of file 文件类型掩码,用于提取 st_mode中表示文件类型的位。
S_IFBLK block special 块设备文件,如磁盘分区,数据按固定大小的块读写。
S_IFCHR character special 字符设备文件,如终端或键盘,数据以字符流形式传输。
S_IFIFO FIFO special 命名管道(FIFO),用于进程间通信,数据按先进先出顺序处理。
S_IFREG regular 普通文件,存储用户数据(如文本、二进制文件)。
S_IFDIR directory 目录文件,包含其他文件的名称和索引节点(inode)指针。
S_IFLNK symbolic link 符号链接(软链接),指向另一个文件的路径名而非 inode。

std.open 和逐行读取

QuickJS 的 std 模块封装了 fopen,返回一个 FILE 对象,支持逐行读取、写入等操作:

js 复制代码
const file = std.open(filename, 'r');
file.getline(); // 一次读取一行,返回 null 表示文件结尾

相比 readFile 这种一次性读入内存的方式,这种方式更适合处理大文件。

结语

本文介绍了如何本地通过源码编译安装 QuickJS,同时讲了如何使用 qjsqjsc 两种工具,并通过一个实际示例简单演示了 QuickJS 的标准库用法。

相关链接

相关推荐
码银1 小时前
ruoyi的前端(vue)新增的时候给字典设置默认值 但不能正常
前端
桦说编程1 小时前
简单方法实现子任务耗时统计
java·后端·监控
盖世英雄酱581362 小时前
物品超领取损失1万事故复盘(一)
java·后端
fengbizhe2 小时前
bootstrapTable转DataTables,并给有着tfoot的DataTables加滚动条
javascript·bootstrap
刘一说2 小时前
TypeScript 与 JavaScript:现代前端开发的双子星
javascript·ubuntu·typescript
凌览2 小时前
别再死磕 Nginx!http-proxy-middleware 低配置起飞
前端·后端
拾玖不会code2 小时前
简单分表场景下的业务发散思考:分表如何保证丝滑?
后端
CryptoRzz2 小时前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链
EndingCoder2 小时前
类的继承和多态
linux·运维·前端·javascript·ubuntu·typescript
用户47949283569152 小时前
React 终于出手了:彻底终结 useEffect 的"闭包陷阱"
前端·javascript·react.js