后端:跨端轻量JavaScript引擎的实现与探索

news2025/7/15 14:58:49

一、JavaScript

1.JavaScript语言

JavaScript是ECMAScript的实现,由ECMA 39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。

ECMAScript发展史:

时间版本说明
1997年7月ES1.0 发布当年7月,ECMA262 标准出台
1998年6月ES2.0 发布该版本修改完全符合ISO/IEC 16262国际标准。
1999年12月ES3.0 发布成为 JavaScript 的通行标准,得到了广泛支持
2007年10月ES4.0草案发布各大厂商意见分歧,该方案未通过
2008年7月发布ES3.1,并改名为ECMAScript 5废除ECMAScript 4.0,所以4.0版本不存在
2009年12月ESt 5.0 正式发布
2011年6月ES5.1 发布该版本成为了 ISO 国际标准(ISO/IEC 16262:2011)
2013年12月ES6 草案发布
2015年6月ES6 正式发布,并且更名为“ECMAScript 2015”TC39委员会决定每年发布一个ECMAScript 的版本
2.JavaScript引擎

JavaScript引擎是指用于处理以及执行JavaScript脚本的虚拟机。

常见的JavaScript引擎:



引擎所属机构/个人浏览器说明
SpiderMonkeyMozillaFirefox第一款JavaScript引擎,早期用于 Netscape Navigator,现时用于 Mozilla Firefox。是用C语言实现的,还有一个Java版本叫Rhino;Rhino引擎由Mozilla基金会管理,开放源代码,完全以Java编写,用于 HTMLUnit;而后TraceMonkey引擎是基于实时编译的引擎,用于Mozilla Firefox 3.5~3.6版本;JaegerMonkey:结合追踪和组合码技术大幅提高性能,用于Mozilla Firefox 4.0以上版本
JavaScriptCoreAppleSafari简称JSC,开源,用于webkit内核浏览器,如 Safari ,2008 年实现了编译器和字节码解释器,升级为了SquirrelFish。苹果内部代号为Nitro的 JavaScript 引擎也是基于 JSC引擎的。至于具体时间,JSC是WebKit默认内嵌的JS引擎,而WebKit诞生于1998年,Nitro是为Safari 4编写,Safari 4是2009年6月发布。
V8GoogleChrome2008年9月,Google的V8引擎第一个版本随着Chrome的第一个版本发布。V8引擎用 C++编写,由 Google 丹麦开发,开源。除了Chrome,还被运用于Node.js以及运用于Android操作系统等
ChakraMicrosoftEdge、IE译名查克拉,用于IE9、10、11和Microsoft Edge,IE9发布时间2011年3月
JerryScript三星三星推出的适用于嵌入式设备的小型 JavaScript 引擎,2015年开源
NashornOracale从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎,JDK1.8发布于2014年
QuickJSFabrice BellardQuickJS 是一个小型的嵌入式 Javascript 引擎。 它支持 ES2023 规范,包括模块、异步生成器、代理和 BigInt。 它可以选择支持数学扩展,例如大十进制浮点数 (BigDecimal)、大二进制浮点数 (BigFloat) 和运算符重载。
HermesFacebook引擎,Facebook在Chain React 2019 大会上发布的一个崭新JavaScript引擎,用于移动端React Native应用的集成,开源
3.JavaScript引擎工作原理
a.V8引擎工作原理



b.Turbofan技术实例说明
function sum(a, b) {
    return a + b;
}

这里ab可以是任意类型数据,当执行sum函数时,Ignition解释器会检查ab的数据类型,并相应地执行加法或者连接字符串的操作。

如果 sum函数被调用多次,每次执行时都要检查参数的数据类型是很浪费时间的。此时TurboFan就出场了。它会分析函数的执行信息,如果以前每次调用sum函数时传递的参数类型都是数字,那么TurboFan就预设sum的参数类型是数字类型,然后将其编译为机器码。

