当前位置: 首页 > 工具软件 > QuickJS > 使用案例 >

quickJS 说明

彭宏义
2023-12-01

过一段时间可能要用qjs,参考着github上的中文翻译再加上自己的机翻整理了一下,方便以后看
作为小白来说这东西真心看的一脸懵逼,很多地方看不懂
慢慢来吧~等变强了就看得懂了

quickJS JavaScript引擎

1. 简介

QuickJS是一个小型并且可嵌入的Javascript引擎,它支持ES2020规范,包括模块,异步生成器和代理器。

它可选支持数学扩展,例如大整数 (BigInt),大浮点数 (BigFloat) 以及运算符重载。

1.1 主要特性

  • 轻量而且易于嵌入:只需几个C文件,没有外部依赖,一个x86下的简单的“hello world”程序只要180 KiB。
  • 具有极低启动时间的快速解释器: 在一台单核的台式PC上,大约在100秒内运行ECMAScript 测试套件56000次。运行时实例的完整生命周期在不到300微秒的时间内完成。
  • 几乎完整实现ES2019支持,包括: 模块,异步生成器和和完整Annex B支持 (传统的Web兼容性)。许多ES2020中带来的特性也依然会被支持。
  • 通过100%的ECMAScript Test Suite测试。
  • 可以将Javascript源编译为没有外部依赖的可执行文件。
  • 使用引用计数(以减少内存使用并具有确定性行为)的垃圾收集与循环删除。
  • 数学扩展:BigInt, BigFloat, 运算符重载, bigint模式, math模式.
  • 在Javascript中实现的具有上下文着色和完成的命令行解释器。
  • 采用C包装库构建的内置标准库。

2. 用法

2.1 安装

提供Makefile可以在Linux或者MacOS/X上编译。通过使用MinGW工具在Linux主机上进行交叉编译,可以获得初步的Windows支持。

如果要选择特定选项,请编辑Makefile顶部,然后运行make

使用root身份执行 make install 可以将编译的二进制文件和支持文件安装到 /usr/local (这不是使用QuickJS所必需的).

2.2 快速入门

qjs命令行解析器 (Read-Eval-Print Loop). 您可以将Javascript文件和/或表达式作为参数传递以执行它们:

./qjs examples/hello.js

qjsc命令行编译器:

./qjsc -o hello examples/hello.js
./hello

生成一个没有外部依赖的 hello 可执行文件。

2.3 命令行选项

2.3.1 qjs解释器

用法: qjs [options] [files]

选项说明
-h / --help命令行选项列表
-e EXPR / --eval EXPR执行EXPR
-i / --interactive转到交互模式(在命令行上提供文件时,它不是默认模式).
-m / --module作为ES6模块加载(默认=自动检测)。如果一个模块的文件名的扩展名是.mjs,或者源文件的第一个关键字是import,则模块会被自动检测。
–script作为ES6脚本加载(默认=自动检测)。
–bignum启用bignum扩展。BigDecimal对象、BigFloat对象和 "use math "指令。
-I file / --include file包括一个附加的文件。

高级选项:

选项说明
–std使加载的脚本可以使用std和os模块,即使它不是一个模块。
-d / --dump转存内存使用情况统计信息。
-q / --quit只是实例化解释器并退出。

2.3.2 qjsc编译器

用法: qjsc [options] [files]

选项说明
-c只输出C文件中的字节码。默认是输出一个可执行文件。
-e在一个C文件中输出main()和字节码。默认是输出一个可执行文件
-o output设置输出文件名(默认= out.c或a.out)。
-N cname设置生成数据的C名称。
-m编译为Javascript模块(默认=自动检测)。
-D module_name编译一个动态加载的模块和它的依赖项。当你的代码使用import关键字或os.Worker构造函数时,需要这个选项,因为编译器不能静态地找到动态加载的模块的名称。
-M module_name[,cname]为一个外部C模块添加初始化代码。见c_module示例
-x字节交换输出(仅用于交叉编译)。
-flto使用链接时间优化。编译速度较慢,但可执行文件较小 和更快。当使用-fno-x选项时,这个选项会自动设置。
-fno-[eval|string-normalize|regexp|json|proxy|map|typedarray|promise|bigint]禁用选定的语言功能,以产生一个较小的可执行文件。
-fbignum启用bignum扩展:BigDecimal对象、BigFloat对象和 "use math "指令。

