在编程语言中,特别是像JavaScript这样的语言中,作用域是一个非常重要的概念,它决定了变量、函数以及其它标识符在何处可以被访问
首先
在了解作用域之前,让我们先来了解一些JS中的基础概念
类型
在JS中有以下几种基础类型:
- Number : 用于存储整数或浮点数,例如
42或3.14。 - String : 用于存储文本,需要用单引号
' '或双引号" "包围,例如'Hello, world!'。 - Boolean : 表示真或假的值,只有两个可能的值:
true和false。 - Undefined: 表示变量已被声明但没有被赋值,默认的初始值。
- Null: 表示一个刻意的空值或缺少对象值,它是一个特殊的原始值。 代码表示为:
JS
var str = 'hello world'; // string 字符串类型
var num = 123; // Number 数字
var flag = true; // Boolean 布尔类型
var u = undefined; // 强调未定义
var n = null; // 强调为空值
以下引用类型:
- Object : 用于存储键值对集合,可以是复杂的数据结构。所有非原始类型都归为此类,包括数组(
Array)、函数(Function)、日期(Date)、正则表达式(RegExp)等。 - Array: 特殊类型的对象,用于存储有序的数据集合。
- Function: 在JavaScript中,函数也是对象,可以被赋值给变量、作为参数传递给其他函数或作为其他函数的返回值。 代码表示为:
JS
var obj = {}; // Object 对象
var arr = []; // Arrar 数组
var fn = function(){}// function 函数
判断 条件语句
-
if 语句: 基础的条件判断结构,如果条件为真,则执行相应的代码块。
Javascriptif (条件) { // 条件为真时执行的代码 } -
if...else 语句: 在条件为真时执行一块代码,在条件为假时执行另一块代码。
Javascriptif (条件) { // 条件为真时执行的代码 } else { // 条件为假时执行的代码 } -
if...else if...else 语句: 用于测试多个条件,依次检查每个条件,直到找到第一个为真的条件并执行对应的代码块。
Javascriptif (条件1) { // 条件1为真时执行的代码 } else if (条件2) { // 条件1为假且条件2为真时执行的代码 } else { // 所有条件都为假时执行的代码 } -
switch 语句 : 当有多重分支选择时使用,基于不同的情况执行不同的代码块。每个
case后面通常跟着一个break语句,以防止代码穿透到下一个case。Javascriptswitch (表达式) { case 值1: // 表达式等于值1时执行的代码 break; case 值2: // 表达式等于值2时执行的代码 break; default: // 没有匹配的case时执行的代码 10}
判断语句
-
for循环: 通常用于已知循环次数的情况。其基本语法结构如下:
Javascriptfor (初始化表达式; 条件表达式; 更新表达式) { // 循环体(需要重复执行的代码) }- 初始化表达式:在循环开始前执行一次,通常用于设置循环变量。
- 条件表达式:在每次循环迭代前检查,如果为真,则执行循环体。
- 更新表达式:在每次循环迭代后执行,通常用于更新循环变量。
-
while循环: 当给定条件为真时,重复执行代码块。语法如下:
Javascriptwhile (条件表达式) { // 循环体(需要重复执行的代码) }- 条件表达式:在每次循环开始前评估,只要条件为真,循环就会继续。
好的,基础知识了解完毕,下面进入正题。
作用域
在JS中,在代码执行时,先要进行编译
就像执行var a = 1这一句代码,在v8引擎(谷歌内置JS执行引擎,node就是类似的引擎)眼里,他会先编译这段代码将这段代码分解为这样一段过程var ,a ,= ,1,这叫解析,然后又重新生成代码var a = 1,然后把这段过程的结果交给执行部分,执行代码。我们写出的代码,首先就会经过这样的编译,如果我们的代码就是简单的输入输出,那么就是流畅的编译->执行,但一旦有了特殊的需求,这样简单的过程明显无法满足需求,这个时候作用域的作用就出来了,我们来看这一段代码:
JS
var a = 1
function foo(){
var a = 2
}
foo()
console.log(a);
这段代码在控制台中的输出是什么呢?输出a的值我们当然知道,问题是a的值是多少呢?是2吗?我刚开始也是这么认为的,这里不是有一个函数把a的值又赋为了2吗?但最后却输出了1,,很奇怪对吧,这就是作用域的作用了,这里我们先了解一个概念,那就是作用域主要分为了
- 全局作用域 :在程序的最外层定义的变量拥有全局作用域,这意味着从定义处开始,直到程序结束,都可以访问到这些变量。在浏览器环境中,全局变量属于
window对象(在Node.js中则是global对象)的一部分。尽量避免过多使用全局变量,因为它们容易造成命名冲突和数据污染。 - 函数作用域:也称为局部作用域,当在一个函数内部定义变量时,这个变量就只在这个函数内部可见。每次函数调用都会创建一个新的作用域,因此相同名称的变量在不同的函数中互不影响。
在代码执行时会先考虑全局,再考虑函数
然后我们来模拟一下这段代码到底是怎么执行的。
首先,在董事长面前放着这一份代码,"董事长"表示我看不懂这一份代码,"秘书"你来帮我安排一下怎么去执行这段代码。然后"秘书"就来分析这段代码了,欸,然后"秘书"就在他的小本本上开始记了
首先

好了,这里"秘书"的工作就完成了,在第一步全局作用域编译时,"秘书"首先识别到了a这个数据,但"秘书"又不能执行,所以在"秘书"这里就定义为了undefined,未被定义但应该有个值,然后就是foo,这是个function函数对象,然后完了,那么就要问了,函数里面呢?后面的函数调用呢?控制台输出呢?
所以这就是全局作用域和函数作用域的区别了,函数在这时的"秘书"眼里是空的,是需要定义的,有东西但我识别不了的意思。
而函数调用以及控制台输出是要执行部分干的事了。
好了,"秘书"把这份笔记交给了"董事长",说你按照这里执行就可以了,"董事长"说好的,这就来执行,于是就有了
"董事长"将a赋值为1后,到foo()时愣住了,说,欸,秘书,这函数你还没告诉我怎么搞呢,你去帮我搞一下。 好,"秘书"就去搞函数了,就有了

函数里就一个给a赋值的操作对吧,于是秘书又交给了"董事长",然后就又有了

好了,调用函数结束,到了最后的输出a的阶段了,那么,我们现在有两个a,输出那个呢?输出全局的那个1 这里我们就有了,作用域的一个特点:
内部作用域可以访问外部作用域,反之则不行
为什么呢?因为这是栈的关系先进后出,后进先出 ,所以在这时,a的输出无法调用函数作用域内的2,于是输出了处在全局作用域的a=1
除去这两个作用域,还有
- 块级作用域 :在一些现代编程语言中(如JavaScript ES6引入了
let和const关键字),支持块级作用域。这意味着在if语句、for循环或者任何一对大括号{}内定义的变量,其作用域仅限于那个块内部。这有助于减少变量泄露到外部作用域的可能性,增强代码的模块性和可维护性。
就是说{} + let||const就是块级作用域,而且只作用于这两个关键字,不会影响其他的关键字
- 词法作用域(静态作用域):JavaScript采用词法作用域,这意味着变量的作用域在代码编写时就已经确定,而非在运行时决定。这意味着函数内部可以访问包含它的函数(父作用域)中的变量,即使父函数已经执行完毕。
变量声明的地方-- 所处的作用域就是词法作用域,就像是我处在这个房间内,那么对于这句话中的我来说,这个房间就是我的词法作用域。
- 欺骗词法作用域
(1)eval() 让原本不属于这里的代码,变得好像天生就定义在这里一样
JS
function foo(a,str){
eval(str);
console.log(a,b);
}
foo(1,'var b = 2')
就像是这段代码的str,它就被eval()执行为了foo中的属性b=2
(2)with(){} 当修改对象中不存在的属性时,这个属性会被泄露到全局,变成全局变量
结语
作用域理解起来稍难,但请记住,实践是学习编程的最佳途径,不断尝试、勇于探索,我们将能更熟练地运用这些知识解决实际问题。在编程的旅途中,遇到挑战是常有的事,请坚持下去,每解决一个问题,我们就会变得更加强大。最后,希望我的理解能帮助到你,我是Ace,我们下次分享再见!!!