但是如果某一次的调用传入的参数不再是数字时,表示TurboFan的假设是错误的,此时优化编译生成的机器代码就不能再使用了,于是就需要进行回退到字节码的操作。



三、QuickJS

1.QuickJS作者简介

法布里斯·貝拉 (Fabrice Bellard)



2.QuickJS简介

QuickJS 是一个小型的嵌入式 Javascript 引擎。 它支持 ES2023 规范,包括模块、异步生成器、代理和 BigInt。

它可以选择支持数学扩展,例如大十进制浮点数 (BigDecimal)、大二进制浮点数 (BigFloat) 和运算符重载。

•小且易于嵌入:只需几个 C 文件,无外部依赖项,一个简单的 hello world 程序的 210 KiB x86 代码。

•启动时间极短的快速解释器:在台式 PC 的单核上运行 ECMAScript 测试套件的 76000 次测试只需不到 2 分钟。 运行时实例的完整生命周期在不到 300 微秒的时间内完成。

•几乎完整的 ES2023 支持,包括模块、异步生成器和完整的附录 B 支持(旧版 Web 兼容性)。

•通过了近 100% 的 ECMAScript 测试套件测试: Test262 Report(test262.fyi)。

•可以将 Javascript 源代码编译为可执行文件,无需外部依赖。

•使用引用计数(以减少内存使用并具有确定性行为)和循环删除的垃圾收集。

•数学扩展:BigDecimal、BigFloat、运算符重载、bigint 模式、数学模式。

•用 Javascript 实现的带有上下文着色的命令行解释器。

•带有 C 库包装器的小型内置标准库。

3.QuickJS工程简介
5.94MB quickjs
├── 17.6kB      cutils.c                /// 辅助函数
├── 7.58kB      cutils.h                /// 辅助函数
├── 241kB       libbf.c                 /// BigFloat相关
├── 17.9kB      libbf.h                 /// BigFloat相关
├── 2.25kB      libregexp-opcode.h      /// 正则表达式操作符
├── 82.3kB      libregexp.c             /// 正则表达式相关
├── 3.26kB      libregexp.h             /// 正则表达式相关
├── 3.09kB      list.h                  /// 链表实现
├── 16.7kB      qjs.c                   /// QuickJS stand alone interpreter
├── 22kB        qjsc.c                  /// QuickJS command line compiler
├── 73.1kB      qjscalc.js              /// 数学计算器
├── 7.97kB      quickjs-atom.h          /// 定义了javascript中的关键字
├── 114kB       quickjs-libc.c
├── 2.57kB      quickjs-libc.h          /// C API
├── 15.9kB      quickjs-opcode.h        /// 字节码操作符定义
├── 1.81MB      quickjs.c               
├── 41.9kB      quickjs.h               /// QuickJS Engine
├── 49.8kB      repl.js                 /// REPL
├── 218kB       libunicode-table.h      /// unicode相关
├── 53kB        libunicode.c            /// unicode相关
├── 3.86kB      libunicode.h            /// unicode相关
├── 86.4kB      unicode_gen.c           /// unicode相关
└── 6.99kB      unicode_gen_def.h       /// unicode相关

4.QuickJS工作原理

QuickJS的解释器是基于栈的。





QuickJS的对byte-code会优化两次,通过一个简单例子看看QuickJS的字节码与优化器的输出,以及执行过程。

function sum(a, b) {
    return a + b;
}

•第一阶段(未经过优化的字节码)

;; function sum(a, b) {

        enter_scope 1    

;;     return a + b;

        line_num 2
        scope_get_var a,1    ///通用的获取变量的指令
        scope_get_var b,1    
        add
        return

;; }

•第二阶段

;; function sum(a, b) {
;;     return a + b;

        line_num 2
        get_arg 0: a        /// 获取参数列表中的变量
        get_arg 1: b
        add
        return
;; }

•第三阶段

;; function sum(a, b) {
;;     return a + b;

        get_arg0 0: a        /// 精简成获取参数列表中第0个参数
        get_arg1 1: b
        add
        return

;; }

sum(1,2);

