扩展开发 API 文档 为 Objector 积木编程平台开发自定义扩展
快速开始
扩展是 Objector 的核心扩展机制,允许你添加自定义积木和执行逻辑。只需 3 步:
- 创建
.js或.json文件 - 定义积木(外观 + 参数)和执行器(逻辑)
- 放入项目的
extensions/目录
// extensions/my-ext.js — 最简扩展示例
ExtensionManager.registerExtension({
id: 'my_ext',
name: '我的扩展',
color: '#FF6B6B',
blocks: [
{
type: 'ext_hello',
label: '打招呼 {name}',
shape: 'stack',
params: [{ name: 'name', type: 'string', default: '世界' }],
},
],
executors: {
ext_hello: function(params) {
alert('你好, ' + params.name + '!');
},
},
});
文件结构
你的项目/
├── project.json // 项目文件
├── extensions/ // 扩展目录(自动加载)
│ ├── my-ext.js // JS 格式扩展
│ ├── math-utils.json // JSON 格式扩展
│ └── ...
└── ...
扩展在打开项目时自动从
extensions/ 目录加载,支持 .js 和 .json 两种格式。JSON / JS 格式对比
| JSON 格式 (.json) | JS 格式 (.js) | |
|---|---|---|
| 执行器写法 | 函数字符串 | 直接写函数 |
| 可读性 | 一般 | 好 |
| 灵活性 | 受限(纯字符串) | 完全灵活 |
| 推荐场景 | 简单积木、数据驱动 | 复杂逻辑、异步操作 |
扩展对象结构
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | ✅ | 扩展唯一标识(同时作为分类 ID) |
name | string | 显示名称(积木面板中的分类名) | |
color | string | 积木颜色(十六进制),默认 #888888 | |
blocks | Array | ✅ | 积木定义数组 |
executors | Object | 执行器映射:{ 积木type: 函数 } |
积木定义
每个积木对象包含以下字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | string | ✅ | 积木类型标识(全局唯一) |
label | string | ✅ | 显示文本,{xxx} 为参数占位符 |
shape | string | ✅ | 积木形状(见下方) |
params | Array | 参数定义数组 | |
ports | Object | 连接端口(默认自动推断) | |
subBlocks | Array | C 型积木的子代码槽名 | |
getLabel | function | 动态标签函数 | |
dynamicParams | boolean | 启用可扩展输入(右键菜单添加参数) | |
dynamicParamsPrefix | string | 动态参数名前缀(默认 item) | |
dynamicParamsDefault | any | 动态参数默认值(默认 '') |
积木形状 shape
shape 决定积木的外观和连接方式:
移动 10 步
stack — 普通堆叠积木,可上下连接
x 坐标
reporter — 圆形返回值积木,可嵌入参数槽
碰到边缘?
boolean — 菱形条件积木,用于 if/while
重复 10 次
c-block — C 型积木,包含子代码
当绿旗被点击
hat — 帽子积木,事件入口
| shape | flowIn | flowOut | 用途 |
|---|---|---|---|
stack | ✅ | ✅ | 普通命令 |
reporter | ❌ | ❌ | 返回值,嵌入参数槽 |
boolean | ❌ | ❌ | 条件判断 |
c-block | ✅ | ✅ | 循环/条件容器 |
hat | ❌ | ✅ | 事件触发入口 |
连接端口 ports
默认根据 shape 自动推断,也可手动指定:
{
ports: {
flowIn: true, // 是否有上方流入端口
flowOut: true, // 是否有下方流出端口
}
}
参数定义 params
每个参数对象:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 参数标识(对应 label 中的 {name}) |
type | string | 参数类型(见下表) |
default | any | 默认值 |
options | Array | dropdown 类型的选项列表 |
getOptions | function | 动态获取下拉选项的函数 |
参数类型
| type | 说明 | 示例 |
|---|---|---|
string | 文本输入 | { default: 'hello' } |
number | 数字输入 | { default: 10 } |
dropdown | 下拉选择 | { default: 'A', options: ['A','B','C'] } |
block | 代码块引用(可用 BlockRunner 运行) | { default: null } |
// 参数定义示例
params: [
{ name: 'text', type: 'string', default: '你好' },
{ name: 'count', type: 'number', default: 5 },
{ name: 'mode', type: 'dropdown', default: '升序',
options: ['升序', '降序'] },
]
子代码槽 subBlocks
用于 C 型积木(c-block),定义内部可嵌套的代码区域:
{
type: 'ext_repeat',
label: '重复 {n} 次',
shape: 'c-block',
subBlocks: ['body'], // 定义名为 'body' 的子代码槽
params: [{ name: 'n', type: 'number', default: 10 }],
}
子代码槽名通常为
'body',一个 C 型积木可以有多个槽(如 if-else 的 if 和 else)。动态标签 getLabel
当静态 label 不够用时(如显示动态参数),可用函数生成标签:
{
label: '调用 {name}', // 基础标签(无动态参数时使用)
getLabel: function(block) {
let label = '调用 ' + (block.params.name || '函数');
const extras = block._extraParams || [];
if (extras.length > 0) {
label += '(';
extras.forEach((ep, i) => {
label += '{' + ep.name + '}';
if (i < extras.length - 1) label += ', ';
});
label += ')';
}
return label;
},
}
可扩展输入 dynamicParams
在积木定义中设置 dynamicParams: true,用户即可通过右键菜单动态添加参数:
{
type: 'ext_call',
label: '调用函数',
shape: 'stack',
dynamicParams: true, // 启用可扩展输入
dynamicParamsPrefix: 'arg', // 参数名前缀(默认 'item')
dynamicParamsDefault: '0', // 参数默认值(默认 '')
getLabel: function(block) { /* 见上方 */ },
}
添加的参数名自动为
arg1、arg2...(取决于 prefix),在执行器中通过 params.arg1 访问。执行器函数
执行器定义积木被运行时的逻辑:
executors: {
ext_my_block: async function(params, scope) {
// params: 解析后的参数对象
// scope: 当前执行作用域
// 函数可以是同步或异步(async)
},
}
params 对象
包含积木定义的所有参数值(已自动解析 reporter 连接):
// 假设积木定义了 params: [{name:'count', type:'number', default:10}]
params.count // → 10 或用户输入的值,或连接的 reporter 返回值
// 动态参数也包含在内
params.arg1 // → 第一个动态参数
params.arg2 // → 第二个动态参数
// 特殊属性:原始积木对象(用于 BlockRunner)
params._block // → 当前积木的原始对象(含 subBlocks、paramConnections 等)
scope 对象
当前执行上下文:
| 属性 | 说明 |
|---|---|
scope.self | 当前对象实例(在类方法/初始化中) |
scope[变量名] | 局部变量 |
scope.__className | 类名(当 scope 是对象实例时) |
返回值
| 返回值 | 说明 |
|---|---|
| 任意值 | reporter 积木的返回值 |
undefined / 不返回 | stack 积木正常结束 |
'__RETURN__' | 中断当前函数/方法执行 |
'__STOP__' | 停止整个程序运行 |
执行器中可用的全局 API
| API | 说明 |
|---|---|
console.log(msg) | 调试输出到控制台 |
EditorState.blocks | 当前精灵的积木数据对象 |
StageManager | 舞台管理(精灵控制、运动、克隆等)→ 详情 |
SoundManager | 声音管理(播放/停止/音量)→ 详情 |
SensingInput | 输入检测(鼠标位置、键盘状态)→ 详情 |
ExtensionManager | 扩展管理器(注册/查询扩展) |
BlockRegistry | 积木注册表(查询积木定义) |
BlockRunner | 积木运行器(运行 C 型子代码 / block 参数)→ 详情 |
Math / Date 等 | 标准 JavaScript 全局对象 |
运行程序时,执行引擎自动收集所有精灵的积木,所有精灵的事件和脚本会同时生效。
registerExtension(extDef)
注册一个扩展。如果同一 ID 已存在则先卸载旧版。
ExtensionManager.registerExtension({
id: 'my_ext',
name: '我的扩展',
color: '#FF6B6B',
blocks: [...],
executors: {...},
});
// 返回: true(成功)/ false(失败)
加载方法
| 方法 | 参数 | 说明 |
|---|---|---|
loadFromJSON(jsonStr) | JSON 字符串 | 从 JSON 解析并注册 |
loadFromJS(jsContent) | JS 代码字符串 | 执行 JS 脚本(内部调用 registerExtension) |
loadFromFile(filePath) | 文件路径 | 自动检测 .js / .json 并加载 |
loadFromProject(projectPath) | 项目目录路径 | 加载 extensions/ 下所有扩展文件 |
查询与管理
| 方法 | 返回值 | 说明 |
|---|---|---|
getExtensions() | Array | 获取所有已加载的扩展定义 |
getExecutor(blockType) | function / null | 获取指定积木类型的执行器 |
unregisterExtension(extId) | boolean | 卸载指定扩展 |
StageManager API
控制舞台上的精灵。所有方法通过 StageManager.方法名() 调用。
查询精灵
| 方法 | 返回值 | 说明 |
|---|---|---|
getSprites() | Array<Sprite> | 获取所有精灵对象 |
getSprite(idx) | Sprite | 获取指定索引的精灵 |
getActiveSprite() | Sprite | 获取当前激活的精灵 |
getActiveSpriteIdx() | number | 获取当前激活精灵的索引 |
getSpriteByName(name) | Sprite / null | 通过名称查找精灵 |
getSpriteIndexByName(name) | number | 通过名称获取索引(-1=未找到) |
getSpriteCount() | number | 获取精灵总数 |
STAGE_W / STAGE_H | number | 舞台宽度/高度(480/360) |
控制精灵
| 方法 | 说明 |
|---|---|
setActiveSprite(idx) | 设置激活精灵(同步切换代码区) |
addSprite(name) | 添加新精灵,返回 Sprite 对象 |
removeSprite(idx) | 删除精灵(至少保留一个) |
cloneSprite(idx) | 克隆精灵(复制所有属性),返回新索引 |
运动控制
| 方法 | 说明 |
|---|---|
moveSprite(idx, steps) | 沿方向前进指定步数 |
rotateSprite(idx, deg) | 旋转指定度数 |
setSpritePos(idx, x, y) | 设置坐标 |
setSpriteDir(idx, deg) | 设置朝向 |
setSpriteVisible(idx, v) | 设置可见性 |
setSpriteSize(idx, size) | 设置大小(百分比) |
setSpriteSay(idx, text) | 显示说话气泡 |
bounceSprite(idx) | 碰到边缘反弹 |
速度系统
| 方法 | 说明 |
|---|---|
setVelocity(idx, vx, vy) | 设置速度向量 |
updateVelocity(idx) | 根据速度更新位置 |
applyFriction(idx, factor) | 应用摩擦力(0~1) |
applyGravity(idx, g) | 应用重力 |
bounceEdgeVelocity(idx, e) | 弹性碰撞边缘 |
追踪与旋转
| 方法 | 说明 |
|---|---|
moveTowards(idx, tx, ty, steps) | 向目标移动 |
moveAwayFrom(idx, tx, ty, steps) | 远离目标移动 |
pointTowards(idx, tx, ty) | 朝向目标 |
orbitAround(idx, tx, ty, deg, r) | 绕目标旋转 |
碰撞检测
| 方法 | 返回值 | 说明 |
|---|---|---|
isTouchingEdge(idx) | boolean | 是否碰到边缘 |
isTouchingSprite(idx1, idx2) | boolean | 两个精灵是否碰撞 |
getDistanceToPoint(idx, x, y) | number | 到目标点的距离 |
getDirectionToPoint(idx, x, y) | number | 到目标点的方向 |
造型控制
| 方法 | 说明 |
|---|---|
setSpriteCostume(idx, path) | 设置精灵造型 |
setSpriteCostumeByName(idx, name) | 通过造型库名称设置 |
clearSpriteCostume(idx) | 清除造型 |
精灵对象属性可以直接读取:
sprite.x、sprite.y、sprite.direction、sprite.size、sprite.visible、sprite.name、sprite.colorSoundManager API
控制声音播放。所有方法通过 SoundManager.方法名() 调用。
| 方法 | 返回值 | 说明 |
|---|---|---|
play(name) | boolean | 播放声音(不等待) |
playAndWait(name) | Promise<boolean> | 播放声音并等待完成 |
stop(name) | boolean | 停止指定声音 |
stopAll() | - | 停止所有声音 |
setVolume(vol) | - | 设置音量(0~100) |
changeVolume(delta) | - | 改变音量(+/-) |
getVolume() | number | 获取当前音量 |
getSoundNames() | Array | 获取已加载的声音列表 |
isPlaying(name) | boolean | 检查声音是否正在播放 |
声音文件放在项目的
sounds/ 目录下(支持 mp3、wav、ogg),加载后通过文件名(无扩展名)引用。// 在扩展执行器中使用声音
ext_play_bgm: async function(params) {
const name = params.soundName;
if (SoundManager.isPlaying(name)) {
SoundManager.stop(name);
} else {
await SoundManager.playAndWait(name);
}
},
SensingInput API
检测键盘和鼠标输入状态。所有方法通过 SensingInput.方法名() 调用。
| 方法/属性 | 返回值 | 说明 |
|---|---|---|
mouseX | number | 鼠标在舞台上的 X 坐标 |
mouseY | number | 鼠标在舞台上的 Y 坐标 |
mouseDown | boolean | 鼠标是否按下 |
keyPressed(key) | boolean | 检测按键是否按下 |
// 跟随鼠标的精灵
ext_follow_mouse: function(params) {
const idx = StageManager.getActiveSpriteIdx();
StageManager.setSpritePos(idx, SensingInput.mouseX, SensingInput.mouseY);
},
JSON 格式示例:数学工具
{
"id": "ext_math",
"name": "数学工具",
"color": "#4ECDC4",
"blocks": [
{
"type": "ext_sqrt",
"label": "√ {num}",
"shape": "reporter",
"ports": { "flowIn": false, "flowOut": false },
"params": [{ "name": "num", "type": "number", "default": 0 }]
},
{
"type": "ext_random",
"label": "随机数 {min} 到 {max}",
"shape": "reporter",
"ports": { "flowIn": false, "flowOut": false },
"params": [
{ "name": "min", "type": "number", "default": 1 },
{ "name": "max", "type": "number", "default": 100 }
]
},
{
"type": "ext_clamp",
"label": "限制 {val} 在 {min} ~ {max} 之间",
"shape": "reporter",
"ports": { "flowIn": false, "flowOut": false },
"params": [
{ "name": "val", "type": "number", "default": 0 },
{ "name": "min", "type": "number", "default": 0 },
{ "name": "max", "type": "number", "default": 100 }
]
}
],
"executors": {
"ext_sqrt": "function(params) { return Math.sqrt(Number(params.num)); }",
"ext_random": "function(params) { var min=Number(params.min),max=Number(params.max); return Math.floor(Math.random()*(max-min+1))+min; }",
"ext_clamp": "function(params) { var v=Number(params.val),lo=Number(params.min),hi=Number(params.max); return Math.max(lo,Math.min(hi,v)); }"
}
}
JS 格式示例:网络工具
/**
* extensions/network-tools.js
* 网络工具扩展 - 异步操作示例
*/
ExtensionManager.registerExtension({
id: 'ext_network',
name: '网络工具',
color: '#45B7D1',
blocks: [
{
type: 'ext_fetch',
label: '获取 {url} 的内容',
shape: 'reporter',
ports: { flowIn: false, flowOut: false },
params: [{ name: 'url', type: 'string', default: 'https://api.example.com' }],
},
{
type: 'ext_log',
label: '输出日志 {msg}',
shape: 'stack',
ports: { flowIn: true, flowOut: true },
params: [{ name: 'msg', type: 'string', default: '' }],
},
{
type: 'ext_delay',
label: '等待 {ms} 毫秒',
shape: 'stack',
ports: { flowIn: true, flowOut: true },
params: [{ name: 'ms', type: 'number', default: 1000 }],
},
],
executors: {
// 异步:网络请求
ext_fetch: async function(params) {
try {
const resp = await fetch(params.url);
return await resp.text();
} catch (e) {
return '错误: ' + e.message;
}
},
// 同步:输出日志
ext_log: function(params) {
console.log('[日志]', params.msg);
},
// 异步:延时
ext_delay: async function(params) {
await new Promise(r => setTimeout(r, Number(params.ms)));
},
},
});
可扩展参数示例:自定义函数调用
ExtensionManager.registerExtension({
id: 'ext_func',
name: '自定义函数',
color: '#E056A0',
blocks: [
{
type: 'ext_func_call',
label: '调用 {name}',
shape: 'stack',
ports: { flowIn: true, flowOut: true },
params: [{ name: 'name', type: 'string', default: '函数' }],
dynamicParams: true, // ✅ 启用可扩展输入
dynamicParamsPrefix: 'arg', // 参数名: arg1, arg2, ...
dynamicParamsDefault: '', // 默认空字符串
getLabel: function(block) {
let label = '调用 ' + (block.params.name || '函数');
const extras = block._extraParams || [];
if (extras.length > 0) {
label += '(' + extras.map(ep => '{' + ep.name + '}').join(', ') + ')';
}
return label;
},
},
],
executors: {
ext_func_call: function(params) {
const name = params.name;
// 收集所有动态参数
const args = [];
let i = 1;
while (params['arg' + i] !== undefined) {
args.push(params['arg' + i]);
i++;
}
console.log('调用', name, '参数:', args);
},
},
});
BlockRunner API
让扩展中的 C 型积木和含 block 类型参数的积木能够运行内部子代码。两者用法一致,通过 BlockRunner 全局对象调用。
为什么需要 BlockRunner?
普通 stack / reporter 积木只需处理参数值即可。但 C 型积木(循环、条件等)和 block 参数(传入的代码块引用)需要执行子积木链,这要求访问执行引擎内部函数。BlockRunner 将这些能力暴露给扩展。
方法列表
| 方法 | 参数 | 说明 |
|---|---|---|
runSubBlock(block, subName, scope) | block: 原始积木对象 subName: 子槽名(通常 'body')scope: 作用域 | 运行 C 型积木内部的子代码链 |
runBlockParam(blockRef, scope) | blockRef: block 参数值 scope: 作用域 | 运行 block 类型参数引用的积木链 |
run(blockOrId, scope) | blockOrId: 积木对象或 ID scope: 作用域 | 运行任意积木链 |
扩展执行器的
params._block 包含当前积木的原始对象,用于传给 runSubBlock。示例:自定义循环 C 型积木
ExtensionManager.registerExtension({
id: 'ext_loop',
name: '自定义循环',
color: '#FFBF00',
blocks: [
{
type: 'ext_repeat_n',
label: '重复 {n} 次',
shape: 'c-block',
ports: { flowIn: true, flowOut: true },
subBlocks: ['body'],
params: [{ name: 'n', type: 'number', default: 10 }],
},
],
executors: {
ext_repeat_n: async function(params, scope) {
const n = Number(params.n);
const block = params._block; // ← 获取原始积木对象
for (let i = 0; i < n; i++) {
const r = await BlockRunner.runSubBlock(block, 'body', { ...scope, __i: i });
if (r === '__RETURN__') return r;
}
},
},
});
示例:block 参数运行传入的代码块
{
type: 'ext_run_twice',
label: '运行两次 {code}',
shape: 'stack',
params: [{ name: 'code', type: 'block', default: null }],
}
// 执行器
ext_run_twice: async function(params, scope) {
const blockRef = params.code; // {__blockRef: 'xxx'}
for (let i = 0; i < 2; i++) {
await BlockRunner.runBlockParam(blockRef, scope);
}
},
block 类型参数的值(
params.code)与 C 型槽内的子积木在运行时无本质区别,均可通过 BlockRunner 执行。C 型积木示例:条件循环
// C 型积木通过 BlockRunner.runSubBlock 执行子代码
ExtensionManager.registerExtension({
id: 'ext_control',
name: '高级控制',
color: '#FFBF00',
blocks: [
{
type: 'ext_repeat',
label: '重复 {n} 次',
shape: 'c-block',
ports: { flowIn: true, flowOut: true },
subBlocks: ['body'],
params: [{ name: 'n', type: 'number', default: 10 }],
},
{
type: 'ext_if',
label: '如果 {cond}',
shape: 'c-block',
ports: { flowIn: true, flowOut: true },
subBlocks: ['body'],
params: [{ name: 'cond', type: 'boolean', default: false }],
},
],
executors: {
ext_repeat: async function(params, scope) {
const n = Number(params.n);
for (let i = 0; i < n; i++) {
const r = await BlockRunner.runSubBlock(params._block, 'body', scope);
if (r === '__RETURN__') return r;
}
},
ext_if: async function(params, scope) {
if (params.cond === true || params.cond === 'true') {
return await BlockRunner.runSubBlock(params._block, 'body', scope);
}
},
},
});
更多 C 型积木和 block 参数用法,参见 BlockRunner API。
精灵控制示例
在扩展中控制精灵运动、碰撞检测和克隆:
ExtensionManager.registerExtension({
id: 'ext_sprite_ai',
name: '精灵 AI',
color: '#FF9F43',
blocks: [
{
type: 'ext_chase_target',
label: '精灵 {name} 追踪 {target}',
shape: 'stack',
params: [
{ name: 'name', type: 'string', default: '精灵1' },
{ name: 'target', type: 'string', default: '精灵2' },
],
},
{
type: 'ext_spawn_clone',
label: '克隆精灵 {name} 并偏移 {dx} {dy}',
shape: 'stack',
params: [
{ name: 'name', type: 'string', default: '精灵1' },
{ name: 'dx', type: 'number', default: 50 },
{ name: 'dy', type: 'number', default: 0 },
],
},
{
type: 'ext_is_near',
label: '{a} 接近 {b} 距离 {dist}',
shape: 'boolean',
ports: { flowIn: false, flowOut: false },
params: [
{ name: 'a', type: 'string', default: '精灵1' },
{ name: 'b', type: 'string', default: '精灵2' },
{ name: 'dist', type: 'number', default: 50 },
],
},
],
executors: {
ext_chase_target: function(params) {
const idx = StageManager.getSpriteIndexByName(params.name);
const tidx = StageManager.getSpriteIndexByName(params.target);
if (idx < 0 || tidx < 0) return;
const t = StageManager.getSprite(tidx);
StageManager.moveTowards(idx, t.x, t.y, 3);
StageManager.pointTowards(idx, t.x, t.y);
},
ext_spawn_clone: function(params) {
const idx = StageManager.getSpriteIndexByName(params.name);
if (idx < 0) return;
const newIdx = StageManager.cloneSprite(idx);
if (newIdx !== null) {
const s = StageManager.getSprite(newIdx);
s.x += Number(params.dx);
s.y += Number(params.dy);
}
},
ext_is_near: function(params) {
const a = StageManager.getSpriteByName(params.a);
const b = StageManager.getSpriteByName(params.b);
if (!a || !b) return false;
const dx = a.x - b.x, dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy) <= Number(params.dist);
},
},
});
多精灵脚本系统
Objector 支持为每个精灵编写独立的脚本。切换精灵时,代码区自动显示该精灵的积木:
工作原理
- 每个精灵拥有独立的积木脚本存储
- 点击精灵切换时,代码区自动更新为该精灵的代码
- 运行程序时,所有精灵的脚本同时生效(事件触发、定时器等)
- 保存项目时,每个精灵的脚本随精灵数据一起存储
扩展中使用
// 获取特定精灵的积木数据
const sprite = StageManager.getSpriteByName('敌人');
if (sprite) {
const blockCount = Object.keys(sprite.blocks || {}).length;
console.log('敌人有', blockCount, '个积木');
}
// 获取所有精灵的合并积木
const allBlocks = StageManager.getAllBlocks();
const startBlocks = Object.values(allBlocks).filter(
b => b.type === 'event_start'
);
旧项目(单个脚本文件)会自动将所有积木分配给第一个精灵,保持完全兼容。
内置扩展清单
以下扩展已预装在 Objector 中:
| 扩展 | ID | 颜色 | 积木数 | 说明 |
|---|---|---|---|---|
| 时间 | ext_time | ■ | 8 | 时间戳、日期格式化、计时器 |
| 绘图 | ext_drawing | ■ | 9 | 画线/圆/矩形、写文字、画笔控制 |
| 字符串 | ext_string | ■ | 11 | 长度/截取/替换/大小写/分割/包含 |
| 颜色 | ext_color | ■ | 3 | RGB/十六进制/随机颜色 |
最佳实践
1. type 命名规范
建议使用 ext_扩展名_功能名 格式,避免与其他扩展冲突:
// ✅ 好
type: 'ext_math_sqrt'
type: 'ext_game_spawn_enemy'
// ❌ 避免
type: 'sqrt' // 太通用,容易冲突
type: 'my_block_1' // 无意义
2. 参数类型转换
参数值始终是字符串,使用时需要手动转换:
const num = Number(params.count);
const str = String(params.text);
const bool = params.flag === 'true';
3. 错误处理
ext_divide: function(params) {
const b = Number(params.b);
if (b === 0) return '错误: 除数不能为0';
return Number(params.a) / b;
},
4. 异步操作
// 使用 async 函数处理异步操作
ext_wait: async function(params) {
await new Promise(r => setTimeout(r, Number(params.ms)));
},
5. 热更新
修改扩展文件后,重新打开项目即可自动加载最新版本。同一 ID 的扩展会先卸载再重新注册。