实例分析 JavaScript 作用域
来源:互联网 发布:兴义网络 编辑:程序博客网 时间:2024/06/07 14:33
作用域模型
作用域有两种常见的模型:词法作用域(Lexical Scope,通常也叫做 静态作用域) 和 动态作用域(Dynamic Scope)。其中词法作用域更常见,被 JavaScript 等大多数语言采用。
首先了解一下这两种模型的说明:
- 词法作用域:词法作用域是指在词法分析阶段就确定了,不会改变。变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。
- 动态作用域:动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。 动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们在何处调用。
JavaScript 的词法作用域
如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:
- 读入第一个代码段(js执行引擎并非一行一行地分析程序,而是一段一段地分析执行的)
- 做词法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5
- 对
var
变量和function
定义做“预解析“(永远不会报错的,因为只解析正确的声明) - 执行代码段,有错则报错(比如变量未定义)
- 如果还有下一个代码段,则读入下一个代码段,重复步骤2
- 完成
JavaScript 解析过程
JavaScript 中每个函数都都表示为一个函数对象(函数实例),函数对象有一个仅供 JavaScript 引擎使用的[[scope]]
属性。通过语法分析和预解析,将[[scope]]
属性指向函数定义时作用域中的所有对象集合。这个集合被称为函数的作用域链(scope chain),包含函数定义时作用域中所有可访问的数据。
JavaScript 执行过程
执行具体的某个函数时,JS引擎在执行每个函数实例时,都会创建一个执行期上下文(Execution Context)和激活对象(active Object)(它们属于宿主对象,与函数实例执行的生命周期保持一致,也就是函数执行完成,这些对象也就被销毁了,闭包例外。)
执行期上下文(Execution Context)定义了一个函数正在执行时的作用域环境。它使用函数[[scope]]
属性进行初始化。
随后,执行期上下文 顶部 的会创建一个激活对象(active Object),这个激活对象保存了函数中的所有形参,实参,局部变量,this
指针等函数执行时函数内部的数据情况。这个时候激活对象中的那些属性并没有被赋值,执行函数内的赋值语句,这才会对变量集合中的变量进行赋值处理。也就是说 激活对象是一个可变对象,里面的数据随着函数执行时的数据变化而变化。
具体请查看 JavaScript 核心概念之作用域和闭包 中的解释。有点啰嗦了。
考虑一下下图中的代码:
分析过程:
- 作用域1 (绿色) :即全局作用域,包含变量
foo
; - 作用域2 (黄色) :
foo
函数的作用域,包含变量a
,bar
,b
- 作用域3 (蓝色) :
bar
函数的作用域,包含变量c
bar
作用域里完整的包含了 foo
的作用域, 因为 bar
是定义在 foo
中的,产生嵌套作用域。值得注意的是,一个函数作用域只有可能存在于一个父级作用域中,不会同时存在两个父级作用域。还有诸如this
, window
, document
等全局对象这里就不说了,避免混乱。
执行过程:
- 语句
console.log
寻找变量a
,b
,c
; - 其中
c
在自己的作用域中找到, a
,b
在自己的作用域中找不到,于是向上级作用域中查找,在foo
的作用域中找到,并且调用。
函数在执行时,每遇到一个变量,都会去执行期上下文的作用域链的顶部,也就是执行函数的激活对象开始搜索,如果在第一个作用域链(即,Activation Object 激活对象)中找到了,那么就返回这个变量。如果没有找到,那么继续向下查找,直到找到为止。如果在整个执行期上下文中都没有找到这个变量,在这种情况下,该变量被认为是未定义的。也就是说如果foo
的作用域中也定义了c
,但bar
函数只调用自己作用域里的c
。这就是我们说的变量取值。
实例分析
上面讲了很多概念性的东西。下面我们用实际代码讨论一下变量的作用域。这些可能在一下“坑人”的面试题中很常见。
不同作用域中的同名变量
同名变量可能在存在于多个作用域中,具体取值在执行时变量查找时决定。
示例1,作用域中变量查找规则:
这个很好理解,全局作用域中定义并赋值了变量a
,DoSomething
函数中也定义并赋值了变量a
。DoSomething
函数在执行时,首先找到的是激活对象中的变量a
,也就是2
。
console.log(window.a)
也很好理解,就是首先找window
对象,在激活对象中没找到,继续向上搜索,本例中,window
对象在Global Object(全局对象)中找到,window
的a
属性值为1
。
这段代码我相信对大家来说,理解起来都没问题。那么我们稍微对代码做一下修改:
示例2,作用域中没赋值的变量:
DoSomething
函数中定义了变量a
,但是没有赋值,那么这里打印结果是什么呢?结果是 undefined
,原因和示例1一样,只不过DoSomething
函数在执行时,首先找到的是激活对象中的变量a
,只不过这里的变量a
并没有赋值,所以是 undefined
。
示例3,激活对象是一个可变对象
根据作用域中变量查找规则,当前的激活对象中找到了有变量 a
的定义,执行到第一条console.log(a)
语句时值为 undefined
。执行到第二条console.log(a)
语句时,变量 a
已经被赋值为2
。也就是说,激活对象是一个可变对象,里面的数据随着函数执行时的数据变化而变化。
参数和同名变量
上面3个示例讲了不同作用域中的同名变量,这里我们来讲讲 形参(parameters)、实参(arguments)与同名变量的关系。
首先了解一下形参和实参:
顾名思义,形参就是形式参数,是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传递的参数。实参就是实际参数,是在调用时传递给函数的参数,即传递给被调用函数的值。实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。
那么形参、实参与变量同名又该如何呢?
示例4,形参、实参与同名全局变量的关系
打印的结果为1
和undefined
,为什么呢?
首先来看形参b
和全局变量b
,这两者毫无关系,只是同名。完全符合作用域中变量查找规则,DoSomething
函数在执行时,首先找到的是激活对象中的形参b
,形参b
没有值,所以是 undefined
。
接着来说说 参数a
和局部变量a
,看上面的代码似乎并不符合作用域中变量查找规则。如果作用域中变量查找规则去理解,应该是这样的,DoSomething
函数在执行时,首先找到的是激活对象中的参数a
,然后定义局部变量a
,不赋值,所以console.log(a)
是 undefined
。但是结果恰恰是1
。这又是为什么呢?
示例4并不能解释这个问题,让我们再来看一段代码。
示例5,形参、实参与同名局部变量的关系
打印的结果是1
,1
,2
,2
。从上面的代码可以看到,参数a
和局部变量a
值是完全相同的,即使是局部变量a
重新定义和赋值之后。这样就好理解了,参数和同名变量之间是 “引用” 关系,也就是说 JavaScript 引擎的处理参数和同名局部变量是都引用同一个内存地址。所以示例5中修改局部变量会影响到arguments
的情况出现。
建议看看 深入理解JavaScript中的作用域和上下文 和 JavaScript 核心概念之作用域和闭包。这篇文章只是上面两篇文章的后续。
- 实例分析 JavaScript 作用域
- 实例分析 JavaScript 词法作用域
- JavaScript 作用域分析
- javascript 作用域链实例
- Javascript函数调用,参数传递,闭包和作用域实例分析
- 实例浅析javascript变量作用域
- JAVASCRIPT作用域链,词法分析
- JavaScript作用域提升问题分析
- Javascript 执行环境,作用域链分析
- 简述 JavaScript 作用域与词法分析
- javascript 学习笔记(二):作用域实例
- 关于JavaScript没有块级作用域的分析
- javascript匿名函数实例分析
- 作用域实例
- javascript的作用域
- javascript中的作用域
- JavaScript中的作用域
- JavaScript 变量作用域
- Java线程池
- @RequestMapping详解
- 超详细React Native实现微信好友/朋友圈分享功能-Android/iOS双平台通用
- C#MD5加密算法的实例
- Typora使用手册和Markdown区别
- 实例分析 JavaScript 作用域
- ThreadLocal源码分析(JDK8)
- 锁(二) 信号量 读写信号量 互斥体
- 5个强大的Java分布式缓存框架推荐
- Linux命令之文件与用户权限
- ubuntu 语句
- php read file
- ajax请求里的contentType: "application/json"作用
- jenkins配置邮箱服务器(126邮箱)