一篇文章带你彻底理解javascript词法作用域

简介: 一篇文章带你彻底理解javascript词法作用域

前言

       上篇文章我们了解到“作用域”是一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

       作用域共有两种主要的工作模型。第一种是最为普遍的,被大多数编程语言所采用的词法作用域,我们会对这种作用域进行深入讨论。另外一种叫作动态作用域,仍有一些编程语言在使用(比如 Bash 脚本、Perl 中的一些模式等)。这一章我们将就词法作用域做一个详细的讨论


正文

词法阶段

       词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变

       举个例子来进一步说明方便大家理解:

39c54c3ac8c2e9d01c514d7013bc8620.png

       在这个例子中有三个逐级嵌套的作用域。

  • 第一个作用域包含着整个全局作用域,其中只有一个标识符:foo。
  • 第二个作用域包含着 foo 所创建的作用域,其中有三个标识符:a、bar 和 b。
  • 第三个包含着 bar 所创建的作用域,其中只有一个标识符:c。

词法作用域的查找

       作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应,作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止。

       无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。


欺骗词法

       词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修改”(也可以说欺骗)词法作用域呢?JavaScript 中有两种机制来实现这个目的。社区普遍认为在代码中使用这两种机制并不是什么好注意。但是关于它们的争论通常会忽略掉最重要的点:欺骗词法作用域会导致性能下降。


eval

       JavaScript 中的 eval(…) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。

       具体可以看下面这个例子:

640.png

       这段代码的输出结果并不是1, 2, 在调用foo的过程中相当于在foo的作用域创建了b,并覆盖了外部的外部作用域的同名变量,起到一个”欺骗“的作用


with

       with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。现在已经不被推荐使用,后面会说明原因,我们来看一下with的应用进一步了解一下它的用法

640.png

       但是其实,with可能会产生一些意料之外的场景,例如下面这个例子:

640.png

       我们来看一下这个例子,首先是o1处,因为有a属性,所以正常赋值,而o2处并没有a属性,所以仍然是undefined,但是全局的a是哪里来的呢,我们上一章提到作用域的两种查询LHS和RHS,这里明显属于LHS,当LHS查询在作用域中找不到a属性的时候,便自动在全局创建了一个新的a变量,这就是with的作用域“欺诈”

       所以这就是eval和with在用法功能上被禁用的原因


eval和with在性能上的损耗

       eval和with不仅仅在用法功能上可能出现意料之外的词法欺骗情况,在性能上也会有很大的损耗

       JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但如果引擎在代码中发现了 eval(…) 或 with,它只能简单地假设关于标识符位置的判断都是无效的,因为无法在词法分析阶段明确知道 eval(…) 会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么。

       如果代码中大量使用 eval(…) 或 with,那么运行起来一定会变得非常慢。无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行得更慢这个事实。


小结

       词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。

       JavaScript 中有两个机制可以“欺骗”词法作用域:eval(…) 和 with。请避免对他们的使用

目录
相关文章
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
12月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
212 1
JavaScript中的原型 保姆级文章一文搞懂
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
203 11
|
JavaScript 前端开发
javascript的作用域
【10月更文挑战第19天javascript的作用域
|
JavaScript 前端开发
如何在 JavaScript 中实现块级作用域?
【10月更文挑战第29天】通过使用 `let`、`const` 关键字、立即执行函数表达式以及模块模式等方法,可以在JavaScript中有效地实现块级作用域,更好地控制变量的生命周期和访问权限,提高代码的可维护性和可读性。
|
JavaScript 前端开发
JavaScript 作用域
JavaScript 作用域是指程序中可访问的变量、对象和函数的集合。它分为函数作用域和局部作用域。函数作用域内的变量仅在函数内部可见,而全局作用域的变量在整个网页中均可访问。局部变量在函数执行完毕后会被销毁,而全局变量则在整个脚本生命周期中都存在。未使用 `var` 关键字声明的变量默认为全局变量。
|
12月前
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
134 0
|
JavaScript 前端开发
js作用域
js作用域
90 1
|
JavaScript 前端开发
js 变量作用域与解构赋值| 22
js 变量作用域与解构赋值| 22
|
缓存 JavaScript 前端开发
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
该文章详细讲解了JavaScript中的作用域、闭包概念及其应用场景,并简要分析了函数柯里化的使用。
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化