了解作用域对于编写代码至关重要,作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。对于 JavaScript 中作用域我们可能已经了解了很多。建议看看 深入理解 JavaScript 中的作用域和上下文 和 JavaScript 核心概念之作用域和闭包。今天从其他方面介绍一下 JavaScript 中作用域,以帮助我们更好的完整的了解 JavaScript 作用域。
作用域有两种常见的模型:词法作用域 (Lexical Scope,通常也叫做 静态作用域) 和 动态作用域 (Dynamic Scope)。其中词法作用域更常见,被 JavaScript 等大多数语言采用。(愚人码头注:这里避开了
和
- with
特殊语句,不再做介绍)。
- eval
首先了解一下这两种模型的说明:
如果一个文档流中包含多个 script 代码段(用 script 标签分隔的 js 代码或引入的 js 文件),它们的运行顺序是:
变量和
- var
定义做 "预解析"(永远不会报错的,因为只解析正确的声明)
- function
JavaScript 中每个函数都都表示为一个函数对象(函数实例),函数对象有一个仅供 JavaScript 引擎使用的
属性。通过语法分析和预解析,将
- [[scope]]
属性指向函数定义时作用域中的所有对象集合。这个集合被称为函数的作用域链(scope chain),包含函数定义时作用域中所有可访问的数据。
- [[scope]]
执行具体的某个函数时,JS 引擎在执行每个函数实例时,都会创建一个执行期上下文(Execution Context)和激活对象(active Object)(它们属于宿主对象,与函数实例执行的生命周期保持一致,也就是函数执行完成,这些对象也就被销毁了,闭包例外。)
执行期上下文(Execution Context)定义了一个函数正在执行时的作用域环境。它使用函数
属性进行初始化。
- [[scope]]
随后,执行期上下文 顶部 的会创建一个激活对象(active Object),这个激活对象保存了函数中的所有形参,实参,局部变量,
指针等函数执行时函数内部的数据情况。这个时候激活对象中的那些属性并没有被赋值,执行函数内的赋值语句,这才会对变量集合中的变量进行赋值处理。也就是说 激活对象是一个可变对象,里面的数据随着函数执行时的数据变化而变化。
- this
具体请查看 JavaScript 核心概念之作用域和闭包 中的解释。有点啰嗦了。
考虑一下下图中的代码:
分析过程:
;
- foo
函数的作用域,包含变量
- foo
,
- a
,
- bar
- b
函数的作用域,包含变量
- 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,作用域中变量查找规则:
- function DoSomething()
- {
- var a = 2;
- console.log(a); // 2
- console.log(window.a); // 1
- }
- var a = 1;
- DoSomething();
这个很好理解,全局作用域中定义并赋值了变量
,
- a
函数中也定义并赋值了变量
- DoSomething
。
- a
函数在执行时,首先找到的是激活对象中的变量
- DoSomething
,也就是
- a
。
- 2
也很好理解,就是首先找
- console.log(window.a)
对象,在激活对象中没找到,继续向上搜索,本例中,
- window
对象在 Global Object(全局对象) 中找到,
- window
的
- window
属性值为
- a
。
- 1
这段代码我相信对大家来说,理解起来都没问题。那么我们稍微对代码做一下修改:
示例 2,作用域中没赋值的变量:
- function DoSomething()
- {
- var a; // 注意这一行,定义了一个变量 a 但是不赋值。
- console.log(a); // 'undefined'
- }
- var a = 1;
- DoSomething();
函数中定义了变量
- DoSomething
,但是没有赋值,那么这里打印结果是什么呢?结果是
- a
,原因和示例 1 一样,只不过
- undefined
函数在执行时,首先找到的是激活对象中的变量
- DoSomething
,只不过这里的变量
- a
并没有赋值,所以是
- a
。
- undefined
示例 3,激活对象是一个可变对象
- function DoSomething()
- {
- console.log(a); // 'undefined'
- var a = 2; // 注意这一行。
- console.log(a); // 2
- }
- var a = 1;
- DoSomething();
根据作用域中变量查找规则,当前的激活对象中找到了有变量
的定义,执行到第一条
- a
语句时值为
- console.log(a)
。执行到第二条
- undefined
语句时,变量
- console.log(a)
已经被赋值为
- a
。也就是说,激活对象是一个可变对象,里面的数据随着函数执行时的数据变化而变化。
- 2
上面 3 个示例讲了不同作用域中的同名变量,这里我们来讲讲 形参(parameters)、实参(arguments)与同名变量的关系。
首先了解一下形参和实参:
- function one(a,b,c) {
- console.log(one.length);//形参数量
- }
- function two(a,b,c,d,e,f,g){
- console.log(arguments.length);//实参数量
- }
- one(1)
- two(1)
顾名思义,形参就是形式参数,是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传递的参数。实参就是实际参数,是在调用时传递给函数的参数,即传递给被调用函数的值。实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。
那么形参、实参与变量同名又该如何呢?
示例 4,形参、实参与同名全局变量的关系
- // DoSomething传入了一个 a 参数。
- var b = 2;
- function DoSomething(a,b)
- {
- var a;
- console.log(a); // 1
- console.log(b); // 'undefined'
- }
- DoSomething( 1 );
打印的结果为
和
- 1
,为什么呢?
- undefined
首先来看形参
和全局变量
- b
,这两者毫无关系,只是同名。完全符合作用域中变量查找规则,
- b
函数在执行时,首先找到的是激活对象中的形参
- DoSomething
,形参
- b
没有值,所以是
- b
。
- undefined
接着来说说 参数
和局部变量
- a
,看上面的代码似乎并不符合作用域中变量查找规则。如果作用域中变量查找规则去理解,应该是这样的,
- a
函数在执行时,首先找到的是激活对象中的参数
- DoSomething
,然后定义局部变量
- a
,不赋值,所以
- a
是
- console.log(a)
。但是结果恰恰是
- undefined
。这又是为什么呢?
- 1
示例 4 并不能解释这个问题,让我们再来看一段代码。
示例 5,形参、实参与同名局部变量的关系
- function DoSomething(a)
- {
- console.log(a); // 1
- console.log(arguments[0]); // 1
- var a = 2;
- console.log(a); // 2
- console.log(arguments[0]); // 2
- }
- DoSomething( 1 );
打印的结果是
,
- 1
,
- 1
,
- 2
。从上面的代码可以看到,参数
- 2
和局部变量
- a
值是完全相同的,即使是局部变量
- a
重新定义和赋值之后。这样就好理解了,参数和同名变量之间是 "引用" 关系,也就是说 JavaScript 引擎的处理参数和同名局部变量是都引用同一个内存地址。所以示例 5 中修改局部变量会影响到
- a
的情况出现。
- arguments
建议看看 深入理解 JavaScript 中的作用域和上下文 和 JavaScript 核心概念之作用域和闭包。这篇文章只是上面两篇文章的后续。
来源: http://www.css88.com/archives/7300