JS的解析原理和变量作用域

来源:互联网 发布:夏普和海信电视 知乎 编辑:程序博客网 时间:2024/04/28 15:10

当我们在谈变量作用域的时候,我们在谈什么?

实际上,变量作用域指的是变量的生命周期与作用范围。比如说:

  • 全局作用域全局都可以访问;
  • 局部作用域只有在局部才能够访问。
    在JS中一个函数作用域就是一个局部作用域。

这里讲的变量作用域,主要包含两个方面:

  • 一个方面是我们看到一个变量定义的时候,我们要知道这个变量的生命周期和作用范围是什么
  • 另一方面,当我们看到一个变量时,我们要知道引用的是哪里定义的变量,也就是说我们要找到这个变量的值。

一、变量作用域

1、静态作用域

静态作用域又称为词法作用域,词法作用域在编译阶段就可以决定变量的引用,它只跟程序定义的位置有关系,而跟执行的顺序无关。代码执行顺序可能是一个函数里面调用另一个函数,也就是有嵌套关系,但是静态作用域是不考虑是是在哪里调用这些函数,它只跟程序定义的原始位置有关。执行时会回到函数被定义的地方执行,由内往外寻找变量。

对于静态作用域,当代码执行到foo函数,就会招到foo函数被定义的地方,alert出x变量的时候,就会顺着这条作用域的链从函数内部往外找,找到x变量,在图中可以很明显地看到,会顺着foo函数这条作用域链找到全局作用域下的x。这个就是静态作用域的解析方式。

这里写图片描述

2、动态作用域

动态作用域在程序运行的时候才能决定引用了哪些变量,在函数执行的地方开始执行,在函数执行的地方开始有内往外寻找变量。动态作用域一般是由动态栈来管理。

代码执行的时候,先定义一个栈,依次把x变量、foo函数、bar函数放到栈里面,当调用bar函数的时候,发现有个x变量,就把x变量放到栈里面,然后调用foo函数,执行到foo函数里面,要alert出x的值,就在栈里面找出最近的x的值,这个值就是20。

这里写图片描述

二、词法环境

1、词法环境的初始化

JS使用的是静态作用域的管理方式,但是JS是没有块级作用域的,在JS里面只有函数作用域,也就是只有函数才会创建一个新的作用域,而JS里面的if语块和for产生的语块等都不会产生新的作用域(像C语言等其它语言是这样的)。另外在ES5中使用词法环境来管理静态作用域。

词法环境是一种描述静态作用域的数据结构,本质上它定义了一种数据结构,这种数据结构可以用来管理静态作用域。

词法环境是由以下几部分组成:

  • 环境记录(形参、变量、函数等)
  • 对外部词法环境的引用(outer)

也就是说这是一种嵌套的结构,在当前的词法环境里,一定有一个对外层引用的指针,称为outer,当然最外层的词法环境指向的是null。

那什么时候会创建词法环境呢?答案是一段代码开始执行前,就会先初始化词法环境。因为JS里没有块级作用域,所以JS里面只有全局代码,或者是函数代码开始执行前会初始化词法环境。那么有哪些东西会被初始化到词法环境呢?

  1. 第一个是形参,形参就是说如果调用了一个函数,函数里面有一些参数,那么这些形参的值是需要初始化到词法环境里的,这个是代码执行前就需要初始化好的。

  2. 第二个就是变量声明,我们使用var声明的变量也是需要写到环境记录里的。就是说这一段代码还没有执行前就需要先将这些东西写到环境记录里,这个是执行前要做的初始化的工作。一个var声明的变量记录到词法环境里的时候是什么值呢?所有的var声明的变量初始化到环境记录里的时候都是undefined,只是声明而未赋值,就是说在这一段代码里声明了很多变量,有的在前有的在后,我们在代码的前面都可以访问到这些变量,但是这些变量的值都是undefined的,只有真正执行到语句的时候,才能够得到变量的定义和赋值。

  3. 第三个是函数声明,这些代码有一些函数声明也是需要写到词法环境的环境记录里的。而且函数声明在初始化词法环境的时候,在引擎内部会创建一个函数对象,这个函数对象会把函数的形参、函数体放到这个对象里面,还会保存当前的作用域,也就是函数声明的时候,要把当前的作用域保存到保存到这个函数对象里面。

这里写图片描述

提示:其实这里可以解析变量声明和函数声明提升的问题!

2、词法环境的构建过程

前面已经说了,当我们要开始执行一段代码的时候,要先初始化词法环境,全局代码也是一样,会先创建一个全局词法环境,全局词法环境创建之后,就要初始化里面的一些环境记录。

var x=10;function foo(y){    var z=30;    function bar(q){        return x + y + z + q;    }    return bar;}var bar = foo(20);bar(40);

全局初始化

这里写图片描述

代码执行:
执行var x=10

这里写图片描述

执行var bar=foo(20)
这里要执行foo函数,所以会创建foo函数的词法环境
对于传入的参数,回到执行时的环境中找。

这里写图片描述

执行var z=30

这里写图片描述

执行return bar
返回一个bar函数,这时全局环境中的bar变量就变成了一个函数

这里写图片描述

这里要执行bar函数,所以会创建bar函数的词法作用域

这里写图片描述

执行bar()
顺着作用域链找到x、y、z的值返回x+y+z+q的值

三、词法环境的相关问题

1、函数定义、形参、变量定义名称冲突

优先级:函数定义>形参>变量定义

2、arguments对象

argument对象是一个在函数里面定义好的对象,通过arguments对象我们可以访问函数传过来的实参,实际上arguments也是算到环境记录里的。其实上面需要记录到环境记录里的东西并没有全部讨论,但主要的就是这些。

3、函数表达式

如果在一个函数作用域里遇到函数表达式会怎样?

函数的词法环境是在函数执行前。但是其实函数声明的outer在声明这个函数的时候就已经保存了这个scope。前面说到把一个函数定义放到一个环境记录的时候,会创建一个函数对象,那么在这个函数对象里面会保存当前的词法环境,为什么呢?实际上当我们初始化这个函数的词法环境的时候就可以把这个词法环境的outer设置成声明函数时保存的当前的词法环境,其实在一段代码开始执行之前就会把这部分初始化工作完成。

函数表达式是不一样的,函数表达式的函数对象是在执行到函数表达式这一条语句的时候才创建的函数对象,才把函数对象里面的作用域设置成当前的作用域,函数声明的函数对象是在代码执行之前就已经创建了的。也许你会问这样不是一样吗?难道函数在执行过程中词法环境还会改变吗?答案是还真的会,在函数执行的过程中,函数的词法环境的确是可能发生改变的,例如with和try…catch

这里写图片描述

这里写图片描述

提示: 关于with和try…catch的相关知识请自行了解。

0 0
原创粉丝点击