提问人:Lance 提问时间:1/3/2023 最后编辑:Lance 更新时间:2/3/2023 访问量:44
当您可以提升嵌套函数时,如何概念化词法范围树?
How to conceptualize a lexical scope tree when you can have hoisted nested functions?
问:
我正在使用 TypeScript 开发编译器,并考虑了很多关于词法范围的问题。我特别想知道你如何处理你提升函数的情况,其中变量可以在一个点上未定义,然后在另一个点定义。例如:
function a() {
let d = 10
b()
return b
let g = 40
function b() {
c()
let f = 30
return c
function c() {
console.log(d, e, f, g)
}
}
let e = 20
}
const x = a()() // returns b, returns c
x() // log the variables
在这里,函数内部的“词法范围”是什么?c
- 在可能的代码评估期间,它是否是它可能/潜在地(在某个时候)访问的所有变量?
(d, e, f, g)
- 它最终只会在其运行时上下文中定义吗?
(d, f)
- 它不会被定义并引发错误吗?
(d)
- 是不是同时出现所有可能的父母组合(就像笛卡尔乘积一样,无论树中有多少父母)?
- 等。
起初,有一段时间(很多年),我认为词汇范围是一个简单的树。您有更高的作用域和嵌套作用域,并且每个嵌套作用域都引入了新变量。很好,很干净,很容易。但是这个,这把它抛出了一个循环。现在就像,专注于 ,里面有评估步骤,在每个步骤中,可能存在不同的父范围。然后也可能有一套父作用域。所以它就像一朵向上开花的天然花朵/树(这在编程中很难想象)。有越来越多的可能的父母。我在想的是,词汇范围是父母向上的每一个组合的组合。但是,如何在编译器中使用此范围呢?c
b
a
现在我很困惑。如果我不能为嵌套函数分辨出它是什么 1 范围定义,我应该在编译器中使用词法范围做什么?我假设每个嵌套函数都有 1 个作用域,但不是,它的父函数是庞大且组合的。只有当范围被绑定时,你才能真正知道它是什么(“绑定”,而不是“范围”)。也就是说,在代码评估的特定步骤中,作用域的值是多少。
那么,如何在编译器中使用这些信息呢?只有当我评估/模拟运行本身的代码时,我才能判断变量是否在范围内。基本上,我现在如何在编译器中使用这个词法范围?
我打算用它作为在嵌套词法范围块中定义哪些变量的真实来源。但现在我不能了,因为我需要在每一步都知道变量的值是什么,然后才能知道范围是什么。还是我错过了什么?
想尝试这样的事情:
type SiteContainerScopeType = {
like: Site.ContainerScopeType
parent?: SiteContainerScopeType
children: Array<SiteContainerStepScopeType>
declarations: Record<string, SitePropertyDeclarationType>
}
type SiteContainerStepScopeType = {
like: Site.ContainerStepScope
previous: SiteContainerStepScopeType
context: SiteContainerScopeType
declarations: Record<string, SitePropertyDeclarationType>
}
答:
如果我正确理解了 Understanding Hoisting in JavaScript 中描述的语义,则可以按如下方式重新组织代码,首先对声明进行排序,然后对变量初始化进行排序,最后对其余表达式进行排序。
function a() {
// DECLARATIONS
let d
let g
let e
function b() {
let f
function c() {
console.log(d, e, f, g)
}
f = 30
c()
return c
}
// INITIALIZATION
e = 20
d = 10
g = 40
// EXPRESSIONS
b()
return b
}
在执行此操作时,我们发现了一种更常见的结构,即在初始化和使用变量之前声明变量。
评论