ECMAScript 函数对象实例化

201 阅读12分钟

前言

ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。

通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。

欢迎关注和订阅专栏 重学前端-ECMAScript协议上篇

看点代码

var d = "d";
function test(a, b = 1){
  let c = 2;
  console.log(a, b, c);
}
test();

如上代码调用test()是如何运作的呢? test在这里不过是文本罢了。

回顾之前 <<script加载和全局顶层代码申明实例化>> 并未细说函数的调用,仅仅涉及到了 各种申明的实例化。本文就详细讲解开发者眼中的函数对象(Function Object)是如何被初始化的。

先回顾一下 script从源码到被执行的基本流程

本文的重点就是 蓝色标注的流程中的 全局申明实例化的一部分逻辑, 函数对象实例化。

本文只阐述 普通函数。

Script Record

Script Record 脚本记录 之前提过,是源代码被 ParseScript ( sourceText, realm, hostDefined ) 初步解析后的数据结构。

这个过程底层是ParseText 把源代码转为 Script的解析树。请记住这个解析树,尤其重要。

Script 节点

一起来看看这个类型为Script的解析树到底长啥模样。 至于树怎么生成,不是本系列的内容。

这种结构,在协议本身是有规范的,语法可以参见 Scripts

有三个语句,和代码的对应关系如下:

可以通过代码获取来论证一下:

完整的解析树JSON版本如下