通过上述简单的函数调用,观察sum函数调用过程中栈帧的变化,通过计算可知sum函数最栈帧大小为两个字节

get_arg0get_arg1addreturn
123将栈顶的数据3返回
1
5.内存管理

QuickJS通过引用计算来管理内存,在使用C API时需要根据不同API的说明手动增加或者减少引用计数器。

对于循环引用的对象,QuickJS通过临时减引用保存到临时数组中的方法来判断相互引用的对象是否可以回收。

6.QuickJS简单使用

从github上clone完最新的源码后,通过执行(macos 环境)以下代码即可在本地安装好qjs、qjsc、qjscalc几个命令行程序

sudo make
sudo make install

qjs: JavaScript代码解释器

qjsc: JavaScript代码编译器

qjscalc: 基于QuickJS的REPL计算器程序

通过使用qjs可以直接运行一个JavaScript源码,通过qsjc的如下命令,则可以输出一个带有byte-code源码的可直接运行的C源文件:

qjsc -e  -o add.c examples/add.js 

#include "quickjs-libc.h"

const uint32_t qjsc_add_size = 135;

const uint8_t qjsc_add[135] = {
 0x02, 0x06, 0x06, 0x73, 0x75, 0x6d, 0x0e, 0x63,
 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x06, 0x6c,
 0x6f, 0x67, 0x1e, 0x65, 0x78, 0x61, 0x6d, 0x70,
 0x6c, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x64, 0x2e,
 0x6a, 0x73, 0x02, 0x61, 0x02, 0x62, 0x0e, 0x00,
 0x06, 0x00, 0xa2, 0x01, 0x00, 0x01, 0x00, 0x05,
 0x00, 0x01, 0x25, 0x01, 0xa4, 0x01, 0x00, 0x00,
 0x00, 0x3f, 0xe3, 0x00, 0x00, 0x00, 0x40, 0xc2,
 0x00, 0x40, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x38,
 0xe4, 0x00, 0x00, 0x00, 0x42, 0xe5, 0x00, 0x00,
 0x00, 0x38, 0xe3, 0x00, 0x00, 0x00, 0xb8, 0xb9,
 0xf2, 0x24, 0x01, 0x00, 0xcf, 0x28, 0xcc, 0x03,
 0x01, 0x04, 0x1f, 0x00, 0x08, 0x0a, 0x0e, 0x43,
 0x06, 0x00, 0xc6, 0x03, 0x02, 0x00, 0x02, 0x02,
 0x00, 0x00, 0x04, 0x02, 0xce, 0x03, 0x00, 0x01,
 0x00, 0xd0, 0x03, 0x00, 0x01, 0x00, 0xd3, 0xd4,
 0x9e, 0x28, 0xcc, 0x03, 0x01, 0x01, 0x03,
};

static JSContext *JS_NewCustomContext(JSRuntime *rt)
{
  JSContext *ctx = JS_NewContextRaw(rt);
  if (!ctx)
    return NULL;
  JS_AddIntrinsicBaseObjects(ctx);
  JS_AddIntrinsicDate(ctx);
  JS_AddIntrinsicEval(ctx);
  JS_AddIntrinsicStringNormalize(ctx);
  JS_AddIntrinsicRegExp(ctx);
  JS_AddIntrinsicJSON(ctx);
  JS_AddIntrinsicProxy(ctx);
  JS_AddIntrinsicMapSet(ctx);
  JS_AddIntrinsicTypedArrays(ctx);
  JS_AddIntrinsicPromise(ctx);
  JS_AddIntrinsicBigInt(ctx);
  return ctx;
}

int main(int argc, char **argv)
{
  JSRuntime *rt;
  JSContext *ctx;
  rt = JS_NewRuntime();
  js_std_set_worker_new_context_func(JS_NewCustomContext);
  js_std_init_handlers(rt);
  JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
  ctx = JS_NewCustomContext(rt);
  js_std_add_helpers(ctx, argc, argv);
  js_std_eval_binary(ctx, qjsc_add, qjsc_add_size, 0);
  js_std_loop(ctx);
  js_std_free_handlers(rt);
  JS_FreeContext(ctx);
  JS_FreeRuntime(rt);
  return 0;
}


上面的这个C源文件,通过如下命令即可编译成可执行文件:

 gcc add.c -o add_exec -I/usr/local/include quickjs-libc.c quickjs.c cutils.c libbf.c libregexp.c libunicode.c  -DCONFIG_BIGNUM

也可以直接使用如下命令,将JavaScript文件直接编译成可执行文件:

qjsc -o add_exec examples/add.js

7.给qjsc添加扩展

QuickJS只实现了最基本的JavaScript能力,同时QuickJS也可以实现能力的扩展,比如给QuickJS添加打开文件并读取文件内容的内容,这样在JavaScript代码中即可通过js代码打开并读取到文件内容了。

通过一个例子来看看添加扩展都需要做哪些操作:

•编写一个C语言的扩展模块

#include "quickjs.h"
#include "cutils.h"

/// js中对应plus函数的C语言函数
static JSValue plusNumbers(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    int a, b;
    if (JS_ToInt32(ctx, &a, argv[0]))
        return JS_EXCEPTION;
    if (JS_ToInt32(ctx, &b, argv[1]))
        return JS_EXCEPTION;
    return JS_NewInt32(ctx, a + b);
}
/// 模块需要导致的列表
static const JSCFunctionListEntry js_my_module_funcs[] = {
    JS_CFUNC_DEF("plus", 2, plusNumbers),
};
/// 模块初始化函数,并将plus导出
static int js_my_module_init(JSContext *ctx, JSModuleDef *m) {
    return JS_SetModuleExportList(ctx, m, js_my_module_funcs, countof(js_my_module_funcs));
}
JSModuleDef *js_init_module_my_module(JSContext *ctx, const char *module_name) {
    JSModuleDef *m;
    m = JS_NewCModule(ctx, module_name, js_my_module_init);
    if (!m)
        return NULL;
    JS_AddModuleExportList(ctx, m, js_my_module_funcs, countof(js_my_module_funcs));
    return m;
}

•Makefile文件中添加my_module.c模块的编译

QJS_LIB_OBJS= ... $(OBJDIR)/my_module.o

•在qjsc.c文件中注册模块

namelist_add(&cmodule_list,“my_module”,“my_module”,0); 

•编写一个my_module.js测试文件

import * as mm from 'my_module';

const value = mm.plus(1, 2);
console.log(`my_module.plus: ${value}`);

•重新编译

sudo make && sudo make install
qjsc -m -o my_module examples/my_module.js /// 这里需要指定my_module模块

最终生成的my_module可执行文件,通过执行my_module输出:

my_module.plus: 3

8.使用C API

在第5个步骤时,生成了add.c文件中实际上已经给出了一个简单的使用C API最基本的代码。当编写一下如下的js源码时,会发现当前的qjsc编译后的可执行文件或者qjs执行这段js代码与我们的预期不符:

function getName() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("张三峰");
        }, 2000);
    });
}
console.log(`开始执行`);
getName().then(name => console.log(`promise name: ${name}`));








上面的代码并不会按预期的效果输出结果,因为js环境下的loop只执行了一次,任务队列还没有来得急执行程序就结束了,稍微改动一下让程序可以正常输出,如下:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <uv.h>
/* File generated automatically by the QuickJS compiler. */

#include "quickjs-libc.h"
#include <string.h>



static JSContext *JS_NewCustomContext(JSRuntime *rt) {
  JSContext *ctx = JS_NewContextRaw(rt);
  if (!ctx)
    return NULL;
  JS_AddIntrinsicBaseObjects(ctx);
  JS_AddIntrinsicDate(ctx);
  JS_AddIntrinsicEval(ctx);
  JS_AddIntrinsicStringNormalize(ctx);
  JS_AddIntrinsicRegExp(ctx);
  JS_AddIntrinsicJSON(ctx);
  JS_AddIntrinsicProxy(ctx);
  JS_AddIntrinsicMapSet(ctx);
  JS_AddIntrinsicTypedArrays(ctx);
  JS_AddIntrinsicPromise(ctx);
  JS_AddIntrinsicBigInt(ctx);
  return ctx;
}

