作用域与作用域链

一、前言

Javascript是解释性语言,Javascript分为两个阶段:解释阶段执行阶段
解释阶段:
  词法分析
  语法分析
  作用域规则确定
执行阶段:
  创建执行上下文
  执行函数代码
  垃圾回收

二、作用域

1.1 什么是作用域

作用域是js引擎根据标识符查找变量和函数的一套规则,它规定变量和函数的可使用范围。

1.2 作用域类型

  1. 全局作用域:在代码中任何地方都能访问到的变量拥有全局作用域。
    (1) 最外层函数和在最外层函数外面定义的变量

    var outVariable = "我是最外层变量"; //最外层变量
    function outFun() { //最外层函数
        var inVariable = "内层变量";
        function innerFun() { //内层函数
            console.log(inVariable);
        }
        innerFun();
    }
    console.log(outVariable); //我是最外层变量
    outFun(); //内层变量
    console.log(inVariable); //inVariable is not defined
    innerFun(); //innerFun is not defined
    

    (2) 所有末定义直接赋值的变量

    function outFun2() {
        variable = "未定义直接赋值的变量";
        var inVariable2 = "内层变量2";
    }
    outFun2();//要先执行这个函数,否则根本不知道里面是啥
    console.log(variable); //未定义直接赋值的变量
    console.log(inVariable2); //inVariable2 is not defined
    

    (3) 所有window对象的属性拥有全局作用域

  2. 函数作用域:只在函数内部可以访问到的变量

    var a = 10; // 全局
    
    function func() {
        var b = 20;// 函数
    }
    console.log(a); // 10
    console.log(b); // error, b in not defined
    

二、作用域链

首先,我们要明确一点,作用域和作用域链是不同的。作用域是一套规则,作用域链是在代码执行过程中,会动态变化的一条索引路径。

2.1 谈谈[[scope]]

  每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。创建一个函数的时候,这个函数中的[[scope]]属性中就会包含所有上级变量对象在其中,这些变量对象以链式连接。你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

2.2 回顾执行上下文生命周期

  • 创建阶段(创建执行上下文)
    (1) 生成变量对象
      创建arguments对象
      检查函数声明
      检查变量声明
    (2) 建立作用域链
    (3) 确定this指向
  • 执行阶段(执行上下文中的代码)
    (1) 变量赋值,函数引用,变量对象变为活跃对象
    (2) 执行其他代码
  • 执行完毕后出栈,等待垃圾回收机制回收

2.3 谈谈执行上下文

在函数调用的时候,会创建一个函数执行上下文,这个上下文定义了函数执行时的环境,函数每次调用时都会创建一个函数执行上下文,函数执行完毕后执行上下文被销毁。执行上下文会建立作用域链。

2.4 [[scope]]和执行上下文中的作用域链

[[scope]]是在函数定义时创建的,执行上下文是在函数调用时创建的,函数执行结束便会销毁。

2.5 什么是作用域链

这里我们用变量对象的观点来解释作用域链:
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链就叫做作用域链。

2.6 作用域链的建立和变化

举个例子:

var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

执行过程如下:
1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];

2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack = [
    checkscopeContext,
    globalContext
];

3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链

checkscopeContext = {
    Scope: checkscope.[[scope]],
}

4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}

5.第三步:将活动对象压入 checkscope 作用域链顶端

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}

7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [
    globalContext
];

三、用词法环境解释作用域链


占位符

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值