写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址

作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码中变量和其他资源的可见性。单纯看这两句话可能并不好理解,来看个🌰:

function fn() {
    var a = '内部变量a';
}
fn();
console.log(a); // Uncaught ReferenceError: a is not defined
1
2
3
4
5

从上面的🌰中可以体会到作用域的概念,变量a在全局作用域没有声明,所以在全局作用域访问会报错。

  1. 理解
    • 作用域就是一块"地盘",一个代码段所在的区域
    • 它是静态的(相对于执行上下文对象),在编写代码时就确定了
    • 执行上下文对象是函数调用时产生的,而作用域是在函数定义时就确定了的,多次调用函数也不会产生多个作用域。
  2. 分类
    • 全局作用域
    • 函数作用域
    • 没有块级作用域(ES6有,let和const可以定义块级作用域)
  3. 作用
    • 隔离变量,不同作用域下同名变量不会有冲突
// 没有块级作用域
if (true) {
    var a = 2;
}
console.log(a); // 2

var b = 3;
function bar() {
    var c = 4;
    console.log('bar', c);
}
console.log('global', b);
bar();
1
2
3
4
5
6
7
8
9
10
11
12
13

无块级作用域:在花括号中定义变量和在花括号外面定义变量是一样。

function c() {
    var b = 1;
    function a() {
        console.log(b); // undefined
        var b = 2;
        console.log(b); // 2
    }
    a();
    console.log(b); // 1
}
c();
1
2
3
4
5
6
7
8
9
10
11

小结

  • js没有块级作用域(ES6以前);
  • 只有全局和函数作用域;
  • 内部作用域可以访问外部作用域,外部的不能访问内部;
  • 优先在内部作用域查找,找不到的话沿着作用域链向上查找。

作用域与执行上下文区别

  • 区别1:产生时机不同
    • 全局作用域之外,每个函数都会创建自己得作用域,作用域在函数定义的时候就已经确定了,而不是在函数调用的时候。
    • 全局执行上下文是在全局作用域确定之后,js代码执行之前创建
    • 函数执行上下文是在函数调用时,函数体代码执行之前创建
  • 区别2:
    • 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
    • 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
  • 联系:
    • 执行上下文是从属于所在的作用域
    • 全局执行上下文==>对应全局作用域
    • 函数执行上下文==>对应函数作用域

作用域链

创建作用域链:在代码在一个执行上下文中执行时,会创建变量对象的一个作用域链。

  1. 理解
    • 多个上下级关系的作用域形成的链,它的作用是:保证对执行环境中有访问权限的变量和函数进行有序访问,方向是从内向外的(内部作用域可以访问外部作用域,反之则不行)。
    • 查找变量时就是沿着作用域链来查找的。
  2. 变量查找规则
    • 在当前作用域下的执行上下文的变量对象中查找对应的属性,如果有则直接返回,否则进入步骤2
    • 在上一级作用域的执行上下文的变量对象中查找对应的属性,如果有直接返回,否则进入步骤3
    • 再次执行步骤2的查找操作,直到全局作用域的变量对象(即window对象),如果还没有找到就抛出找不到的异常。

举个🌰:

var a = 1;
function fn() {
    var b = 2;
    function bar() {
        var c = 3;
        console.log(a); // 1
        console.log(b); // 2
        console.log(c); // 3
        console.log(d); // Uncaught ReferenceError: d is not defined
    }
    bar();
}
fn();
1
2
3
4
5
6
7
8
9
10
11
12
13

面试题

var x = 10;
function fn() {
    console.log(x); // 10
}
function show(fn) {
    var x = 20;
    fn();
}
show(fn);
1
2
3
4
5
6
7
8
9

自由变量:当前作用域中没有定义的变量。当前作用域没有,就去父级作用域中找。

特别注意:父级作用域是函数定义时的作用域,而不是函数调用的时候的作用域。

在fn函数中,取自由变量x的值时,要到哪个作用域中取?到创建fn函数的那个作用域(这里是全局作用域)中取,无论fn函数将在哪里调用。

var fn = function() {
    console.log(fn);
}
fn();
var obj = {
    fn2: function() {
        console.log(fn2); // 报错 Uncaught ReferenceError: fn2 is not defined
        // 正确写法
        // console.log(this.fn2);
    }
};
obj.fn2();
1
2
3
4
5
6
7
8
9
10
11
12

首先在当前作用域查找fn2没有找到,继续沿着作用域链向上查找,全局作用域中也没有找,所以抛出找不到的异常。

需要注意的小细节

去掉var的局部变量

var name = 'wangwu';
function setName() {
   name = 'lisi'; // 去掉var变成了全局变量,会覆盖全局中的name
}
setName();
console.log(name); // lisi
1
2
3
4
5
6

形参也是局部变量

var name = 'wangwu';
function setName(name) { // 形参也是局部变量
   console.log(name);
}
setName('lisi'); // lisi
console.log(name); // wangwu
1
2
3
4
5
6

参考文档

  1. 深入理解Javacript从作用域作用域链开始
  2. 深入理解JavaScript作用域和作用域链

评 论: