干货分享-由浅入深理解JS作用域
前言
想要深入理解JavaScript语言,作用域是必需掌握的前置知识!作用域在JavaScript中扮演着至关重要的角色,它决定了变量的可见性和生命周期,直接影响着代码的行为和执行结果。通过深入理解作用域,开发者可以更加清晰地把握代码的逻辑结构,避免变量命名冲突和不必要的错误,提高代码的可维护性和可读性。
例题引入
Example One
var globalVar = 10;
function outerFunction() {
var outerVar = 20;
function innerFunction() {
var innerVar = 30;
console.log(innerVar); // 输出:30
console.log(outerVar); // 输出:20
console.log(globalVar); // 输出:10
}
innerFunction();
}
outerFunction();
Example two
var globalVar = 10;
function outerFunction() {
let outerVar = 20;
if (true) {
let blockVar = 30;
console.log(blockVar); // 输出:30
console.log(outerVar); // 输出:20
console.log(globalVar); // 输出:10
}
console.log(blockVar); // 这里会报错,因为blockVar在代码块外不可见
}
outerFunction();
Example three
function outerFunction() {
var outerVar = 10;
function innerFunction() {
var innerVar = 20;
function closureFunction() {
console.log(innerVar); // 输出:20
console.log(outerVar); // 输出:10
}
return closureFunction;
}
return innerFunction();
}
var closure = outerFunction();
closure();
作用域
上面的例子引入,你或许会疑问为什么会输出这样的结果,下面让我们正式介绍一下作用域!
在 JavaScript 中,作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。三种主要类型的作用域:全局作用域 函数作用域 块级作用域
全局作用域
- 解释: 全局作用域是整个程序的最外层作用域,其中定义的变量可以在程序的任何地方访问。
- 访问规则: 在全局作用域中声明的变量可以在程序的任何地方访问,包括函数内部和代码块内部。
函数作用域
- 解释: 函数作用域是在函数内部声明的变量所处的作用域,变量的作用范围限定在函数内部。
- 访问规则: 在函数内部声明的变量可以在函数内部任何地方访问,但不能在函数外部访问。函数外部的变量不能在函数内部直接访问,除非通过参数传递或闭包的形式
块级作用域
- 解释: 块级作用域是通过花括号
{}
创建的作用域,例如在条件语句、循环语句和函数内部的代码块中。 - 访问规则: 在块级作用域中声明的变量只能在当前块内部访问,超出当前块的范围则无法访问。这意味着在 if 语句、for 循环、while 循环或者函数内部声明的变量只在相应的代码块内部可见。
总结:内层作用域可以访问外层作用域中声明的变量,但外层作用域不能直接访问内层作用域中声明的变量。
欺骗词法作用域
在 JavaScript 中,词法作用域是由代码中变量声明的位置决定的。但是,有一些特殊的情况可以干扰词法作用域的正常行为,这就是欺骗词法作用域。主要的欺骗词法作用域的技术包括:
eval 函数:
eval
函数可以接受一个字符串作为参数,并将其当作 JavaScript 代码来执行。
使用 eval 函数
function evalExample() {
var localVar = 10;
eval('var evalVar = 20;');
console.log(evalVar); // 输出:20,eval 函数在当前作用域声明了一个新变量
}
evalExample();
不使用 eval 函数
function noEvalExample() {
var localVar = 10;
console.log(evalVar); // 输出:ReferenceError: evalVar is not defined
}
noEvalExample();
with 语句:
with
语句用于修改一个对象中的属性值,但如果修改的属性在原对象中不存在,那么该属性会泄漏到全局
var obj = { x: 10 };
with (obj) {
x = 20; // 修改对象中已存在的属性值
y = 30; // 添加新的属性到对象中
}
console.log(obj.x); // 输出:20,对象中的 x 属性被修改
console.log(y); // 输出:30,变量y泄漏至全局
作用域链
作用域链(Scope Chain)是在 JavaScript 中用于查找变量的一种机制,它定义了在特定执行上下文中查找变量时的顺序和范围。
在 JavaScript 中,当需要访问一个变量时,JavaScript 引擎首先在当前执行上下文的变量对象中查找,如果找不到,则会沿着作用域链向上逐级查找,直到找到变量或者到达全局作用域。这个查找过程就是作用域链的工作原理,它决定了变量的可见性和访问范围。
先了解这些
.[[scope]]
作用域属性 给js引擎访问的
我们拿不到---隐式属性
GO(Global Object)
- 概念: GO 是全局执行上下文的一部分,是全局环境中的对象。在浏览器环境中,GO 是
window
对象;在 Node.js 环境中,GO 是global
对象。 - 作用: GO 包含了全局范围内可访问的属性和方法,它是 JavaScript 程序的顶层对象。
AO(Activation Object)
- 概念: 每次执行函数时,都会创建一个称为“执行环境(Execution Context)”的内部对象。其中的一个属性就是 AO,也叫作活动对象,它存储了当前执行上下文中的所有局部变量、函数声明和形参信息。
- 作用: AO 是函数执行上下文的一部分,用于存储当前函数执行过程中的变量、参数和函数声明等信息。
通过例子讲解
function a() {
function b() {
var b = 22;
console.log(glob);
}
var a = 111;
b();
}
var glob = 100;
a();
画图分析作用域链:
分析作用域链的工作过程:
- 当调用函数
a()
时,会创建一个名为a
的变量,其值为111
,并且会创建函数b()
。 - 在函数
b()
中,我们声明了一个名为b
的局部变量,其值为22
。 - 在函数
b()
的内部,我们试图访问全局变量glob
。 - JavaScript 引擎首先在函数
b()
的作用域中查找变量glob
,由于没有在函数b()
的作用域中找到,因此会沿着作用域链向上查找。 - JavaScript 引擎进入函数
a()
的作用域,继续查找变量glob
。在函数a()
的作用域中也没有找到变量glob
。 - JavaScript 引擎继续沿着作用域链向上查找,最终在全局作用域中找到了变量
glob
,其值为100
。 - 最后,JavaScript 引擎将找到的全局变量
glob
的值100
输出到控制台。
变量声明关键字
通过上述对作用域的了解,我们再补充一个变量声明关键字的知识!
在JavaScript中,变量声明的关键字主要有var
, let
, 和 const
。下面是它们的区别:
关键字 | 变量作用域 | 声明提升 | 可重新赋值 | 可重复声明 |
---|---|---|---|---|
var | 函数作用域/全局作用域 | 是 | 是 | 是 |
let | 块级作用域 | 否 | 是 | 否 |
const | 块级作用域 | 否 | 否 | 否 |
- var 与 let const声明在全局作用域的区别:
在JavaScript中,let
和 const
也可以在全局作用域中声明变量,但是它们和 var
的行为略有不同。
在全局作用域中使用 var
声明的变量会成为全局对象的属性,例如在浏览器中,全局对象是 window
。而使用 let
和 const
声明的变量不会成为全局对象的属性,它们仅在当前的全局执行上下文中存在。
举个例子,在浏览器环境中:
var globalVar = 10;
let globalLet = 20;
const globalConst = 30;
console.log(window.globalVar); // 10
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined
在这个例子中,globalVar
是 window
对象的属性,而 globalLet
和 globalConst
不是。这是因为 let
和 const
声明的变量不会被添加到全局对象中。
-
声明提升:
- 在 JavaScript 中,使用
var
声明的变量存在声明提升(hoisting)的现象,即在代码执行前就已经被声明了,但是在声明前使用它会得到undefined
。 - 使用
let
和const
声明的变量不会出现变量提升的情况,这意味着在声明之前访问变量会导致 ReferenceError。
- 在 JavaScript 中,使用
-
可重新赋值:
- 使用
var
和let
声明的变量都可以重新赋值。 - 使用
const
声明的变量一旦被赋值后就不能再重新赋值。(针对基本数据类型,复杂数据类型另外讨论)
- 使用
-
可重复声明:
- 在同一个作用域中,使用
var
可以多次声明同一个变量名,后续的声明会覆盖之前的声明。(根本原因就是因为var可以声明提升) - 使用
let
或const
声明的变量不允许在同一作用域中重复声明同一个变量名,否则会导致语法错误。
- 在同一个作用域中,使用
最后
如果觉得小编的总结有所帮助得话,请"一键三连"吧,有问题也可以在评论区提出!
关注我,我将输出更多优质内容!
转载自:https://juejin.cn/post/7362080157237821490