2.4 qjscalc 应用程序

qjscalc应用程序是qjsbn命令行解释器的超集,它实现了一个具有任意大整数和浮点数,分数,复数,多项式和矩阵的Javascript计算器。源代码在qjscalc.js中。http://numcalc.com上提供了更多文档和Web版本。

2.5 内置测试

运行make test以运行QuickJS存档中包含的一些内置测试。

2.6 Test262 (ECMAScript测试套件)

test262是一个测试套件,旨在检查JavaScript实现和ECMA-262,ECMAScript语言规范(目前是5.1版)之间的一致性。该测试套件包含数千个单独的测试,每个测试都是对ECMAScript语言规范的一些具体要求进行测试。

一个test262运行器包含在QuickJS档案中。test262的测试可以安装在 QuickJS源代码目录中。

git clone https://github.com/tc39/test262.git test262
cd test262
patch -p1 < ../tests/test262.patch
cd ..

该补丁添加了特定的harness函数,并优化了低效的RegExp正则字符类和Unicode属性转义测试(测试本身没有被修改,只是优化了一个缓慢的字符串初始化函数)。

测试可以通过以下方式运行

make test2

欲了解更多信息,请运行./run-test262,查看test262的命令行选项。

3. 规格

3.1 语言支持

3.1.1 ES2020支持

ES2020规范几乎完全得到支持,包括附录B(传统的Web兼容性)和Unicode相关功能。

目前还不支持以下功能:

  • 尾部调用

3.1.2 ECMA402

不支持ECMA402(国际化API)。

3.1.3 扩展

  • 指令 "use strip" 不保留调试信息 (包括函数源代码) 以节省内存。 与"use strict"指令一样,它可以应用全局脚本,或者特定函数。
  • 脚本开头第一行 #! 会被忽略

3.1.4 数学扩展

数学扩展与标准Javascript完全向后兼容。参见 jsbignum.pdf获取更多信息。

  • 支持BigDecimal:以10为基数的任意大浮点数
  • 支持BigFloat:以2为基数的任意大浮点数
  • 运算符重载
  • 指令 "use bigint "启用了bigint模式,在该模式下整数默认为BigInt。
  • 指令"use math"启用数学模式,其中整数上的除法和幂运算符产生分数。浮点符号默认为BigFloat,整数符号默认为BigInt。

3.2 模块

完全支持ES6模块。默认的名称解析规则如下:

  • 模块名称带有前导...是相对于当前模块的路径。
  • 模块名称没有前导...是系统模块,例如stdos
  • 模块名称以.so结尾,是使用QuickJS C API的原生模块

3.3 标准库

默认情况下,标准库包含在命令行解释器中。它包含了两个模块std和os,以及一些全局对象。

3.3.1 全局对象

scriptArgs:

提供了命令行参数。第一个参数是脚本名称。

print(...args):

打印由空格和尾部换行符分隔的参数。

console.log(...args):

print()相同

3.3.2 std模块

std模块提供了对libc stdlib.h和stdio.h以及其他一些工具的封装。

可用的导出内容:

