鸿蒙(API 12 Beta2版)NDK开发【JSVM-API使用规范】

JSVM-API使用规范

生命周期管理

【规则】 合理使用OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope管理JSVM_Value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。

每个JSVM_Value属于特定的HandleScope,HandleScope通过OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope来建立和关闭,HandleScope关闭后,所属的JSVM_Value就会自动释放。

注意事项

  1. JSVM_Value必须在HandleScope打开后才可创建(Node-API无该限制),否则会造成应用崩溃;
  2. JSVM_Value不能在其对应的HandleScope关闭后使用,如需持久化持有,需调用OH_JSVM_CreateReference转化为JSVM_Ref;

C++使用封装

class HandleScopeWrapper {public:HandleScopeWrapper(JSVM_Env env) : env(env) {OH_JSVM_OpenHandleScope(env, &handleScope);}~HandleScopeWrapper() {OH_JSVM_CloseHandleScope(env, handleScope);}HandleScopeWrapper(const HandleScopeWrapper&) = delete;HandleScopeWrapper& operator=(const HandleScopeWrapper&) = delete;HandleScopeWrapper(HandleScopeWrapper&&) = delete;void* operator new(size_t) = delete;void* operator new[](size_t) = delete;protected:JSVM_Env env;JSVM_HandleScope handleScope;
};

示例:

// 在for循环中频繁调用JSVM接口创建js对象时,要加handle_scope及时释放不再使用的资源。 
// 下面例子中,每次循环结束局部变量res的生命周期已结束,因此加scope及时释放其持有的js对象,防止内存泄漏
// 每次for循环结束后,触发HandleScopeWrapper的析构函数,释放scope持有的js对象
for (int i = 0; i < 100000; i++)
{ HandleScopeWrapper scope(env);JSVM_Value res;OH_JSVM_CreateObject(env, &res);if (i == 1000) {// break退出循环后会自动调用HandleScopeWrapper析构函数释放资源break;}
}

多引擎实例上下文敏感

【规则】 多引擎实例(VM)场景下,禁止通过JSVM-API跨引擎实例访问JS对象。

引擎实例是一个独立运行环境,JS对象创建访问等操作必须在同一个引擎实例中进行。若在不同引擎实例中操作同一个对象,可能会引发程序崩溃。引擎实例在接口中体现为JSVM_Env。

错误示例:

// 线程1执行,在env1创建string对象,值为"bar"、
OH_JSVM_CreateStringUtf8(env1, "value1", JSVM_AUTO_LENGTH , &string);
// 线程2执行,在env2创建object对象,并将上述的string对象设置到object对象中
JSVM_Status status = OH_JSVM_CreateObject(env2, &object);
if (status != JSVM_OK)
{ return;
} status = OH_JSVM_SetNamedProperty(env2, object, "string1", string);
if (status != JSVM_OK)
{ return;
}

所有的JS对象都隶属于具体的某一JSVM_Env,不可将env1的对象,设置到env2中的对象中。在env2中一旦访问到env1的对象,程序可能会发生崩溃。

多线程共享引擎实例

【规则】多线程同时使用同一个引擎实例的场景下,需要加锁使用。保证一个引擎实例在同一时刻只能在一个线程执行。多线程同一时刻同时使用引擎实例可能造成应用崩溃。

注意事项:

  1. OH_JSVM_IsLocked的结果为当前线程是否持有引擎实例的锁,无需设置循环等待其他线程释放锁;
  2. OH_JSVM_AcquireLock在同一线程中嵌套使用不会造成死锁;
  3. 使用OH_JSVM_ReleaseLock时需判断是否在最外层,避免同一线程中嵌套使用OH_JSVM_AcquireLock的场景下内层释放了整个线程的锁;
  4. OH_JSVM_AcquireLock后需调用OH_JSVM_OpenHandleScope让引擎实例进入线程;OH_JSVM_ReleaseLock后需调用OH_JSVM_ReleaseLock让引擎实例退出线程;
  5. 不同线程禁止嵌套使用引擎实例,如需临时切换线程使用引擎实例,请确保JSVM_Value已保存为JSVM_Ref,释放锁后对JSVM_Value将不可访问;
  6. 需注意资源获取的顺序为:锁 -> VMScope -> EnvScope -> HandleScope,释放资源的顺序正好相反,错误的顺序可能导致程序崩溃;

C++使用封装

class LockWrapper {public:// 构造函数,获取锁、VMScope、EnvScopeLockWrapper(JSVM_Env env) : env(env) {OH_JSVM_IsLocked(env, &isLocked);if (!isLocked) {OH_JSVM_AcquireLock(env);OH_JSVM_GetVM(env, &vm);OH_JSVM_OpenVMScope(vm, &vmScope);OH_JSVM_OpenEnvScope(env, &envScope);}}// 析构函数,释放EnvScope、VMScope、锁~LockWrapper() {if (!isLocked) {OH_JSVM_CloseEnvScope(env, envScope);OH_JSVM_CloseVMScope(vm, vmScope);OH_JSVM_ReleaseLock(env);}}LockWrapper(const LockWrapper&) = delete;LockWrapper& operator=(const LockWrapper&) = delete;LockWrapper(LockWrapper&&) = delete;void* operator new(size_t) = delete;void* operator new[](size_t) = delete;private:JSVM_Env env;JSVM_EnvScope envScope;JSVM_VMScope vmScope;JSVM_VM vm;bool isLocked;
};

正确示例

// 该用例演示了多线程中使用vm
// t1线程先获取锁,并继续JSVM-API的调用
// t2线程会在获取锁处阻塞,直到t1线程执行结束释放锁后,t2线程继续执行,调用JSVM-API接口
static napi_value Add([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) {static JSVM_VM vm;static JSVM_Env env;if (aa == 0) {OH_JSVM_Init(nullptr);aa++;// create vmJSVM_CreateVMOptions options;memset(&options, 0, sizeof(options));OH_JSVM_CreateVM(&options, &vm);// create envOH_JSVM_CreateEnv(vm, 0, nullptr, &env);}std::thread t1([]() {LockWrapper lock(env);JSVM_HandleScope handleScope;OH_JSVM_OpenHandleScope(env, &handleScope);JSVM_Value value;JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical valueif (rst == JSVM_OK) {OH_LOG_INFO(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 suc");} else {OH_LOG_ERROR(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 fail");}int32_t num1;OH_JSVM_GetValueInt32(env, value, &num1);OH_LOG_INFO(LOG_APP, "JSVM:t1 num1 = %{public}d", num1);OH_JSVM_CloseHandleScope(env, handleScope);});std::thread t2([]() {LockWrapper lock(env);JSVM_HandleScope handleScope;OH_JSVM_OpenHandleScope(env, &handleScope);JSVM_Value value;JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical valueif (rst == JSVM_OK) {OH_LOG_INFO(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 suc");} else {OH_LOG_ERROR(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 fail");}int32_t num1;OH_JSVM_GetValueInt32(env, value, &num1);OH_LOG_INFO(LOG_APP, "JSVM:t2 num1 = %{public}d", num1);OH_JSVM_CloseHandleScope(env, handleScope);});t1.detach();t2.detach();return nullptr;
}

获取JS传入参数及其数量

【规则】 当传入OH_JSVM_GetCbInfo的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。

当argv不为nullptr时,OH_JSVM_GetCbInfo会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明的argc数量的参数写入argv;而当argc大于实际参数数量时,该接口会在argv的尾部填充undefined。

错误示例

static JSVM_Value IncorrectDemo1(JSVM_Env env, JSVM_CallbackInfo info) {// argc 未正确的初始化,其值为不确定的随机值,导致 argv 的长度可能小于 argc 声明的数量,数据越界。size_t argc;JSVM_Value argv[10] = {nullptr};OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);return nullptr;
}static JSVM_Value IncorrectDemo2(JSVM_Env env, JSVM_CallbackInfo info) {// argc 声明的数量大于 argv 实际初始化的长度,导致 OH_JSVM_GetCbInfo 接口在写入 argv 时数据越界。size_t argc = 5;JSVM_Value argv[3] = {nullptr};OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);return nullptr;
}

正确示例

static JSVM_Value GetArgvDemo1(napi_env env, JSVM_CallbackInfo info) {size_t argc = 0;// argv 传入 nullptr 来获取传入参数真实数量OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);// JS 传入参数为0,不执行后续逻辑if (argc == 0) {return nullptr;}// 创建数组用以获取JS传入的参数JSVM_Value* argv = new JSVM_Value[argc];OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);// 业务代码// ... ...// argv 为 new 创建的对象,在使用完成后手动释放delete argv;return nullptr;
}static JSVM_Value GetArgvDemo2(napi_env env, JSVM_CallbackInfo info) {size_t argc = 2;JSVM_Value* argv[2] = {nullptr};// OH_JSVM_GetCbInfo 会向 argv 中写入 argc 个 JS 传入参数或 undefinedOH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);// 业务代码// ... ...return nullptr;
}

异常处理

【建议】 JSVM-API接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑,否则程序可能发生不可预期行为。

根据主从类型,异常处理可以分为两类:

  1. 回调函数(JS主,Native从)中Native发生异常,需往JSVM中抛出异常
// Native主,JSVM从
void ThrowError() {
bool isPending = false;
if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) {JSVM_Value error;// 获取并清空JSVM异常if (JSVM_OK == OH_JSVM_GetAndClearLastException((env), &error)) {// 获取异常堆栈JSVM_Value stack = nullptr;CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "stack", &stack));JSVM_Value message = nullptr;CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "message", &message));// 需实现将JS字符串转化为C++的std::stringstd::string stackstr = stack? GetValueString(stack) : "";std::string messagestr = message? GetValueString(message) : "";// 抛出异常,需实现JSErrorthrow JSError(*this, messagestr, stackstr);}
}
// 抛出异常,需实现JSError
throw JSError(*this, "JSVM Runtime: unkown execption");
}status = OH_JSVM_SetNamedProperty(env, object, "foo", string);
// JSVM-API调用失败,清空JS引擎实例pending的异常,抛出C++异常
if (status != JSVM_OK) { ThrowError();
}
  1. C++调用JSVM-API(Native主,JS从)失败,需清理JSVM中等待处理的异常,避免影响后续JSVM-API的执行,并设置C++异常处理分支(或抛出C++异常)
// JSVM主, Native从
void DoSomething() {throw("Do something failed");
}// Demo1: 捕获到C++异常,抛出异常到JSVM中
JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) {try {DoSomething();} catch (const char *ex) {OH_JSVM_ThrowError(env, nullptr, ex);return nullptr;}return nullptr;
}// Demo2: JSVM-API调用失败,抛出异常到JSVM中
JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) {JSVM_Value JSBool = nullptr;bool value = false;auto status = OH_JSVM_GetValueBool(env, JSBool, &value);if (status != JSVM_OK) {OH_JSVM_ThrowError(env, nullptr, "Get bool value failed");return nullptr;}return nullptr;
}// Demo3:JSVM-API调用失败且在调用过程中已向JSVM中添加等待处理的异常,则无需再向JSVM中抛出异常
JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) {std::string sourcecodestr = R"JS(throw Error('Error throw from js');)JS";JSVM_Value sourcecodevalue = nullptr;OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);JSVM_Script script;auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);JSVM_Value result;// 执行JS脚本,执行过程中抛出JS异常status = OH_JSVM_RunScript(env, script, &result);if (status != JSVM_OK) {bool isPending = false;// 如果已有异常,则无需再向JSVM中抛出异常;// 如需处理并抛出新异常,需先处理JSVM中等待的异常if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) {return nullptr;}OH_JSVM_ThrowError(env, nullptr, "Runscript failed");return nullptr;}return nullptr;
}// 绑定NativeFunction到JSVM中,省略
std::string sourcecodestr = R"JS(// consolelog需用户实现try {// 调用Native函数NativeFunction()} catch (e) {// 处理Native中产生的异常consolelog(e.message)}
)JS";
JSVM_Value sourcecodevalue = nullptr;
OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);
JSVM_Script script;
auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);
OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}d", (uint32_t)status);
JSVM_Value result;
// 执行JS脚本,JS调用Native方法
status = OH_JSVM_RunScript(env, script, &result);

