跨端輕量JavaScript引擎的實現與探索

一、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引擎:







引擎 所屬機構/個人 瀏覽器 說明
SpiderMonkey Mozilla Firefox 第一款JavaScript引擎,早期用於 Netscape Navigator,現時用於 Mozilla Firefox。是用C語言實現的,還有一個Java版本叫Rhino;Rhino引擎由Mozilla基金會管理,開放源代碼,完全以Java編寫,用於 HTMLUnit;而後TraceMonkey引擎是基於實時編譯的引擎,用於Mozilla Firefox 3.5~3.6版本;JaegerMonkey:結合追蹤和組合碼技術大幅提高性能,用於Mozilla Firefox 4.0以上版本
JavaScriptCore Apple Safari 簡稱JSC,開源,用於webkit內核瀏覽器,如 Safari ,2008 年實現了編譯器和字節碼解釋器,升級爲了SquirrelFish。蘋果內部代號爲Nitro的 JavaScript 引擎也是基於 JSC引擎的。至於具體時間,JSC是WebKit默認內嵌的JS引擎,而WebKit誕生於1998年,Nitro是爲Safari 4編寫,Safari 4是2009年6月發佈。
V8 Google Chrome 2008年9月,Google的V8引擎第一個版本隨着Chrome的第一個版本發佈。V8引擎用 C++編寫,由 Google 丹麥開發,開源。除了Chrome,還被運用於Node.js以及運用於Android操作系統等
Chakra Microsoft Edge、IE 譯名查克拉,用於IE9、10、11和Microsoft Edge,IE9發佈時間2011年3月
JerryScript 三星  三星推出的適用於嵌入式設備的小型 JavaScript 引擎,2015年開源
Nashorn Oracale  從 JDK 1.8 開始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成爲 Java 的嵌入式 JavaScript 引擎,JDK1.8發佈於2014年
QuickJS Fabrice Bellard  QuickJS 是一個小型的嵌入式 Javascript 引擎。 它支持 ES2023 規範,包括模塊、異步生成器、代理和 BigInt。 它可以選擇支持數學擴展,例如大十進制浮點數 (BigDecimal)、大二進制浮點數 (BigFloat) 和運算符重載。
Hermes Facebook  引擎,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(https://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_arg0 get_arg1 add return
1 2 3 將棧頂的數據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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章