zoukankan      html  css  js  c++  java
  • 深入js——作用域链

    作用域

    作用域是可访问对象的集合,确定当前执行代码对变量的访问权限。
    作用域可分为静态作用域和动态作用域,JavaScript采用静态作用域,也叫词法作用域。

    静态作用域

    函数的作用域在函数定义的时候就决定了,与函数如何被调用,在何处被调用无关。

    var a = 1;
    	function foo() {
    		console.log(a); // 1
    	}
    	function bar () {
    		var a = 2;
    		foo()
    	}
    	bar()
    

    以上代码,foo函数为打印出1。虽然foo是在bar函数中执行,但它的作用域是在它定义时就确定了。首先查找foo内部有没有a,没有就从外层找a,于是最终打印出了1。

    [[scope]]

    在将作用域链之前,先讲讲[[scope]]。用console.dir随便打印一个函数

    function bar () {
    	var a = 1;
    	function foo () {
    		console.log(a)
    
    	}
    	console.dir(foo)
    }
    console.dir(bar)
    bar()
    

    可以看到有一个[[scope]]属性。从这个例子可以看出:

    • [[scope]]是所有父变量对象的层级链
    • [[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。

    Q:有一点需要注意下,以上例子中如果foo函数中不使用bar中的变量,也就是如果不加console.log(a),则foo的[[scope]]中不会有bar这一级。
    A:这一点也很好理解,js在编译时就发现foo不会引用到bar中的变量,所以不将bar的作用域加入foo的父级层级中,因为没必要;但foo的可访问层级还是包括bar的,只要有使用到bar中的变量,bar就会加入foo的[[scope]]中。这一点从某种程度上说,也正好进一步说明了[[scope]]是在函数定义时(js编译时)就确定好了。

    结合上一节所说的:js采用静态作用域,函数的作用域在定义时就确定了;[[scope]]也是在函数定义时就确定了,是函数的一个属性而不是上下文,与如何调用无关。

    作用域链

    定义

    作用域链在进入上下文时被创建,定义为:当前上下文+[[scope]],即

    Scope = AO|VO + [[Scope]]
    

    也就是,将当前执行上下文的变量对象置于作用域数组前端;当查找变量时,首先查找当前AO|VO中是否存在,然后沿着函数定义时的层级([[scope]])查找,直到最后的Global。

    实践

    文章深入js——变量对象中提到了利用控制台的scope查看变量对象,这下我们就可以真正的好好看看scope了。

    function bar () {
    	var a = 1;
    	function foo () {
    		debugger
    		var b = 2;
    		console.log(a)
    	}
    	foo()
    }
    bar()
    


    可以看到,scope的最上层是Local,也就是当前执行上下文的变量对象,下面就是函数的[[scope]]属性里保存的父级层级链。点击Call Stack中的函数,还可以切换当前执行上下文,观察下面scope的变化。

    整体流程

    讲了变量对象和作用域,最后我们整体梳理下函数执行时发生了什么,整体流程是怎样的。

    var a = 1;
    function bar () {
    	var a = 2;
    	console.log(a)
    }
    bar()
    
    • bar函数被创建,同时将父级作用域保存到其属性[[scope]]中
    bar.[[scope]] = [
        globalContext.VO
    ]
    
    • 进入bar函数,创建bar执行上下文,bar执行上下文被push到执行上下文的栈顶
    Stack = [
        barContext,
        globalContext
    ]
    
    • 赋值bar函数的[[scope]]属性并创建作用域链
    barContext = {
        Scope: bar.[[scope]],
    }
    
    • 用 arguments 初始化活动对象,并加入形参、函数声明、变量声明
    barContext = {
        AO: {
            arguments: {
                length: 0
            },
            a: undefined
        },
        Scope: bar.[[scope]],
    }
    
    • 将活动对象AO压如作用域链顶端
    barContext = {
        AO: {
            arguments: {
                length: 0
            },
            a: undefined
        },
        Scope: bar.AO.concat(bar.[[scope]]),
    }
    
    • 执行bar函数
  • 相关阅读:
    将Excel表中的数据导入到数据库
    别人面试的学习路线
    和同门一起做的PHP网站
    正则表达式
    python 编码形式简单入门
    游戏开发者面临的几大问题
    quick-cocos2d-x与 cocos2d-x的关系
    cocos2dx中的坐标系统
    重载new和delete来检测内存泄漏
    VS下使用Google Protobuf完成SOCKET通信
  • 原文地址:https://www.cnblogs.com/youhong/p/12212755.html
Copyright © 2011-2022 走看看