注意事项:回调函数中调用JSVM-API失败,如要向JSVM中抛异常,需保证JSVM中无等待处理的异常,也可以不抛出异常,JS的try-catch块可以捕获回调函数调用API失败产生的JS异常,见NativeFunctionExceptionDemo3。

上下文绑定对象

【规则】 :调用JSVM-API生成的JS函数、对象需绑定到上下文中才能从JS侧访问,OH_JSVM_CreateFunction接口中的const char *参数为创建函数的属性name,不代表上下文中指向该函数的函数名。调用JSVM-API生成的类、对象同理。

示例

JSVM_Value JSFunc = nullptr;
const char *name = "NativeFunction";
JSVM_CallbackStruct cb = {NativeFunction, nullptr};
// 创建JS函数,该函数的属性 "name" 为 "NativeFunction"
OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc);
// 绑定函数到上下文
// 获取上下文的global对象
JSVM_Value global = nullptr;
OH_JSVM_GetGlobal(env, &global);
// 创建JS字符串"FunctionNameInJSContext"
JSVM_Value key = nullptr;
OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key);
// 设置global的属性"FunctionNameInJSContext"为JSFunc,将函数绑定到上下文中
OH_JSVM_SetProperty(env, global, key, JSFunc);
// 在JS中调用函数
std::string sourcecodestr = R"JS(// consolelog需用户实现FunctionNameInJSContext() // 调用成功consolelog(FunctionNameInJSContext.name) // 打印 "NativeFunction"try {NativeFunction() // 无法找到该函数,抛出异常} catch (e) {consolelog(e.message)}
)JS";