{
    "type": "Script",
    "location": {
        "startIndex": 0,
        "endIndex": 84,
        "start": {
            "line": 1,
            "column": 1
        },
        "end": {
            "line": 6,
            "column": 8
        }
    },
    "strict": false,
    "ScriptBody": {
        "type": "ScriptBody",
        "location": {
            "startIndex": 0,
            "endIndex": 84,
            "start": {
                "line": 1,
                "column": 1
            },
            "end": {
                "line": 6,
                "column": 8
            }
        },
        "strict": false,
        "StatementList": [
            {
                "type": "VariableStatement",
                "location": {
                    "startIndex": 0,
                    "endIndex": 12,
                    "start": {
                        "line": 1,
                        "column": 1
                    },
                    "end": {
                        "line": 1,
                        "column": 12
                    }
                },
                "strict": false,
                "VariableDeclarationList": [
                    {
                        "type": "VariableDeclaration",
                        "location": {
                            "startIndex": 4,
                            "endIndex": 11,
                            "start": {
                                "line": 1,
                                "column": 5
                            },
                            "end": {
                                "line": 1,
                                "column": 9
                            }
                        },
                        "strict": false,
                        "BindingIdentifier": {
                            "type": "BindingIdentifier",
                            "location": {
                                "startIndex": 4,
                                "endIndex": 5,
                                "start": {
                                    "line": 1,
                                    "column": 5
                                },
                                "end": {
                                    "line": 1,
                                    "column": 5
                                }
                            },
                            "strict": false,
                            "name": "d"
                        },
                        "Initializer": {
                            "type": "StringLiteral",
                            "location": {
                                "startIndex": 8,
                                "endIndex": 11,
                                "start": {
                                    "line": 1,
                                    "column": 9
                                },
                                "end": {
                                    "line": 1,
                                    "column": 9
                                }
                            },
                            "strict": false,
                            "value": "d"
                        }
                    }
                ]
            },
            {
                "type": "FunctionDeclaration",
                "location": {
                    "startIndex": 13,
                    "endIndex": 76,
                    "start": {
                        "line": 2,
                        "column": 1
                    },
                    "end": {
                        "line": 5,
                        "column": 1
                    }
                },
                "strict": false,
                "BindingIdentifier": {
                    "type": "BindingIdentifier",
                    "location": {
                        "startIndex": 22,
                        "endIndex": 26,
                        "start": {
                            "line": 2,
                            "column": 10
                        },
                        "end": {
                            "line": 2,
                            "column": 10
                        }
                    },
                    "strict": false,
                    "name": "test"
                },
                "FormalParameters": [
                    {
                        "type": "SingleNameBinding",
                        "location": {
                            "startIndex": 27,
                            "endIndex": 28,
                            "start": {
                                "line": 2,
                                "column": 15
                            },
                            "end": {
                                "line": 2,
                                "column": 15
                            }
                        },
                        "strict": false,
                        "BindingIdentifier": {
                            "type": "BindingIdentifier",
                            "location": {
                                "startIndex": 27,
                                "endIndex": 28,
                                "start": {
                                    "line": 2,
                                    "column": 15
                                },
                                "end": {
                                    "line": 2,
                                    "column": 15
                                }
                            },
                            "strict": false,
                            "name": "a"
                        },
                        "Initializer": null
                    },
                    {
                        "type": "SingleNameBinding",
                        "location": {
                            "startIndex": 30,
                            "endIndex": 35,
                            "start": {
                                "line": 2,
                                "column": 18
                            },
                            "end": {
                                "line": 2,
                                "column": 22
                            }
                        },
                        "strict": false,
                        "BindingIdentifier": {
                            "type": "BindingIdentifier",
                            "location": {
                                "startIndex": 30,
                                "endIndex": 31,
                                "start": {
                                    "line": 2,
                                    "column": 18
                                },
                                "end": {
                                    "line": 2,
                                    "column": 18
                                }
                            },
                            "strict": false,
                            "name": "b"
                        },
                        "Initializer": {
                            "type": "NumericLiteral",
                            "location": {
                                "startIndex": 34,
                                "endIndex": 35,
                                "start": {
                                    "line": 2,
                                    "column": 22
                                },
                                "end": {
                                    "line": 2,
                                    "column": 22
                                }
                            },
                            "strict": false,
                            "value": 1
                        }
                    }
                ],
                "FunctionBody": {
                    "type": "FunctionBody",
                    "location": {
                        "startIndex": 36,
                        "endIndex": 76,
                        "start": {
                            "line": 2,
                            "column": 24
                        },
                        "end": {
                            "line": 5,
                            "column": 1
                        }
                    },
                    "strict": false,
                    "directives": [],
                    "FunctionStatementList": [
                        {
                            "type": "LexicalDeclaration",
                            "location": {
                                "startIndex": 40,
                                "endIndex": 50,
                                "start": {
                                    "line": 3,
                                    "column": 3
                                },
                                "end": {
                                    "line": 3,
                                    "column": 12
                                }
                            },
                            "strict": false,
                            "LetOrConst": "let",
                            "BindingList": [
                                {
                                    "type": "LexicalBinding",
                                    "location": {
                                        "startIndex": 44,
                                        "endIndex": 49,
                                        "start": {
                                            "line": 3,
                                            "column": 7
                                        },
                                        "end": {
                                            "line": 3,
                                            "column": 11
                                        }
                                    },
                                    "strict": false,
                                    "BindingIdentifier": {
                                        "type": "BindingIdentifier",
                                        "location": {
                                            "startIndex": 44,
                                            "endIndex": 45,
                                            "start": {
                                                "line": 3,
                                                "column": 7
                                            },
                                            "end": {
                                                "line": 3,
                                                "column": 7
                                            }
                                        },
                                        "strict": false,
                                        "name": "c"
                                    },
                                    "Initializer": {
                                        "type": "NumericLiteral",
                                        "location": {
                                            "startIndex": 48,
                                            "endIndex": 49,
                                            "start": {
                                                "line": 3,
                                                "column": 11
                                            },
                                            "end": {
                                                "line": 3,
                                                "column": 11
                                            }
                                        },
                                        "strict": false,
                                        "value": 2
                                    }
                                }
                            ]
                        },
                        {
                            "type": "ExpressionStatement",
                            "location": {
                                "startIndex": 53,
                                "endIndex": 74,
                                "start": {
                                    "line": 4,
                                    "column": 3
                                },
                                "end": {
                                    "line": 4,
                                    "column": 23
                                }
                            },
                            "strict": false,
                            "Expression": {
                                "type": "CallExpression",
                                "location": {
                                    "startIndex": 53,
                                    "endIndex": 73,
                                    "start": {
                                        "line": 4,
                                        "column": 3
                                    },
                                    "end": {
                                        "line": 4,
                                        "column": 22
                                    }
                                },
                                "strict": false,
                                "CallExpression": {
                                    "type": "MemberExpression",
                                    "location": {
                                        "startIndex": 53,
                                        "endIndex": 64,
                                        "start": {
                                            "line": 4,
                                            "column": 3
                                        },
                                        "end": {
                                            "line": 4,
                                            "column": 11
                                        }
                                    },
                                    "strict": false,
                                    "MemberExpression": {
                                        "type": "IdentifierReference",
                                        "location": {
                                            "startIndex": 53,
                                            "endIndex": 60,
                                            "start": {
                                                "line": 4,
                                                "column": 3
                                            },
                                            "end": {
                                                "line": 4,
                                                "column": 3
                                            }
                                        },
                                        "strict": false,
                                        "escaped": false,
                                        "name": "console"
                                    },
                                    "IdentifierName": {
                                        "type": "IdentifierName",
                                        "location": {
                                            "startIndex": 61,
                                            "endIndex": 64,
                                            "start": {
                                                "line": 4,
                                                "column": 11
                                            },
                                            "end": {
                                                "line": 4,
                                                "column": 11
                                            }
                                        },
                                        "strict": false,
                                        "name": "log"
                                    },
                                    "PrivateIdentifier": null,
                                    "Expression": null
                                },
                                "Arguments": [
                                    {
                                        "type": "IdentifierReference",
                                        "location": {
                                            "startIndex": 65,
                                            "endIndex": 66,
                                            "start": {
                                                "line": 4,
                                                "column": 15
                                            },
                                            "end": {
                                                "line": 4,
                                                "column": 15
                                            }
                                        },
                                        "strict": false,
                                        "escaped": false,
                                        "name": "a"
                                    },
                                    {
                                        "type": "IdentifierReference",
                                        "location": {
                                            "startIndex": 68,
                                            "endIndex": 69,
                                            "start": {
                                                "line": 4,
                                                "column": 18
                                            },
                                            "end": {
                                                "line": 4,
                                                "column": 18
                                            }
                                        },
                                        "strict": false,
                                        "escaped": false,
                                        "name": "b"
                                    },
                                    {
                                        "type": "IdentifierReference",
                                        "location": {
                                            "startIndex": 71,
                                            "endIndex": 72,
                                            "start": {
                                                "line": 4,
                                                "column": 21
                                            },
                                            "end": {
                                                "line": 4,
                                                "column": 21
                                            }
                                        },
                                        "strict": false,
                                        "escaped": false,
                                        "name": "c"
                                    }
                                ]
                            }
                        }
                    ]
                }
            },
            {
                "type": "ExpressionStatement",
                "location": {
                    "startIndex": 77,
                    "endIndex": 84,
                    "start": {
                        "line": 6,
                        "column": 1
                    },
                    "end": {
                        "line": 6,
                        "column": 7
                    }
                },
                "strict": false,
                "Expression": {
                    "type": "CallExpression",
                    "location": {
                        "startIndex": 77,
                        "endIndex": 83,
                        "start": {
                            "line": 6,
                            "column": 1
                        },
                        "end": {
                            "line": 6,
                            "column": 6
                        }
                    },
                    "strict": false,
                    "CallExpression": {
                        "type": "IdentifierReference",
                        "location": {
                            "startIndex": 77,
                            "endIndex": 81,
                            "start": {
                                "line": 6,
                                "column": 1
                            },
                            "end": {
                                "line": 6,
                                "column": 1
                            }
                        },
                        "strict": false,
                        "escaped": false,
                        "name": "test"
                    },
                    "Arguments": []
                }
            }
        ]
    }
}

