JavaScript 对象是一组属性(properties)和元素(elements)。 出于性能和内存的原因,V8 设计了几种不同的属性表示形式,例如对象内(in-object)、慢速属性(slow property)和自字典(self-dict)。 在本章中,让我们更深入地研究 JavaScript 对象。
下面的图 1 显示了一个基本的 JavaScript 对象在内存中的样子。
元素和属性存储在两个独立的数据结构中,这使得添加和访问属性(property)或元素(elements)对于不同的使用模式更加高效。
元素主要用于各种 Array.prototype 方法,例如 pop 或 slice。 鉴于这些函数访问连续范围内的属性,V8 内部大多数情况下也将它们表示为简单数组。
命名属性以类似的方式存储在单独的数组中。 然而,与元素不同的是,我们不能简单地使用键来推断它们在属性数组中的位置; 我们需要一些额外的元数据。 在 V8 中,每个 JavaScript 对象都有一个关联的 HiddenClass。 HiddenClass 存储关于对象详情的信息,以及从属性名称到属性值的映射。
在图 2 中,有元素(elements)、属性(propertis)和对象内(in-object)属性。 与元素或属性不同,对象内属性存储在对象本身中,这意味着您可以直接访问它们而无需映射。 in-object 的数量是在创建 JavaScript 对象时分配的(不同 V8 版本数量可能不一样)。
在图 3 中,存在三种不同的命名属性类型:in-object、fast/slow dictionary。
图 4 显示了 JavaScript 对象内存布局,所有 V8 托管堆对象都必须有一个存储在首地址的映射指针。 当然,也有元素和属性。 描述符是 map 中的一个重要成员,它负责描述一个对象的属性,这个我们以后会讲到。
在下面的例子中,sayname 是一个等同于 console.log 的函数。这里可以想一个问题:一个对象的函数存储在哪里? 即,sayname 在哪里?
1. function person(name) {
2. this.name=name;
3. this.sayname=function(){console.log(this.name);}
4. }
5. worker = new person("Nicholas");
6. worker.sayname();
7. //separation........................................
8. //separation........................................
9. Bytecode Age: 0
10. 000001DAA2FA1E96 @ 0 : 13 00 LdaConstant [0]
11. 000001DAA2FA1E98 @ 2 : c2 Star1
12. 000001DAA2FA1E99 @ 3 : 19 fe f8 Mov , r2
13. 0 E> 000001DAA2FA1E9C @ 6 : 64 51 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
14. 100 S> 000001DAA2FA1EA1 @ 11 : 21 01 00 LdaGlobal [1], [0]
15. 000001DAA2FA1EA4 @ 14 : c2 Star1
16. 000001DAA2FA1EA5 @ 15 : 13 02 LdaConstant [2]
17. 000001DAA2FA1EA7 @ 17 : c1 Star2
18. 000001DAA2FA1EA8 @ 18 : 0b f9 Ldar r1
19. 109 E> 000001DAA2FA1EAA @ 20 : 68 f9 f8 01 02 Construct r1, r2-r2, [2]
20. 107 E> 000001DAA2FA1EAF @ 25 : 23 03 04 StaGlobal [3], [4]
21. 134 S> 000001DAA2FA1EB2 @ 28 : 21 03 06 LdaGlobal [3], [6]
22. 000001DAA2FA1EB5 @ 31 : c1 Star2
23. 141 E> 000001DAA2FA1EB6 @ 32 : 2d f8 04 08 LdaNamedProperty r2, [4], [8]
24. 000001DAA2FA1EBA @ 36 : c2 Star1
25. 141 E> 000001DAA2FA1EBB @ 37 : 5c f9 f8 0a CallProperty0 r1, r2, [10]
26. 000001DAA2FA1EBF @ 41 : c3 Star0
27. 151 S> 000001DAA2FA1EC0 @ 42 : a8 Return
28. Constant pool (size = 5)
29. 000001DAA2FA1E29: [FixedArray] in OldSpace
30. - map: 0x024008ac12c1
第一部分是 JavaScript 代码,最后是相应的字节码。第 19-21 行使用 person 对象构造一个名为 worker 的实例,第 23 行加载名称为 sayname 的属性。
你了解 line23 吗? 它告诉我们 sayname 只是一个属性,与其类型无关。 如果再深入一点,您会发现 console 和 log 也是属性。
因此,在 JavaScript 对象中,所有函数都被视为属性(property)。
为了调试上述代码,我们将单步执行以下代码:
1. RUNTIME_FUNCTION(Runtime_NewObject) {
2. HandleScope scope(isolate);
3. DCHECK_EQ(2, args.length());
4. CONVERT_ARG_HANDLE_CHECKED(JSFunction, target, 0);
5. CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, 1);
6. RETURN_RESULT_OR_FAILURE(
7. isolate,
8. JSObject::New(target, new_target, Handle::null()));
9. }
10. //separation.....................................
11. MaybeHandle JSObject::New(Handle constructor,
12. Handle new_target,
13. Handle site) {
14. Isolate* const isolate = constructor->GetIsolate();
15. DCHECK(constructor->IsConstructor());
16. DCHECK(new_target->IsConstructor());
17. DCHECK(!constructor->has_initial_map() ||
18. !InstanceTypeChecker::IsJSFunction(
19. constructor->initial_map().instance_type()));
20. Handle
RUNTIME_FUNCTION(Runtime_NewObject) 是我们在上一章中提到的一个 MACRO。它调用 JSObject::New() 来创建一个新对象, JSObject::New() 调用 JSFunction::GetDerivedMap() 来分配 map,在 JSFunction::GetDerivedMap() 中,将调用以下函数。
1. void JSFunction::EnsureHasInitialMap(Handle function) {
2. DCHECK(function->has_prototype_slot());
3. DCHECK(function->IsConstructor() ||
4. IsResumableFunction(function->shared().kind()));
5. if (function->has_initial_map()) return;
6. Isolate* isolate = function->GetIsolate();
7. int expected_nof_properties =
8. CalculateExpectedNofProperties(isolate, function);
9. if (function->has_initial_map()) return;
10. InstanceType instance_type;
11. if (IsResumableFunction(function->shared().kind())) {
12. instance_type = IsAsyncGeneratorFunction(function->shared().kind())
13. ? JS_ASYNC_GENERATOR_OBJECT_TYPE
14. : JS_GENERATOR_OBJECT_TYPE;
15. } else {
16. instance_type = JS_OBJECT_TYPE;
17. }
18. int instance_size;
19. int inobject_properties;
20. CalculateInstanceSizeHelper(instance_type, false, 0, expected_nof_properties,
21. &instance_size, &inobject_properties);
22. Handle
第 7 行,返回构造函数期望的属性数,第 26 到 30 行生成原型,关键字 function 是一个构造函数。 在我们的例子中,函数是 person,原型为 null,因为 person 是第一次执行,所以第 29 行被执行。
生成的原型与构造函数绑定,并为所有实例共享,这就是 V8 实现的原型原则。下面是在第 7 行中调用的 CalculateExpectedNofProperties。
int JSFunction::CalculateExpectedNofProperties(Isolate* isolate,
2. Handle function) {
3. int expected_nof_properties = 0;
4. for (PrototypeIterator iter(isolate, function, kStartAtReceiver);
5. !iter.IsAtEnd(); iter.Advance()) {
6. Handle current =
7. PrototypeIterator::GetCurrent(iter);
8. if (!current->IsJSFunction()) break;
9. Handle func = Handle::cast(current);
10. Handle shared(func->shared(), isolate);
11. IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate));
12. if (is_compiled_scope.is_compiled() ||
13. Compiler::Compile(isolate, func, Compiler::CLEAR_EXCEPTION,
14. &is_compiled_scope)) {
15. DCHECK(shared->is_compiled());
16. int count = shared->expected_nof_properties();
17. if (expected_nof_properties <= JSObject::kMaxInObjectProperties - count) {
18. expected_nof_properties += count;
19. } else {
20. return JSObject::kMaxInObjectProperties;
21. }
22. } else {
23. continue;
24. }
25. }
26. if (expected_nof_properties > 0) {
27. expected_nof_properties += 8;
28. if (expected_nof_properties > JSObject::kMaxInObjectProperties) {
29. expected_nof_properties = JSObject::kMaxInObjectProperties;
30. }
31. }
32. return expected_nof_properties;
33. }
第 28 行,MaxInObject 是 in-object 的最大数量,超过的属性存入 properties 列表。下面图 5 显示了调用堆栈。
https://javascript.plainenglish.io/lets-understand-chrome-v8-how-does-v8-implement-a-javascript-object-ddde32120bb5
https://v8.dev/blog/fast-properties
更新时间:2024-10-26
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号