最后呢

很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

在这里插入图片描述

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。

  • 《鸿蒙 (OpenHarmony)开发学习视频》
  • 《鸿蒙生态应用开发V2.0白皮书》
  • 《鸿蒙 (OpenHarmony)开发基础到实战手册》
  • OpenHarmony北向、南向开发环境搭建
  • 《鸿蒙开发基础》
  • 《鸿蒙开发进阶》
  • 《鸿蒙开发实战》

在这里插入图片描述

总结

鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL精简笔记

基础类型 bit&#xff0c;tinyint&#xff0c;smallint&#xff0c;int&#xff0c;bigintfload&#xff0c;double&#xff08;M&#xff1a;整数小数的位数&#xff0c;D&#xff1a;小数的位数&#xff09;decimal&#xff0c;numeric&#xff08;M&#xff0c;D)&#xff0…

大模型分布式训练之DeepSpeed优化器并行(ZeRO)原理

由于大模型参数量非常庞大&#xff0c;所以我们常常需要用到分布式训练来解决训练过程中计算资源不足的问题&#xff0c;现在也出现了很多大模型相关的分布式训练框架&#xff0c;但是使用的比较多的还是deepspeed的数据并行&#xff0c;那么deepspeed是怎么实现数据并行的呢 文…

【Unity】web gl inputFied 中文输入,同时支持TextMeshInputFied,支持全屏

同时支持TextMeshInputFied&#xff0c;支持全屏。 使用github包【WebGLInput】&#xff1a;https://github.com/kou-yeung/WebGLInput 需要资源的在这里也可以下载 https://download.csdn.net/download/weixin_46472622/89600795 用于unity web gl 中文输入&#xff0c;只需…

【人工智能】边缘计算与 AI:实时智能的未来

&#x1f48e; 我的主页&#xff1a;2的n次方_ &#x1f48e;1. 引言 随着物联网设备数量的爆炸性增长和对实时处理需求的增加&#xff0c;边缘计算与人工智能&#xff08;Edge AI&#xff09;成为一个热门话题。Edge AI 通过在本地设备上运行 AI 算法&#xff0c;减少对云计…

大数据-61 Kafka 高级特性 消息消费02-主题与分区 自定义反序列化 拦截器 位移提交 位移管理 重平衡

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Python酷库之旅-第三方库Pandas(062)

目录 一、用法精讲 241、pandas.Series.view方法 241-1、语法 241-2、参数 241-3、功能 241-4、返回值 241-5、说明 241-6、用法 241-6-1、数据准备 241-6-2、代码示例 241-6-3、结果输出 242、pandas.Series.compare方法 242-1、语法 242-2、参数 242-3、功能 …

最新小猫咪PHP加密系统源码V1.4_本地API接口_带后台

简介&#xff1a; 最新小猫咪PHP加密系统源码V1.4_完全本地化加密API接口_带后台 小猫咪PHP加密系统历时半年&#xff0c;它再一次迎来更新&#xff0c;更新加密算法&#xff08;这应该是最后一次更新加密算法了&#xff0c;以后主要更新都在框架功能上面了&#xff09;&…

Python 爬虫项目实战(一):破解网易云 VIP 免费下载付费歌曲

前言 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网页蜘蛛&#xff08;Web Spider&#xff09;或网页机器人&#xff08;Web Bot&#xff09;&#xff0c;是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…

代码随想录27天|贪心

