在使用JS的过程中,一直对var关键字的动态分配内存感到神奇。V8是怎么在运行时为未知的数据类型分配内存的呢?
既然疫情封在家里,就把这个问题解决一下吧。
分析
JS 是动态语言,编译时无法确定变量内存大小,只有在运行时才能确定占用内存情况。JS 是使用 C++ 编写的,C++ 在编译时能够确定变量的内存大小。
C++ 使用了什么方法实现了 JS 的动态类型的呢?解决方法是:操作JS数据前先查询类型,再操作。这又产生了新问题——性能损耗,因为类型查询是极为耗时的操作,频繁使用严重影响程序运行速度。为此,V8采用了Map机制,也称为隐藏类(Hidden Class)。
用户编辑的 JS 变量会被 C++ 修改,添加一些附加信息。在用户定义的 JS 变量前面添加一个pointer,指向该变量的描述信息。
然后我们分析一下V8能够看到什么。
上图描述的“类型说明”中,Map的数据结构为:
// All heap objects have a Map that describes their structure.
// A Map contains information about:
// - Size information about the object
// - How to iterate over an object (for garbage collection)
//
// Map layout:
// +---------------+-------------------------------------------------+
// | _ Type _ | _ Description _ |
// +---------------+-------------------------------------------------+
// | TaggedPointer | map - Always a pointer to the MetaMap root |
// +---------------+-------------------------------------------------+
// | Int | The first int field |
// `---+----------+-------------------------------------------------+
// | Byte | [instance_size] |
// +----------+-------------------------------------------------+
// | Byte | If Map for a primitive type: |
// | | native context index for constructor fn |
// | | If Map for an Object type: |
// | | inobject properties start offset in words |
// +----------+-------------------------------------------------+
// | Byte | [used_or_unused_instance_size_in_words] |
// | | For JSObject in fast mode this byte encodes |
// | | the size of the object that includes only |
// | | the used property fields or the slack size |
// | | in properties backing store. |
// +----------+-------------------------------------------------+
// | Byte | [visitor_id] |
// +----+----------+-------------------------------------------------+
// | Int | The second int field |
// `---+----------+-------------------------------------------------+
// | Short | [instance_type] |
// +----------+-------------------------------------------------+
// | Byte | [bit_field] |
// | | - has_non_instance_prototype (bit 0) |
// | | - is_callable (bit 1) |
// | | - has_named_interceptor (bit 2) |
// | | - has_indexed_interceptor (bit 3) |
// | | - is_undetectable (bit 4) |
// | | - is_access_check_needed (bit 5) |
// | | - is_constructor (bit 6) |
// | | - has_prototype_slot (bit 7) |
// +----------+-------------------------------------------------+
// | Byte | [bit_field2] |
// | | - new_target_is_base (bit 0) |
// | | - is_immutable_proto (bit 1) |
// | | - elements_kind (bits 2..7) |
// +----+----------+-------------------------------------------------+
// | Int | [bit_field3] |
// | | - enum_length (bit 0..9) |
// | | - number_of_own_descriptors (bit 10..19) |
// | | - is_prototype_map (bit 20) |
// | | - is_dictionary_map (bit 21) |
// | | - owns_descriptors (bit 22) |
// | | - is_in_retained_map_list (bit 23) |
// | | - is_deprecated (bit 24) |
// | | - is_unstable (bit 25) |
// | | - is_migration_target (bit 26) |
// | | - is_extensible (bit 28) |
// | | - may_have_interesting_symbols (bit 28) |
// | | - construction_counter (bit 29..31) |
// | | |
// +*****************************************************************+
// | Int | On systems with 64bit pointer types, there |
// | | is an unused 32bits after bit_field3 |
// +*****************************************************************+
// | TaggedPointer | [prototype] |
// +---------------+-------------------------------------------------+
// | TaggedPointer | [constructor_or_back_pointer_or_native_context] |
// +---------------+-------------------------------------------------+
// | TaggedPointer | [instance_descriptors] |
// +*****************************************************************+
// | TaggedPointer | [dependent_code] |
// +---------------+-------------------------------------------------+
// | TaggedPointer | [prototype_validity_cell] |
// +---------------+-------------------------------------------------+
// | TaggedPointer | If Map is a prototype map: |
// | | [prototype_info] |
// | | Else: |
// | | [raw_transitions] |
// +---------------+-------------------------------------------------+
V8通过查询Map,可以知道存储空间内存放了什么,怎么存放的,进而正确操作JS对象。一句话总结:V8利用类型确定的Map类(c++实现的class对象)管理JS的动态对象。其实,在V8角度看,Map类型是确定的,所以整体数据类型就是确定的。最重要是Map提高了效率,因为它代替了耗时的JS对象类型检索操作。