JS Binding 技术(1)

本文介绍了Lynx框架在JS Binding中的实践,特别是V8和JSC引擎的初始化、Extension和内存管理。通过对比V8与JSC的异同,展示了JS对象生命周期控制的重要性,以及Lynx早期版本如何在Android和iOS上建立JS通信。文章还探讨了Lynx中JS Binding的前期设计,包括基础类如JSContext、JavaObjectWrap和ElementObject的作用与流程,为后续的JS Binding技术演进奠定基础。
摘要由CSDN通过智能技术生成

背景

Lynx 作为一个基于 JavaScript 语言(后续简称 JS )的跨平台开发框架,与 JS 的通信是”与生俱来”的,框架和 JS 引擎打交道是必不可少的能力。JS 引擎提供了 Extension 功能,提供接入方间接和 JS 通信的桥梁,Lynx 的 JS Binding 正是基于这个能力进行了封装,构建一套基础的 JS API,将能力开放给前端开发者。

当前主流浏览器基本都拥有自己的 JS 引擎,在当前移动端最为流行的 JS 引擎属 V8 和 JavascriptCore (别名 Nitro,后续简称 JSC),Lynx 框架围绕这两个引擎打造高效的 JS Binding。

JS Binding 最主要的目的是利用 JS 引擎和 JS 通信,开放底层框架能力,也可以称它为 JS Bridge,它决定了 JS 和框架层通信的速度。Lynx 早期版本为了快速实现高效通信,依赖 V8 引擎(后续简称 V8),使用的是“纯粹”的 Extension 方式,即依照 V8 的拓展方式实现了 JS Binding,建立一套 JS API ,在 Android 系统上首先实现了渲染层的平台拓展。

Lynx 以这种方式在早期快速实现可靠高效的通信能力。但当 Lynx 把平台拓展到 iOS 时,由于 V8 无法在 iOS 平台使用,JS Binding 必须把 V8 切换成 JSC ,所有关于 V8 的类和函数定义以及初始化流程,均要替换成 JSC 的标准。第一个 JSC 版本的 JS Binding 是基于 JSC iOS 标准实现的。而第二个 JSC 版本的 JS Binding 是基于纯 JSC 标准实现的,这次改动的目的是希望能统一 Android 和 iOS 的底层 JS 引擎。

本文主要介绍主流 JS 引擎使用姿势及 Lynx 中 JS Binding 的内存管理方式,作为 JS Binding 技术演进分析的铺垫。

JS 引擎使用姿势

在了解 Lynx 中 JS Binding 技术的做法前,先了解一下 V8 和 JSC 在初始化和 Extension 方面的标准实现,从中发现两个引擎的异同,当掌握了基础的用法之后,能更好的理解 Lynx 中 JS Binding 的发展路线。下面 Extension 拓展以 example 对象为例。

example.h 头文件,这个类定义了即将暴露给 JS 端的 example 对象所具有的接口,包括 TestStatic 和 TestDynamic 方法及变量 num 的设置和获取。

class Example {
public:
    Example();
    virtual ~Example();

    void TestStatic();
    void TestDynamic();

    int num();
    void set_num(int num);

private:
    void Initialize();
};

在具体实现代码中,主要功能是创建 JS 上下文,创建 example 的 JS 对象,静态注册 testStatic 方法和 num 变量,动态注册 testDynamic 并暴露到上下文中。完成后可以通过在 JS 端使用如下代码访问到 example c++ 对象的接口。

example.testStatic();
example.testDynamic();
example.num = 99;
console.log(example.num);

接下来将分析两个引擎中的实现代码,包括环境初始化和 Extension 方式,代码主要关注以下点

  • 如何初始化环境及运行上下文
  • 如何关联 c++ 对象和 JS 对象
  • 如何创建对象,并注册到上下文中
  • 如何向在 JS 引擎对象原型中静态注册变量和方法的钩子
  • 如何向在 JS 引擎对象中动态注册方法钩子
  • 如何销毁虚拟机

静态注册指的是对 JS 的原型 prototype 设置属性、方法及钩子函数,从持有该原型的构造函数创建的对象均有设置的方法和属性及钩子函数。

动态注册指直接对 JS 对象设置方法的钩子函数,仅有被设置过的对象才拥有被设置的方法。动态注册属性钩子函数的方式在 JS 引擎中暂时没有提供直接的方式.

V8 初始化和 Extension 方式

example_v8.cc 文件,以下为 V8 Extension 示例工程部分代码,完整代码请看附录 。整体流程总结如下:

  1. 初始化 V8 的环境

    V8::InitializeICUDefaultLocation(argv[0]);
    V8::InitializeExternalStartupData(argv[0]);
    v8::Platform* platform = v8::platform::CreateDefaultPlatform();
    V8::InitializePlatform(platform);
    v8::V8::Initialize();
  2. 创建 global 对象模板,据此创建 JS 运行上下文 context,从 context 中获取 global 对象

    // 创建isolate
    v8::Isolate* isolate = v8::Isolate::New(create_params);
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HandleScope handle_scope(isolate);
    // 创建global 对象模板
    v8::Local <v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
    // 创建 JS 运行上下文 context
    v8::Local <v8::Context> context = v8::Context::New(isolate, nullptr, global_template);
    v8::Context::Scope context_scope(context);
    //  context 中获取 global 对象
    v8::Local<v8::Object> global = context->Global();
  3. 创建 example 对象的构造函数模板,在构造函数模板中获取原型模板,并设置静态方法和变量的钩子

    // 创建 example 的构造函数模板, 使用该 c++ 类的初始化函数作为参数(函数钩子),初始化构造器函数模
    // 板。即当调用构造函数创建对象时,会调用该钩子函数做构造处理
    v8::Local<v8::FunctionTemplate> example_tpl = v8::FunctionTemplate::New(isolate);
    // 设置构造函数模板的类名
    example_tpl->SetClassName(V8Helper::ConvertToV8String(isolate, "Example"));
    // 设置内部关联 c++ 对象的数量为 1
    example_tpl->InstanceTemplate()->SetInternalFieldCount(1);
    // 设置构造函数模板中的原型模板的对应函数名的钩子
    example_tpl->PrototypeTemplate()->Set(V8Helper::ConvertToV8String(isolate, "testStatic"), v8::FunctionTemplate::New(isolate, TestStaticCallback));
    // 设置构造函数模板中的原型模板的属性的 Get 和 Set 钩子方法
    example_tpl->PrototypeTemplate()->SetAccessor(V8Helper::ConvertToV8String(isolate, "num"), GetNumCallback, SetNumCallback);

    用于静态注册的函数钩子,包括 testStatic 方法钩子和 num 的 get / set 钩子

    // example.testStatic() 调用时对应的 c++ 函数钩子
    static void TestStaticCallback(const v8::FunctionCallbackInfo <v8::Value> &args) {
       Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
       example->TestStatic();
    }
    
    // console.log(example.num) 调用时对应触发的 c++ 钩子函数
    static void GetNumCallback(v8::Local<v8::String> property, const PropertyCallbackInfo<Value>& info) {
       Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
       int num = example->num();
       info.GetReturnValue().Set(v8::Number::New(isolate, num));
    }
    
    // example.num = 99 时会触发该的 c++ 函数钩子
    static void SetNumCallback(v8::Local<v8::String> propert
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值