JSRuntime *rt = NULL;
JSContext *ctx = NULL;
void *run(void *args) {
    const char *file_path = "/Volumes/Work/分享/quickjs/code/quickjs/examples/promise.js";
    size_t pbuf_len = 0;
    
    
    js_std_set_worker_new_context_func(JS_NewCustomContext);
    js_std_init_handlers(rt);
    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
    ctx = JS_NewCustomContext(rt);
    
    
    js_std_add_helpers(ctx, 0, NULL);
    js_init_module_os(ctx, "test");
    const uint8_t *code = js_load_file(ctx, &pbuf_len, file_path);
    JSValue js_ret_val = JS_Eval(ctx, (char *)code, pbuf_len, "add", JS_EVAL_TYPE_MODULE);
    if(JS_IsError(ctx, js_ret_val) || JS_IsException(js_ret_val)) {
        js_std_dump_error(ctx);
    }
    return NULL;
}

pthread_t quickjs_t;
int main(int argc, char **argv) {
    rt = JS_NewRuntime();
    pthread_create(&quickjs_t, NULL, run, NULL);
    while (1) {
        if(ctx) js_std_loop(ctx);
    }
    js_std_free_handlers(rt);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
    return 0;
}




这样的操作只适合用于测试一下功能,实际生产中使用需要一个即可以在必要的时候调用loop又可以做到不抢占过多的CPU或者只抢占较少的CPU时间片。



四、libuv

1.libuv简价

libuv 是一个使用C语言编写的多平台支持库,专注于异步 I/O。 它主要是为 Node.js 使用而开发的,但 Luvit、Julia、uvloop 等也使用它。

功能亮点

•由 epoll、kqueue、IOCP、事件端口支持的全功能事件循环。

•异步 TCP 和 UDP 套接字

•异步 DNS 解析

•异步文件和文件系统操作

•文件系统事件

•ANSI 转义码控制的 TTY

•具有套接字共享的 IPC,使用 Unix 域套接字或命名管道 (Windows)

•子进程

•线程池

•信号处理

•高分辨率时钟

•线程和同步原语

2.libuv运行原理





int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  ...
  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);
  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);
    ...
    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);
    ...
  }
}

3.简单使用
static void timer_cb(uv_timer_t *handler) {
    printf("timer_cb exec.\r\n");
}

int main(int argc, const char * argv[]) {
   uv_loop_t *loop = uv_default_loop();
   uv_timer_t *timer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
   uv_timer_init(loop, timer);
   uv_timer_start(timer, timer_cb, 2000, 0);
   uv_run(loop, UV_RUN_DEFAULT);
}

五、QuickJS + libuv

console.log(`开始执行`);
function getName() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("张三峰");
        }, 2000);
    });
}
getName().then(name => console.log(`promise name: ${name}`));






#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <uv.h>
/* File generated automatically by the QuickJS compiler. */

#include "quickjs-libc.h"
#include <string.h>


typedef struct once_timer_data {
    JSValue func;
    JSValue this_val;
    JSContext *ctx;
} once_timer_data;

void once_timer_cb(uv_timer_t *once_timer) {
    once_timer_data *data = (once_timer_data *)once_timer->data;
    JSContext *ctx = data->ctx;
    JSValue js_ret_val = JS_Call(data->ctx, data->func, data->this_val, 0, NULL);
    if(JS_IsError(ctx, js_ret_val) || JS_IsException(js_ret_val)) {
        js_std_dump_error(ctx);
    }
    JS_FreeValue(data->ctx, js_ret_val);
    JS_FreeValue(data->ctx, data->func);
    JS_FreeValue(data->ctx, data->this_val);
    free(data);
    uv_timer_stop(once_timer);
    free(once_timer);
}

