简单理解 JavaScript 的词法作用域

更新日期: 2022-09-03阅读: 694标签: 作用域

前言

关于作用域的有关知识点有全局作用域、局部作用域、函数作用域、块级作用域、词法作用域、作用域链。

作用域

作用域就像是一个教室,上课时教室里面的人互相可见,A 教室里的人不可以看见 B 教室里的人。作用域决定了代码生效的区域以及资源(变量、函数)可见的区域。

function fun() {
  let a = 20;
}();

console.log(a); // Uncaught ReferenceError: a is not defined

无法获得fun函数中定义的变量a。

全局作用域

全局作用域的范围比其他的作用域的范围更大,关系就像是一切 JavaScript 对象的顶层都是 Object。<script>或.js可以算作是一个全局作用域,定义在全局作用域的变量叫全局变量。

在全局作用域声明的变量,其他的作用域都可以访问:

let a = 20;

function fun() {
  console.log(a) // => 20
}

fun();

局部作用域

定义在局部作用域里面的变量就是局部变量。局部变量只可以在局部作用域生效,局部作用域可以访问到全局作用域的变量,或是比局部作用域大一点的父作用域(嵌套作用域)。

局部作用域有块级作用域、函数作用域。

块级作用域

在 ES5 及以前,块级作用域受var影响是无效的,具体请看ES6 关键字 let 和 ES5 及以前关键字 var 的区别

{
  var x = 10;
}

console.log(x) // => 10

for (var i = 0; i < 10; i++) {
  // ...
}

console.log(i); // 10

最后打印for语句的变量 i,得到 10,实际上在语句内部打印最终循环的结果是 9。ES6 之后的关键字let声明的变量,外部想要使用块级作用域的变量x就会报错:

{
  let x = 10;
}

console.log(x); // Uncaught ReferenceError: x is not defined

for (let i = 0; i < 10; i++) {
  // ...
}

console.log(i); // Uncaught ReferenceError: i is not defined

函数作用域

关于函数作用域,有一个面试题,需要结合下面的词法作用域进行分析。

let a = 123;

function fun1() {
  console.log(a);
}

function fun2() {
  let a = 456;
  fun1();
}

fun2();

最终的结果是 123。这是因为词法作用域已经决定了函数fun1引用的外部作用域的变量是全局作用域中的变量 a,而非函数fun2定义的局部变量a。

词法作用域

函数引用变量的静态性

词法作用域(静态作用域)是一种就近原则,也就是在我们写下代码的时候就已经决定了函数引用的变量应该是按照就近原则来引用的:

词法作用域的静态性原则,规定函数引用变量必须按照代码书写的顺序来,即便是函数被其他函数调用了,这个函数的作用域也不会发生变化,也不会因此变成了嵌套函数。

函数自身局部作用域内没有定义变量 a,而在全局作用域中,定义了变量 a,根据就近原则,所以引用的是a = 123。

let a = 123;

function fun() {
  console.log(a); // => 123
}

如果函数体内有一个变量 a,结果就是:

let a = 123;

function fun() {
  let a = 456;
  console.log(a); // => 456
}

总而言之,函数引用变量时是按照一种自上而下,顺序来的。函数引用一个变量,前提是变量不能在函数声明之后出现。

错误:

fun();

function fun() {
  console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
}

let a = 123;

正确:

let a = 123;

fun();

function fun() {
  console.log(a); // 123
}
这里有一个悬念,为什么调用函数可以在函数声明之前?

函数调用的动态性

函数要成功引用作用域外的变量必须是变量声明在函数之前,但是函数调用可以在函数声明之前,但也必须是在变量声明之后。

在函数调用时体现出作用域的动态性,函数引用变量就体现出作用域的静态性。

function f() { g(); }

function g() {}

f();

当我们调用 f(),它会调用 g()。在执行期间,g 被 f 调用代表了一种动态的关系。

作用域链

当在函数使用一个变量的时候,首先 Javascript 会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。


把作用域比喻成一个建筑,这份建筑代表程序中的嵌套作用域链,第一层代表当前的执行作用域,顶层代表全局作用域。

根据词法作用域静态性的原则,函数引用变量不会因为调用顺序和位置从而改变当前的作用域。所以查找变量的位置按照代码书写的位置来看。

面试题

现在可以回答那一道面试题了函数作用域

let a = 123;

function fun1() {
  console.log(a);
}

function fun2() {
  let a = 456;
  fun1();
}

fun2();

函数fun2声明了一个与全局作用域同名的变量 a,根据词法作用域的静态性原则,函数调用不会改变函数的作用域。函数fun1作用域内没有定义变量a,它的上级作用域就是全局作用域,而全局作用域中声明了变量 a,所以最终打印结果是 123。

引用文献

  1. 《JavaScript 权威指南》- 第 3 章 变量作用域;
  2. 《深入理解 JavaScript》- 第 16 章 变量:作用域、环境和闭包;
  3. web前端面试 - 面试官系列 - 面试官:说说你对作用域链的理解。

链接: https://fly63.com/article/detial/12069

ES6之块级作用域

在ES5中,只全局作用域和函数作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。

es6块级作用域

在JavaScript中使用var定义一个变量,无论是定义在全局作用域函数函数的局部作用域中,都会被提升到其作用域的顶部,这也是JavaScript定义变量的一个令人困惑的地方。由于es5没有像其它类C语言一样的块级作用域,因此es6增加了let定义变量,用来创建块级作用域。

Js作用域和执行上下文

作用域是在函数声明的时候就确定的一套变量访问规则,而执行上下文是函数执行时才产生的一系列变量的集合体。也就是说作用域定义了执行上下文中的变量的访问规则,执行上下文是在这个作用域规则的前提下执行代码的。

Js中的Function类型_设置函数的作用域

先声明一个name变量,然后声明一个person对象,person包含name和sayName属性。当直接在对象上进行方法的调用时:person.sayName(),函数的作用域遵循“谁调用就是谁”的原则,sayName的作用域(也就是this)指向的就是person。

ES5中模仿块级作用域

有一定JavaScript开发经验的人应该会熟悉下面这种立即执行函数的写法:不过即使不熟悉也没关系,这里我会讲解这种写法的含义。先来看下面这个更容易理解的示例:

作用域 CSS 回来了

几年前,消失的作用域 CSS,如今它回来了,而且比以前的版本要好得多。更好的是,W3C规范基本稳定,现在Chrome中已经有一个工作原型。我们只需要社区稍微关注一下,引诱其他浏览器构建它们的实现

Js静态作用域和动态作用域

静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量)。 动态作用域–函数的作用域是在函数调用的时候才决定的

深入理解 JavaScript, 从作用域与作用域链开始

作用域是你的代码在运行时,某些特定部分中的变量,函数和对象的可访问性。换句话说,作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

Js作用链、作用域

函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合

不只是块级作用域,你不知道的let和const

ES6新增了两个重要的关键字let和const,相信大家都不陌生,但是包括我在内,在系统学习ES6之前也只使用到了【不存在变量提升】这个特性。let声明一个块级作用域的本地变量

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!