455.分发饼干 代码随想录 第一想法 将孩子胃口值g[i] 按从小到达的顺序排列&#xff0c;饼干尺寸也按照从小到大的顺序去排列。 优先将大尺寸喂给大胃口孩子。如果满足不了胃口那么久试着分给下一个孩子。 要尽量满足更多的孩子&#xff0c;那么大尺寸的饼干就不能喂给小胃口…

【多线程】线程状态与并发三大特性的细节剖析

这篇文章主要用于对于多线程的一些查缺补漏。 一、 线程的状态 1&#xff0c;操作系统层面&#xff0c;线程的5种状态 关于线程有几种状态&#xff0c;有多种说法&#xff0c;5、6、7都有。 首先对于操作系统来说&#xff0c;只有5种状态&#xff0c;状态如下新建&#xff…

浅谈KMP算法(c++)

目录 前缀函数应用【模板】KMP题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示样例 1 解释数据规模与约定 思路AC代码 本质不同子串数 例题讲解[NOI2014] 动物园题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路AC代码 [POI2006] OKR-Periods of …

智慧水务项目(一)django(drf)+angular 18 通过pycharm建立项目

一、环境准备 windows 10 pycharm python3.11 二、pycharm 创建django项目 三、建立requirements.txt 在根目录创建requirements.txt,也就是与manage.py同一目录下&#xff0c;先放下面几个依赖 Django djangorestframeworkpip install -r .\requirements.txt 更新下pip python…

ShardingSphere实战(1)- 分库分表基础知识

一、为什么要分库分表 分库分表是一种数据库优化策略&#xff0c;主要用于解决大型应用或高并发场景下数据库性能瓶颈的问题。具体来说&#xff0c;分库分表可以带来以下好处&#xff1a; 提高性能&#xff1a; 减少单个数据库实例的负载&#xff0c;避免单点性能瓶颈。当数据…

【香橙派系列教程】(五)Linux的热拔插UDEV机制

【五】Linux的热拔插UDEV机制 在上一篇中我们发现&#xff0c;当手机接入开发板时&#xff0c;系统并不认识&#xff0c;当我们在/etc/udev目录下创建一个规则后&#xff0c;就可以通过adb访问到手机了&#xff0c;这里到底是怎么回事&#xff1f; 文章目录 【五】Linux的热拔插…

武汉流星汇聚:亚马逊平台消费者众多,助力中国卖家销售额大幅增长

在全球电商的浩瀚星空中&#xff0c;亚马逊凭借其庞大的消费者规模和强大的市场影响力&#xff0c;为无数商家特别是中国卖家提供了前所未有的发展机遇。近年来&#xff0c;越来越多的中国卖家选择通过亚马逊平台&#xff0c;将优质产品直接送达全球消费者的手中&#xff0c;并…

精选3款国内wordpress 主题,建站首选

WordPress作为一款功能强大且易于使用的建站平台&#xff0c;已经成为了许多企业和个人搭建网站的首选。为了帮助大家更好地选择适合自己的WordPress主题&#xff0c;小编将为大家推荐三款国内优秀的WordPress主题&#xff1a;子比主题、OneNav主题和RiTheme主题。 1.子比主题…

JavaScript ES6语法详解(下)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是码喽的自我修养&#xff01;今天给大家分享JavaScript ES6语法详解(下)&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到带大家&#xff0c;欢迎收藏关注…

Qt Creator 与 ESP-IDF QEMU 模拟器使用指南

标题: Qt Creator 与 ESP-IDF QEMU 模拟器使用指南 概要: 本文为开发者提供了使用 Qt Creator 和 ESP-IDF QEMU 模拟器进行 ESP32 开发的详细指南&#xff0c;包括环境准备、项目创建和编译、模拟器设置、编程和调试等方面的内容。通过本指南&#xff0c;可以快速上手 Qt Crea…

Learning vtkjs之Calculator

过滤器 公式计算器 Calculator 介绍 The Calculator filter is a fast way to add derived data arrays to a dataset. These arrays can be defined over points, cells, or just field data that is “uniform” across the dataset (i.e., constant over all of space). Va…

手把手教你实现日期类

目录 前言 1.头文件的实现 2.日期类函数各项功能实现 2.1 初始化和打印&#xff08;比较简单&#xff09; 2.2日期大小判断 2.3日期的加减运算 3.日期类的输入输出 4.测试代码参考 结束语 前言 前面我们讲解了类的对象的大部分知识&#xff0c;例如拷贝构造&#xff0c…