ECMAScript 函数对象实例化

前言

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

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

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

看点代码

javascript 复制代码
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版本如下

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, 其对应如下的代码。

javascript 复制代码
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是如下内容,是包含前后两个大括符的,上面不好标注,敬请谅解。

javascript 复制代码
{
  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() {} 部分就是函数表达式。

javascript 复制代码
const fn = function test() {

};

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

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

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

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

额,希望后会有期把。

属性方法定义 MethodDefinition

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

javascript 复制代码
var obj = {
    fn() {

    }
}

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

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

协议描述流程如下:

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

一起用代码验证一下:

javascript 复制代码
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

函调调用流程

javascript 复制代码
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 能找到函数调用的相关描述,调用形式有多种。

以下面的代码为例:

javascript 复制代码
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]]

下文见。

相关推荐
layman05283 分钟前
node.js 实战——(fs模块 知识点学习)
javascript·node.js
毕小宝4 分钟前
编写一个网页版的音频播放器,AI 加持,So easy!
前端·javascript
万水千山走遍TML4 分钟前
JavaScript性能优化
开发语言·前端·javascript·性能优化·js·js性能
Aphasia3114 分钟前
react必备JS知识点(一)——判断this指向👆🏻
前端·javascript·react.js
会飞的鱼先生20 分钟前
vue3中slot(插槽)的详细使用
前端·javascript·vue.js
小小小小宇35 分钟前
一文搞定CSS Grid布局
前端
0xHashlet40 分钟前
Dapp实战案例002:从零部署链上计数器合约并实现前端交互
前端
知心宝贝41 分钟前
🔍 从简单到复杂:JavaScript 事件处理的全方位解读
前端·javascript·面试
安余生大大43 分钟前
关于Safari浏览器在ios<16.3版本不支持正则表达式零宽断言的解决办法
前端
前端涂涂44 分钟前
express查看文件上传报文,处理文件上传,以及formidable包的使用
前端·后端