导出内容描述
exit(n)退出进程
evalScript(str, options = undefined)将字符串str作为一个脚本运行(全局eval)。
loadScript(filename)将文件filename以脚本方式运行(全局eval)
loadFile(filename)加载文件名,并假设为UTF-8编码,将其作为一个字符串返回。如果出现I/O错误,则返回null。
open(filename, flags, errorObj = undefined)打开一个文件(对libc fopen()的封装)。返回FILE对象或在I/O错误的情况下返回空值null。如果errorObj不是undefined,则将其errno属性设置为错误代码,如果未发生错误,则设置为0。
popen(command, flags, errorObj = undefined)通过创建一个管道打开一个进程(对libc popen()的封装)。返回FILE对象或在I/O错误的情况下返回空值null。如果errorObj不是undefined,将其errno 属性为错误代码,如果没有发生错误则为0。
fdopen(fd, flags, errorObj = undefined)从一个文件句柄打开一个文件(对libc fdopen()的封装)。返回FILE对象或者在I/O错误的情况下返回空值null。如果errorObj不是undefined,将其errno 属性为错误代码,如果没有错误发生则为0。
tmpfile(errorObj = undefined)打开一个临时文件。返回FILE对象或在I/O错误的情况下返回空值null。如果 errorObj不是undefined,将其errno属性设置为错误代码,如果没有发生错误发生则为0。
puts(str)相当于std.out.puts(str)。
printf(fmt, …args)相当于std.out.printf(fmt, …args)。
sprintf(fmt, …args)相当于libc的sprintf()。
in / out / err对libc文件的stdin, stdout, stderr的封装
SEEK_SET / SEEK_CUR / SEEK_ENDseek()的常量
Error枚举对象,包含常见错误的整数值(可定义额外的错误代码):EINVAL EIO EACCES EEXIST ENOSPC ENOSYS EBUSY ENOENT EPERM EPIPE
strerror(errno)返回一个描述错误errno的字符串
gc()手动调用循环清除算法。循环清除算法在需要时自动启动,所以这个功能在特定的内存限制或测试时很有用。
getenv(name)返回环境变量名称的值,如果没有定义,则返回undefined
setenv(name, value)将环境变量名称的值设置为字符串值
unsetenv(name)删除环境变量名称。
getenviron()返回一个包含环境变量键值对的对象。
urlGet(url, options = undefined)使用curl命令行工具下载url。 options是一个可选的对象,包含以下可选的属性:①binary:布尔值(默认=false)。如果为真,响应是一个ArrayBuffer,而不是字符串。当返回一个字符串时,数据被假定为UTF-8 编码的。 ②full:布尔值(默认=false)。如果为真,则返回一个包含以下属性的对象:response(响应内容)、responseHeaders(用CRLF分隔的头信息)、status(状态码)。当出现协议或网络错误时response为null。如果full为false,则仅在状态码在200到299之间时才返回响应。否则,将返回null。
parseExtJSON(str)使用JSON.parse的超集来解析str。

FILE对象原型的方法

方法名描述
close()关闭该文件。如果OK则返回0,如果I/O错误则返回-errno。
puts(str)输出UTF-8编码的字符串。
printf(fmt, …args)格式化printf,与libc中的printf格式相同。
flush()刷新缓冲区的文件。
seek(offset, whence)寻找到一个给定的文件位置(即std.SEEK_*)。偏移量可以是一个数字或一个 bigint。如果OK则返回0,如果I/O错误则返回-errno。
tell()返回当前文件的位置。
tello()以bigint形式返回当前文件位置
eof()如果到达文件末尾,返回true。
fileno()返回关联的os句柄。
error()如果有错误,返回true。
clearerr()清除错误指示。
read(buffer, position, length)从文件中读取length个字节到ArrayBuffer缓冲区的字节位置(对libc库中fread的封装)。
write(buffer, position, length)从ArrayBuffer缓冲区的字节位置向文件写入length个字节(对libc库中fwrite的封装)。
getline()返回文件的下一行,假设为UTF-8编码。不包括尾部的换行。
readAsString(max_size = undefined)从文件中读取max_size字节,并假设为UTF-8编码,将其作为一个字符串返回。如果max_size不存在,文件就会被读到尽头。
getByte()返回文件中的下一个字节。如果到达文件的末端,则返回-1。
putByte©写一个字节到文件。

3.3.3 os模块

os模块提供操作系统的具体功能。

  • 低级别的文件访问
  • 信号
  • 计时器
  • 异步I/O
  • workers(工作者线程)

OS函数通常在OK时返回0,或者返回一个OS特定的负数错误代码。

可用导出内容:

导出内容说明
open(filename, flags, mode = 0o666)打开一个文件。返回一个文件句柄fd,如果出错则返回< 0。其中flags包括:O_RDONLY O_WRONLY O_RDWR O_APPEND O_CREAT O_EXCL O_TRUNC(POSIX打开标志) O_TEXT(Windows专用。以文本模式打开文件。默认是二进制模式)
close(fd)关闭文件句柄fd。
seek(fd, offset, whence)在文件中寻找。whence参数使用std.SEEK_*表示从何处开始。offset是一个数字或一个bigint。 如果offset是一个bigint,也将返回一个bigint。
read(fd, buffer, offset, length)从文件句柄fd读取length个字节到ArrayBuffer缓冲区的指定字节位置offset。返回读取的字节数,如果出错则返回< 0。
write(fd, buffer, offset, length)从ArrayBuffer缓冲区的offset(字节位置)向文件句柄fd写入length个字节 。返回写入的字节数,如果出错则<0。
isatty(fd)如果fd是一个TTY(终端)句柄,则返回true。
ttyGetWinSize(fd)返回TTY尺寸为[width, height],如果没有则为空。
ttySetRaw(fd)将TTY设置为原始模式。
remove(filename)删除一个文件。如果OK则返回0,否则返回-errno。
rename(oldname, newname)重命名一个文件。如果OK则返回0,否则返回-errno。
realpath(path)返回 [str, err] 其中str是规范化绝对路径名,err是错误代码。
getcwd()返回 [str, err] 其中str是当前工作目录,err是错误代码。
chdir(path)改变当前目录。如果OK则返回0,否则返回-errno。
mkdir(path, mode = 0o777)在路径上创建一个目录。如果OK则返回0,否则返回-errno。
utimes(path, atime, mtime)改变文件路径的访问和修改时间。这些时间被指定为以毫秒为单位,从1970年开始。如果OK则返回0,否则返回-errno。
symlink(target, linkpath)在linkpath创建一个包含目标字符串的链接。如果OK则返回0,否则返回-errno。
readlink(path)返回 [str, err] 其中str是链接目标,err是错误代码。
readdir(path)返回 [array, err] 其中array是一个字符串数组,包含目录路径的文件名,err是错误代码。
setReadHandler(fd, func)向文件句柄fd中添加一个读取处理程序。 每次 fd 有待处理的数据时都会调用 func。 支持每个文件句柄拥有一个读取处理程序。 使用 func = null 删除读处理程序。
setWriteHandler(fd, func)向文件句柄 fd 添加一个写处理程序。 每次可以将数据写入 fd 时都会调用 func。 支持每个文件句柄拥有一个写入处理程序。 使用 func = null 删除写处理程序。
signal(signal, func)当信号发生时调用函数 func 。 每个信号编号仅支持一个处理程序。 使用 null 设置默认处理程序或使用 undefined 忽略信号。 信号处理程序只能在主线程中定义。
kill(pid, sig)向进程pid发送信号sig
exec(args[, options])执行一个带有参数args的进程。options是可选参数
waitpid(pid, options)waitpid Unix系统调用。返回数组[ret, status],ret包含-errno 以防出现错误。
dup(fd)dup Unix 系统调用
dup2(oldfd, newfd)dup2 Unix 系统调用
pipe()pipe Unix 系统调用,返回两个句柄[read_fd, write_fd],如果出错则为空
sleep(delay_ms)休眠delay_ms毫秒
setTimeout(func, delay)在延迟ms后调用函数func。返回定时器的句柄
clearTimeout(handle)取消一个定时器
platform返回一个代表平台的字符串。“linux”、“darwin”、"win32 "或 “js”
Worker(module_filename)这是一个构造函数,该构造函数用于创建一个新的线程(工作者线程),其API与WebWorkers接近。 module_filename是一个字符串,指定在新创建的线程中执行的模块文件名。对于动态导入的模块,它是相对于当前脚本或模块的路径。线程通常不共享任何数据,并且互相之间用消息进行通信。不支持嵌套的工作者。test/test_worker.js中提供了一个例子。

其中工作者线程类拥有以下静态属性:

  • parent:在已创建的worker中,Worker.parent表示父worker,用于发送或接收消息。

worker对象实例具有以下属性:

  • postMessage(msg):向相应的worker发送消息。 SharedArrayBuffer 在 worker 之间共享。 当前限制:尚不支持 Map 和 Set。

  • onmessage:获取器和设置器(Getter and setter)。 设置每次收到消息时调用的函数。该函数拥有一个参数, 该参数是一个对象,有一个包含收到的消息数据的属性。 如果至少有一个非空的 onmessage 处理程序,则线程不会终止。

3.4 QuickJS C API

C API 的设计在简单高效。 C API 在头文件 quickjs.h 中定义。

3.4.1 运行时和上下文

