共计 2785 个字符,预计需要花费 7 分钟才能阅读完成。
一、什么是作用域?
作用域是你的代码在运行时,各个变量、函数和对象的可访问性。(可产生作用的区域)
二、JavaScript 中的作用域
在 JavaScript 中有两种作用域
全局作用域
局部作用域
当变量定义在一个函数中时,变量就在局部作用域中,而定义在函数之外的变量则从属于全局作用域。每个函数在调用的时候会创建一个新的作用域。
1. 全局作用域
当你在文档中(document)编写 JavaScript 时,你就已经在全局作用域中了。JavaScript 文档中(document)只有一个全局作用域。定义在函数之外的变量会被保存在全局作用域中。
// 作用域默认为全局作用域
var name = ‘andy’;
全局作用域里的变量能够在其他作用域中被访问和修改。
var name = ‘andy’;
console.log(name); // 输出 ‘andy’
function logName() {
console.log(name); // ‘name’ 变量可以在这里和其他地方访问
}
logName(); // 输出 ‘andy’
2. 局部作用域
定义在函数中的变量就在局部作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。
// 全局作用域
function someFunction() {
// 局部作用域 ##1
function someOtherFunction() {
// 局部作用域 ##2
}
}
// 全局作用域
function anotherFunction() {
// 局部作用域 ##3
}
3. 块语句(JS 没有块级作用域)
块级声明包括 if 和 switch,以及 for 和 while 循环,和函数不同,它们不会创建新的作用域。在块级声明中定义的变量从属于该块所在的作用域。也就是说在 for、if、while 等语句内部的声明的变量与在外部声明是一样的,在这些语句外部也可以访问和修改这些变量的值。
if (true) {
// 这里的 if 条件不会创建一个新的作用域
var name = ‘Hammad’; // name 这个变量仍在全局作用域
}
console.log(name); // logs ‘Hammad’
ECMAScript 6 引入了 let 和 const 关键字。这些关键字可以代替 var。
var name = ‘Hammad’;
let likes = ‘Coding’;
const skills = ‘Javascript and PHP’;
和 var 关键字不同,let 和 const 关键字支持在块级声明中创建使用局部作用域。(块级作用域)
if (true)
// 这个 ‘if’ 块语句没有创建一个块级作用域
// name 变量处于全局作用域,因为由 var 关键字声明
var name = ‘Hammad’;
// likes 变量处于块级作用域因为由 let 关键字声明
let likes = ‘Coding’;
// skills 变量处于块级作用域因为由 const 关键字声明
const skills = ‘JavaScript and PHP’;
}
console.log(name); // 输出 ‘Hammad’
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined
一个应用中全局作用域的生存周期与该应用相同。局部作用域只在该函数调用执行期间存在。
4. 词法作用域
所谓的 词法 (代码) 作用域, 就是代码在编写过程中体现出来的作用范围. 代码一旦写好, 不用执行, 作用范围就已经确定好了. 这个就是所谓词法作用域. 这意味着函数运行在定义它的作用域中,而不是在调用它的作用域中。
在 js 中词法作用域规则:
函数允许访问函数外的数据.
整个代码结构中只有函数可以限定作用域.
作用规则首先使用提升规则分析
如果当前作用规则中有名字了, 就不考虑外面的名字
词法作用域
var 用来将变量定义在词法作用域中(也就是 function 中)
function someFunc(){
var a;
}
a 就被函数 someFunc 框在了词法作用域中
块级作用域
let 和 const 用来将变量定义在块级作用域中(也就是花括号中)
if(true){
let b;
}
b 就被 if 的花括号框在了块级作用域中
5. 作用域链
可以发现只有函数可以制造作用域结构. 那么只要是代码, 至少有一个作用域, 即全局作用域. 凡是代码中有函数, 那么这个函数就构成另一个作用域. 如果函数中还有函数, 那么再这个作用域中就 又可以诞生一个作用域. 那么将这样的所有的作用域列出来, 可以有一个结构: 函数内指向函数外的链式结构.
作用域嵌套
作用域是可以嵌套的,任务一中提到的词法作用域和块级作用域都可以嵌套其他作用域(块级作用域仅对 ES6 而言)
function someFunc(){
function inner(){
}
}
inner 就是嵌套在 someFunc(词法作用域)中的词法作用域
if(true){
while(false){
}
}
while 就是嵌套在 if(块级作用域)中的块级作用域
function someFunc(){
if(true){
}
}
if 就是嵌套在 someFunc(词法作用域)中的块级作用域
作用域中的变量访问
所有的嵌套作用域都遵循以下规则:内部作用域有权访问外部作用域,反之不成立。
栗子:
function someFunc(){
var outerVar = 1;
function inner(){
var innerVar = 2;
}
}inner 有权访问 innerVar 和 outerVar,但是 someFunc 只能访问到 outerVar
多重嵌套作用域
作用域是可以任意嵌套的,但是都要遵循上面的规则。再附加一个规则:兄弟作用域不可相互访问
栗子:
function someFunc(){
function inner(){
}
function inner2(){
}
}
inner 和 inner2 都是 someFunc 中的作用域,正如 someFunc 不能访问 inner 们的作用域一样,inner 们之间也不能相互访问。
作用域树
从上向下看这个嵌套作用域,就是棵树!看代码:
function someFunc() {
function inner() {
}
function inner2() {
function foo() {
}
}
}
看树:
someFunc()
|
/ \
/ \
/ \
↓ ↓
inner() inner2()
|
↓
foo()
要记住的是:inner 作用域可以访问外部作用域,但是反之不成立; foo()可以访问 inner2()中的变量,inner2()可以访问 someFunc()中的变量,这棵树倒过来似乎更有意义,就成了链!!
作用域链
从最里面看到最外面就构成了作用域链
someFunc()
↑
\
\
\
inner2()
↑
|
foo()