你这说了半天,函数还是没初始化啊,别急。

InstantiateFunctionObject

在之前 <<script怎么加载和申明实例化>> 在全局申明实例化 GlobalDeclarationInstantiation 协议内容的尾部有几行不显眼但是 极其重要的文字。

InstantiateFunctionObject 的作用就是把函数申明解析节点转为 函数对象 (Function Object), 参数env和privateEnv分别是环境记录和私有环境记录。

这里的 functionsToInitialize 可还知道是什么吗? 这就是抽象操作 VarScopedDeclarations 结果中包含的各种函数申明节点

你也许会诧异,为什么函数申明跑到变量申明中取了。因为在全局顶层代码中,函数申明表现和var是一样的,这协议中是有明确提到的。

在本例的代码中,需要 通过InstantiateFunctionObject 转为函数对象的解析节点就一个FunctionDeclaration, 其对应如下的代码。

function test(a, b = 1){
  let c = 2;
  console.log(a, b, c);
}

InstantiateOrdinaryFunctionObject

InstantiateFunctionObject 这个抽象操作把 解析节点转为 函数对象Function Object, 根据函数类型不同,解析逻辑也是不同的

这里实例化后的函数对象,才是可以被调用的,这里可以对开发者代码中的function概念了。

test也是如此,在全局申明实例化之后,test有了对应的 Function Object,并在全局环境记录存在绑定关系。

