本地安装 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 的标准库用法。

相关链接

相关推荐
玲小珑2 分钟前
Next.js 教程系列(二十二)代码分割与打包优化
前端·next.js
二闹3 分钟前
后端开发:这5个技巧让你少写一半代码!
java·后端·project lombok
coding随想11 分钟前
HTML5插入标记的秘密:如何高效操控DOM而不踩坑?
前端·html
༺ཌༀ傲世万物ༀད༻11 分钟前
前端与后端部署大冒险:Java、Go、C++三剑客
java·前端·golang
TheRedAce19 分钟前
状态未保存,拦截页面跳转通用方法
前端
袁煦丞20 分钟前
小雅全家桶+cpolar影音库自由随身:cpolar内网穿透实验室第519个成功挑战
前端·程序员·远程工作
前端Hardy24 分钟前
HTML&CSS:超丝滑抛物线飞入购物车效果
前端·javascript·css
VisuperviReborn25 分钟前
打造自己的前端监控---前端错误监控
前端·javascript·vue.js
泉城老铁27 分钟前
Spring Boot 应用打包部署到 Tomcat ,如何极致调优看这里
java·spring boot·后端
WindrunnerMax28 分钟前
从零实现富文本编辑器#6-浏览器选区与编辑器选区模型同步
前端·前端框架·github