Open cisen opened 5 years ago
ecma_gc_run
函数回收对象内存的情况有:jerry_gc
回收ecma_free_unused_memory
函数到全局的context,jmem_free_unused_memory_callback
,每次分配内存的时候(jmem_heap_gc_and_alloc_block),判断总共内存是否超出限制,如果超出则执行回收没用内存,也会根据优先级来回收jerry_cleanup
也可以触发ecma_finalize
回收内存,ecma_finalize
这个函数不单单清理内存还清理很多缓存/**
* Description of ECMA-object or lexical environment
* (depending on is_lexical_environment).
*
* ECMA对象或词汇环境的描述
(取决于is_lexical_environment)。
*/
typedef struct
{
/** type : 4 bit : ecma_object_type_t or ecma_lexical_environment_type_t
depending on ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV
flags : 2 bit : ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV,
ECMA_OBJECT_FLAG_EXTENSIBLE or ECMA_OBJECT_FLAG_NON_CLOSURE
refs : 10 bit (max 1023) */
// 2个字节,共16位,4位是对象类型,2位是标志,剩下10位是对象的指针?
// 这个其实就是该对象被引用的次数,引用数小于ECMA_OBJECT_REF_ONE则会被gc回收
uint16_t type_flags_refs;
/** next in the object chain maintained by the garbage collector 接下来在垃圾收集器维护的对象链中 */
// uint16_t 2字节
jmem_cpointer_t gc_next_cp;
/** compressed pointer to property list or bound object 压缩指向属性列表或绑定对象的指针 */
jmem_cpointer_t property_list_or_bound_object_cp;
/** object prototype or outer reference 对象原型或外部引用 */
jmem_cpointer_t prototype_or_outer_reference_cp;
} ecma_object_t;
/**
* Description of built-in properties of an object.
*/
typedef struct
{
uint8_t id; /**< built-in id */
uint8_t length_and_bitset_size; /**< length for built-in functions and
* bit set size for all built-ins */
uint16_t routine_id; /**< routine id for built-in functions */
uint32_t instantiated_bitset[1]; /**< bit set for instantiated properties */
} ecma_built_in_props_t;
/**
* Start position of bit set size in length_and_bitset_size field.
*/
#define ECMA_BUILT_IN_BITSET_SHIFT 5
/**
* Description of extended ECMA-object.
* 扩展ECMA对象的描述。
*
* The extended object is an object with extra fields.
* 扩展对象是具有额外字段的对象。
*/
typedef struct
{
ecma_object_t object; /**< object header 对象头部*/
/**
* Description of extra fields. These extra fields depend on the object type.
* 其他字段的描述。这些字段有对象类型决定
*/
union
{
ecma_built_in_props_t built_in; /**< built-in object part 内置对象部分 */
/**
* Description of objects with class.
* class的对象描述
*/
struct
{
uint16_t class_id; /**< class id of the object 对象的class id */
uint16_t extra_info; /**< extra information for the object
* e.g. array buffer type info (external/internal) 对象的额外信息,例如 数组缓冲区类型信息(外部/内部)*/
/**
* Description of extra fields. These extra fields depend on the class_id.
* 其他字段。这些字段由class_id决定
*/
union
{
ecma_value_t value; /**< value of the object (e.g. boolean, number, string, etc.) 对象的值(比如 boolean, number, string) */
uint32_t length; /**< length related property (e.g. length of ArrayBuffer) 长度相关属性(例如ArrayBuffer的长度) */
} u;
} class_prop;
/**
* Description of function objects.
* 函数对象的描述
*/
struct
{
ecma_value_t scope_cp; /**< function scope 函数的作用域 */
ecma_value_t bytecode_cp; /**< function byte code 函数的字节码 */
} function;
/**
* Description of array objects.
* 数组对象的描述
*/
struct
{
uint32_t length; /**< length property value 值的长度 */
ecma_property_t length_prop; /**< length property 长度属性 */
} array;
/**
* Description of pseudo array objects.
* 伪数组对象的描述。 Arguments, TypedArray, ArrayIterator
*/
struct
{
uint8_t type; /**< pseudo array type, e.g. Arguments, TypedArray, ArrayIterator 伪数组类型,比如 Arguments, TypedArray, ArrayIterator */
uint8_t extra_info; /**< extra information about the object. 这种对象的其他信息
* e.g. element_width_shift for typed arrays, 比如element_width_shift用于类型化数组
* [[IterationKind]] property for %Iterator% */
union
{
uint16_t length; /**< for arguments: length of names 给arguments,名字的长度 */
uint16_t class_id; /**< for typedarray: the specific class name 给typedarray,具体的类名称*/
uint16_t iterator_index; /**< for %Iterator%: [[%Iterator%NextIndex]] property 给Iterator,[[%Iterator%NextIndex]] */
} u1;
union
{
ecma_value_t lex_env_cp; /**< for arguments: lexical environment 给arguments, lexical的环境 */
ecma_value_t arraybuffer; /**< for typedarray: internal arraybuffer 给typedarray, 内部arraybuffer */
ecma_value_t iterated_value; /**< for %Iterator%: [[IteratedObject]] property 给Iterator,[[%Iterator%NextIndex]] */
} u2;
} pseudo_array;
/**
* Description of bound function object.
* 绑定函数对象的描述。
*/
struct
{
ecma_value_t target_function; /**< target function 目标函数 */
ecma_value_t args_len_or_this; /**< length of arguments or this value 这个值参数的数量 */
} bound_function;
ecma_external_handler_t external_handler_cb; /**< external function 外部函数 */
} u;
} ecma_extended_object_t;
总结
Lexer token types
,token的类型,lexer阶段产生CBC Opcode
,压缩字节码,lexer之后产生VM_OC
,虚拟机执行命令,执行的时候由CBC码转化而来aa
, 123之类的常量列表parser_context
, 运行阶段jerry_context
,栈帧vm_frame_ctx_t
parser_context
是parser阶段主要存储区,包括parser时的临时变量和paresr后产出的CBC链表和literal_pool变量链表。parser_parse_var_statement
先通过lexer_expect_identifier
确定变量名,然后再通过parser_parse_expression
函数将操作符根据token类型,比如等号插入cbc链表。这里使用上下文无关文法,所以不需要关心作用域parser_saved_context_t
,内部存储子作用域的cbc链表和参数池literal_pool优化
难点
注意
问答
lexer
保留关键字如何匹配的?
keyword_strings_list
里面的,比如do
就是在keyword_strings_list[0]
里面,keyword_strings_list[3]
就是存储for
,var
之类的0-9, a-z, $, _
这几个符号,其他的就可以作为断词符号截取lit_char_is_identifier_start
函数参数名如何确定?
lexer_parse_identifier
函数,它同时确定关键字和参数名,第二参数是确定寻找关键字还是参数内存是如何申请的?
jerry_global_heap
jmem_heap_alloc_block_internal
内存优先级如何分,有什么用?
jerry_global_heap
jmem_run_free_unused_memory_callbacks
函数jmem_heap_gc_and_alloc_block
函数var aa=1;
这个parse阶段经历了什么?byte_code
对象本身就是一个链表parser_flush_cbc (parser_context_t *context_p)
函数byte_code链表的执行入口在vm的哪里?
*byte_code_p++
就是读取整个parser_context.byte_code
里面的链表,读取出来的CBC_CODE,根据CBC_OPCODE_LIST里面跟OC_CODE的匹配关系,循环变成c语言执行byte_code链表和literal_pool链表都是存储在
jerry_global_heap
堆里面的?默认会生成一个全局的函数执行环境?
就是生成全局环境的
函数的作用域划分是如何实现的?
jerry-core\ecma\operations\ecma-function-object.c
frame_ctx.prev_context_p
指向父级frame字段,其他相关字段有frame_ctx.context_depth
当前context的深度作用域链是如何通过栈帧实现的?变量堆是如何设置实现的?
实现在作用域链寻找变量的代码在哪里?
箭头函数的栈帧有何不同?
ecma_op_create_arrow_function_object这个函数里面,vm调用这个函数使用的是父的作用域指针,this_binding也是父的
有两种内存方式?
jerry_global_heap
jmem_heap_free_block
函数的最后和jmem_heap_alloc_block_internal
的最后jmem
来管理内存什么东西存全局heap?
jmem封装相关
jerry_global_heap
的first里面的size和next_offset分配内存大小,然后返回地址jerry_global_heap
可以通过JERRY_HEAP_CONTEXT获取和设置JERRY_CONTEXT (jmem_heap_allocated_size)
和jmem_heap_limit
, jmem_heap_list_skip_p,jmem_free_8_byte_chunk_p函数作用域和全局作用域有何不同?
ecma_get_object_from_value (func_val);
构造一个函数对象,然后代码执行都是在这个函数对象里面的大括号{}和小括号()有何不同?
为什么中括号[]和.号都可以获得对象的属性?而数组不行?
为什么变量声明比如
var aa = 1
的变量名被提升了,值aa=1没有被提升?而函数function bb(){}是函数名和内容都被提升了?为什么要有token, CBC, VM_三种code
不引用的内存自动回收时如何实现的?
看上面代码,type_flags_refs其实时对象被引用的次数,ecma_gc_run函数会根据type_flags_refs的值绝对是否释放该对象
加法和赋值的实现流程?
运算符的优先级时如何实现的?
parser_context的literal_pool和stack有何关系?有何不同?
this是如何实现的?
ecma_op_function_call
会执行vm_run,然后把this指向的对象作为参数传进去作为执行环境栈帧的this_binding指针function aa(){this.aa=22}
new关键字是如何生成作用域(栈帧)的?
Cat.prototype = new Animal();
实现的If Type(result) is Object then return result.
如果new 构造函数返回的是一个对象,则获取到的是该对象,而不是new的类对象(包括数组)。具体实现再ecma_op_function_construct
函数的/* 9. */
创建栈帧的有几种情况
null,NaN和undefined有什么区别?
typeof的实现
jerry_value_get_type关键函数
==和===的运算逻辑
关键函数:jerry_binary_operation。==是JERRY_BIN_OP_EQUAL,===是JERRY_BIN_OP_STRICT_EQUAL
==判断总结
如果是字符串:
new String("a") == "a" 和 "a" == new String("a")
皆为 true。new String("a") == new String("a")
为 false。如果任何一方是bool,则转化为数字(0/1)比较
其他:
null == undefine
是特殊处理的toString和valueOf取值逻辑
===判断总结:
为什么
null instanceof Object
为false为什么
null == undefined
为true为什么
false == undefined
为falsenull是没有prototype和proto对象的嘛?
ecma_find_named_property
函数,ecma_get_property_list
函数通过property_list_or_bound_object_cp指针获取所有property。箭头函数是跟父作用域绑一起的,在哪里实现的?
ecma_op_create_arrow_function_object
这个函数里面,vm调用这个函数使用的是父的作用域指针,this_binding也是父的this_binding是什么?
this_binding = ecma_make_object_value (global_obj_p);
super函数做了什么?
vm_super_call
如何创建属性prop的?
ecma_create_property
函数对象的[[Get]], [[Call]], [[Construct]], [[HasInstance]]的执行位置?
ecma_op_function_construct
ecma_op_function_call
函数内没有使用var定义变量,该变量就被定义为全局变量是在哪里定义的?
promise的then方法为什么是异步的?但又比setTimeout快?
job_queue_head_p
堆的队列中。等执行完jerry_run
之后,再调用jerry_run_all_enqueued_jobs
执行队列代码。断点关键函数:ecma_promise_do_then
promise是如何实现的?
ecma_op_create_promise_object
对象在里面是如何存储的和实现的?
super函数做了什么?
vm_super_call
函数箭头函数有何特别
ecma_op_create_arrow_function_object
里面创建ecma_op_function_call
的ECMA_OBJECT_TYPE_ARROW_FUNCTION
调用vm.run生成新栈帧执行对象的本质时什么?如何存储的
闭包的本质是prev_context_p指针?
prototype本质是什么?
ecma_create_object
函数prototype_or_outer_reference_cp
(proto)的指针指向prototype对象property_list_or_bound_object_cp
指针指向父原型对象ecma_create_property
,主要就是连接到原型链上内置对象如Date,Boolean,Number,String的内置prototype有何不同?
ecma_instantiate_builtin
函数valueOf和toString函数有何不同
ecma_op_general_object_default_value
函数var,let,const有何不同
LEXER_KEYW_CONST
,LEXER_KEYW_LET
,LEXER_KEYW_VAR
instanseof做了什么?
opfunc_instanceof
函数和ecma_op_function_has_instance(关键)prototype_or_outer_reference_cp
(proto)指针指向的prototype对象,判断这个指针跟向上找到的prototype对象指针是否一样加减乘除遇到字符串,数组,对象的时候会怎么处理?
VM_OC_ADD
opfunc_addition
opfunc_addition
的处理逻辑是+ 'b'
返回的是NaNdo_number_arithmetic
,除法直接调用do_number_arithmetic
do_number_arithmetic
,其实就是只是用c的加减乘除操作左右值,错误则返回。所以只有+号是比较复杂的number的转化规则:
undefined和null有何不同?
ecma_is_value_null
ECMA_VALUE_UNDEFINED = ECMA_MAKE_VALUE (4)
,其他内置的值有:CBC编码是在哪里生成?被存储到哪里的?
parser_process_binary_opcodes
函数,会把token转化为CBC,然后使用parser_emit_cbc
函数中的parser_flush_cbc
使用parser_emit_two_bytes
类似的把CBC压入全局context的byte_code链表中parser_emit_cbc
是token转化为CBC的直接接口,在parser_parse_unary_expression
函数里面遇到null,false,true等直接使用该函数生成token如何改变this?
call和apply做了什么?如何实现绑定this的?
ecma_builtin_function_prototype_object_bind
,ecma_builtin_function_prototype_object_call
js如何实现继承的?
Cat.prototype = new Animal();
实现的__proto__
是什么?Object.prototype.isPrototypeOf()
,Object.getPrototypeOf()
,Object.setPrototypeOf()
prototype
对象的,只有函数有。实例化对象可以通过instanceof
获取实例化的类constructor
构造函数是什么?为什么typeof null是object,undefined是undefined
写死的
各种函数结构
词法环境(Lexical Environments)是什么?
变量名存储在哪里了?
lexer_process_char_literal
,lexer_process_char_literal
parser_list_append
函数分配一个节点内存,然后再在lexer_process_char_literal函数里面插入变量数据// 2个字节或者4个字节
ifdef PARSER_DUMP_BYTE_CODE
struct
else / !PARSER_DUMP_BYTE_CODE /
union
endif / PARSER_DUMP_BYTE_CODE /
{ prop_length_t length; /*< length of ident / string literal 关键字的长度 2个字节,变量名字符窜的长度,再根据char_p地址确定变量名 / uint16_t index; /*< real index during post processing 在提交过程中真实的index 2个字节 / } prop;
uint8_t type; /*< type of the literal 常量的类型 1个字节 / uint8_t status_flags; /*< status flags 状态标志 1个字节 / } lexer_literal_t;
为什么需要
parser_restore_context
和parser_save_context
arguments对象时什么?
ecma_op_create_arguments_object
函数,具体执行逻辑:iterator是什么?数组继承自Iterator?还有什么继承自Iterator
ES自带的bind函数做了什么?
ecma_builtin_function_prototype_object_bind
函数ext_function_p->u.bound_function.target_function
指向就函数地址ext_function_p->u.bound_function.args_len_or_this
指向目标对象ES自带的bind跟call/apply有何不同?
为什么
[].slice.call()
可以分解类数组之类的非数组?ecma_builtin_array_prototype_object_slice
ecma_op_object_find
,所以函数的取值跟对象的取值基本是一样的。虽然·ecma_op_object_find底层会根据具体对象类型来取值函数取值跟对象的取值有何不同?一个是.一个是[]
ecma_op_object_find
, 只是底层实现会有一点点不同。主要是在ecma_op_object_find_own
函数的ECMA_OBJECT_TYPE_CLASS
为什么使用对象池可以大幅度提升性能?
indexOf是如何比较是否是其中一员的?
[].indexOf.call(ul, li)
这种寻找如何实现的?ecma_builtin_string_prototype_object_index_of
ecma_builtin_array_prototype_object_index_of
ecma_op_strict_equality_compare
函数比较列表的item和要比较的目标ecma_op_strict_equality_compare
就是===判断,对象的话直接比较内存指针。for in/for of实现有何不同?
opfunc_for_in
函数,for of的实现不同,没有实现函数只是在vm.c文件中通过VM_OC_FOR_OF_CREATE_CONTEXT
多种操作码实现VM_OC_FOR_IN_CREATE_CONTEXT
,VM_OC_FOR_IN_GET_NEXT
,VM_OC_FOR_IN_HAS_NEXT
ecma_op_iterator_value
获取iterator的值和ecma_op_iterator_step
,ecma_op_get_iterator
opfunc_for_in
逻辑说明Array.prototype.reduce为什么可以配合promise实现顺序调用?为什么reduce的部分执行可以在微任务里面?
ecma_builtin_typedarray_prototype_reduce
调用ecma_builtin_typedarray_prototype_reduce_with_direction
ecma_builtin_typedarray_prototype_reduce_with_direction
的逻辑是ecma_op_typedarray_get_index_prop
循环获取数组的值ecma_op_function_call
调用函数获取返回的计算结果,并作为toltal或者accumulator,用于下次的ecma_op_function_call
调用ecma_op_function_call
调用promise时,会把任务插入微队列里面,然后再循环执行ESM ( ECMAScript Module) 原理和流程?
https://github.com/cisen/blog/issues/633
关键字:
JERRY_ES2015_MODULE_SYSTEM
, 关键文件jerry-core\ecma\base\ecma-module.c
,jerry-core\parser\js\js-parser-module.c
,执行模块文件:vm_run->ecma_module_connect_imports->ecma_module_evaluate->vm_run_module
,对于ES模块,这个过程有三个步骤。
使用
parser_module_add_names_to_node
函数将模块名构造一个module record添加到module map在parse阶段,
import/export
都会将模块名添加到以文件路径为key的module map上 (登记模块名)vm_run
执行字节码前,连接module map模块到各个模块的执行域。这里会采用深度优先后序遍历继续深度优先后续遍历执行依赖模块代码,执行完之后再执行当前文件名字节码。注意这里只运行导出的模块,不导出的不会运行。因为它时根据变量池的变量来取导出作用域执行。没获取到的模块时不会运行的。但其父作用域还是会被执行
ECM 第二步connect做了什么?
ecma_module_connect_imports
函数vm_run
执行字节码前,根据导入名称遍历所有已经parse的export的作用域和其变量池,直到找到对应的导出模块并返回如何保证每个module只执行一次?
new A()
是先执行()
还是new
?为什么为什么自动执行函数访问不到外部的变量?
其他发现
变量提升的原因