JS全局变量

简介: 1. Scope(作用域)2. Lexical Environments (词法环境)3. 全局对象(global object)4. 浏览器环境下的globalThis5. 全局环境(global envrionment)• Script 作用域 和Module 作用域• 生成变量: 声明环境记录 vs 对象环境记录• 访问变量• 全局ECMAScript变量和全局宿主变量6. 一图胜前言


你从互联网上学东西、掌握新的技能,或者互联网帮助你实现自我,那么互联网就是你的工具;如果你只是在互联网上玩乐,花了自己的时间和金钱,却只得到了精神的满足,那你是互联网的工具。在免费的江湖里,你就是产品 --《向上生长》

简明扼要

  1. 作用域是静态
  2. 作用域通过词法环境实现的
  3. 词法环境 = 环境记录 + OuterEnv
  4. 变量的作用域链由词法环境中OuterEnv串联
  5. 在浏览器环境下,globalThis不直接指向全局对象
  6. WindowProxy是一个将所有访问转发到当前窗口的对象
  7. 全局环境记录使用对象环境记录声明环境记录来管理变量
  8. 通过const,letclass创建的变量被绑定到声明环境记录中
  9. 通过var函数声明的变量被绑定到对象环境记录中
  10. 声明环境变量中变量优先访问
  11. 使用ECMAScript和宿主环境的内置全局变量初始化全局对象

文章概要

  1. Scope(作用域)
  2. Lexical Environments (词法环境)
  3. 全局对象(global object)
  4. 浏览器环境下的globalThis
  5. 全局环境(global envrionment)
  • Script 作用域 和Module 作用域
  • 生成变量: 声明环境记录 vs 对象环境记录
  • 访问变量
  • 全局ECMAScript变量和全局宿主变量
  1. 一图胜前言

1. Scope(作用域)

我们平时常说的变量的作用域(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 作用域。


2. Lexical Environments (词法环境)

ecma262(自备🪜)语言规范中定义:

作用域通过词法环境实现的

而词法环境由两个重要部分组成:

  1. 环境记录(environment record): 将变量名映射到变量值(类似于Map)。这是作用域变量的实际存储空间。记录中的名称-值条目称为绑定binding)。
  2. OuterEnv内部属性:指向外部环境(outer environment)的引用

代码中嵌套的作用域树由指向外部环境的OuterEnv连接起来。即:

变量的作用域链由词法环境中OuterEnv串联


3. 全局对象

全局对象是其属性成为全局变量的对象。可以通过如下方式访问全局对象

  1. globalThis: 所有平台/宿主环境都可以访问,它与全局变量this的值相等。
// 浏览器环境
`globalThis === this //true`
// node REPL
`globalThis === this //true`
复制代码
  1. 其他指向全局对象的变量只有在指定的平台才可以被访问
  • window:它在浏览器主线程环境下生效,但是在Web Workers/Node环境下失效
  • self: 在浏览器环境下生效(主/Web Worker),在Node环境下失效
  • global: 只有在Node环境下生效

4. 浏览器环境下的globalThis

在浏览器环境下,globalThis不直接指向全局对象。

例如,现在有一个网页存在一个iframe:

  • 每当iframe中的src的值发生变更,它会获得一个新的全局对象
  • 无论iframesrc的值如何变化,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通过两个内部属性保持值的不变:

  1. Window指向全局对象。每次变更location(向window.location.href赋值/通过改变iframesrc)它的值也会随之改变。
  2. WindowProxy是一个将所有访问转发到当前窗口的对象。该对象永远不会改变

在浏览器环境下,globalThis指向WindowProxy;在其他环境下,globalThis直接指向全局对象。


5. 全局环境

全局作用域是最外层的作用域:不存在包含它的作用域了。与之匹配的环境变量(environment)为全局环境(global environment)。每一个内部环境变量通过outerEnv构建的作用域链最终与全局环境进行相连。全局环境的outerEnv是null。

全局环境记录(注意:和全局环境有区别) 使用两类环境记录来管理变量

  • 对象环境记录(object environment record ):将binding(环境记录中的名称-值条目称为绑定) 保存在对象中。在全局环境下,这个对象指向全局对象。
  • 声明环境记录(declarative environment record): 拥有属于自己的存储空间来存放binding

5.1 Script 作用域 和Module 作用域

在JS中,只有在script的顶层才属于全局作用域。相反的,每一个模块都有属于自己的作用域,而这个作用域作为全局作用域的子集。

通过一段伪代码来描述他们之间的关系:

{ // 全局作用域 
  // (全局变量)
  { // module 1 作用域
    ···
  }
  { // module 2 作用域
    ···
  }
  // (....)
}
复制代码

5.2 生成变量: 声明环境记录 vs 对象环境记录

为了能够创建一个全局变量,我们需要在全局作用域下(script的顶层),进行变量的定义和赋值:

  • 通过const,letclass创建的变量被绑定到声明环境记录中
  • 通过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>
复制代码

5.3 访问变量

当我们访问一个在声明环境记录和对象环境记录中都存在绑定的变量时

声明环境变量中变量优先访问。

<script>
  let gv = 1; // 存放到声明环境记录中
  globalThis.gv = 2; // 存放到对象环境记录中
  console.log(gv); // 1 声明环境变量中变量优先访问
  console.log(globalThis.gv); // 2
</script>
复制代码

5.4 全局ECMAScript变量和全局宿主变量

除了通过var和函数声明创建的变量之外,全局对象还包含以下属性

  • 所有ECMAScript内置的全局变量
  • 宿主环境的所有内置全局变量(浏览器、Node.js等)

使用const/let定义的全局变量可以保证不受ECMAScript和宿主环境的内置全局变量影响。

例如,浏览器环境下,存在全局变量.location

// 改变当前页面的路径信息
var location = 'https://789.com';
// 将window.location截断了,并不会修改当前页面的页面信息
let location = 'https://789.com';
复制代码

注意: 这种情况只有在全局环境下,才会发生

一图胜前言

全局作用域的环境通过一个全局环境记录来管理它的绑定,这个全局环境记录又基于两个环境记录:

  1. 对象环境记录,它的绑定存储在全局对象中
  2. 声明性环境记录使用内部存储来存储它的绑定

可以通过向全局对象添加属性或通过各种声明来创建全局变量。使用ECMAScript和宿主环境的内置全局变量初始化全局对象。每个ECMAScript模块都有自己的环境,其外部环境是全局环境。

相关文章
|
6月前
|
JavaScript 前端开发 安全
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量(三)
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量
|
6月前
|
存储 JavaScript 前端开发
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量(一)
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量
|
2月前
|
JavaScript 前端开发
JavaScript如何创建,全局变量
JavaScript如何创建,全局变量
|
1月前
|
存储 JavaScript 前端开发
Vue.js项目中全面解析定义全局变量的常用方法与技巧
Vue.js项目中全面解析定义全局变量的常用方法与技巧
41 0
|
3月前
|
JavaScript 前端开发
js中this是指向的哪个全局变量,改变this指向的方法有什么?
js中this是指向的哪个全局变量,改变this指向的方法有什么?
19 0
|
6月前
|
JavaScript 前端开发
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量(二)
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量
|
6月前
|
JavaScript 前端开发
js中this是指向的哪个全局变量,改变this指向的方法有什么
js中this是指向的哪个全局变量,改变this指向的方法有什么
41 0
|
JavaScript 前端开发
【JS 经典面试题】全局变量和局部变量
【JS 经典面试题】全局变量和局部变量
113 1
|
JavaScript 前端开发
JavaScript 全局变量
JavaScript 全局变量
76 0
|
自然语言处理 JavaScript 前端开发
【译】JavaScript的全局变量到底是怎么工作的
【译】JavaScript的全局变量到底是怎么工作的
111 0
【译】JavaScript的全局变量到底是怎么工作的