文档
官网 打开编辑器
文档/更快地写积木

高级数据结构的文本写法

作品装了「高级数据结构」扩展时,文本语法里多出一套对象和列表的写法,读起来像 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 比;扩展改了其中任何一个方法,哈希对不上,替身就不装,退回扩展自己的实现。集市版哈希对不上,不带快路径。