使用扩展的Node-API接口加速属性访问
=====================

从API 24版本开始，HarmonyOS提供了基于调用点缓存的快速属性访问接口。通过为每个调用点创建napi\_callsite\_info句柄，在重复访问相同键名属性时，可以缓存对象的结构信息，从而加速属性访问，显著提升属性访问性能。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/63/v3/FFVU2dL_TReJgIV698q4iA/caution_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T132818Z&HW-CC-Expire=86400&HW-CC-Sign=1A41A21FB874CF2A40AEAB165B63A5EC62F83D10F10F6A4A1586371F755303D7)

*   每个不同的调用点应创建独立的napi\_callsite\_info句柄，同一句柄可在多次调用中复用。
*   napi\_callsite\_info不支持跨线程使用，每个句柄只能在创建它的线程中使用。
*   当不再需要时，必须调用napi\_delete\_callsite\_info释放napi\_callsite\_info句柄。
*   napi\_get\_property\_with\_callsite\_info和napi\_set\_property\_with\_callsite\_info的info参数可以传入NULL，此时行为等同于普通的napi\_get\_property/napi\_set\_property。
*   napi\_get\_property\_with\_callsite\_info和napi\_set\_property\_with\_callsite\_info支持可为NULL的hit输出参数（bool\*）：true表示命中（快速路径），false表示未命中。

#### 场景介绍

当需要对相同的属性键名进行大量重复的属性读写操作时（例如在热点循环中批量处理对象数组），使用调用点缓存可以提升性能。

**性能特征：**

*   **缓存命中**（对象具有相同的结构）：性能相比于napi\_get\_property/napi\_set\_property显著提升。
*   **缓存失效**：由于额外的检查和回退开销，性能相比普通属性访问接口劣化。
    *   当同一调用点传入对象类型过多时，该调用点对应缓存会溢出并失效，永久回退到慢速路径。
        
    *   **同一napi\_callsite\_info句柄不可同时用于get和set。** Get/Set时缓存的信息不可共用，将用于napi\_get\_property\_with\_callsite\_info的句柄传给napi\_set\_property\_with\_callsite\_info（或反过来），缓存失效，进入慢速路径。
        
        // 错误示例：同一句柄同时用于 get 和 set，导致缓存失效
        napi\_callsite\_info shared\_info;
        napi\_create\_callsite\_info(env, &shared\_info);
        napi\_get\_property\_with\_callsite\_info(env, obj, key, shared\_info, &val, nullptr);
        napi\_set\_property\_with\_callsite\_info(env, obj, key, new\_val, shared\_info, nullptr); // 缓存失效！
        napi\_delete\_callsite\_info(env, shared\_info);
        
        // 正确示例：get 和 set 各自使用独立的句柄
        napi\_callsite\_info get\_info, set\_info;
        napi\_create\_callsite\_info(env, &get\_info);
        napi\_create\_callsite\_info(env, &set\_info);
        napi\_get\_property\_with\_callsite\_info(env, obj, key, get\_info, &val, nullptr);
        napi\_set\_property\_with\_callsite\_info(env, obj, key, new\_val, set\_info, nullptr); // 正常命中缓存
        napi\_delete\_callsite\_info(env, get\_info);
        napi\_delete\_callsite\_info(env, set\_info);
        
*   **缓存未命中**：如果缓存有效但没有命中，由于额外的检查和回退开销，性能相比普通属性访问接口降低10-100%。

**适用场景：**

*   对象结构稳定的批量重复属性访问。
*   对同一键名属性的高频读写。

**不适用场景：**

*   一次性属性访问，缓存初始化开销无法被后续调用摊销。
*   高度多态的对象（同一调用点频繁遇到不同结构的对象），会导致缓存失效，性能会劣化。

#### 接口描述

| 接口 | 描述 |
| :-- | :-- |
| [napi\_create\_callsite\_info](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_create_callsite_info) | 创建调用点信息句柄，用于缓存属性访问信息。 |
| [napi\_delete\_callsite\_info](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_delete_callsite_info) | 删除调用点信息句柄，释放关联的缓存资源。 |
| [napi\_get\_property\_with\_callsite\_info](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_get_property_with_callsite_info) | 使用调用点信息快速获取对象属性值。 |
| [napi\_set\_property\_with\_callsite\_info](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_set_property_with_callsite_info) | 使用调用点信息快速设置对象属性值。 |

#### 示例代码