void check_cb(uv_check_t *check) {
    JSContext *ctx = (JSContext *)check->data;
    js_std_loop(ctx);
}
void idle_cb(uv_idle_t *idle) {
    
}


JSValue set_timeout(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
 
    if(argc != 2) return JS_NULL;
    JSValue func_val = argv[0];
    JSValue delay_val = argv[1];
    int64_t delay = 0;
    int ret = JS_ToInt64(ctx, &delay, delay_val);
    if(ret < 0) js_std_dump_error(ctx);
    uv_timer_t *once_timer = (uv_timer_t *)malloc(sizeof(uv_timer_t));
    once_timer_data *data = (once_timer_data *)malloc(sizeof(once_timer_data));
    data->func = JS_DupValue(ctx, func_val);
    data->this_val = JS_DupValue(ctx, this_val);
    data->ctx = ctx;
    once_timer->data = data;
    uv_timer_init(uv_default_loop(), once_timer);
    uv_timer_start(once_timer, once_timer_cb, delay, 0);
    JSValue js_timer = JS_NewInt64(ctx, (uint64_t)once_timer);
    return js_timer;
}



static JSContext *JS_NewCustomContext(JSRuntime *rt) {
  JSContext *ctx = JS_NewContextRaw(rt);
  if (!ctx)
    return NULL;
  JS_AddIntrinsicBaseObjects(ctx);
  JS_AddIntrinsicDate(ctx);
  JS_AddIntrinsicEval(ctx);
  JS_AddIntrinsicStringNormalize(ctx);
  JS_AddIntrinsicRegExp(ctx);
  JS_AddIntrinsicJSON(ctx);
  JS_AddIntrinsicProxy(ctx);
  JS_AddIntrinsicMapSet(ctx);
  JS_AddIntrinsicTypedArrays(ctx);
  JS_AddIntrinsicPromise(ctx);
  JS_AddIntrinsicBigInt(ctx);
  return ctx;
}


void js_job(uv_timer_t *timer) {
    JSRuntime *rt = timer->data;
    const char *file_path = "/Volumes/Work/分享/quickjs/code/quickjs/examples/promise.js";
    size_t pbuf_len = 0;
    
    JSContext *ctx;
    js_std_set_worker_new_context_func(JS_NewCustomContext);
    js_std_init_handlers(rt);
    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
    ctx = JS_NewCustomContext(rt);
    
    uv_check_t *check = (uv_check_t *)malloc(sizeof(uv_check_t));
    uv_check_init(uv_default_loop(), check);
    check->data = ctx;
    uv_check_start(check, check_cb);
    
    JSValue global = JS_GetGlobalObject(ctx);
    JSValue func_val = JS_NewCFunction(ctx, set_timeout, "setTimeout", 1);
    JS_SetPropertyStr(ctx, global, "setTimeout", func_val);
    JS_FreeValue(ctx, global);
    
    
    js_std_add_helpers(ctx, 0, NULL);
    js_init_module_os(ctx, "test");
    const uint8_t *code = js_load_file(ctx, &pbuf_len, file_path);
    JSValue js_ret_val = JS_Eval(ctx, (char *)code, pbuf_len, "add", JS_EVAL_TYPE_MODULE);
    if(JS_IsError(ctx, js_ret_val) || JS_IsException(js_ret_val)) {
        js_std_dump_error(ctx);
    }
    js_std_free_handlers(rt);
    JS_FreeContext(ctx);
  
}


int main(int argc, char **argv) {
    JSRuntime *rt = JS_NewRuntime();
    uv_loop_t *loop = uv_default_loop();
    
    uv_timer_t *timer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
    timer->data = rt;
    uv_timer_init(loop, timer);
    uv_timer_start(timer, js_job, 0, 0);
    
    uv_idle_t *idle = (uv_idle_t *)malloc(sizeof(uv_idle_t));
    uv_idle_init(loop, idle);
    uv_idle_start(idle, idle_cb);

    uv_run(loop, UV_RUN_DEFAULT);
    JS_FreeRuntime(rt);
    return 0;
}




