跨端轻量JavaScript引擎的实现与探索
myzbx 2025-01-01 21:42 33 浏览
一、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 | 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在Chain React 2019 大会上发布的一个崭新JavaScript引擎,用于移动端React Native应用的集成,开源 |
3.JavaScript引擎工作原理
a.V8引擎工作原理
??
b.Turbofan技术实例说明
function sum(a, b) {
return a + b;
}
这里a和b可以是任意类型数据,当执行sum函数时,Ignition解释器会检查a和b的数据类型,并相应地执行加法或者连接字符串的操作。
如果 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;
}
相关推荐
- 别让水 “跑” 出卫生间!下沉设计打造滴水不漏的家
-
你是否遭遇过卫生间的水“偷偷溜”进客厅,导致木地板鼓起、墙角发霉的糟心事?又是否为卫生间门口反复渗漏,不得不一次次返工维修而头疼不已?在家庭装修中,卫生间防水堪称“兵家必争之地”,而卫生间门口下...
- 歼-10CE vs 阵风:谁才是空中霸主?全面性能对比解析
-
歼10CE与法国阵风战斗机性能深度对比分析一、总体定位与设计哲学歼10CE:单发中型多用途战斗机,侧重于空优(制空权争夺)和对地对海打击,具有较高的性价比和较强的多任务能力。法国阵风战斗机:双发中型多...
- 知名移植工作室肯定Switch2的图形性能,却被CPU拖了后腿
-
虽然Switch2发售多日,但没入手的玩家对其性能还是有顾虑。近日,知名移植工作室Virtuos的技术总监在接受采访时讨论了Switch2的性能,并给出了他们工作室的评价。简单来说,Switch2在D...
- 虹科实测 | CAN XL vs CAN FD传输性能深度对比:速率翻倍,抖动锐减!
-
导读在汽车电子与工业通信领域,CAN协议持续进化,推动着数据传输效率的提升。本次实测基于虹科PCAN-USBXL与虹科PCAN-USBProFD硬件,在同等严苛条件下对比CANXL与CANF...
- 1J117合金材料优异的耐腐蚀性、机械性能
-
1J117合金材料概述定义:1J117是一种不锈软磁精密合金,属于铁铬基合金,其圆棒产品具有特定的形状和尺寸,可满足各种工业应用中的特定需求。标准:技术条件标准为GB/T14986,品种规格标准...
- 据高管所称,Switch2能轻松移植XSS平台60帧游戏
-
任天堂,作为主机游戏界的御三家之一,一直注重游戏性而不注重更新升级硬件设备是其最大的特点。各位任豚们,忍受着任天堂早已落后硬件设备,真想感叹一句,天下苦任久矣!但Switch2的出现或许正在渐渐的改变...
- FJK-110LED-HXJSN磁传感器有哪应用
-
作为一名从事电子技术相关工作的自媒体人,我经常会遇到各种传感器的应用问题。其中,FJK-110LED-HXJSN磁传感器是一款在工业自动化、智能设备等领域比较常见的磁场检测元件。今天我想和大家聊一聊这...
- 浅谈欧标方管200x200x5-12mm质S275JRH的优势与劣势
-
欧标方管200x200x5-12mm材质S275JRH是一种常见的结构用钢材,广泛应用于建筑、机械制造、桥梁、钢结构等领域。本文将对这种方管的优势与劣势进行浅谈,以帮助读者更好地了解其特性和适用场景。...
- 宽带拨号错误 651 全解析:故障定位与修复方案
-
在使用PPPoE拨号连接互联网时,错误651提示「调制解调器或其他连接设备报告错误」,通常表明从用户终端到运营商机房的链路中存在异常。以下从硬件、系统、网络三层维度展开排查:一、故障成因分类图...
- 模型微调:从理论到实践的深度解析
-
在人工智能领域,模型微调已成为提升模型性能、使其适应特定任务的关键技术。本文将全面系统地介绍模型微调的各个方面,帮助读者深入理解这一重要技术。一、什么是模型微调模型微调是指在已经训练好的预训练模型基础...
- 汉语拼音 z、c、s图文讲解(拼音字母表zcs教学视频)
-
以下是汉语拼音z、c、s的图文讲解,结合发音要领、书写规范及教学技巧:一、发音方法与口诀1.z的发音发音要领:舌尖轻抵上齿背,形成阻碍后稍放松,气流从窄缝中挤出,声带不振动(轻短音)。口诀:“写字写...
- 吴姗儒惹怒刘宇宁粉丝!吴宗宪护航「是综艺梗」叮咛女儿对话曝光
-
记者孟育民/台北报道Sandy吴姗儒在《小姐不熙娣》因为节目效果,将男星刘宇宁的头像踩在地上,引起粉丝怒火,节目发声明道歉后仍未平息,她也亲自发文郑重道歉:「我对刘宇宁本人完全没有任何恶意,却在综艺表...
- 苹果错误地发布了macOS Tahoe公开测试版 现已将其撤下
-
一些Beta测试人员下载了他们以为是macOSSequoia15.6RC的版本,但却错误地下载了macOSTahoe26公开测试版,后来苹果修复了该问题。苹果预计将于7月25...
- make的多种用法!(make 的用法总结)
-
一、make的用法美make[meik]①V.制造;制定,拟定;使变得,使处于;造成,引起;整理(床铺);做,作出;强迫;挑选,任命…②n.(机器、设备等的)品牌,型号;结构,构造;通电,接电⑤[...
- 北顿尖刀哗变?俄第20近卫集团军损失惨重,拒绝执行指挥官命令?
-
【军武次位面】作者:太白近日,外国社交媒体“电报”上传出了一些消息,称俄罗斯在北顿涅兹克战场上的“尖刀”部队之一,俄第20近卫集团军因为损失惨重,已经出现了部分部队拒绝执行指挥官命令,甚至哗变的情况。...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)