本示例是一个普通函数,走的逻辑就是 InstantiateOrdinaryFunctionObject, 实例化普通函数,基本流程:

真正实例化函数的是 OrdinaryFunctionCreate, 为了进一步了解流程,还是得需要进一步了解一点解析节点FunctionDeclaration的信息。

属性名备注
BindingIdentifier标志符信息,本例为函数名的信息 test
FormalParameters形参的信息,本例为形参a, b的信息
FunctionBody函数体,即函数一对大括号里面的内容信息
location位置信息,基本上每个解析节点都有这个信息。记录了节点对应代码的起始位置,可以快速的获取节点对应的源码部分。
type解析节点的类型
strict是否是严格模式

实际FunctionBody是如下内容,是包含前后两个大括符的,上面不好标注,敬请谅解。

{
  let c = 2;
  console.log(a, b, c);
}

具备这些知识后,再看看函数申明FunctionDeclaration 实例化为函数对象的协议描述

OrdinaryFunctionCreate 的参数

  1. %Function.prototype%就是开发者常用的Function.prototype
  2. sourceText 是 FunctionDeclaration 对应的源码
  3. FormalParameters 形参
  4. FunctionBody 函数体,包含大括号
  5. non-lexical-this: lexical表示是非箭头函数 ,non-lexical-this 即是 strict 或者 global
  6. env(环境记录)和 privateEnv (私有环境记录)都是从 GlobalDeclarationInstantiation 传入的

所以,这个中间商的主要作用就是取参数,让OrdinaryFunctionCreate 生成函数对象,然后设置一下名字,把函数转为构造函数。

现在把灯光给 OrdinaryFunctionCreate

OrdinaryFunctionCreate