作者:京东零售客服研发 游小彬

来源:京东零售技术 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1473832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年sCrypt编程马拉松即将开幕

BSV区块链的建设者们&#xff0c;你们在哪&#xff1f;2024年sCrypt编程马拉松即将拉开帷幕&#xff01; 2024年3月16日至17日&#xff0c;我们将在旧金山市举办一场以比特币智能合约&#xff08;即 sCrypt&#xff09;和比特币通证&#xff08;如Ordinals&#xff09;相结合为…

快速搭建宠物医院服务小程序的步骤,无需编程经验

如果你是一家宠物医院或者宠物服务机构&#xff0c;想要拥有一款方便用户预约、查询信息的小程序&#xff0c;那么乔拓云网提供的轻应用小程序是你的不二选择。下面将为你详细介绍如何轻松打造宠物医院服务小程序。 1. 进入乔拓云网后台&#xff0c;点击【轻应用小程序】中的【…

天翼云登录参数JavaSrcipt逆向

天翼云登录参数 password 、comParam_curTime、comParam_seqCode、comParam_signature JavaSrcipt逆向 目标网站 https://m.ctyun.cn/wap/main/auth/login?redirect/my 目标参数 要逆向的有 password、comParam_curTime、comParam_seqCode、comParam_signature 四个参数 …

面试redis篇-08数据淘汰策略

原理 当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。 Redis支持8种不同策略来选择要删除的key: noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是…

一款开源.NET WPF界面库介绍

一款开源.NET WPF界面库介绍 这是一个WPF版的Layui前端UI样式库&#xff0c;该控件库参考了Web版本的LayUI风格&#xff0c;利用该控件库可以完成现代化UI客户端程序&#xff0c;让你的客户端看起来更加简洁丰富又不失美感 如何使用 步骤一 : 添加LayUI.Wpf Nuget包; Inst…

vscode与vue/react环境配置

一、下载并安装VScode 安装VScode 官网下载 二、配置node.js环境 安装node.js 官网下载 会自动配置环境变量和安装npm包(npm的作用就是对Node.js依赖的包进行管理)&#xff0c;此时可以执行 node -v 和 npm -v 分别查看node和npm的版本号&#xff1a; 配置系统变量 因为在执…

备战蓝桥杯Day17 - 链表

链表 基本概念 链表是由一系列节点组成的元素集合。 每个节点包含两部分&#xff1a;数据域 item 、指向下一个节点的指针 next 通过节点之间的相互链接&#xff0c;形成一个链表 1. 链表的初始化 # 手动建立链表 # 链表的初始化 class Node(object):def __init__(self, …

消息中间件篇之Kafka-高性能设计

一、高性能设计 消息分区&#xff1a;不受单台服务器的限制&#xff0c;可以不受限的处理更多的数据。 顺序读写&#xff1a;磁盘顺序读写&#xff0c;提升读写效率。 页缓存&#xff1a;把磁盘中的数据缓存到内存中&#xff0c;把对磁盘的访问变为对内存的访问。 零拷贝&a…

互联网加竞赛 机器视觉的试卷批改系统 - opencv python 视觉识别

文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…

C#,动态规划(DP)N皇后问题(N Queen Problem)的回溯(Backtracking)算法与源代码

1 N皇后问题&#xff08;N Queen Problem&#xff09; 在N*N的方格棋盘放置了N个皇后&#xff0c;使得它们不相互攻击&#xff08;即任意2个皇后不允许处在同一排&#xff0c;同一列&#xff0c;也不允许处在与棋盘边框成45角的斜线上。 2 回溯算法 回溯算法实际上一个类似枚…

水果FL Studio21.2.3.4004里的一个动态视觉插件Fruity Dance的教程

