高级数据结构的文本写法
作品装了「高级数据结构」扩展时,文本语法里多出一套对象和列表的写法,读起来像 JavaScript 原生对象。
没装这个扩展时下面的写法都不启用,原有语法一字不变。写法只是积木的另一种样子:积木打印成文本、文本编回积木,结构完全一致;
写不成这些写法的积木(输入框里直接填了名字的、形状不标准的)读回时保持块式 moreDataTypes.xxx(...)。
命令行和 AI 助手用 gandi guide data-structures 离线读到这套写法的速查、陷阱和可编译的示例。
这个扩展有两个版本,扩展 id 不同,积木集合也不同(写法按作品里装的那个 id 自动对应,同一份文字在两个作品里编出的积木前缀不同):
| 线上版 | 集市版(「高级数据结构 v1.6.0 预览版」) | |
|---|---|---|
| 扩展 id | moreDataTypes |
test.moreDataTypes(带点,块式写成成员链 test.moreDataTypes.xxx(...),见积木式写法) |
| 积木 | 57 块 | 87 块:多 22 块积木(集合、遍历、回调、几个对象 / 列表函数)和 8 块回调参数(item、index……) |
| 写法 | 下面「速查」里的全部 | 全部,再加集合、遍历、回调方法和其他函数 |
集市版才有的写法在线上版里报错(new Set() 报 E_JS_EXPR,for...of 报 E_UNSUPPORTED,o.map(...)、o.add(x)、isSet(x)、merge(...) 报 E_MDT_MISSING,提示要装集市版 test.moreDataTypes);线上版有的写法两边都能用。
一个作品里同时装两个版本时,文本只对上先识别到的那一个。
作品里一个版本都没装时,这些写法都不启用:写入 / 贴入前的预检和编译都报 E_MDT_MISSING(「需要先装高级数据结构」,fix 里写明用 add_extension 装 moreDataTypes),
不会说成「扩展库里找不到 store」。装了之后,store.set、o.push(1)、JSON.stringify(o) 这些是糖,不是名叫 store / o / JSON 的扩展的积木,
预检只把真正不认识的带点 id(FurryR.NotJS.run())当成缺扩展去库里装。
const o = stage.variable("o", 0);
const hits = stage.variable("hits", 0);
when.flagclicked(() => {
o = { name: "bob", hp: 100, tags: ["a", "b"] };
o.pos = { x: 1, y: hits };
o.hp += 5;
o.items = [];
o.items.push(3);
o.items.sort();
if (o.has("hp") && size(o.items) > 0) {
hits = o.pos.x + o.items.pop();
}
delete o.pos.y;
looks.say(JSON.stringify(o));
});速查#
对象和列表#
| 写法 | 积木 |
|---|---|
{}、[] |
空对象、空列表(getNewObject、getNewList) |
{a: 1, b: [1, 2]}、[1, "x", true],值全是字面量 |
解析 JSON(getObjFromJson),数字、布尔、嵌套都保留类型 |
{a: x, b: 1}、[x, 1],含变量或表达式 |
getNewObject / getNewList,键值成对的动态参数;{[k]: v} 键可以是表达式,{x} 等于 {x: x} |
list(3, 0)、list([3, 4], 0) |
3 项的列表、3 行 4 列的列表(createListWithLength) |
JSON.parse(s)、JSON.stringify(x) |
解析 JSON、转 JSON(getObjFromJson、JSONOf) |
clone(o)、structuredClone(o) |
浅拷贝、深拷贝(copyFrom) |
读写属性#
| 写法 | 积木 |
|---|---|
o.k、o["a b"]、o[k]、o.a.b[2].c |
取属性,路径写成同一块积木的动态参数(getPropOfObject) |
(o.a).b |
先取 o.a 再取它的 b:括号里的接收者是单独一块积木,不并进路径 |
o.k = v、o.a.b = v |
设属性(setPropOfObject,设为) |
o.k += n、o.k -= n、o.k++、o.k-- |
设属性(增加,数字相加) |
delete o.k、delete o.a.b |
删除属性(delPropOfObject);列表按下标删除,后面的项前移 |
o.has("k")、"k" in o |
有没有这个属性(ifObjectPropExist),列表是「包含这个值」;打印写 o.has(...) |
size(o) |
属性数 / 项目数(sizeOfObject) |
Object.keys(o)、Object.values(o)、Object.entries(o) |
getAllProperties |
keyAt(o, i)、valueAt(o, i) |
第几项的键 / 值(getPropOfObjectByIdx) |
pick(o, keys)、pickValues(o, keys) |
取出指定键的子对象 / 值列表(getSubObject) |
Object.assign(o, other) |
把 other 并进 o(mergeObject) |
o.clear() |
清空(clear) |
typeof x |
typeOf,结果是 "list"、"object"、"string"、"number"、"boolean" |
Array.isArray(x)、isObject(x)、isObjectOrList(x) |
判断类型(checkType),isObject 不含列表 |
same(a, b) |
是不是同一个对象(strictlyEqual) |
列表方法#
| 写法 | 积木 |
|---|---|
L.push(v)、L.pushUnique(v)、L.removeValue(v) |
加入、不存在才加入、移出第一个等于 v 的项(addItemToList2);o.a.b.push(v) 的路径是动态参数 |
L.insert(i, v) |
插入到第 i 项前(insertItemIntoList) |
L.indexOf(v)、L.lastIndexOf(v)、L.nthIndexOf(v, n) |
位置(indexOf,第一次、最后一次、第 n 次;负数从末尾数);找不到是 0 |
L.pop()、L.pop(i) |
取出并删除最后一项 / 第 i 项(pop) |
L.range(a, b) |
第 a 到 b 项,两端都包含,-1 表示到末尾(slice) |
L.join(s)、text.split(s) |
join、split |
a.concat(b)、a.union(b)、a.intersection(b)、a.difference(b) |
合并、并集、交集、差集(mergeList) |
L.sort()、L.sortDesc()、L.reverse()、L.shuffle()、L.dedupe() |
opList |
L.sortBy("p")、L.sortByDesc("p") |
按对象的属性排序(sortListByProp) |
Scratch 自己的列表(const L = sprite.list(...))只支持原来的写法:L.push(v)、L.insert(i, v)、L.remove(i)、L.clear()、L.includes(v)、L.indexOf(v)、L.length、L[i]。
上面表里别的写法用在 Scratch 列表上(L.sort()、L.join(",")、L.pop()、L.range(1, 2)、size(L)、delete L[1]、Object.keys(L)、typeof L、"k" in L、for (const v of L)……)
一律报 E_MDT_RECEIVER:扩展把列表积木读出来的文字当成「临时数据名」,找不到就静默什么都不做,所以编译期直接拦下。
要对 Scratch 列表的内容用这些方法,先读成对象列表:块式 o = moreDataTypes.getScratchList("L")(集市版 test.moreDataTypes.getScratchList("L"))返回的就是那个列表本身,
o.sort()、o.push(x) 直接改 Scratch 列表;元素在编译模式下是数字,解释模式下是文字。或者一开始就把数据放进对象列表(o = [])。
临时数据和设置#
| 写法 | 积木 |
|---|---|
store.set("k", v)、store.add("k", n) |
临时数据设为 / 增加 |
store.get("k")、store.has("k")、store.names() |
读取、是否存在、所有名字(逗号隔开的文字) |
store.delete("k")、store.clear() |
删除一项、全部 |
ads.autoCreate(true)、ads.indexStart(1)、ads.allowJSON(false)、ads.showPrefix(false)、ads.caseSensitive(true) |
设置命令。全局生效,存在舞台上的配置注释里,随作品保存 |
临时数据只在内存里,不存档。
设置命令改的是整个作品的设置,对每段脚本都生效;新写进一条设置命令时,编译结果会带一条说明。
编译执行(默认)里 for...of 的键从几开始是脚本编译时就定死的:同一段脚本里先 ads.indexStart(0) 再 for...of,键仍然是 1、2(解释执行是 0、1);
map、reduce 这类 lambda 积木读运行时的设置,不受影响。
文本里的名字规则:变量(文件里声明的,或作品里本来就有的)叫 store、ads、JSON、Object、Array、Set 时,变量压过同名的糖:
store.set("k", 1) 读成「变量 store 的方法」,而变量上没有 set,报 E_MDT_SHADOWED,fix 里给块式写法 moreDataTypes.setTempData(...),也可以给变量改名;
store.has(...)、store.clear() 这类对象本来就有的方法名照常读成变量的方法。
集市版的写法#
下面几节的写法只在装了集市版(test.moreDataTypes)时启用。
集合(集市版)#
| 写法 | 积木 |
|---|---|
new Set()、new Set([1, "a", x]) |
空集合、带初值的集合(getNewSet,动态参数);初值和列表字面量同一条规则:数字写成数字、true / false 写成 JSON |
new Set(L)、new Set(o.items) |
把列表或集合转成集合,去掉重复(toSet);来源本身是数组字面量时多套一层括号 new Set(([1, 2])),不加括号的数组字面量是上一行的集合字面量 |
s.add(x)、s.delete(x) |
加入、移出(addToSet、removeFromSet),列表也能用 |
s.has(x)、size(s)、typeof s |
是否包含(ifObjectPropExist)、项目数、类型("set") |
isSet(x) |
判断类型(checkType,选「集合」) |
Set.union(a, b)、Set.intersection(a, b)、Set.difference(a, b) |
并集、交集、差集(mergeSet);列表的 a.union(b) 是另一块积木(mergeList),写法不同才分得开 |
「设为空集合」(createOrClearSet)没有糖,写块式 test.moreDataTypes.createOrClearSet("s")。
遍历(集市版)#
for...of 是「遍历」积木(forEachWithObj):积木上有两个参数槽(键、值),循环体是它的 C 形口。
for (const [key, value] of Object.entries(o)) { // 键和值都用
total += value;
}
for (const item of L) { ... } // 只用值:列表、对象、集合都行
for (const k of Object.keys(o)) { ... } // 遍历 Object.keys(o) 这个列表本身- 键:列表和集合是项目的序号(从 1 起,
ads.indexStart(0)之后从 0 起),对象是属性名;值:项目本身。 - 循环变量只在循环体里有效,不能赋值;名字只是文字里的写法,不存进积木:打印时取
key/value,和变量、别的形参重名就加数字(value2)。 for (const [key] of Object.entries(o))只绑定键;for (const x of ...)绑定的是值,键槽照常在积木上。- 循环体里写
break;/continue;只在解释执行有效;编译执行里被忽略(扩展 1.6.18 的循环没接这两个积木),文字里写了会给W_FOREACH_BREAK警告。要提前结束就把后面的语句放进if。 - 块式等价写法是带形参的回调:
test.moreDataTypes.forEachWithObj(o, (key, value) => { ... }),见回调形参。
回调方法(集市版)#
回调是 (形参...) => 表达式 的箭头函数,形参依次绑定积木上的参数槽,回调体是一个表达式(不是 { ... } 语句)。
| 写法 | 积木 |
|---|---|
L.map((item, index) => 表达式) |
每项算一个值,得到新列表(mapList) |
L.reduce((acc, item, index) => 表达式, 初值) |
累积(reduceList),初值写在回调后面,和 JavaScript 一样 |
L.some((item, index) => 条件)、L.every(...) |
有一项 / 每一项满足条件(someAll) |
L.find(...)、L.findLast(...)、L.filter(...) |
第一个 / 最后一个 / 全部满足条件的项(findInList,取值) |
L.findIndex(...)、L.findLastIndex(...)、L.findIndexes(...) |
同上,取位置(findIndexes 得到位置列表) |
L.sort((a, b) => 数字) |
按规则排序(sortListByRule);不带回调的 L.sort() 仍是升序排序 |
list(5, (index) => 表达式) |
长度 5、第 index 项等于表达式的新列表(listFrom);list(5, 0) 仍是填充同一个值 |
- 回调体产出的值放进列表时,数字字面量是数字、
true/false是布尔:L.map((item) => 5)得到数字 5,和L.push(5)同一条规则。 - 形参只在回调体里有效,名字是文字里的写法:打印时取积木上槽的名字(
item、index、acc、a、b),重名加数字;少写形参也行,没写的槽照样在积木上。 index从 1 起(受ads.indexStart控制);some/every/find*作用在对象上时index是属性名。reduce对对象也能累加(把属性值加起来),map对对象得到空列表[],sort(回调)只用于列表。- 里层回调会盖住外层的同名积木:
item、index在各个回调积木里是同一块积木,运行时读最近一层的值。在里层回调体里写外层的item报E_PARAM_SHADOWED(连里层没写形参的槽也算:reduce有index槽,写(acc, cur)它也在运行时定义index)。要用外层的值,先放进变量。 - 回调积木在编译模式下靠扩展自己的编译支持(扩展设置里的「开启编译优化」,默认开);关掉后编译模式里这些积木在日志里报错、什么都不做。解释执行不受影响。
其他函数(集市版)#
| 写法 | 积木 |
|---|---|
equivalent(a, b) |
深度相等:结构和内容一样就是真,不要求是同一个对象(equivalent);same(a, b) 仍比引用 |
merge(a, b, c, ...) |
合并成一个新的列表 / 集合 / 对象,参数可以继续加(merge);列表和集合接起来,对象后面的覆盖前面的;种类不一样得到空文字;Object.assign(o, other) 才是改 o |
L.flat(深度) |
展平嵌套的列表(flat);不写深度是 1 |
L.findByProp("name", v)、L.findIndexByProp("name", v) |
在对象列表里找属性等于 v 的项 / 位置(findByProp),找不到时取项得空文字,取位置得 0(位置从 0 数时是 -1) |
"a=1,b=2".splitToObj("=", ",")、o.joinObj("=", ",") |
文字拆成对象、对象拼成文字(splitToObj、joinObj),拆出来的值都是文字,键和值两端的空白去掉 |
getPath(o, ["a", "b"]) |
按路径列表取属性(getPropByPath),路径是一个值(可以是变量里的列表);o.a.b 的路径写死在文字里 |
jsonPath(o, "$..price")、jsonPath(o, 路径, "path") |
JSONPath 查询(getByJsonPath),第三个参数是结果类型:"value"(默认,可省)、"path"、"pointer"、"parent"、"parentProperty"、"all";用到的 JSONPath 库在扩展加载时从网上取,没加载好时得到空文字 |
ads.negativeIndex(true) |
允许负数下标(allowNegIdx) |
数字、文字和值#
- 值放进对象、列表时,数字字面量是数字:
o.hp = 100之后typeof o.hp是"number",JSON.stringify(o)里是100,两种运行模式都一样。 编出来的积木是一个0 + 100的运算(运算的结果才是数字),true/false编成解析 JSON。0 + n同理:两种模式都存真数字。 - 别的情况解释模式和编译模式(项目默认)不一样,两个版本实测相同(下表)。原因是编译器认得常量:数字样的常量按数字传给扩展,变量被赋成常量后它的类型也被编译器推出来。
| 写法 | 解释模式 | 编译模式(默认) |
|---|---|---|
n = 7; o = {a: n}、o.b = n |
存文字 "7" |
存数字 7 |
n = 7; typeof n |
"string" |
"number" |
typeof "5" |
"string" |
"number" |
o.k = "5"、o.push("5")、store.set("k", "5") |
存文字 "5" |
存数字 5 |
same("5", 5) |
false |
true |
JSON.parse("5") |
5 |
线上版:空文字 "";集市版:5 |
n = 5; JSON.parse(n)、s = "5"; JSON.parse(s) |
5 |
空文字 ""(两个版本都是) |
new Set([1, 2]).has(2)(集市版) |
false(传的是文字 "2") |
true |
ads.indexStart(0) 之后同一脚本里 for...of 的键(集市版) |
0、1 | 1、2(idxStartsFrom1 在编译时烧进生成的代码,同一脚本里改设置不生效;lambda 积木 map 等不受影响) |
需要数字样的文字在两种模式下都保持文字:写在 {...} / [...] 字面量里({k: "5"},全字面量按 JSON 解析,文字原样保留),或者拼一下(o.k = "5" + "")。
编译模式下 o.k = "5" 存不成文字,o.k = `${"5"}` 也一样(单段模板就是那个字符串字面量)。JSON.parse 要读数字样的文字时,别让编译器把它看成常量(来自 store.get、拼接的结果等),或直接写成数字。
- 字符串字面量在解释模式里是文字:
o.id = "5"存文字;积木里输入框敲的5是文字,读回时写成"5"。 - 缺失的属性读出空文字
"",null存不进去也比不了:用o.k == ""或o.has("k")。o.k = null、x == null报E_MDT_NULL;{a: null}、[null]这样字面量里的null同样报E_MDT_NULL(和动态路径一致)。 积木里 JSON 文本含null是合法的(手拖的getObjFromJson("{\"a\":null}")):存进去,读出来是"",size会把它计数;这样的积木打印成JSON.parse("{\"a\":null}"),读回无损。 - 字面量里不是有限数的数字(
1e400、-1e400)报E_MDT_NUMBER:JSON 里没有 Infinity,静态字面量会悄悄变成null。先建对象,再o.k = 1e400(运行时存真的 Infinity),或写文字"Infinity"。 - 键始终是文字:
o[1]和o["1"]是同一个键。整数样的键("1"、"12")运行时被引擎提到对象最前面:o = {"2": "b", "1": "a"}用起来是{"1":"a","2":"b"}, 只有积木里保住了书写顺序,运行时顺序不归 DSL 管。
下标#
列表下标从 1 数起,负数从末尾倒数(L[-1] 最后一项),0 取不到东西:Scratch 列表上的字面量 0 和 L.insert(0, v) 给 W_INDEX_FROM_ONE 警告。
对象 / 列表变量上的 o[0]、delete o[0]、o.items[0] = 1 不给警告:接收者的类型这里看不出,0 也可能是对象的键 "0"。
ads.indexStart(0) 把整个作品的列表改成从 0 数,之后写法不变,字面量下标自己对应调整;文本里 ads.indexStart(0) 之后的 0 下标不再给 W_INDEX_FROM_ONE(再写 ads.indexStart(1) 恢复提示)。
自动创建默认关:o.a.b = 1 在 o.a 不存在时什么都不做;ads.autoCreate(true) 之后会沿路径建出对象。
ads.allowJSON(false) 之后,内容像 JSON 的文字不再被悄悄当成对象读。
陷阱#
| 写法 | 实际行为 | 怎么办 |
|---|---|---|
if (o)、while (L)、!o、o && r、o ? 1 : 2、if (o.a)、if (size(o)) |
对象在 Scratch 里永远为真,条件槽拒绝变量和对象,报 E_SLOT_TYPE |
size(o) > 0、o.has("k");Scratch 列表 L.length > 0。不要写 o != 0:对任何对象 / 列表恒为真,{} 和 [] 也是 |
a == b |
对象按 JSON 文字比较(不分大小写),不是比引用 | same(a, b) 比引用;== {} 给 W_MDT_EQ |
.length |
对象 / 列表上是值的文字长度(列表是 JSON 文字的长度)。接收者明确是对象 / 列表(Object.keys(o).length、JSON.parse(s).length、[1, 2].length)时给 W_MDT_LENGTH;o.items.length 没有类型信息(也可能是字符串的长度),不警告 |
size(o.items);文字长度写 `${o.name}`.length |
x[i] |
装了本扩展后,接收者是变量、成员、调用结果时是属性访问;只有字符串字面量和模板后面的 [i] 是取字符 |
变量里取字符写 operator.letter_of(i, x) |
Scratch 列表上的 L.sort()、size(L)、L.join(",")、delete L[1]…… |
扩展把列表读成「临时数据名」,静默什么都不做,报 E_MDT_RECEIVER |
先 o = moreDataTypes.getScratchList("L")(返回列表本身),或一开始就用对象列表 |
变量叫 store、ads、JSON、Object、Array |
变量压过同名的糖,store.set(...) 报 E_MDT_SHADOWED |
给变量改名,或用块式 moreDataTypes.setTempData(...) |
{a: null}、[null]、o.k = null |
报 E_MDT_NULL;积木里 JSON 文本含 null 时存进去、读出为 ""、size 会计数 |
删除属性 delete o.k,或存 "" |
{a: 1e400}、[1e999] |
报 E_MDT_NUMBER(JSON 放不下 Infinity,会变成 null) |
先建对象再 o.k = 1e400,或写 "Infinity" |
字符串写进对象 o.k = "5"(数字样的文字) |
编译模式存成数字 5,解释模式存文字(见数字、文字和值) |
写进 {k: "5"} 字面量,或 o.k = "5" + "" |
o.hasOwnProperty("k")、o.toString()、o.valueOf()、o.constructor(...) |
对象 / 列表积木上没有这些方法,报 E_JS_EXPR(属性读写 o.constructor 当普通键用没问题) |
o.has("k")、JSON.stringify(o) |
字符串字面量当接收者:"cfg".k |
积木输入框里的文字是「临时数据名」,不是文字 | store.get("cfg").k,报 E_MDT_RECEIVER |
| 变量的值恰好等于某个临时数据名 | 被当成那份临时数据 | 避免同名 |
o.s += "x" |
「增加」是数字相加,不拼接文字,报 E_MDT_ADD_TEXT |
o.s = ${o.s}x`` |
o.k *= 2 等 |
属性不支持复合赋值,报 E_JS_EXPR |
o.k = o.k * 2 |
L.indexOf(v, n) |
积木的第二个参数是「第几次出现」,不是起点,报 E_MDT_INDEXOF |
nthIndexOf(v, n) |
nthIndexOf(v, 0) |
总是返回第一项的位置(扩展的行为),给 W_MDT_INDEXOF_ZERO |
用 1 或 -1 |
积木的 N 默认 -1 |
默认找最后一次出现 | indexOf(v) 是第一次,lastIndexOf(v) 是最后一次 |
对象字面量写在积木调用的第一个参数:looks.say({a: 1}) |
被读成具名参数,报 E_UNKNOWN_ARG |
加括号 looks.say(({a: 1}));打印出来的文本会自己加 |
for (const k of ...)(线上版) |
线上版没有遍历积木,报 E_UNSUPPORTED |
for (let i = 1; i <= size(o); i++) { keyAt(o, i); valueAt(o, i); };集市版直接用 for...of |
JSON.stringify(s),s 是集合 |
集合转 JSON 得 {}(扩展的行为) |
先转成列表:JSON.stringify(merge([], s)) |
Set.union(a, b) 等,编译执行 |
扩展 1.6.18 的编译实现总是得到空集合;解释执行正常 | 并集写 merge(a, b)(两个集合得到集合,两种模式都对);交集、差集遍历着 add |
s.has(2),编译执行 |
编译把数字样的常量当数字传,集合里存的是数字所以找得到;解释执行传文字 "2" 找不到 |
写成算式 s.has(1 + 1),或先放进数字变量 |
集合上用列表方法:s.pop()、s.union([...]) |
编译执行抛 list.splice is not a function / list1.concat is not a function,解释执行得空文字 |
集合只用 add、delete、has、size、typeof、Set.union 等、merge、for...of;要列表方法先 merge([], s) 转成列表 |
ads.indexStart(0) 后同一脚本里的 for...of 键 |
编译执行仍是 1、2(编译时定死),解释执行是 0、1 | 把 for...of 放进另一段脚本,或循环里自己算序号 |
for...of 里的 break; / continue;,编译执行 |
被忽略,整个循环照跑;解释执行有效,给 W_FOREACH_BREAK |
把后面的语句放进 if |
里层回调里用外层回调的 item / index |
同一块积木,读到里层的值,报 E_PARAM_SHADOWED |
先 v = item; 再在里层用 v |
回调体写成 { ... } |
回调体是一个表达式,报 E_SLOT_TYPE |
(item) => item * 2 |
对象和列表是引用:p = o; p.x = 2 之后 o.x 也是 2,要复制用 clone / structuredClone。
积木式写法#
所有积木照常可以写成块式 moreDataTypes.getPropOfObject("k", o, ["b", "c"])(扩展 id 前缀随作品里装的 id)。扩展 id 带点时(集市版),
前缀是成员链:test.moreDataTypes.getPropOfObject("k", o, ["b", "c"]);id 或积木名写不成标识符(连字符、空格、中文)时写成
ext["my-ext"]["积木名"](...),规则见 dsl-syntax.md。
动态参数写在固定参数后面,
一个槽的组写数组 ["b", "c"],键值成对的组(getNewObject)写 [["a", 1], ["b", x]],具名写法用 DYNAMIC_ARGS1、DYNAMIC_ARGS2…
动态参数存档时是积木 mutation 的 dynamicargids / dynamicargtypes,输入名从 DYNAMIC_ARGS1 起连续编号,编辑器里的积木和文本编出来的完全一致。
集市版的 getNewSet、merge 也是动态参数(merge 的组插在 B 后面)。
带参数槽的积木(遍历、mapList、reduceList、someAll、findInList、listFrom、sortListByRule)块式写成回调:
test.moreDataTypes.mapList(o, (item, index) => 表达式)、test.moreDataTypes.forEachWithObj(o, (key, value) => { ... }),
回调永远是最后一个参数,形参依次绑定积木上的参数槽(forEachWithObj 是键、值,reduceList 是 acc、item、index,sortListByRule 是 a、b)。
具名写法把回调放在回调体输入的键里:{NAME_OR_OBJ: o, EXP: (item) => item},或者作为第二个参数。
参数槽里的影子是积木自己的可再生参数(test.moreDataTypes.item()、forEachKey()……);回调体里引用形参就是引用这块积木。
没有写成回调的形状(槽里被别的积木盖住、缺输入的)读回时保持位置参数写法,参数槽里的标准影子写成它自己的调用,同样无损。
回调形参名写不成标识符的槽(带空格、连字符、数字开头、保留字、空名……)不打印成回调,保持位置参数写法 cb.loopWith(3, "my item", () => { ... }),读回无损。
扩展 id 是 JavaScript 保留字或 DSL 自己占用的名字(if、null、class、when、call、Math、JSON、Object、store、ads、ext、stage……)时,
一律写成 ext["id"].积木名(...),不会打印出读不回的 null.go("p")。
带点 id 的链没对上已装扩展、根名又不是任何变量时(FurryR.NotJS.run())是还没装的扩展:编译报 E_UNKNOWN_BLOCK,fix 里写出这个 id 的 add_extension;
写入 / 贴入前的预检按文字里写的 id 装它(id 带下划线时也按写的 id,不是第一段)。
缺扩展的页读出来是只读预览,预览里高级数据结构的积木也统一写成块式,不会混出一半糖(size(o)、store.set)一半块式;装上缺的扩展之后读到的才是糖。
编译配置#
编译执行时,自制积木里只用同步积木才能内联,所以每块积木在 编译配置 里声明 sync:线上版共 57 块(原有的 10 块 + 47 块),
集市版 72 块 sync(再加 15 块),其余 7 块是 lambda / 循环积木(会让出,不声明)、8 块是参数 reporter。
线上版里取 / 设属性两块另有「快路径」替身,对着扩展源码写,所以只在扩展还是写它时那个版本时装上:fastGuard 把扩展里相关几个方法的源码(getPropOfObject、setPropOfObject、
__getObjByNameOrObj、__getDeepestObjAndProp、__setDataByOption、anythingToSCArg,加上 SafeObject 的 isSafeObjectLike、getActualObject、toSafeObject)拼起来做 FNV-1a 哈希,
和配置里记的 eb13b2a5 比;扩展改了其中任何一个方法,哈希对不上,替身就不装,退回扩展自己的实现。集市版哈希对不上,不带快路径。