JavaScript的作用域介绍
作用域(scope)是编程语言中的一个基本概念,它定义了程序中变量、函数、对象等标识符(identifier)的可见性和生命周期。简单说,就是决定了在程序的哪些部分可以访问或使用这些标识符。
JavaScript的作用域规定了变量和函数的可访问性和可见性。作用域定义了变量和函数的有效范围和生命周期。
JavaScript中有以下几种作用域:
全局作用域(Global Scope):
全局作用域是定义在代码的最外层,不在任何函数内部的作用域。在全局作用域中定义的变量和函数可以在代码的任何地方访问。
var globalVar = 'I am global';function globalFunction() {console.log(globalVar); // 输出 "I am global"
}console.log(globalVar); // 输出 "I am global"
globalFunction();
在全局作用域中使用var声明变量时,在整个脚本范围内都可以访问,且这些变量会成为全局对象(在浏览器中是window对象)的属性。这意味着可以通过全局对象来访问这些变量,例如window.x。
在全局作用域中使用let声明变量时,在整个脚本范围内都可以访问,但这些变量不会成为全局对象(在浏览器中是window对象)的属性。这意味着不能通过全局对象来访问这些变量,例如window.y。
例如
var x = 10;
let y = 20;
console.log(window.x); // 10
console.log(window.y); // undefined
在现代JavaScript开发中,推荐使用let(或const,如果变量不需要重新赋值)来替代var。let提供了更好的作用域控制,减少了因变量提升或作用域混淆造成的错误,提高代码的可维护性和可读性。
函数作用域(Function Scope):
函数作用域是指在函数内部定义的变量和函数只在函数内部可见。函数作用域可以避免变量命名冲突,并且提供了封装和信息隐藏的特性。
function myFunction() {var x = 10;console.log(x); // 输出 10
}myFunction();
console.log(x); // 报错:x is not defined
块级作用域(Block Scope):
块级作用域是指在一对花括号{}内部定义的变量和函数,只在该块级作用域内可见。在ES6之前,JavaScript中没有块级作用域,只有函数作用域和全局作用域。
function blockScopeExample() {if (true) {var x = 10; // 在块级作用域之外仍可访问let y = 20; // 只在块级作用域内可访问const z = 30; // 只在块级作用域内可访问}console.log(x); // 输出 10console.log(y); // 报错:y is not definedconsole.log(z); // 报错:z is not defined
}blockScopeExample();
在上述示例中,变量x在块级作用域之外仍可访问,而使用let和const声明的变量y和z则只在块级作用域内可访问。
词法作用域(Lexical Scope)
词法作用域(Lexical Scope)和闭包相关。
闭包(Closure)是指一个函数能够访问和操作其词法作用域之外的变量的能力。简而言之,闭包是由函数以及其相关的引用环境组合而成的包裹(或封闭)体。
在JavaScript中,当一个函数被定义时,它会创建一个词法作用域,并捕获(或保存)它所在的作用域链。作用域链是一个包含所有父级作用域的链式结构,它决定了函数在执行时可以访问的变量。当函数形成闭包时,它会保留对其词法作用域中变量的引用,即使该词法作用域的上下文已经销毁。
闭包的特性使得函数可以在其定义的作用域之外被调用,但仍然可以访问其词法作用域中的变量。这使得闭包非常有用,可以用于创建私有变量和实现模块化的代码结构。
JavaScript采用词法作用域,也称为静态作用域。词法作用域意味着变量的作用域是在函数定义时确定的,而不是在函数调用时确定的。
function outer() {var x = 10;function inner() {console.log(x); // 闭包:可以访问外部函数outer的变量x}return inner;
}var closureFunc = outer(); // 返回inner函数形成的闭包
closureFunc(); // 输出 10
在上述示例中,函数inner形成了一个闭包,可以访问外部函数outer中的变量x。即使outer函数执行完毕,变量x的相关信息仍然保留在闭包中,使得closureFunc可以继续访问并输出x的值。
Javascript中,在“严格模式”中不会自动创建全局变量
"自动创建全局变量"是指在非严格模式下,当在全局作用域或函数内部使用一个未声明的变量时,JavaScript会自动将该变量创建为一个全局变量。这意味着该变量可以在程序的任何地方被访问和修改,而不仅仅在当前作用域内。
在 JavaScript 的严格模式(strict mode)下,如果在全局作用域或函数内部使用未声明的变量,会抛出一个错误,而不是自动创建一个全局变量。可以在脚本或函数的顶部添加 "use strict"; 来启用严格模式。
在非严格模式下,如果你在全局作用域或函数内部使用一个未声明的变量,JavaScript 会自动创建一个全局变量。例如:
function foo() {x = 10; // 在非严格模式下,会自动创建一个全局变量 x
}
foo();
console.log(x); // 输出 10
但是,在严格模式下,这种行为是不允许的。你必须显式地声明变量,否则会抛出一个错误。例如:
"use strict";function foo() {x = 10; // 在严格模式下,会抛出一个 ReferenceError,因为 x 没有被声明
}foo(); // 抛出 ReferenceError: x is not defined
使用严格模式可以帮助你避免一些常见的编程错误,例如使用未声明的变量,并促使你编写更加规范和可维护的代码。建议在 JavaScript 文件的开头或函数的开头启用严格模式,以确保整个脚本或函数都运行在严格模式下。
Javascript的作用域链
JavaScript的作用域链(scope chain)是指在程序执行过程中,变量和函数的查找顺序。当在一个作用域中引用一个变量或函数时,JavaScript会按照作用域链的顺序从内层作用域向外层作用域逐级查找,直到找到该变量或函数为止。
作用域链的构建是基于函数的嵌套关系。每当定义一个函数时,JavaScript会创建一个包含该函数的作用域,并将其嵌套在当前作用域中。这样形成了一个作用域的层级结构,每个作用域都有一个指向外层作用域的引用。
当在一个作用域中引用一个变量时,JavaScript首先在当前作用域中查找该变量,如果找到了,则使用该变量;如果未找到,则会沿着作用域链向上查找,直到全局作用域。如果在全局作用域中仍未找到该变量,则会抛出一个错误。
以下是一个简单的示例来说明作用域链的概念:
let x = 10;function outer() {let y = 20;function inner() {let z = 30;console.log(x + y + z); // 在此处的作用域链为 inner -> outer -> global}inner();
}outer();
在上面的示例中,当在inner函数中引用变量x、y和z时,JavaScript会按照作用域链的顺序查找这些变量。首先在inner的作用域中查找变量z,然后在外层作用域outer中查找变量y,最后在全局作用域中查找变量x。因此,最终输出的结果为60。
附录
作用域https://developer.mozilla.org/zh-CN/docs/Glossary/Scope
闭包https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
实用的闭包https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
理解Javascript的作用域和作用域链https://juejin.cn/post/6844904069413224462
一文彻底搞懂JS作用域https://segmentfault.com/a/1190000044251549
深入理解JavaScript作用域和作用域链https://segmentfault.com/a/1190000018513150
说说你对作用域链的理解https://vue3js.cn/interview/JavaScript/scope.html#%E4%B8%80%E3%80%81%E4%BD%9C%E7%94%A8%E5%9F%9F