这是一篇关于水果&#xff08;FL Studio&#xff09;里的一个动态视觉插件Fruity Dance的教程。前部分较多地引用了FL的帮助文档&#xff0c;后部分结合了我操作的一些经验。 &#xff08;文中这种颜色的字可略过不看&#xff0c;属于一些基础操作的解释补充&#xff09; 准备材…

Qt6.8 GRPC功能使用(1)标准GRPC C++ exmple编译环境搭建

MSYS2 环境搭建 1. MSYS2 安装 https://www.msys2.org/#installation 网页中找到Installation&#xff0c; 提供下载&#xff0c; 本次下载版本是 [msys2-x86_64-20240113.exe](https://github.com/msys2/msys2-installer/releases/download/2024-01-13/msys2-x86_64-202401…

NXP实战笔记(十一):32K3xx基于RTD-SDK在S32DS上配置DFLASH、MemAcc、Fee

目录 1、概述 2、RTD-SDK配置之Cache_Ip 3、RTD-SDK配置之Mem_43_InFls 4、RTD-SDK配置之MemAcc 5、RTD-SDK配置之Fee 6、代码示例 1、概述 S32K3目前安装的RTD普遍使用的是R22-11版本的AUTOSAR规范&#xff0c;作为一直使用AUTOSAR4.2.2的程序员来讲&#xff0c;属实迭代…

备考2025年考研数学(三):2015-2024年考研数学真题练一练

今天&#xff0c;我们继续分享2015年-2024年的考研数学三填空题&#xff0c;随机做5道真题&#xff0c;并提供解析。看看正在备考2025年考研的你能做对几道。 考研数学和政治、英语一项&#xff0c;都是拉分大户&#xff0c;但是数学如果掌握了技巧&#xff0c;吃透了知识点的话…

跨境电商营销进化史:从传统广告到智能化策略的全面探析

随着全球化的不断推进和互联网技术的飞速发展&#xff0c;跨境电商在过去几年里取得了显著的发展。在这个竞争激烈的市场中&#xff0c;企业们纷纷调整营销策略以应对不断变化的消费者需求和市场趋势。本文Nox聚星将和大家探讨跨境电商营销策略的演变过程&#xff0c;从传统的推…

H6601 惠海 降压恒压芯片 支持12V24V36V48V60V72V80V 外围简单 动态负载良好 纹波小

降压恒压芯片的工作原理及其特性&#xff1a; 开关模式&#xff1a;降压恒压芯片通常使用开关模式电源供应&#xff08;SMPS&#xff09;技术&#xff0c;通过高速开关晶体管&#xff08;通常是MOSFET&#xff09;来控制电流的流动&#xff0c;从而将输入电压降低到所需的输出…

提速互联 智向未来 | 广和通AIoT模组及解决方案惊艳MWC 2024

2月26日&#xff0c;以“未来先行”为主题的世界移动通信大会2024&#xff08;以下简称&#xff1a;MWC 2024&#xff09;在巴塞罗那正式拉开帷幕。全球移动运营商、垂直行业客户、生态伙伴齐聚一堂&#xff0c;共同探讨5G-A、人工智能、NTN、可持续循环等全球科技趋势与创新技…

GaussDB SQL调优:选择合适的分布列

一、背景 GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库&#xff0c;该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持优异的分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000扩展能力&#xff0c;PB级海量存储等企业级数…

OSI参考模型和TCP/IP网络参考模型

1、OSI参考模型 1.1 产生背景 为了解决网络之间的兼容性问题,实现网络设备间的相互通讯,国际标准化组织ISO于1984年提出了OSIRM(Open System Interconnection Reference Model,开放系统互连参考模型)。OSI参考模型很快成为计算机网络通信的基础模型。由于种种原因,并没有…

React回顾

一、基础 1、使用babel解析 2、不直接使用jsx&#xff0c;jsx写起来很繁琐 3、jsx语法规则 4、函数式组件的使用 5、函数式组件渲染 6、类组件渲染 7、类组件中事件调用this指向问题 8、类组件不能直接改变状态 9、props接收数据类型限制 类型限制放到类组件内部&#xff0c;用…