引言:在新手学JS最易懵的3个点——作用域、闭包、原型,其中作用域是基础
一、什么是 JS 作用域
大白话:作用域就是“变量/函数的可用范围”——哪里能访问,哪里不能访问,由它的“出生地”决定
其中,可分为三大作用域(全局作用域,函数作用域,块级作用域)
1.全局作用域
顾名思义,该作用域作用于全局,在该作用域声明的变量能在任何地方被访问
var a = 100
if(true){
console.log(a) // 100
}
function foo(){
console.log(a)
}
foo() // 100
console.log(a) // 100
由此可见 : 在全局作用域声明的变量,在语句(例如 if ), 函数, 全局内均能访问到。
2. 函数作用域
该作用域作用于函数内部,仅在函数内部生效,函数的形参也包含在函数作用域内部
function foo(){
var a = 100
console.log(a)
}
foo() // 100
console.log(a) // 未定义
由此可见,定义在函数内部的变量无法在函数作用域外访问到
3.块级作用域
块级作用域 (let,const 和 { } 语法配合使用会导致声明的变量处在一个作用域中)
{
var a = 100
}
{
let b = 200
}
{
const c = 300
}
console.log(a) // 100
console.log(b) // 未定义
console.log(c) // 未定义
如上述代码所示,在被{ } 包裹 var 声明的 a 还是能在全局访问到, 而 let / const 声明的变量则不能被全局访问到,这是因为他们形成了自己的块级作用域
作用域的核心规则
1.变量提升(var的“坑”,let/const的优化)
先来谈谈何为变量提升
var a = 100
console.log(a) // 100
console.log(b) // undefined
var b = 200
由 var 定义的变量会存在变量提升的现象,就好像头和身子分离了,a 的例子看不出来, b 就豁然开朗了, 一般别的语言这个时候就已经开始报错了,但是 JS 的 var 不一样,它特殊,在 V8 引擎里它长这样
var a = 100
console.log(a) // 100
var b
console.log(b) // undefined
b = 200
可以看到 b 的声明跑到前面去了,这种现象我们称之为变量提升
又如
// 全局作用域
{
var a = 1; // var 声明的变量,穿透 {},属于外层(全局)作用域
console.log(a); // 1
}
console.log(a); // 1 能访问到,说明 {} 没形成独立作用域
// 函数作用域(对比)
function fn() {
var b = 2; // 函数作用域内的变量,外层访问不到
}
console.log(b); // 报错:b is not defined(函数作用域生效)
这种反人类直觉的东西不应该存在,所以 JS 后续推出了 let/const 的优化
console.log(c) // 未定义
let c = 100
console.log(d) // 未定义
const d = 300
这样才符合直觉
2.作用域链(变量查找的规则)
作用域的查找规则就像是剥洋葱,一层一层往外层找,直到全局作用域,找不到就报错
// 全局作用域
let all = "全局变量";
// 外层函数作用域
function outerFn() {
let outer = "外层变量";
// 内层函数作用域
function innerFn() {
let inner = "内层变量";
// 查找顺序:inner(当前内层作用域) outer(外层作用域) global(全局作用域)
console.log(inner); // 输出:内层变量(当前作用域找到)
console.log(outer); // 输出:外层变量(当前找不到,去外层找)
console.log(all); // 输出:全局变量(内层、外层都找不到,去全局找)
// 注释打开会报错(全局也找不到)
}
innerFn();
}
outerFn();
3. if/for 不是对象,也没有“作用域”
这里的意思是 if/for 不会像上述的全局,函数拥有自己独特的作用域
{
let c = 3; // let 声明,被 {} 的块级作用域包裹
const d = 4; // const 同理
console.log(c); // 3
console.log(d); // 4
}
console.log(c); // 报错:c is not defined(块级作用域隔离)
console.log(d); // 报错:d is not defined
// 经典例子:for 循环的块级作用域
for (let i = 0; i < 3; i++) {
// 每次循环都会创建一个独立的块级作用域,i 只在当前循环生效
setTimeout(() => console.log(i), 0); // 输出 0、1、2
}
for (var j = 0; j < 3; j++) {
// var 无块级作用域,j 是全局的,循环结束后 j=3
setTimeout(() => console.log(j), 0); // 输出 3、3、3
}
就像上述代码所展示,var 声明的变量还是跑到全局了,所以输出的都是 3 , 当然这里还涉及到了定时器的异步,后续文章也会的对异步展开讨论