JSRuntime代表一个对应于对象堆的Javascript运行时。多个运行时可以同时存在,但它们不能交换对象。在一个给定的运行时内,不支持多线程。

JSContext 代表一个 Javascript 上下文(或 Realm)。 每个 JSContext 都有自己的全局对象和系统对象。 每个 JSRuntime 可以有多个 JSContext 并且它们可以共享对象,类似于在 Web 浏览器中共享 Javascript 对象的同源框架。

3.4.2 JSValue

JSValue 表示一个 Javascript 值,它可以是原始类型或对象。 使用引用计数,因此显式复制(JS_DupValue(),增加引用计数)或释放(JS_FreeValue(),减少引用计数)JSValues 很重要。

3.4.3 C函数

C函数可以用JS_NewCFunction()创建。JS_SetPropertyFunctionList()是一个捷径,可以很容易地将函数、设置器和获取器(setters and getters)属性添加到一个给定的对象中。

与其他嵌入式Javascript引擎不同的是,这里没有隐式堆栈,因此 C 函数可以像普通 C 参数一样获取它们的参数。 一般来说,C 函数将常量 JSValues 作为参数(所以他们不需要释放它们),并返回一个新分配的(=live)JSValue。

3.4.4 异常

异常:大多数 C 函数都可以返回 Javascript 异常。 它必须由 C 代码显式测试和处理。 具体的 JSValue JS_EXCEPTION 表示发生了异常。 实际的异常对象存储在 JSContext 中,可以使用 JS_GetException() 检索。

3.4.5 Script代码执行

使用JS_Eval()来执行一个脚本或模块的源代码

如果脚本或模块使用 qjsc 编译为字节码,则可以通过调用 js_std_eval_binary()来执行它。 优点是不需要编译,所以它更快更小,因为如果不需要 eval,编译器可以从可执行文件中删除。

注意:字节码格式与特定的QuickJS版本相联系。此外,在执行前没有进行安全检查。这就是为什么在qjsc中没有选项可以将字节码输出到二进制文件。

3.4.6 JS类

C不透明数据可以附加到一个Javascript对象上。C不透明数据的类型是由该对象的类ID(JSClassID)决定的。因此,第一步是注册一个新的类ID和JS类(JS_NewClassID(), JS_NewClass())。然后,您可以使用JS_NewObjectClass()创建此类的对象,并使用JS_GetOpaque() / JS_SetOpaque()获取或设置C不透明点。

在定义一个新的 JS 类时,可以声明一个终结器,它会在对象被销毁时调用。 终结器应该用于释放 C 资源。 从中执行JS代码是无效的。 可以提供 gc_mark 方法,以便循环删除算法可以找到此对象引用的其他对象。 其他方法可用于定义奇异对象行为。

类的ID是全局分配的(即对所有运行时)。JSClass是按JSRuntime分配的。JS_SetClassProto()用于在一个给定的JSContext中为一个给定的类定义一个原型。JS_NewObjectClass()在创建的对象中设置这个原型。

在quickjs-libc.c中提供了一些示例。

3.4.7 C模块

支持原生 ES6 模块,可以动态或静态链接。 查看 test_bjson 和 bjson.so 示例。 标准库 quickjs-libc.c 也是原生模块的一个很好的例子。

3.4.8 内存处理

使用JS_SetMemoryLimit()为给定的JSRuntime设置一个全局内存分配限制。

JS_NewRuntime2() 可以提供自定义内存分配函数

可以使用 JS_SetMaxStackSize() 设置最大系统堆栈大小。

3.4.9 执行超时和中断

使用 JS_SetInterruptHandler() 设置一个回调,引擎在执行代码时会定期调用该回调。 此回调可用于实现执行超时

命令行解释器使用它来实现 Ctrl-C 处理程序。

4 内部实现

4.1 字节码

编译器直接生成字节码,没有解析树等中间表示,因此速度非常快。 对生成的字节码进行了多次优化。

我们选择了基于堆栈的字节码,因为它很简单,能生成紧凑的代码。

对于每个函数,最大堆栈大小是在编译时计算的,因此不需要在运行时进行堆栈溢出测试。

为调试信息维护一个单独的压缩行号表。

对闭包变量的访问进行了优化,几乎与局部变量一样快。

优化了严格模式下的直接eval

