1. 类比:JavaScript作用域如同一家族的家庭,而作用域链就像是这个家庭的家谱。
-
JavaScript作用域:
想象一个家庭,其中有父母、孩子和祖父母。每个成员都有自己的空间和规则,他们可以在自己的房间内做事情,但不能随意访问其他成员的房间。在JavaScript中,这个家庭就是一个作用域,而每个成员的房间就是一个变量或函数定义的作用域。每个作用域都有其自己的变量和函数,它们被称为局部变量和局部函数,只能在自己的作用域内访问。
-
全局作用域:
类比中的家庭有一间共享的大厅,每个成员都可以在那里做一些事情,这个大厅就像是全局作用域。在全局作用域中定义的变量和函数可以被所有的成员访问,就像家庭中的大厅是大家共用的空间。
-
JavaScript作用域链:
现在,想象一个家庭有多个世代,祖父母、父母和孩子,他们的规则和房间都不同。如果孩子想要在房间里找一些东西,但找不到,他会向父母问,如果父母也找不到,他们会向祖父母问。这个寻找的过程就像JavaScript的作用域链。当你在一个作用域中引用一个变量或函数时,JavaScript引擎会首先查找当前作用域,如果找不到,就会向父级作用域继续查找,一直到全局作用域。这种嵌套的搜索过程就构成了作用域链。
-
词法作用域:
类比中的家庭有自己的规则,孩子知道他们可以向父母或祖父母问问题,而不会问到其他家庭的成员。这就是词法作用域的概念,它指的是作用域的嵌套关系是在代码编写时就确定的,而不是在运行时动态确定的。这使得JavaScript可以在作用域链中正确地查找变量和函数。
总结一下,JavaScript作用域是一种规定了变量和函数可见性和访问性的机制,就像一个家庭中的房间和共用空间。作用域链是在这个家庭中的不同世代之间寻找信息的方式,而词法作用域确保了在编码时就确定了作用域的嵌套关系,使得JavaScript能够准确地查找变量和函数。这个类比希望能帮助你更生动地理解JavaScript作用域和作用域链的概念。
2.具体的介绍
全局作用域和函数作用域
全局作用域
全局作用域指的是在整个JavaScript程序中都可以访问的作用域。下面是一些关于全局作用域的示例:
- 最外层函数和最外层函数外面定义的变量拥有全局作用域:
var globalVar = "I am in the global scope"; // 全局作用域
function exampleFunction() {
var localVar = "I am in a function scope"; // 函数作用域
}
console.log(globalVar); // 可以访问,输出 "I am in the global scope"
console.log(localVar); // 无法访问,会引发错误
- 所有未定义直接赋值的变量自动声明为全局作用域:
undeclaredVar = "I am in the global scope"; // 自动成为全局变量
function exampleFunction() {
localVar = "I am also in the global scope"; // 修改了全局变量
}
console.log(undeclaredVar); // 可以访问,输出 "I am in the global scope"
- 所有
window对象的属性拥有全局作用域,特别适用于浏览器环境:
window.globalProperty = "I am a global property"; // 全局作用域
function exampleFunction() {
var localVar = "I am in a function scope"; // 函数作用域
}
console.log(window.globalProperty); // 可以访问,输出 "I am a global property"
但要小心全局作用域的弊端,因为过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。为了避免这种情况,通常推荐将变量限制在局部作用域内。
函数作用域
函数作用域是指变量声明在函数内部,只有在该函数内部可访问。函数作用域可以让我们控制变量的可见性,如下所示:
function exampleFunction() {
var localVar = "I am in a function scope"; // 函数作用域
console.log(localVar); // 可以访问,输出 "I am in a function scope"
}
console.log(localVar); // 无法访问,会引发错误
function anotherFunction() {
console.log(localVar); // 无法访问,会引发错误
}
作用域是分层的,内层作用域可以访问外层作用域的变量,但反之不行。这就是函数作用域的核心概念。在上面的示例中,exampleFunction内部可以访问其内部声明的localVar,但外部的代码无法访问它。
在函数作用域中,var不受块级作用域的影响
块级作用域
块级作用域是在ES6(ECMAScript 2015)引入的概念,它允许在代码块中创建独立的作用域,由 {} 包裹的代码片段就是一个代码块。在块级作用域中,使用 let 和 const 声明的变量具有特定的作用域,不同于全局作用域和函数作用域。
下面是有关块级作用域的详细信息和示例:
-
使用
let和const声明块级作用域:function exampleFunction() { if (true) { let blockScopedVar = "I am in a block scope"; const constVar = "I am also in a block scope"; } console.log(blockScopedVar); // 无法访问,会引发错误 console.log(constVar); // 无法访问,会引发错误 }在上面的示例中,
blockScopedVar和constVar只在if语句块内部可见,因为它们是在块级作用域中声明的。 -
变量不会有变量提升:
console.log(blockScopedVar); // 无法访问,会引发错误 let blockScopedVar = "I am in a block scope";与
var不同,使用let声明的变量不会发生变量提升,所以在变量声明之前尝试访问它会引发错误。
变量提升是 JavaScript 中的一个概念,它指的是在代码执行阶段,变量和函数的声明会被提升到它们所在作用域的顶部,无论它们在代码中的实际位置。这意味着你可以在变量或函数声明之前尝试访问它们,而不会引发错误。
-
不允许重复声明:
let blockScopedVar = "I am in a block scope"; let blockScopedVar = "I am another block scope"; // 会引发错误在同一作用域内,不允许使用
let重复声明同名变量。 -
在循环中创建块级作用域:
块级作用域在循环中特别有用,可以将变量限制在循环内部,避免循环变量泄漏到外部作用域。示例:
for (let i = 0; i < 5; i++) { console.log(i); // 可以访问,输出 0, 1, 2, 3, 4 } console.log(i); // 无法访问,会引发错误在这个示例中,
i是在for循环的块级作用域内声明的,因此在循环外部无法访问它。
块级作用域允许更好地控制变量的作用范围,提高代码的可维护性和安全性。
作用域链
在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到 window 对象就被终止, 这一层层的关系就是作用域链。
作用域链的作用
保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全 局对象)始终是作用域链的最后一个对象。
当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找。
局部作用域:每次创建一个函数时,都会创建一个新的作用域,这个作用域包含了函数的参数、局部变量以及内部函数。这个作用域成为当前执行上下文的变量对象(Variable Object)。在这个局部作用域内,可以访问这些变量和函数。
作用域链:当你在一个函数内部引用一个变量或函数时,JavaScript 引擎会首先查找当前函数内部的变量对象,如果没有找到,它会继续查找外部(父级)作用域的变量对象,这个过程会一直持续,直到达到全局作用域。这个链式查找的过程就是作用域链。
全局作用域:作用域链的最后一个对象始终是全局作用域(通常是 window 对象),其中包含了全局范围内定义的变量和函数。如果在所有作用域链中都找不到所需的变量或函数,JavaScript 引擎会在全局作用域中查找,如果仍然找不到,它会报错。
var globalVar = "I'm global"; // 全局变量 function outer() { var outerVar = "I'm in outer"; // outer 函数内的局部变量 function inner() { var innerVar = "I'm in inner"; // inner 函数内的局部变量 console.log(innerVar); // 可以直接访问 innerVar console.log(outerVar); // 可以直接访问 outerVar console.log(globalVar); // 可以直接访问 globalVar } inner(); console.log(innerVar); // 无法访问 innerVar,会报错 } outer(); console.log(outerVar); // 无法访问 outerVar,会报错在上述示例中,
inner函数可以访问它自己的局部变量、外部outer函数的变量以及全局作用域的变量,因为它们都在作用域链中。如果尝试访问不在作用域链中的变量,会导致错误。