作用域和作用域链

62 阅读4分钟

1.词法作用域

在说作用域之前,需要说一下词法作用域,词法作用域又叫做静态作用域,即在词法阶段就确定了变量和函数所在的作用域,在编译阶段即确定了作用域。Js使用的就是词法作用域。 来看一个例子:

var a = 20
function fn1(){
    console.log(a); // 20
}
function fn2(){
    var a = 40
    fn1()
}
fn2() 

执行函数fn2()会输出20,因为他会沿着自身的作用域链一直查找,而上层作用域就是全局作用域,这是在编译时就决定了的,而不会去找执行上下文中的40。

2.作用域分类

2.1 全局作用域

当没有声明或者,在非函数作用域中以var声明,都会有全局的作用域

var a = 123
if (a) {
    var b = 456
}
console.log(a) // 123
console.log(b) //456

2.2 函数作用域

函数内部的作用域,在函数外部无法进行访问。

function fn() {
    var c = 7
    console.log(c) // 7
}
fn()
console.log(c) // Uncaught ReferenceError: c is not defined

2.3 块级作用域

块级作用域为用使用let,const声明的变量或函数所在的块内,即{....}内。

// let const 具有块级作用域 级在{}中定义,就只在{}中生效,外部无法访问
if (a) {
    let d = 4
    console.log(d) // 4
}
console.log(d) //Uncaught ReferenceError: d is not defined

3.执行上下文

那么执行上下文是什么呢,执行上下文也就是Js被解析和执行时所在的环境,执行上下文共分为三种类型:

  • 全局执行上下文
  • 函数执行上下文
  • eval执行上下文(暂不了解)

而每种执行上下文又包含三部分:

  • 变量对象(Variable Object)
  • 作用域链(Scope Chain)
  • this

执行上下文的声明周期:创建阶段 -> 执行阶段 -> 回收阶段

  • 创建阶段:会创建变量对象,首先初始化arguments,然后提升函数声明和变量声明;创建作用域链;确定this的指向
  • 执行阶段:执行代码
  • 回收阶段:执行完毕后,等待垃圾回收机制进行回收

变量对象

变量对象与执行上下文息息相关,变量对象的创建规则如下:

  1. 建立Arguments对象,建立该对象下的属性与属性值。(已经确立)
  2. 检查当前执行上下文中的函数声明,在vO中以函数名建立属性,属性值指向函数所在内存地址的引用,如果该属性已经存在,则会进行覆盖
  3. 检查执行上下文中的变量声明,为声明变量创建属性,属性名为变量名,属性值为undefined,如果已经存在该属性,则直接跳过

4.作用域链

了解了变量对象,那么作用域链是什么呢? 作用域链就是对应的变量对象串联起来组成的链表,作用域链保证了当前执行上下文对符合访问权限的变量和函数的有序访问。

作用域链的查找遵循着从内向外的规则,最顶端为当前执行上下文所在的作用域,最低端为全局作用域。

(1)

function fn1() {
    console.log(fn2) //ƒ fn2(){console.log("fn2")}
    function fn2() {
        console.log('fn2')
    }
}
fn1()
// 分析:
VO = {
    // 1. arguments 没有
    // 2. 函数声明
    fn2: function
    // 3.变量声明:没有
}
执行代码 输出结果

这个相当于:

function fn1() {
    // 把fn2() 放在前面
    function fn2() {
        console.log('fn2')
    }
    console.log(fn2) //ƒ fn2(){console.log("fn2")}
}
fn1()

(2)

function test(a, b) {
    console.log(a)
    console.log(b)
    var b = 234
    console.log(b)
    a = 123
    console.log(a)
    function a() {}
    var a
    b = 234
    var b = function () {} // 函数表达式跳过
    console.log(a)
    console.log(b)
}
test(1)

// VO = {
//     arguments:
//         a: 1
//         b: undefined
//     function:
//         a: function(a已经变成function了)
//     Variable:
//         b会被直接跳过
//         a已经为function了,跳过
// }
// AO = {
//     arguments: 
//         a:function,
//         b:undefined
//     function:
//     Variable:
//         b:234 -> 234 -> function 
//         a:123
// }
// 结果: function a(){}, undefined, 234,123, 123, function (){}

当执行上下文进入到执行阶段后,变量对象VO会变为活动对象(Active Object,AO),此时原先声明的变量会被赋值,变量对象和活动对象都指向同一个对象,只是处于执行上下文的阶段不同。

Reference

coffe1891.gitbook.io/frontend-ha… www.cnblogs.com/wangzheng98… github.com/mqyqingfeng…

结束语

以上仅为我个人理解,如果不对之处欢迎指出,相互学习,共同进步!