❝你从互联网上学东西、掌握新的技能,或者互联网帮助你实现自我,那么互联网就是你的工具;如果你只是在互联网上玩乐,花了自己的时间和金钱,却只得到了精神的满足,那你是互联网的工具。在免费的江湖里,你就是产品 --《向上生长》 ❞
OuterEnv
串联globalThis
不直接指向全局对象WindowProxy
是一个将所有访问转发到当前窗口的对象对象环境记录
和声明环境记录
来管理变量const
,let
和class
创建的变量被绑定到声明环境记录中var
和函数声明的变量被绑定到对象环境记录中globalThis
我们平时常说的变量的作用域(scope
),它全名应该叫「词法作用域」(lexical scope
)。它是程序中可以访问变量的区域,即作用域控制着变量和函数的可见性和生命周期。
我们在前期的文章中,描述了,V8执行JS代码核心流程 1. 先编译 2. 后执行。在这个编译的过程就是「静态」的。所以我们可以这么说,作用域是「不随代码的运行而改变」的变量查找机制。
❝JS的作用域是静态的 ❞
同时,作用域还可以被嵌套。
function func() { // (A)
const v1 = 1;
if (true) { // (B)
const v2 = 2;
}
}
如上所示: 在B行的if
语句内嵌在A行的func()
函数作用域。
我们把内部作用域外面的作用域称为:outer
作用域。例如:func
作用域就是if
作用域的 outer
作用域。
在ecma262(自备🪜)语言规范中定义:
❝作用域通过「词法环境」实现的 ❞
而词法环境由两个重要部分组成:
binding
)。OuterEnv
内部属性:指向「外部环境」(outer environment)的引用代码中嵌套的作用域树由指向外部环境的OuterEnv连接起来。即:
❝变量的作用域链由词法环境中
OuterEnv
串联 ❞
全局对象是其属性成为全局变量的对象。可以通过如下方式访问全局对象
globalThis
: 所有平台/宿主环境都可以访问,它与全局变量this
的值相等。// 浏览器环境
`globalThis === this //true`
// node REPL
`globalThis === this //true`
window
:它在浏览器「主线程」环境下生效,但是在Web Workers/Node
环境下失效self
: 在浏览器环境下生效(主/Web Worker),在Node
环境下失效global
: 只有在Node
环境下生效globalThis
在浏览器环境下,globalThis
不直接指向全局对象。
例如,现在有一个网页存在一个iframe
:
iframe
中的src
的值发生变更,它会获得一个新的全局对象iframe
的src
的值如何变化,globalThis
的值一直不变现在有两个html
1. parent 2. child
parent.html
<iframe src="child.html?first"></iframe>
<script>
const iframe = document.querySelector('iframe');
const icw = iframe.contentWindow; // iframe的`globalThis`
iframe.onload = () => {
// 通过globalThis访问iframe的全局属性
const firstGlobalThis = icw.globalThis;
const firstArray = icw.Array;
console.log(icw.iframeName); // 'first'
iframe.onload = () => {
const secondGlobalThis = icw.globalThis;
const secondArray = icw.Array;
// 全局对象发生变更
console.log(icw.iframeName); // 'second'
console.log(secondArray === firstArray); // false
// globalThis 还是原来的那个值
console.log(firstGlobalThis === secondGlobalThis); // true
};
iframe.src = 'iframe.html?second';
};
</script>
child.html
<script>
// 通过globalThis向全局对象新增一个binding(环境记录中的名称-值条目称为绑定)
globalThis.iframeName = location.search.slice(1);
</script>
globalThis
通过两个内部属性保持值的不变:
Window
指向全局对象。每次变更location
(向window.location.href
赋值/通过改变iframe
的src
)它的值也会随之改变。WindowProxy
是一个将所有访问转发到当前窗口的对象。该对象永远不会改变在浏览器环境下,globalThis
指向WindowProxy
;在其他环境下,globalThis
直接指向全局对象。
全局作用域是「最外层」的作用域:不存在包含它的作用域了。与之匹配的环境变量(environment)为全局环境(global environment)。每一个内部环境变量通过outerEnv
构建的作用域链最终与全局环境进行相连。全局环境的outerEnv
是null。
全局环境记录(注意:和全局环境有区别) 使用两类环境记录来管理变量
在JS中,只有在script
的顶层才属于全局作用域。相反的,每一个模块都有属于自己的作用域,而这个作用域作为全局作用域的子集。
通过一段伪代码来描述他们之间的关系:
{ // 全局作用域
// (全局变量)
{ // module 1 作用域
···
}
{ // module 2 作用域
···
}
// (....)
}
为了能够创建一个全局变量,我们需要在全局作用域下(script的顶层),进行变量的定义和赋值:
const
,let
和class
创建的变量被绑定到声明环境记录中var
和函数声明的变量被绑定到对象环境记录中<script>
const one = 1;
var two = 2;
</script>
<script>
// 所有script共享全局作用域
console.log(one); // 1
console.log(two); // 2
// 并非所有的变量声明都被存到全局对象中
console.log(globalThis.one); // undefined
console.log(globalThis.two); // 2
</script>
当我们访问一个在声明环境记录和对象环境记录中都存在绑定的变量时
❝声明环境变量中变量优先访问。 ❞
<script>
let gv = 1; // 存放到声明环境记录中
globalThis.gv = 2; // 存放到对象环境记录中
console.log(gv); // 1 声明环境变量中变量优先访问
console.log(globalThis.gv); // 2
</script>
除了通过var和函数声明创建的变量之外,全局对象还包含以下属性
使用const
/let
定义的全局变量可以保证不受ECMAScript和宿主环境的内置全局变量影响。
例如,浏览器环境下,存在全局变量.location
// 改变当前页面的路径信息
var location = 'https://789.com';
// 将window.location截断了,并不会修改当前页面的页面信息
let location = 'https://789.com';
注意:这种情况只有在全局环境下,才会发生
全局作用域的环境通过一个全局环境记录来管理它的绑定,这个全局环境记录又基于两个环境记录:
可以通过向全局对象添加属性或通过各种声明来创建全局变量。使用ECMAScript和宿主环境的内置全局变量初始化全局对象。每个ECMAScript模块都有自己的环境,其外部环境是全局环境。