导航
简单点理解:
<script>
var b = 2;
function fn() {
var a = 1;
console.log(b);
}
fn(); // 2
console.log(a); // a is not defined 不能访问函数作用域中的变量a
</script>
全面理解:
比如特殊的全局执行环境中的变量对象 window 对象,因此所有全局变量和函数都作为 window 对象的属性和方法创建的。在 Node 环境中,全局执行环境是 global 对象
<script>
var a = 1;
function sum(a, b) {
return a + b;
}
var n = window.sum(2, 3); // sum 相当于window对象上的方法
console.log(window.n); // 5 n相当于window对象的属性
console.log(window.a); // 1 a相当于window对象的属性
</script>
解析
- 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。
- 而在函数执行之后,栈将被环境弹出,把控制权返回给之前的执行环境。
- ECMAScript 程序中的执行流正是由这个方便的机制控制着。
JS 中有 3 种类型的作用域:
编写在 script 标签中的 js 代码(或单独 js 文件),都是在全局作用域中。
let arr = [1, 2, 3]; // 堆内存开辟空间,用来保存[1,2,3]
arr = null; // 垃圾回收器会把[1,2,3]回收掉,释放其在堆内存中占用的空间
写在函数内部的代码,就是在局部作用域中
<script>
function fn(a, b) {
var c = 10;
console.log((a + b) * c);
}
fn(1, 2); //函数调用创建函数作用域,代码执行用,作用域和变量a,b,c销毁
fn(2, 3); //函数调用创建函数作用域,代码执行用,作用域和变量a,b,c销毁
</script>
<script>
function checkWeight(weight) {
return function (_weight) {
weight > _weight ? alert("过胖") : alert("ok达标");
};
}
var P1 = checkWeight(100); // 调用完毕,作用域和变量weight不会被销毁
P1(110); // 调用完毕,作用域和变量_weight会被销毁
</script>
解析
- 如果我们在最后加上
P1 = null
,则垃圾回收器回在下一次清理内存时- 销毁掉 checkWeight 调用形成的作用域和作用域中的变量 weight。
使用 let 或 const 关键字声明的变量,会形成块级作用域。
{
let a = 1;
}
console.log(a); // 会报错,{}里是块级作用域,外面是访问不到里面的变量的
<script>
for (let i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
console.log(i); // i is not defined
</script>
注意点:
对象的
{}
不会形成块级作用域
在 Javascript 中,作用域分为 全局作用域 、 函数作用域 和 块级作用域
作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)
作用域链查找:
内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。
<script>
var a = 1;
var c = 4;
function fn1() {
var a = 2;
var b = 3;
function fn2() {
var b = 2;
console.log(a); // 2 自身没有,沿着作用域链向上找
console.log(b); // 2 自身有,就用自身的
console.log(c); // 4 自身没有,沿着作用域链向上找,直到全局作用域中找到c=4
}
fn2();
}
fn1();
</script>
一般情况下,变量取值,是到 创建这个变量 的 函数的作用域 中取值
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
<script>
var a = 1;
function fn() {
console.log(a);
}
function test() {
var a = 2;
fn();
}
test(); // 1
</script>
最终输出的结果为 1
说明 fn 中打印的是全局下的 a ,这也印证了 JavaScript 使用了静态作用域。
静态作用域执行过程
当执行 fn 函数时,先从内部的 AO
对象查找是否有 a
变量,如果没有,沿着作用域链往上查找(由于 JavaScript
是词法作用域),上层为全局 GO
,所以结果打印 1
****
动态作用域执行过程
如果采用的是动态作用域执行过程,那么就是执行 fn
函数,首先从函数内部查询 a
变量,如果没有,就从调用函数的作用域,即 test
函数的作用域内部查找变量 a
,所以打印结果 2
通过习题理解静态作用域: 函数定义位置就决定了作用域。
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn()
上面代码中:
a
,赋值为 1
fn1
,函数的内部分别声明了函数 fn3
,定义局部变量 a
,赋值为 2
,返回值为 fn3
函数fn3
函数内部定义局部变量 a
,赋值为 4
,执行 fn2()
fn2
, 函数的功能是,打印 a
的值fn
赋值为 fn1()
的返回值fn()
(相当于执行 fn3
函数)做题之前,一定要理解 静态作用域 的概念。该题 fn2
定义在全局上,当 fn2
中找不到变量 a
时,它会去全局中寻找,与 fn1
和 fn3
毫无关系,打印 1
.
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn()
fn2
是定义在函数 fn1
内部,因此当 fn2
内部没有变量 a
时,它会去 fn1
中寻找,跟函数 fn3
毫无关系,如果 fn1
中寻找不到,会到 fn1
定义的位置的上一层(全局)寻找,直至寻找到第一个匹配的标识符。本题可以在 fn1
中找到变量 a
,打印 2
var a = 1;
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a;
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn()
该题 fn2
定义在函数 fn3
中,当 fn2
中找不到变量 a
时,会首先去 fn3
中查找,如果还查找不到,会到 fn1
中查找。本题可以在 fn3
中找到变量 a
,但由于 fn2()
执行时,a
未赋值,打印 undefined
。
关于JavaScript
的静态作用域,我们只需要记住一句话:函数定义的位置就决定了函数的作用域,遇到题目时不要被别的代码干扰到。
而且习题二,习题三查找变量的过程,其实本质上就是沿着作用域链在查找。