4.2 可执行文件的生成

4.2.1 qjsc编译器

qjsc 编译器从 Javascript 文件生成 C 源码。 默认情况下,C 源代码是使用系统编译器(gcc 或 clang)编译的。

生成的 C 源码包含已编译函数或模块的字节码。 如果需要一个完整的可执行文件,它还包含一个 main() 函数和必要的 C 代码来初始化 Javascript 引擎并加载和执行编译的函数和模块。

Javascript代码可以与C模块混合使用

为了拥有更小的可执行文件,可以禁用特定的 Javascript 功能,特别是 eval 或正则表达式。 代码移除了依赖于系统编译器的链接时间优化。

4.2.2 二进制JSON

qjsc的工作方式是编译脚本或模块,然后将其序列化为二进制格式。这种格式的一个子集(没有函数或模块)可以作为二进制JSON使用。test_bjson.js这个例子展示了如何使用它。

警告:二进制JSON格式可能会在没有通知的情况下改变,所以它不应该被用来存储持久性数据。test_bjson.js示例仅用于测试二进制对象格式的功能。

4.3 运行时

4.3.1 Strings字符串

字符串存储为 8 位或 16 位字符数组。 因此随机访问字符速度很快。

C API提供了将Javascript字符串转换为C语言UTF-8编码字符串的函数。最常见的情况是,Javascript字符串只包含ASCII字符,不涉及复制。

4.3.2 Objects对象

对象的形状(对象的原型、属性名称和标志)在对象之间共享,以节省内存。

没有holes的数组(除了在数组末尾)已被优化。

TypedArray定型数组的访问已被优化。

4.3.3 Atoms原子

对象属性名称和一些字符串存储为Atoms(唯一字符串)以节省内存并允许快速比较。 Atoms表示为 32 位整数。Atoms范围的一半用于保留从0到pow(2,31) - 1的整数

4.3.4 Numbers数字

数字被表示为32位有符号整数或64位IEEE-754浮点值。对于 32 位整数,大多数操作都有快速方式。

4.3.5 垃圾回收

引用计数用于自动且确定性地释放对象。 当分配的内存变得太大时,会执行单独的循环删除过程。 循环删除算法仅使用引用计数和对象内容,因此不需要在 C 代码中显式的进行垃圾回收。

4.3.6 JSValue

它是一个Javascript值,可以是原始类型(如Number, String, …),也可以是一个Object。在 32 位版本中,NaN装箱用于存储64位浮点数。该表示法经过了优化,以便可以有效地测试 32 位整数和引用计数值。

在64位代码中,JSValue是128位大的,没有使用NaN编码。其理由是,在64位代码中,内存的使用不太重要。

在这两种情况下(32 位或 64 位),JSValue 正好适合两个 CPU 寄存器,因此它可以被 C 函数有效地返回。

4.3.7 函数调用

引擎经过优化,因此函数调用速度很快。 系统堆栈保存 Javascript 参数和局部变量。

4.4 RegExp正则表达式

我们开发了一个特定的正则表达式引擎。它既小又高效,支持所有ES2020的功能,包括Unicode属性。作为Javascript的编译器,它直接生成没有解析树的字节码。

使用显式堆栈的回溯使得系统堆栈上没有递归。简单的量化器经过专门优化,以避免递归。

避免了来自空项的量化器的无限递归。

完整的正则表达式库约 15 LiB(x86 代码),不包括 Unicode 库。

4.5 Unicode

开发了一个特定的 Unicode 库,因此不依赖于外部大型 Unicode 库,例如 ICU。 所有 Unicode 表都被压缩,同时保持合理的访问速度。

该库支持大小写转换、Unicode规范化、Unicode脚本查询、Unicode一般类别查询和所有Unicode二进制属性。

完整的 Unicode 库约 45 KiB(x86 代码)

4.6 BigInt, BigFloat, BigDecimal

BigInt、BigFloat和BigDecimal是用libbf库实现的。大小约为 90 KiB(x86代码),提供任意精度的IEEE 754浮点运算和具有精确四舍五入的超越函数。

5. 许可协议

QuickJS是在MIT许可下发布的。

除非另有说明,QuickJS的版权归Fabrice Bellard和Charlie Gordon所有。

 类似资料: