资讯专栏INFORMATION COLUMN

JavaScript 作用域不完全指北

coordinate35 / 2308人阅读

摘要:一旦到达顶层全局作用域,可能找到,也可能没有找到,查找过程都必须停止。当引擎执行查询时,如果查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出异常。代表作用域判别失败相关,而则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

什么是作用域

对于几乎所有编程语言,最基本的功能之一就是能够存储变量的值,并且能在之后对这个值进行访问和修改。这样就会带来几个问题,这些变量存储在哪里?程序在需要的时候又是如何找到它们的?要解决这些问题,就需要引入一套规则来存储变量和访问变量,这套规则就是作用域。

理解作用域

在刚开始接触 JavaScript 这门语言时,肯定会经常接触到 JavaScript 是动态语言, 是解释执行的,但事实上 JavaScript 是一门编译语言。只不过和 Java、C# 这些传统意义上的编译语言不同,JavaScript 的编译过程不是发生在构建之前的。大部分情况下,JavaScript 的编译过程发生在代码执行前的很短时间内。也就是说 JavaScript 代码在执行前都要进行编译。

为了更好地理解作用域,我们需要明确下面几个概念

引擎

从头到尾负责整个 JavaScript 程序的编译及执行过程

编译器

负责语法分析及代码生成等脏活累活

作用域

负责收集并维护由所有声明的标识符(变量) 组成的一系列查询, 并实施一套非常严格的规则, 确定当前执行的代码对这些标识符的访问权限。

下面我们从引擎、编译器和作用域的角度,分析 var a = 2 这条声明语句,看看它们是如何协同完成工作的

遇到 var a, 编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。 如果是, 编译器会忽略该声明, 继续进行编译; 否则它会要求作用域在当前作用域的集合中声明一个新的变量, 并命名为a。

接下来编译器会为引擎生成运行时所需的代码, 这些代码被用来处理 a = 2 这个赋值操作。 引擎运行时会首先询问作用域, 在当前的作用域集合中是否存在一个叫作 a 的变量。 如果是, 引擎就会使用这个变量; 如果否, 引擎会继续查找该变量。如果引擎最终找到了 a 变量, 就会将 2 赋值给它。 否则引擎就会举手示意并抛出一个异常!

简单来说,变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值,否则就会并抛出一个异常。

作用域嵌套

我们知道引擎查找变量的过程在作用域中进行的,而这个过程通常会涉及多个作用域。

当一个块或函数嵌套在另一个块或函数中时, 就发生了作用域的嵌套。 因此, 在当前作用域中无法找到某个变量时, 引擎就会在外层嵌套的作用域中继续查找, 直到找到该变量,或抵达最外层的作用域(也就是全局作用域) 为止。

为了便于理解,可以将作用域嵌套比喻成一栋高楼,我们从一楼(当前作用域)开始查找,如果没有找到,就会前往上一个楼层继续查找,以此类推。一旦到达顶层(全局作用域),可能找到,也可能没有找到,查找过程都必须停止。

LHS查询和RHS查询

继续上文的示例,引擎在执行编译器生成的代码时,会通过查找变量 a 来判断它是否已经声明过。查找的过程由作用域进行协助, 但是引擎执行怎样的查找, 会影响最终的查找结果。查找过程分为两类:LHS查询和RHS查询。其实很简单,当变量出现在赋值操作的左侧时进行 LHS 查询, 出现在右侧时进行 RHS 查询
更准确一点的讲, RHS 查询是查找某个变量的值,而 LHS 查询是查找变量的容器本身,从而可以对其赋值。如下面的示例:

var a = 2; // a: LHS查询
var b = 3; // b: LHS查询
a = b; //a: LHS查询 b:RHS查询

为什么区分 LHS 和 RHS 是一件重要的事情?

因为在变量还没有声明(在任何作用域中都无法找到该变量) 的情况下,  LHS 和 RHS两种查询的行为是不一样的

1.当引擎执行 RHS 查询时,如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量, 引擎就会抛出 ReferenceError异常。 

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

2.当引擎执行 LHS 查询时, 如果在顶层(全局作用域) 中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量, 并将其返还给引擎, 前提是程序运行在非“严格模式” 下。如果在“严格模式”下,引擎也会抛出 ReferenceError异常。

//非严格模式
var a =2;
b = a;
console.log(b); //2
//严格模式
"use strict";
var a =2;
b = a;
console.log(b); //ReferenceError: b is not defined

另外,如果 RHS 查询找到了一个变量, 但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用, 或着引用 null 或 undefined 类型的值中的属性, 那么引擎会抛出另外一种类型的异常, 叫作TypeError。

ReferenceError 代表作用域判别失败相关, 而 TypeError 则代表作用域判别成功了, 但是对结果的操作是非法或不合理的。

参考

《你不知道的JavaScript》

微信公众号:

声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。转载与引用请注明作者及出处。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104559.html

相关文章

  • styled-components 中文文档翻译及不完全指北

    摘要:前言官方文档地址中文文档地址是一个的第三方库,是的优秀实践。初次了解是在读林昊翻译的设计模式与最佳实践一书时。能力所限,已翻译部分可能仍有字词错误或语句不通顺的地方,欢迎有能力的同学帮助纠正。就是其中的佼佼者。 前言 官方文档地址: https://www.styled-components.com/ 中文文档地址:https://github.com/hengg/styled-com...

    Vicky 评论0 收藏0
  • styled-components 中文文档翻译及不完全指北

    摘要:前言官方文档地址中文文档地址是一个的第三方库,是的优秀实践。初次了解是在读林昊翻译的设计模式与最佳实践一书时。能力所限,已翻译部分可能仍有字词错误或语句不通顺的地方,欢迎有能力的同学帮助纠正。就是其中的佼佼者。 前言 官方文档地址: https://www.styled-components.com/ 中文文档地址:https://github.com/hengg/styled-com...

    OnlyLing 评论0 收藏0
  • ES6指北【5】——展开语法(spread syntax)

    摘要:我们先来看一看的官方定义展开语法可以在函数调用数组构造时将数组表达式或者在语法层面展开还可以在构造字面量对象时将对象表达式按的方式展开。 我们先来看一看MDN的官方定义 展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。(译者注: 字面量一般指 [1...

    ZoomQuiet 评论0 收藏0
  • ES6指北【6】——详谈解构赋值【附赠练习题】

    摘要:指北详谈解构赋值附赠练习题一何谓解构赋值基本概念首先我们看一下给的定义解构赋值语法是一个表达式,这使得可以将值从数组或属性从对象提取到不同的变量中从定义中,我们可以发现解构赋值的作用是对变量进行赋值主要通过两个方面实现这个作用数组将数组中的 ES6指北【6】——详谈解构赋值【附赠练习题】 一、何谓解构赋值? 1. 基本概念 首先我们看一下MDN给的定义 解构赋值语法是一个 Javasc...

    sorra 评论0 收藏0
  • ES6指北【2】—— 箭头函数

    摘要:箭头函数基本语法函数语法具名函数匿名函数三句话第一句话声明第二句话声明匿名函数第三句话把匿名函数赋值给箭头函数语法特点只能做赋值,不能做声明第一种写法完全写法不省略参数个数,不省略函数体花括号参数个数函数体内语句个数第二种写法省略参数括号参 1.箭头函数基本语法 1.1 ES3 函数语法 // 具名函数 function xxx(arg1, arg2) { console.lo...

    DobbyKim 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<