如何通过Node.js C++ Addon引用JavaScript原始值?
可以借助Node.js C++ Addon实现引用JS原始值的需求
JS原始值(如number、string)本身是不可变且按值传递的,无法直接创建引用,但可以通过C++ Addon创建包装/代理对象来模拟引用效果,让两个变量共享同一个可变的值容器。以下是具体实现步骤:
1. 准备开发环境
确保已安装:
- Node.js(版本>=12)
node-gyp(全局安装:npm install -g node-gyp)- 对应平台的C编译工具链(Windows需安装Visual Studio,macOS需安装Xcode Command Line Tools,Linux需安装gcc/g)
2. 编写Addon配置文件(binding.gyp)
创建binding.gyp文件,用于告诉node-gyp如何编译Addon:
{ "targets": [ { "target_name": "mymodule", "sources": ["mymodule.cc"], "include_dirs": ["<!(node -p \"require('node-addon-api').include\")"], "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"], "cflags!": ["-fno-exceptions"], "cflags_cc!": ["-fno-exceptions"] } ] }
3. 编写C++ Addon代码(mymodule.cc)
使用N-API(跨Node版本稳定的API)实现一个可变值包装对象,提供ref方法返回包装对象:
#include <napi.h> // 自定义包装类,持有可变的JS值 class ValueRef : public Napi::ObjectWrap<ValueRef> { public: static Napi::Object Init(Napi::Env env, Napi::Object exports) { Napi::Function func = DefineClass(env, "ValueRef", { InstanceAccessor<&ValueRef::GetValue, &ValueRef::SetValue>("value"), InstanceMethod("valueOf", &ValueRef::ValueOf), InstanceMethod("toString", &ValueRef::ToString) }); Napi::FunctionReference* constructor = new Napi::FunctionReference(); *constructor = Napi::Persistent(func); env.SetInstanceData(constructor); exports.Set("ValueRef", func); return exports; } explicit ValueRef(const Napi::CallbackInfo& info) : Napi::ObjectWrap<ValueRef>(info) { Napi::Env env = info.Env(); if (info.Length() < 1) { Napi::TypeError::New(env, "Expected an initial value").ThrowAsJavaScriptException(); return; } // 保存初始值 _value = info[0]; } // 获取内部值 Napi::Value GetValue(const Napi::CallbackInfo& info) { return _value; } // 设置内部值 void SetValue(const Napi::CallbackInfo& info, const Napi::Value& value) { _value = value; } // 实现valueOf,让对象在数值运算时自动拆箱 Napi::Value ValueOf(const Napi::CallbackInfo& info) { return _value; } // 实现toString,让console.log时显示值 Napi::Value ToString(const Napi::CallbackInfo& info) { return _value.ToString(); } private: Napi::Value _value; }; // 暴露给JS的ref函数 Napi::Value Ref(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); if (info.Length() < 1) { Napi::TypeError::New(env, "Expected a value to reference").ThrowAsJavaScriptException(); return env.Undefined(); } // 创建ValueRef实例,传入初始值 Napi::FunctionReference* constructor = env.GetInstanceData<Napi::FunctionReference>(); return constructor->New({info[0]}); } // 模块初始化函数 Napi::Object Init(Napi::Env env, Napi::Object exports) { ValueRef::Init(env, exports); exports.Set("ref", Napi::Function::New(env, Ref)); return exports; } NODE_API_MODULE(mymodule, Init)
4. 编译Addon
在项目目录下执行:
node-gyp configure build
编译完成后,会在build/Release目录生成mymodule.node文件。
5. 测试使用
修改测试代码为符合实际实现的版本(因为JS原始值无法直接引用,需通过包装对象的属性修改):
let addon = require('./build/Release/mymodule.node') let numObj = addon.ref(999) // 修改包装对象的value属性 numObj.value++ console.log(numObj.value) // 输出1000 console.log(numObj) // 输出1000(因实现了toString方法) // 数值运算时自动拆箱 console.log(numObj + 1) // 输出1001
如果想要更贴近你示例的写法,可以结合JS的Proxy来拦截变量赋值,模拟原始变量的语法:
let addon = require('./build/Release/mymodule.node') let numObj = addon.ref(999) // 创建Proxy模拟原始变量的写法 let num1 = new Proxy(numObj, { get(target, prop) { if (prop === 'valueOf') return () => target.value if (prop === Symbol.toPrimitive) return () => target.value return target[prop] }, set(target, prop, value) { if (prop === 'value') { target.value = value return true } return false } }) num1++ console.log(num1) // 输出1000 console.log(numObj.value) // 输出1000
原理说明
- JS原始值不可变且按值传递,所以我们用C++ Addon创建一个可变的包装对象,内部持有原始值的副本,所有修改操作都是针对这个对象的内部值。
- 通过实现
valueOf和toString方法,让包装对象在数值运算或打印时自动拆箱,模拟原始值的行为。 - 若要完全模拟原始变量的赋值语法,可以结合JS Proxy拦截赋值操作,将赋值转发给包装对象的内部值。
内容的提问来源于stack exchange,提问作者Time Killer
相关产品推荐
相关产品推荐

