zoukankan      html  css  js  c++  java
  • javascript 之作用域链-10

     前言

    在《javascript 之执行环境-08》文中说到,当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文(execution context)。对于每个执行上下文,都有三个重要属性:

    • 变量对象(Variable object,VO)
    • 作用域链(Scope chain)
    • this

    词法作用域

    在《作用域》中说到JavaScript采用词法作用域也叫静态作用域,这个作用域是在函数编译(js执行前很短的时间内编译)时决定的,而不是函数调用时决定;

    这是因为函数(一个function 是Function 的一个实例,这个后面会写到)有一个内部属性 [[scope]],当函数编译时,就会把所有父变量对象保存在内部属性[[scope]]中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

    注意:虽然js是浏览器解释执行,但是js也是存在编译(计算机只认识二进制哪里认识你写的abcd),只是跟java .net等语言有点区别,具体可以查看《你不知道javadcript》,上卷,词法作用域这个本上也有详细解释;

    作用域链

    在《引出作用域链》中说到作用域链本质是一个指向变量对象的指针链表。

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

    下面以一个例子看看作用域链是怎么构建的:

     1 var a=10;
     2 function run(){
     3   var name='Joel';
     4 function say(){
     5   var content='hello',name=' Word';
     6 
     7   console.log(content+name+','+a);
     8 }
     9 say();
    10 }
    11 run();//hello Word,10

     编译之后函数内部属性

    //编译时各自的[[scope]]
       run.[[scope]] = [
        globalContext.VO //全局变量对象
     ];
    
       say.[[scope]] = [
       run.VO,
       globalContext.VO
     ];

    执行函数
    函数执行分为两部分

    • 创建上下文对象
    • 代码执行

    创建上下文对象,创建vo 变量对象、scope chain 作用域链、this 指针以及把函数对象内部属性[[scope]]的值复制给上下文对象scope chain 属性

    run.ec={
         VO:{
         //变量对象初始化
     },
       //scope chain :run.[[scope]],
       scope chain :globalContext.VO,
      this:thisValue
    }

    执行阶段此时上下文被推入环境栈,VO激活为AO,此时VO 已经初始化完成,此时把当前环境的AO 被插入到scope chain 顶端
    即 Scope = AO+[[Scope]]  

    AO会添加在作用域链的最前面 

    Scope = [AO].concat([[Scope]])

    函数开始执行阶段

    //执行
    run.ec={
       AO:{
       //变量对象初始化
    },
        // scope chain:AO+run.[[scope]],
       scope chain:AO+globalContext.VO,
       this:thisValue
    }

    作用域链 = (动)活动对象(AO) + (静) scope属性 

    动指的是执行的时候的变量对象,静指的是词法作用域,即父级变量对象;

    创建过程

    以下面的例子为例,结合着之前讲的执行上下文、变量对象、执行上下文栈,来总结下函数执行上下文中作用域链的创建过程:

    1 var scope = "global scope";
    2 function checkscope(){
    3     var scope2 = 'local scope';
    4     return scope2;
    5 }
    6 checkscope();

    执行过程如下:

    1.checkscope 函数被创建/编译,保存父级变量对象到内部属性[[scope]]

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

    2.执行checkscope 函数,此时并不立刻执行,js内部开始做准备工作,创建上下文对象 ,推入执行环境栈

    checkscopeContext ={}

     第一步:创建上下文对象的作用域链:复制函数内部属性[[scope]]来创建作用域链

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

    第二步:创建上下文变量对象:一开始只是用 arguments 来初始化变量对象,值为默认值 undefined,继续初始化function 函数、var 变量

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

     第三步:绑定this 指针 变量对象初始化完成,开始执行函数,此时VO 激活为AO

    3.准备工作完成,开始执行函数

    执行 checkscope 函数,checkscope 函数执行上下文对象被压入执行上下文栈

    ECStack = [
        checkscopeContext,
        globalContext
    ];  

    4.VO激活成AO,将活动对象压入 checkscope 作用域链顶端

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

    5.随着函数的执行,修改 AO 的属性值

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

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

    ECStack = [
        globalContext
    ];

     总结

    • 分析代码的时候,务必回看函数的定义,毕竟是词法作用域。

    • 函数作用域链 = (动)活动对象(AO) + (静)scope属性

  • 相关阅读:
    算法---递归及尾递归
    ScheduledThreadPoolExecutor之remove方法
    数据结构---队列及简单实现有界队列
    数据结构---栈及四则运算实现
    数据结构---链表及约瑟夫环问题带来的思考
    数据结构---数组
    时间复杂度和空间复杂度
    Redis缓存设计与性能优化
    Springboot+ELK实现日志系统简单搭建
    Docker学习笔记(三):Dockerfile及多步骤构建镜像
  • 原文地址:https://www.cnblogs.com/CandyManPing/p/8252601.html
Copyright © 2011-2022 走看看