作用是创建普通函数, 基本逻辑如下:

  1. 创建标准的对象
  2. 设置[[Call]], [[SourceText]], [[FormalParameters]], [[ECMAScript],[[Script]][[ThisMode]]等等各种函数内置参数
  3. 设置的长度属性 length。

17,18,20都是和class相关的,暂时无需关注。

到此为止,函数对象基本初始化完毕,基本经理了下面三个阶段

  • 源代码 (文本)
  • 解析节点
  • 函数对象

这里记住,函数对象本身也只是记录了一些外部信息,函数内部的代码是什么样子的,除了简单的前期静态语法检查,目前为止还是一个黑盒。

里面具体的细节,都是函数调用时,动态分析的。

函数表达式 FunctionExpression

当然函数对象可以从 函数申明 FunctionDeclaration 实例化,也可以从 函数表达式 FunctionExpression 初始化。

如下的代码 function test() {} 部分就是函数表达式。

const fn = function test() {

};

协议 InstantiateOrdinaryFunctionExpression 部分描述了普通函数表达式的初始化逻辑。

下图与 普通函数表达式的初始化逻辑做了个对比,除了执行上下文和环境记录有区别外,函数本身实例化的逻辑是基本一致的。 至于具名的函数表达式为什么会多一个环境记录,在 闭包 章节有详细的解答。

左边:函数申明 vs 右边 函数表达式

当然除了普通函数表达式 FunctionExpression, 还有

额,希望后会有期把。

属性方法定义 MethodDefinition

当然,如下的代码也会实例化函数对象,这种方式协议称为 MethodDefinition

var obj = {
    fn() {

    }
}

大致的节点信息如下图标注

方法定义有好多种语法格式,本示例对应的 红色标记处的部分。

协议描述流程如下:

与普通的函数申明实 例化的函数对象有一些区别

一起用代码验证一下:

var obj = {
	fn(){
		console.log(super.toString)
	}
};

// 可以使用super
obj.fn()      // ƒ toString() { [native code] }

// 不可以被new
new obj.fn()  // Uncaught TypeError: obj.fn is not a constructor

当然除了普通方法定义外, 还有

额,希望后会有期把。

函数申明 vs 函数表达式 vs 属性方法定义

一起看看 通过 函数申明 FunctionDeclaration,函数表达式 FunctionExpression ,方法定义 MethodDefinition 实例化的函数对象, 从下面的维度比较

方式节点可以被new可以super
函数申明FunctionDeclaration
函数表达式FunctionExpression
方法定义MethodDefinition

函调调用流程

var d = "d";
function test(a, b = 1){
  let c = 2;
  console.log(a, b, c);
}
test();

test() 执行时,实际是先找 test标志符对应的函数对象,然后评估函数对象属性[[ECMAScriptCode]] 里面的语句列表 FunctionStatementList(等于如下红色圈出部分),协议描述的流程大致如下:

函数调用前序

首先 Script 节点是怎么执行的呢?前文提到的 ScriptEvaluation ( scriptRecord ) 也有一段不起眼的脚本。

Evaluation of script简简单单几个字,其后面非常复杂,就是对整个树进行解析,遇到可以执行的代码就进行调用,然后函数嵌套函数,懂的都懂,新的上下文,新的环境记录统统走起,...............。

当然解析树的节点类型有很多,函数调用关联最密切的就是CallExrpression

本例中的三个语句中的第三个语句ExpressionStatement, 对应源码 test();

其属性 Expression 是一个 CallExrpression,她被解析的时候,就会进行函数调用。具体见下图

CallExrpression

在协议 13.3.6 Function Calls 能找到函数调用的相关描述,调用形式有多种。

以下面的代码为例:

var eName = 'global eName';
function log(){
  return this.eName
}
log();

函数调用是从对 函数对应的解析节点(CallExpression)的解析开始的。 结合源码一起先了解一下 CallExpression的结构。

type为CallExpression节点有个叫做 type为IdentifierReferenceCallExpression节点,两个节点对应的源码部分已经用不不同颜色标记。

所以,如何通过解析节点找到其对应的 函数对象呢? 答案也是很明显的,

  • 通过type为IdentifierReferenceCallExpression节点的name属性,能得到函数的标志符
  • 上下文用标志符通过 ResolveBinding 查找对应的引用记录
  • 引用记录 通过 GetValue 从属性上或者环境记住中取的 函数对象(Function Object)

具体的协议有些不好懂, 配合如下的图更加好理解:

Evaluate(memberExpr)的流程如下:

再简单汇总一下

到此,已经获得了函数对象的,在此之后函数本身真正执行之前,剩余链路如下

EvaluateCall ( func, ref, arguments, tailPosition )

作用很简单,

  • 取this的值,
  • 检查函数对象是否可以被调用
  • 调用内部函数 Call(func, thisValue, argList)

Call ( F, V [ , argumentsList ] )

这个的作用就更简单了,

  • 按需设置参数,
  • 检查函数对象是否可以调用,
  • 调用函数自身的 F.[[Call]]

到此为止,就要真正进入函数调用了,你准备好了吧,激动的时刻就要到来了。

F.[[Call]]

哦豁,请见下篇。

小结

对于本例来说:

函数从文本到函数对象的蜕变过程:

  • 源代码 => parseText
  • Script解析树(此时函数函数还是Script解析节点上的 Statement 解析节点 )
  • 全局申明实例化过程 GlobalDeclarationInstantiation ( script, env )中,执行 => InstantiateFunctionObject 函数初始化
  • 函数被实例化,生成函数对象(Function Object),同时在全局环境记录(Global Enviroment Record)生成绑定,以及挂载到全局对象上

函数被调用的过程

  • 通过标志符 查找到 对应的函数对象

通过标志符,从全局环境记录(Global Enviroment Record)查找并成成引用记录,以及取出函数对象(Function Object)

取函数的this,判断是否可调用

按需设置参数,判断是否可调用

  • F.[[Call]]

下文见。