我总是假设对象的键存储为字符串,并且任何非字符串值都将被强制转换。因此,正是在这种假设下,在编写一些必须为数千个密钥存储一个小值的代码时,我将所有密钥转换为基数36:
// theKey is an integer
myMap[theKey.toString(36)] = theValue;
然后,我决定看看我的假设是否真的正确,并使用Chrome的分析器检查内存使用情况。这里大概是我运行的测试和内存使用情况:
window.objIntegers = {};
for (i = 100000; i--) window.objIntegers[i] = 'a';
// 786kb
window.objStrings = {};
for (i = 100000; i--) window.objStrings[i.toString(36)] = 'a';
// 16.7mb!
// and the same pattern but with:
key = i + .5; // 16.7mb
key = i + ''; // 786kb
key = '0' + i; // 16.7mb
key = i + '0'; // 16.7mb
显然,我的假设是关闭的。我想知道的是,它们是如何存储的,以及这种行为是标准的,还是只是Chromium / WebKit团队添加的一些额外技巧?
答案 0 :(得分:3)
这确实是V8的一些额外诡计。
JSObject
(JS Object
的内部C ++表示)有两个属性elements
和properties
,其中“元素”是带有数字索引的JS属性,而“属性”是带有字符串索引的JS属性。
显然,数字索引在这里消耗的内存要少得多,因为不需要存储属性名称。
http://code.google.com/intl/de-DE/chrome/devtools/docs/memory-analysis-101.html#primitive_objects
典型的JavaScript对象有两个数组:一个用于存储命名属性,另一个用于存储数字元素。
从v8源代码可以看出:
http://code.google.com/p/v8/source/browse/trunk/src/objects.h#1483
// [properties]: Backing storage for properties.
...
// [elements]: The elements (properties with names that are integers).
http://code.google.com/p/v8/source/browse/trunk/src/runtime.cc#4462
MaybeObject* Runtime::SetObjectProperty(Isolate* isolate,
Handle<Object> object,
Handle<Object> key,
Handle<Object> value,
PropertyAttributes attr,
StrictModeFlag strict_mode) {
...
// Check if the given key is an array index.
uint32_t index;
if (key->ToArrayIndex(&index)) {
// In Firefox/SpiderMonkey, Safari and Opera you can access the characters
// of a string using [] notation. We need to support this too in
// JavaScript.
// In the case of a String object we just need to redirect the assignment to
// the underlying string if the index is in range. Since the underlying
// string does nothing with the assignment then we can ignore such
// assignments.
if (js_object->IsStringObjectWithCharacterAt(index)) {
return *value;
}
Handle<Object> result = JSObject::SetElement(
js_object, index, value, attr, strict_mode, set_mode);
if (result.is_null()) return Failure::Exception();
return *value;
}
if (key->IsString()) {
Handle<Object> result;
if (Handle<String>::cast(key)->AsArrayIndex(&index)) {
result = JSObject::SetElement(
js_object, index, value, attr, strict_mode, set_mode);
} else {
Handle<String> key_string = Handle<String>::cast(key);
key_string->TryFlatten();
result = JSReceiver::SetProperty(
js_object, key_string, value, attr, strict_mode);
}
if (result.is_null()) return Failure::Exception();
return *value;
}
// Call-back into JavaScript to convert the key to a string.
bool has_pending_exception = false;
Handle<Object> converted = Execution::ToString(key, &has_pending_exception);
if (has_pending_exception) return Failure::Exception();
Handle<String> name = Handle<String>::cast(converted);
if (name->AsArrayIndex(&index)) {
return js_object->SetElement(
index, *value, attr, strict_mode, true, set_mode);
} else {
return js_object->SetProperty(*name, *value, attr, strict_mode);
}
}
我不会详细介绍,但请注意,SetObjectProperty
会调用SetElement
或SetProperty
,具体取决于密钥。不确定为什么检查在您的测试用例key = i + '0'
中失败。
答案 1 :(得分:0)
这是Chromium的优化。我相信它具有启发式(here's one mention of it)来确定内部存储属性的最有效方法。 ECMAScript规范所要求的只是JavaScript和环境之间的接口,并没有说明如何在内部实现暴露给JavaScript的对象。