为什么 V8(一个 javascript 模块)反序列化两个 2 个不同的十六进制字符串但带来 2 个相同的对象?
Why V8 (a javascript module) deserializes two 2 different hex strings but brings 2 identical objects?
我想弄清楚为什么会这样。
const v8 = require('v8')
let stringa = 'ff0d6f220762616c616e63655a200070824fdc89df747141410000000000220a6465706c6f7965644279220673797374656d220773746f726167656f220a63616e646964617465736f222c49364e4d6f2b4b5a3531634b2b39626f6543554f716e6570724361723566727752746771746c493350596b3d6f2205626c6f636b4e000000000000244022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65220d4e65772056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b7b04222c576b5a6c346470454666797436585a506662433270766f5777424d6a314f497161794958304b47714a654d3d6f2205626c6f636b4e000000000000000022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65221147656e657369732056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b2206766f746572736f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b5a200000e8c69583abe311000000000000007b017b057b02220877697468647261776f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b6f49005a100000dcce86b42ad07b017b017b02220673797374656d547b04'
let a = v8.deserialize(Buffer.from(stringa, 'hex'))
console.dir(a, {depth: null})
let stringb = 'ff0d6f220762616c616e63655a200070824fdc89df747141410000000000220a6465706c6f7965644279220673797374656d220773746f726167656f220a63616e646964617465736f222c49364e4d6f2b4b5a3531634b2b39626f6543554f716e6570724361723566727752746771746c493350596b3d6f2205626c6f636b491422076465706f7369745a20000088b116afe3b5020000000000000022046e616d65220d4e65772056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b7b04222c576b5a6c346470454666797436585a506662433270766f5777424d6a314f497161794958304b47714a654d3d6f2205626c6f636b490022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65221147656e657369732056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b2206766f746572736f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b5a200000e8c69583abe311000000000000007b017b057b02220877697468647261776f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b6f49005a100000dcce86b42ad07b017b017b02220673797374656d547b04'
let b = v8.deserialize(Buffer.from(stringb, 'hex'))
console.dir(b, {depth: null})
stringa
和stringb
明显不同。但是当我打印两个对象 a
和 b
时。我意识到他们是一样的。
谁能给我解释一下为什么?非常感谢大家。
借助 SerializationTag 定义,手动解码这些字符串并不难,例如:
ff 0d // non-legacy version: 13
6f // object start
22 07 // one-byte string, length=7
62 61 6c 61 6e 63 65 // "balance"
5a 20 // bigint, bitfield: sign=0 length=16
00 70 82 4f dc 89 df 74 71 41 41 00 00 00 00 00
22 0a // one-byte string, length=10
64 65 70 6c 6f 79 65 64 42 79 // "deployedBy"
22 06 // one-byte string, length=6
73 79 73 74 65 6d // "system"
...
当你这样做的时间足够长时,你最终会看到 stringa
使用:
22 05 // one-byte string, length=5
62 6c 6f 63 6b // "block"
4e // double
0000000000002440 // little-endian encoding of "10.0" as an IEEE754 double
而 stringb
使用:
22 05 // one-byte string, length=5
62 6c 6f 63 6b // "block"
49 // int32
14 // "zig-zag" encoding of "10"
所以当你将这些字符串反序列化为JavaScript对象时,那么当然整数10
和双精度10.0
是无法区分的,因为在JS中,两者都是Number
s.
退一步说:不管这里的具体解释如何,依赖您无法控制的序列化格式的特定行为并不是一个好主意。它可能会发生不可预测的变化。 V8 的 serialize/deserialize API 到底做了什么是内部实现细节(这也是为什么没有关于它的文档;你必须阅读源代码才能弄清楚)可以改变。而且,事实上,它确实发生了变化!此字符串的序列化格式版本为 13,这意味着在此之前还有 12 个其他版本,并且随时可能推出新版本。
即使除了新的序列化格式版本之外,我还能想到其他几个原因,为什么不同的编码可以反序列化为外观相同的 JS 对象(例如字符串编码、NaN 模式、varint 或 BigInt 数据中的前导零、可选的可忽略字节, ...).
如果您需要对对象的序列化格式做出任何保证,您应该实现自己的序列化算法,以确保这些保证确实有效。
我想弄清楚为什么会这样。
const v8 = require('v8')
let stringa = 'ff0d6f220762616c616e63655a200070824fdc89df747141410000000000220a6465706c6f7965644279220673797374656d220773746f726167656f220a63616e646964617465736f222c49364e4d6f2b4b5a3531634b2b39626f6543554f716e6570724361723566727752746771746c493350596b3d6f2205626c6f636b4e000000000000244022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65220d4e65772056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b7b04222c576b5a6c346470454666797436585a506662433270766f5777424d6a314f497161794958304b47714a654d3d6f2205626c6f636b4e000000000000000022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65221147656e657369732056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b2206766f746572736f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b5a200000e8c69583abe311000000000000007b017b057b02220877697468647261776f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b6f49005a100000dcce86b42ad07b017b017b02220673797374656d547b04'
let a = v8.deserialize(Buffer.from(stringa, 'hex'))
console.dir(a, {depth: null})
let stringb = 'ff0d6f220762616c616e63655a200070824fdc89df747141410000000000220a6465706c6f7965644279220673797374656d220773746f726167656f220a63616e646964617465736f222c49364e4d6f2b4b5a3531634b2b39626f6543554f716e6570724361723566727752746771746c493350596b3d6f2205626c6f636b491422076465706f7369745a20000088b116afe3b5020000000000000022046e616d65220d4e65772056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b7b04222c576b5a6c346470454666797436585a506662433270766f5777424d6a314f497161794958304b47714a654d3d6f2205626c6f636b490022076465706f7369745a20000088b116afe3b5020000000000000022046e616d65221147656e657369732056616c696461746f7222086f70657261746f72222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b2206766f746572736f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b5a200000e8c69583abe311000000000000007b017b057b02220877697468647261776f222b74676c6331613979667337653765763270376a6164396b71757774767870637a6e6e65633432327564726b6f49005a100000dcce86b42ad07b017b017b02220673797374656d547b04'
let b = v8.deserialize(Buffer.from(stringb, 'hex'))
console.dir(b, {depth: null})
stringa
和stringb
明显不同。但是当我打印两个对象 a
和 b
时。我意识到他们是一样的。
谁能给我解释一下为什么?非常感谢大家。
借助 SerializationTag 定义,手动解码这些字符串并不难,例如:
ff 0d // non-legacy version: 13
6f // object start
22 07 // one-byte string, length=7
62 61 6c 61 6e 63 65 // "balance"
5a 20 // bigint, bitfield: sign=0 length=16
00 70 82 4f dc 89 df 74 71 41 41 00 00 00 00 00
22 0a // one-byte string, length=10
64 65 70 6c 6f 79 65 64 42 79 // "deployedBy"
22 06 // one-byte string, length=6
73 79 73 74 65 6d // "system"
...
当你这样做的时间足够长时,你最终会看到 stringa
使用:
22 05 // one-byte string, length=5
62 6c 6f 63 6b // "block"
4e // double
0000000000002440 // little-endian encoding of "10.0" as an IEEE754 double
而 stringb
使用:
22 05 // one-byte string, length=5
62 6c 6f 63 6b // "block"
49 // int32
14 // "zig-zag" encoding of "10"
所以当你将这些字符串反序列化为JavaScript对象时,那么当然整数10
和双精度10.0
是无法区分的,因为在JS中,两者都是Number
s.
退一步说:不管这里的具体解释如何,依赖您无法控制的序列化格式的特定行为并不是一个好主意。它可能会发生不可预测的变化。 V8 的 serialize/deserialize API 到底做了什么是内部实现细节(这也是为什么没有关于它的文档;你必须阅读源代码才能弄清楚)可以改变。而且,事实上,它确实发生了变化!此字符串的序列化格式版本为 13,这意味着在此之前还有 12 个其他版本,并且随时可能推出新版本。
即使除了新的序列化格式版本之外,我还能想到其他几个原因,为什么不同的编码可以反序列化为外观相同的 JS 对象(例如字符串编码、NaN 模式、varint 或 BigInt 数据中的前导零、可选的可忽略字节, ...).
如果您需要对对象的序列化格式做出任何保证,您应该实现自己的序列化算法,以确保这些保证确实有效。