「Chrome系列」深入理解V8:V8如何实现Object?

JavaScript 对象是一组属性(properties)和元素(elements)。 出于性能和内存的原因,V8 设计了几种不同的属性表示形式,例如对象内(in-object)、慢速属性(slow property)和自字典(self-dict)。 在本章中,让我们更深入地研究 JavaScript 对象。

1.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 
31.   - length: 5
32.             0: 0x01daa2fa1d11 
33.             1: 0x01daa2fa1c09 
34.             2: 0x01daa2fa1c39 
35.             3: 0x01daa2fa1c21 
36.             4: 0x01daa2fa1c51 

第一部分是 JavaScript 代码,最后是相应的字节码。第 19-21 行使用 person 对象构造一个名为 worker 的实例,第 23 行加载名称为 sayname 的属性。

你了解 line23 吗? 它告诉我们 sayname 只是一个属性,与其类型无关。 如果再深入一点,您会发现 console 和 log 也是属性。

因此,在 JavaScript 对象中,所有函数都被视为属性(property)

2.实例化 一个 JavaScript 对象

为了调试上述代码,我们将单步执行以下代码:

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 initial_map;
21.    ASSIGN_RETURN_ON_EXCEPTION(
22.        isolate, initial_map,
23.        JSFunction::GetDerivedMap(isolate, constructor, new_target), JSObject);
24.    int initial_capacity = V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL
25.                               ? SwissNameDictionary::kInitialCapacity
26.                               : NameDictionary::kInitialCapacity;
27.    Handle result = isolate->factory()->NewFastOrSlowJSObjectFromMap(
28.        initial_map, initial_capacity, AllocationType::kYoung, site);
29.    isolate->counters()->constructed_objects()->Increment();
30.    isolate->counters()->constructed_objects_runtime()->Increment();
31.    return result;
32.  }

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 map = isolate->factory()->NewMap(instance_type, instance_size,
23.                                                 TERMINAL_FAST_ELEMENTS_KIND,
24.                                                 inobject_properties);
25.    Handle prototype;
26.    if (function->has_instance_prototype()) {
27.      prototype = handle(function->instance_prototype(), isolate);
28.    } else {
29.      prototype = isolate->factory()->NewFunctionPrototype(function);
30.    }
31.    DCHECK(map->has_fast_object_elements());
32.    DCHECK(prototype->IsJSReceiver());
33.    JSFunction::SetInitialMap(isolate, function, map, prototype);
34.    map->StartInobjectSlackTracking();
35.  }

第 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-04-01

标签:慢速   原型   函数   实例   属性   元素   对象   数量   内存   代码   系列

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top