学废了,JavaScript 中的作用域与作用域链

发布 2021-12-01 阅读 13分钟
# 什么是作用域? 作用域定义了变量的可见性或可访问性。大白话来说,就是一个变量能不能被访问或引用,是由它的作用域决定的。 在 JavaScript 中有三种作用域。 - 全局作用域 - 函数作用域(局部作用域) - 块作用域 ```jsx let globalVariable = "我是全局作用域下的变量" function func() { let localVariable = "我是局部作用域下的变量" } if (true) { let blockVariable = "我是块作用域下的变量" } ``` ## 全局作用域 **Global Scope** `一个在最外层定义的变量便处于全局作用域,全局作用域内的变量可以在程序的任意地方访问。` ```jsx var globalVariable = "全局作用域变量" function func() { // 在函数内访问全局作用域的变量 console.log("函数内访问:", globalVariable) } func() console.log("函数外访问:", globalVariable) ``` 输出: ```jsx 函数内访问: 全局作用域变量 函数外访问: 全局作用域变量 ``` 使用 `var 关键字` 在大括号内(包括纯粹的大括号、if、while、for)定义的变量仍然`属于全局作用域。 ```jsx if (true) { var globalVariable = "全局作用域变量" } console.log("外部访问:", globalVariable) ``` 输出: ```jsx 外部访问: 全局作用域变量 ``` ## 函数作用域(局部作用域) **Function Scope(Local Scope)** `在函数内定义的变量则属于函数作用域,又称局部作用域。局部作用域内的变量只能在自身作用域内被访问。` ```jsx function func(params) { var localVariable = "局部作用域变量" console.log("函数内访问:", localVariable) } func() console.log("外部访问:", localVariable) // Uncaught ReferenceError: localVariable is not defined ``` 输出: ```jsx 函数内访问: 局部作用域变量 Uncaught ReferenceError: localVariable is not defined ``` 例子中,我们尝试在外部访问局部作用域中定义的变量,报了 `变量未定义` 的错误。 ## 块作用域 **Block Scope** ES6 中引入了 let 与 const,与 var 不同的是。之前的例子中,在大括号(包括纯粹的大括号、if、while、for)间用 var 定义的变量处在全局作用域。如果我们用 let 与 const 在大括号中定义,变量将处于块作用域。 块作用域内的变量只能在自身作用域内被访问。 **let 与 const 的不同点在于, const 定义的是一个常量,无法修改定义后的值。** ```jsx { let blockVariable = "块作用域变量" console.log("块内访问:", blockVariable) } console.log("外部访问:", blockVariable) // Uncaught ReferenceError: blockVariable is not defined ``` 输出: ```jsx 块内访问: 块作用域变量 Uncaught ReferenceError: blockVariable is not defined ``` 例子中,我们尝试在外部访问块作用域中定义的变量,报了 `变量未定义` 的错误。 ## 什么是作用域链? **Scope Chain** `当一个变量在当前作用域无法找到时,便会尝试寻找其外层的作用域,如果还找不到,再继续往外寻找(只会往外寻找,不会寻找兄弟作用域,更不会往内寻找)。`这种如同链条一样的寻找规则便被称为作用域链。 ```jsx let variable1 = "我是变量 1,外部的" let variable2 = "我是变量 2" function func() { let variable1 = "我是变量 1,内部的" { let variable3 = "我是变量 3" } { // 往外寻找,在上一层函数内找到了 console.log(variable1) // 往外寻找,直到全局作用域 console.log(variable2) // 找不到,报错 console.log(variable3) // Uncaught ReferenceError: variable3 is not defined } } func() ``` 输出: ```jsx 我是变量 1,内部的 我是变量 2 Uncaught ReferenceError: variable3 is not defined ``` 在例子中,打印 variable1 变量时,由于在上层作用域也就是函数中就找到了 variable1 变量,便停止了寻找,不会找到全局作用域下的 variable1 变量。 寻找 variable2 变量时,在上层作用域中未找到,便一直找到了上上层作用域,也就是全局作用域下的 variable2 变量。 寻找 variable3 变量时,由于 variable3 变量被定义在兄弟作用域中,并不会被寻找到,因为作用域链的规则是只会往上层作用域寻找,并不会寻找兄弟作用域。因此这里报了变量未定义的错误。 ### 函数的作用域是它定义时的作用域,而不是调用时 ```jsx function func() { let variable = "我是 func 内的变量" function func2() { console.log(variable) } return func2 } { let variable = "我是大括号内的变量" let func2 = func() func2() } ``` 输出: ```jsx 我是 func 内的变量 ``` 在例子中,执行 func2 函数时往上寻找的作用域是在 func2 定义时的作用域,而不是调用时的作用域。 ## 如果找不到变量会怎样? 如果一个变量直到全局作用域也找不到便会执行以下操作。 1. 非严格模式:隐式声明全局变量 2. 严格模式:报错 ### 非严格模式 非严格模式下,尝试赋值一个变量时,如果找不到则会隐性声明成全局域的变量。 ```jsx { variable = "我是一个隐性声明的变量" } console.log(variable) ``` 输出: ```jsx 我是一个隐性声明的变量 ``` 以上的例子中,variable 由于未声明,因此被隐性声明成了全局作用域下的变量,这使得在最外部也能打印出 variable 变量的值。 非严格模式下,尝试使用一个变量的值时,如果找不到同样会报错。 ```jsx { console.log(variable) // Uncaught ReferenceError: variable is not defined } ``` 输出: ```jsx Uncaught ReferenceError: variable is not defined ``` 以上的例子中,由于使用 variable 时未定义,因此报了未定义的错误。 ### 严格模式 加入 `"use strict"` 表明是严格模式,严格模式下不论赋值还是使用未事先声明的变量都会报错。 ```jsx "use strict" { variable = "我是一个隐性声明的变量" // Uncaught ReferenceError: variable is not defined } ``` 输出: ```jsx Uncaught ReferenceError: variable is not defined ``` ## 作用域的好处? 1. `防止命名冲突`:你写了一万行的代码文件,如果没有作用域,你要给每个变量取独一无二的名字,屁股想想也知道是种折磨。 2. `安全性`: 变量不会被外部访问,保证了变量值不会被随意修改。你定义在函数内的变量,如果能在几千行之后不小心被修改,脚趾头想想也知道是种折磨。 3. `更高级的语法`:封装、面向对象等的实现离不开对变量的隔离,这是依靠作用域所达到的。 ## 说人话! 写代码时不用区分它什么全局使用域、局部作用域、块作用域啥的概念。只用记得大括号就是一个作用域,寻找变量永远是从内往外找。现在我们的编辑器基本都有缩进格式化, `从当前代码块的位置一层一层往左,就是它所能引用到的所有变量。` 打个比方,就像我们每个家庭就是一个作用域,当我们需要一笔手术费掏不出钱的时候,肯定是先在家里找,问问父母兄弟姐妹啥的,不会去求助其他陌生的家庭。还没有的话就往外到熟人关系这个作用域里问问。还不行就向街道居委会求助。居委会也没办法再向国家求援。从最亲近的关系找起,一层一层圈子往外,这就是作用域与作用域链。 **最后强烈建议大家使用 let 命名变量,放弃 var!**