javaScript预编译、作用域,作用域链详解

ES5中只分为全局作用域和函数作用域,也就是说for,if,while等语句是不会创建作用域的。ES6(let,const)除外。

几个概念

    执行环境:也称为执行期上下文,当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。它定义了变量或函数有权访问的其他数据,决定了它们的各自行为。

    执行环境分为全局执行环境函数执行环境(这个名字我自己取的),其中在浏览器中全局执行环境是一个Windows对象。

    活动对象(AO)与变量对象(OV): 活动对象也就是前面说的执行期上下文的内部对象,每个函数执行的时候都会创建这样一个活动对象,当函数函数还未执行完毕的时候,又进入到其他的作用域中,那么这个活动对象就变为变量对象(OV),比如说在一个函数里面执行另一个函数,另一个函数会创建一个活动对象,而当前的变为变量对象。当另一个函数执行完毕,它的执行上下文被销毁,返回上一个函数,那么上一个函数的变量对象又变为活动对象。可以理解为当前执行环境的执行期上下文的内部对象称为活动对象,否者称为变量对象。另外变量对象和活动对象都存储了本级环境中所定义的变量和函数,函数的话还包括参数

    预编译:在进入全局环境和函数环境之前就会进行

    预编译步骤:

    1、创建AO对象(活动对象 或者说执行期上下文) 如果是全局环境就叫做window对象或者Go对象
2、查找形参和变量声明,值为undefined
3、将实参和形参相统一,值为实参里面的值值为实参里面的值

4、找函数声明,属性值为声明时候的属性值

[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了前面说的执行期上下文的集合,这个属性是在函数被定义的时候就创建

作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

       寻找变量的过程就是从变量作用域链开始查找的,如果在当前的变量作用域没找到,那么就去上一个变量作用域里面去早

       作用域链创建的过程:


预编译

先来理解一下预编译,看例子:

		var a = 10
		b = 20
		function test (a, b) {
			console.log(a, b, test1, d)
			var a = 10
			console.log(a)
			if (b) {
				var c = 20
			}
			console.log(a, b, test1, d)
			function test1 () {
				
			}
			var d = function () {
				
			}
			console.log(a, b, test1, d)
		}
		test(1)

    第一步 全局环境下预编译

      进入全局环境并在执行全局环境代码之前进行预编译,跟着我前面说步骤

         1、生成GO对象

             GO: {          }

         2、查找形参和变量声明(这里不是函数,因此没有查找形参这一步),值为undfined

               GO: { a: undefined } // 注意这里因为b是没有声明的所以不会被放到GO这个活动对象里面

         3、将实参和形参相统一,值为实参里面的值值为实参里面的值(在全局环境依然没有这一步)

         4、找函数声明,属性值为声明时候的属性值

                GO: { a: undefined,  test: function () {} }

第二步 全局环境下执行代码

    先执行 a = 10 ,注意因为声明的步骤我们已经在预编译的时候执行过了,所以这里就只是赋值操作,此时的GO对象是

 GO: { a: 10, test: function () {} }

    再执行 b = 10 ,注意这里由于没有声明预编译的时候没有被放到GO对象里面去,因此在这行代码之前访问这个对象会报错,当执行到这个代码的时候会被放到GO对象里面去,此时的GO对象为

GO: { a: 10, test: function () {} , b: 10} 

   第三步 函数环境下预编译

        现在该执行test函数了,进入到一个新的执行环境 ,在执行函数代码之前会进行预编译

         1、生成AO对象

        AO: {          }         

         2、查找形参和变量声明, 值为undfined

        AO: { a: undefined, b: undefined, c: undefined, d: undefined }  

          3、将实参和形参相统一,值为实参里面的值

        AO: { a: 1, b: undefined, c: undefined, d: undefined }                 

         4、找函数声明,属性值为声明时候的属性值

        AO: { a: 1, b: undefined, c: undefined, d: undefined,  test1: function() {} }                
第四步 函数环境下执行代码

           依旧和在全局环境下执行代码一样,执行赋值语句,下面就说当执行到某个console.log() 的时候它的AO对象是什么

            第一个console.log()的时候AO对象为

        AO: { a: 1, b: undefined, c: undefined, d: undefined,  test1: function() {} }  

            第二个console.log()的时候AO对象为

        AO: { a: 10, b: undefined, c: undefined, d: undefined,  test1: function() {} }         

            第三个console.log()的时候AO对象为

        AO: { a: 10, b: undefined, c: undefined, d: undefined,  test1: function() {} }            

            第三个console.log()的时候AO对象为

         AO: { a: 10, b: undefined, c: undefined, d: function () {},  f: function() {} }  

注意: 在预编译的时候,像let和const这种块级作用域的,如果放到if语句里面,是不会被添加到AO或者GO对象里面的,像上面的例子中if里面如果换成let或者const的话,预编译阶段是不会被添加进去的

作用域链

    看例子:


第一步:a函数被定义,创建[[scope]]

        

    上面函数被定义的时候,[[scope]]会把当前函数所在的上下文放入这个对象中。需要注意的时候,函数存储被定义时的上下文的时候,只是存储的是一个引用,而不是副本,正是因为这样,才能形成作用域链,当函数在本函数的AO对象找不到的时候,就沿着本函数的[[scope]]存储的上一个作用域的变量对象的引用到上一个作用域里面去早。

第二步,执行函数

       执行函数的时候,会先发生预编译,然后会把AO对象放入到这个[[scope]]中


    第三步:函数b在函数中是被定义了的,在执行预编译的时候,会为它创建一个[[scope]]属性,里面存储了b函数当前所在的上下文,也就是a的[[scope]]。


    第四步,执行b函数   

     和a函数执行一样,依然创建一个AO对象放入


作用域链就是这样形成的,每个函数都有一个[[scope]]里面储存了运行期上下文的集合。寻寻找变量的过程,就是沿着作用域链从上到下寻找,找不到就在最上面一层定义,也就是在global object里面成为全局变量,也就是我们常说的变量不定义就使用会成为全局变量。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值