作用域
作用域( scope)就是变量和函数的有效范围
,在这个范围内可以对变量和函数进行读写操作;
=> 作用域控制着变量和函数的可见性
和生命周期
学习目标
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
分类
作用域分为全局作用域和局部作用域
作用域对程序执行的影响
作用域对程序执行有以下影响:
- 变量的可见性:在一个作用域内定义的变量只能在该作用域内访问,无法在其他作用域中访问。这样可以避免变量名冲突和不必要的干扰。
- 变量的生命周期:在程序执行期间,变量的寿命与其作用域相关。当程序执行离开一个作用域时,该作用域中定义的变量通常会被销毁,因此不能再使用。
- 命名空间:每个作用域都有自己的命名空间,其中包含了该作用域中定义的所有变量和函数名。这使得代码组织更加清晰,并且可以避免变量名冲突。
- 函数调用:当程序执行调用一个函数时,将创建一个新的局部作用域。函数参数和在函数内部定义的变量和函数都只能在该函数的作用域内访问。函数返回时,该作用域将被销毁。
全局作用域
常见全局作用域
在 JavaScript 中,以下内容具有全局作用域,
- 在最外层函数之外声明的变量,
- 在全局范围内定义的函数
this
关键字指向的对象(在浏览器中是window
对象,在 Node.js 中是global
对象)===>即一个.js文件
也是一个全局作用域
例如,在以下代码中,变量 x
、函数 fn
和 this
都具有全局作用域:
var x = 1;
function fn() {
console.log("Hello World!");
}
console.log(this === window); // true (在浏览器中)
在以上代码中,变量 x
是在最外层函数之外声明的,因此在全局作用域中。函数 fn
是在全局范围内定义的,因此也在全局作用域中。而 this
关键字在浏览器中指向全局对象 window
,因此它也具有全局作用域。
需要注意的是,在编写 JavaScript 代码时应尽量避免使用全局变量和函数,因为这会增加代码耦合性并导致命名冲突等问题。
可见性和生命周期
可见性
:全局作用域中的变量/函数,在任何地方都可以访问到 生命周期
:伴随着应用程序的生命周期,关闭应用程序不能访问(销毁)
/ eg1. 任何地方都能访问全局作用域中变量
const num = 10
function fn(){
// 函数内部是可以访问的
console.log(num)//10
}
fn()
// eg2. 函数内部,如果不是用let或者const声明的变量,会成为全局变量
function bar(){
abc = 20
}
bar()//20
在JavaScript中,全局作用域的变量和函数具有全局范围的可见性。这意味着可以从任何地方(包括函数和代码块内外)访问和调用它们。
全局作用域中的变量和函数的生命周期与应用程序生命周期相同。它们会在应用程序启动时创建,并在应用程序关闭时销毁。因此,在开发中应该遵循最小化全局作用域的原则,以避免出现不必要的资源占用和命名冲突问题。
同时,对于使用 let
和 const
关键字声明的变量,它们存在于块级作用域中,并且仅在作用域内部可见。这意味着它们可以在其他代码块或函数中定义相同名称的变量而不会发生冲突;它们也只会在其声明的代码块或函数内部存在,并在代码块结束后自动被销毁。
尽量避免滥用全局作用域,以提高代码清晰性和可维护性。
局部作用域
在 JavaScript 中,局部作用域是指变量和函数只在其被定义的区域(代码块或函数)内有效。具有“封闭性”,可以避免同名变量之间的冲突,并且可以限制代码中变量的可见性。
常见局部作用域
在编程中常见的局部作用域包括:
- 函数内部:函数参数和在函数内部定义的变量和函数都是局部作用域。
- 代码块:使用花括号
{}
定义的任何代码块都是一个独立的作用域,其中定义的变量只能在该代码块中访问。 - 循环语句:for 循环语句中定义的计数器变量只能在循环内部访问。
注意:具体的局部作用域规则可能会因编程语言而异。
可见性和生命周期
可见性
:
- 局部声明的变量,只能在内部访问,外部无法被访问,
- 不同局部作用域内部声明的变量无法相互访问
- 函数的参数也是函数内部的局部变量,函数内部可以访问这个参数
生命周期
:
一般,局部作用域中声明的变量,代码执行结束时,它们被销毁;
- 函数作用域,函数调用结束后后销毁
- 块级作用域,代码块执行结束后销毁
以下是一些有关局部作用域的详细信息:
-
函数作用域:在 JavaScript 中,每个函数都具有自己的作用域,其中声明的变量和函数仅在该函数内部可见。这意味着变量和函数在函数外部是不可访问的。例如,在以下代码中,变量
x
只在函数fn
内可见:function fn() { var x = 1; console.log(x); // 输出 1 } fn() console.log(x); // 抛出 ReferenceError 错误
-
块级作用域:自 ES6 开始,JavaScript 引入了
let
和const
关键字,可以用于声明块级作用域中的变量。块级作用域是指由花括号{}
所包围的任何代码块,如if
语句、循环等。例如,在以下代码中,变量x
只在if
代码块中可见:if (true) { let x = 1; console.log(x); // 输出 1 } console.log(x); // 抛出 ReferenceError 错误
需要注意的是,在使用 var
声明变量时不会形成块级作用域,因此应该尽量使用 let
或 const
来声明变量以避免出现意外的错误。
ES6 新增块级作用域
原因
:新增块级作用域是为了解决使用 var 关键字定义变量时可能出现的问题。
1.内层变量可能会覆盖外层变量
正常情况下,内存函数访问外层全局作用域中的num变量,如下所示:
var num=1
function fn(){
console.log(num)// 输出 1
}
fn()
但是,如果函数内部,用var声明了同名变量num
,此时,就会把if块级中声明的变量var num=100
语句中的声明var num
提升到fn函数作用域的最前面,并且覆盖外部声明的全局变量num=1,所以得到的结果是undefined,如下所示:
var num=1
function fn(){
console.log(num) //输出 undefined
if(false){
var num=100
}
}
fn()
2.用来计数的变量可能会泄露为全局变量
for (var i=0; i <= 3 ; i++){
console.log(i)
}
console.log(i)//输出 4 --->泄露为全局变量
作用域链
作用域链是 JavaScript 中非常重要的概念之一,它决定了在当前执行环境中如何查找变量和函数以及它们能否被访问。
当 JavaScript 代码在执行时,会生成一个执行环境
(Execution Context),包括变量对象
(Variable Object)、作用域链
(Scope Chain)和 this 值
,
当前执行环境与外部环境的嵌套形成作用域链,它可以使内部环境访问所有外部环境的变量和函数,但外部环境不能访问内部环境的任何变量和函数。
作用域链本质上就是**底层变量**的查找机制
- js代码执行时,首先,JavaScript 引擎将当前执行环境的变量对象添加到作用域链的前端。
- JavaScript 引擎从当前执行环境的变量对象中查找该标识符对应的变量
- 如果当前执行环境中没有找到,会沿着作用域链,向上逐层作用域中查找,一直查找到全局作用域
- 如果没有找到时,抛出引用错误ReferenceError,表示没有定义过,
闭包
===>内存函数访问外层函数的变量和函数,会形成闭包,实现数据的私有化,能避免全局污染,且能够使外层作用域访问内层函数中的变量和函数;
例如:
var x = 1;
function fn() {
var y = 2;
console.log(x); // 1
console.log(y); // 2
}
fn();
在这个示例中,fn
函数的作用域链是由其自身作用域和全局作用域组成的。当 fn
被调用时,JavaScript 引擎首先在函数作用域内查找变量 y
,然后再顺着作用域链向父级作用域查找变量 x
,在全局作用域中找到了它。
需要注意的是,每个执行环境都有自己的变量对象和作用域链。当函数被调用时,它会创建一个新的执行环境,并将其添加到作用域链的前端。函数执行完毕后,该执行环境会被销毁,同时从作用域链中移除。这样就可以确保变量和函数的访问不会影响到其他执行环境。
延长作用域链
在 JavaScript 中,我们可以通过一些方式来延长当前执行环境的作用域链,以访问外部环境中的变量和函数,其中常见的方式包括:
-
with 语句:将一个对象添加到作用域链的前端,从而可以直接访问该对象中的属性和方法。
var obj = { x: 100 }; with (obj) { console.log(x); // 输出100 }
-
try-catch 语句中的 catch 块: catch 块的参数会被添加到作用域链的前端,因此可以访问 catch 块中定义的变量。
try{ throw new Error('error!!') }catch(e){ console.log(e)//输出 Error:error!! console.log(e.message)//输出 error!! }
-
eval() 函数:将传入的字符串当作 JavaScript 代码执行,并将执行结果添加到作用域链的前端。
eval('var x = 100;'); console.log(x); // 输出100
需要注意的是,尽管这些方法可以扩展作用域链,但它们可能会影响代码的可读性、可维护性和安全性。with 语句和 eval() 函数容易导致意想不到的错误,因此应该尽量避免使用。
原文链接:https://juejin.cn/post/7235091963313127485 作者:zn_100