*   模块注册
    
    // napi\_init.cpp
    #include "napi/native\_api.h"
    #include <vector>
    
    // 使用C++的RAII机制管理napi\_callsite\_info的生命周期，避免遗漏释放
    // 仅用作示例，也可以通过其他方式进行封装，或直接使用原始接口
    class MyCallsiteInfo
    {
       napi\_env env\_{};
       napi\_callsite\_info info\_{};
    
    public:
       explicit MyCallsiteInfo(napi\_env env) : env\_(env)
       {
          if (napi\_create\_callsite\_info(env, &info\_) != napi\_ok)
          {
              info\_ = nullptr;
              // logging
          }
       }
       ~MyCallsiteInfo()
       {
          if (info\_ != nullptr) {
                napi\_delete\_callsite\_info(env\_, info\_);
          }
       }
       napi\_callsite\_info get() const { return info\_; }
    };
    
    // 示例：批量读取对象数组中指定属性名的值
    // 注意：此处的 csInfo 仅用于 get，不可同时用于 set（需另建句柄）
    static napi\_value NAPI\_Global\_batchGetProperty(napi\_env env, napi\_callback\_info info)
    {
       napi\_value args\[2\]{};
       size\_t argc = 2;
       napi\_get\_cb\_info(env, info, &argc, args, /\* thisVar \*/ nullptr, /\* data \*/ nullptr);
       if (argc < 2) {
          return nullptr;
       }
    
       // args\[0\]为对象数组，args\[1\]为属性键名
       uint32\_t length = 0;
       napi\_get\_array\_length(env, args\[0\], &length);
    
       // 创建调用点信息，缓存对象结构以加速后续访问
       MyCallsiteInfo csInfo(env);
    
       napi\_value result;
       napi\_create\_array\_with\_length(env, length, &result);
    
       for (uint32\_t i = 0; i < length; i++) {
          napi\_value element;
          napi\_get\_element(env, args\[0\], i, &element);
    
          napi\_value val;
          napi\_get\_property\_with\_callsite\_info(env, element, args\[1\], csInfo.get(), &val, nullptr);
    
          napi\_set\_element(env, result, i, val);
       }
    
       return result;
    }
    
    // 示例：批量设置对象数组中指定属性名的值
    static napi\_value NAPI\_Global\_batchSetProperty(napi\_env env, napi\_callback\_info info)
    {
       napi\_value args\[3\]{};
       size\_t argc = 3;
       napi\_get\_cb\_info(env, info, &argc, args, /\* thisVar \*/ nullptr, /\* data \*/ nullptr);
       if (argc < 3) {
          return nullptr;
       }
    
       // args\[0\]为对象数组，args\[1\]为属性键名，args\[2\]为要设置的值
       uint32\_t length = 0;
       napi\_get\_array\_length(env, args\[0\], &length);
    
       MyCallsiteInfo csInfo(env);
    
       for (uint32\_t i = 0; i < length; i++) {
          napi\_value element;
          napi\_get\_element(env, args\[0\], i, &element);
    
          napi\_set\_property\_with\_callsite\_info(env, element, args\[1\], args\[2\], csInfo.get(), nullptr);
       }
    
       napi\_value undefined;
       napi\_get\_undefined(env, &undefined);
       return undefined;
    }
    
    // 模块注册
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
       std::vector<napi\_property\_descriptor> desc{
          {"batchGetProperty", nullptr, NAPI\_Global\_batchGetProperty, nullptr, nullptr, nullptr, napi\_default, nullptr},
          {"batchSetProperty", nullptr, NAPI\_Global\_batchSetProperty, nullptr, nullptr, nullptr, napi\_default, nullptr},
       };
       napi\_define\_properties(env, exports, desc.size(), desc.data());
       return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule = {
       .nm\_version = 1,
       .nm\_flags = 0,
       .nm\_filename = nullptr,
       .nm\_register\_func = Init,
       .nm\_modname = "entry",
       .nm\_priv = ((void \*)0),
       .reserved = {0},
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void)
    {
       napi\_module\_register(&demoModule);
    }
    
*   接口声明
    
    // index.d.ts
    export const batchGetProperty: (objects: object\[\], key: string) => (string | number | boolean | undefined | null)\[\];
    export const batchSetProperty: (objects: object\[\], key: string, value: string | number | boolean) => void;
    
*   ArkTS代码示例：
    
    // index.ets
    import testNapi from "libentry.so"
    
    class Item {
       name: string = '';
       value: string = '';
    
       constructor(name: string, value: string) {
          this.name = name;
          this.value = value;
       }
    }
    
    @Entry
    @Component
    struct Index {
       @State message: string = '点击运行示例';
    
       build() {
          Row() {
             Column() {
                Text(this.message)
                   .fontSize(20)
                   .fontWeight(FontWeight.Bold)
                   .onClick(() => {
                      // 构造一批具有相同结构的对象
                      const objects: Item\[\] = \[\];
                      for (let i = 0; i < 3; i++) {
                         objects.push(new Item(\`item\_${i}\`, \`val\_${i}\`));
                      }
    
                      // 使用napi\_get\_property\_with\_callsite\_info批量读取属性
                      const names = testNapi.batchGetProperty(objects, "name");
                      // 预期结果: \["item\_0","item\_1","item\_2"\]
    
                      // 使用napi\_set\_property\_with\_callsite\_info批量设置属性
                      testNapi.batchSetProperty(objects, "value", "updated");
                      const values = testNapi.batchGetProperty(objects, "value");
                      // 预期结果: \["updated","updated","updated"\]
    
                      this.message = \`get "name": ${JSON.stringify(names)}\\nget "value" after set: ${JSON.stringify(values)}\`;
                   })
             }
             .width('100%')
          }
          .height('100%')
       }
    }