js函数作用域和对象作用域里变量的不同

2013-3-3

写js时碰到一个坑,查了半天简化后问题是这样的:

<script>
  alert(document) //[object HTMLDocument]
  var document
</script>
<script>
  (function(){
    alert(document) //undefined
    var document
  })()
</script>

在全局作用域上document没变,但在函数作用域上执行后document变undefined了。来看看这两种情况下都发生了什么事。

函数作用域

在函数作用域上这样的结果虽然难发现,但容易理解,var是声明局部变量,js在函数执行之前会把作用域内所有用var声明抽到头部统一生成,就是说

(function(){
  alert(document)
  var document
})
//等价于
(function(){
  var document
  alert(document)
})

在执行alert(document)访问document变量时,先从当前作用域寻找这个变量,如果当前作用域没有这个变量,会通过作用域链一层层往上寻找。若没有var document,就会找到上层的window.document。在这里当前作用域声明了document这个变量,优先访问这个变量,因为这个变量只声明未赋值,此时document==undefined。

在函数作用域下是这样的,但全局作用域下就不同了。

对象作用域

先看看raphelguo提供的的例子:

var obj = {a : 1}
with (obj) {
  var a = 2
}
alert(obj.a) //2
with (obj) {
  a = 5
}
alert(obj.a) //5

可以看到在obj作用域下,var变量声明已经失去了作用,无论有没有用var声明变量,对变量赋值都是等价于对当前obj的属性赋值。换句话说var是用来声明局部变量的,但在对象作用域内并没有地方存储局部变量,所以var是无效的。

再回来看看最初的例子,就很容易理解了,浏览器上在全局作用域下等价于在window这个对象下执行代码:

with(window) {
  alert(document)
  var document
}

这时var document完全无作用,alert出来的自然是window.document了。

看来,js的变量不是存在函数里,就是存在对象(包括宿主对象如window)里,要区分这两种情况的不同避免遇到坑。

分类:技术文章 Tags:
评论

*

*

2013年3月8日 19:02

关于with statement的例子,我猜也是scope chain的问题。执行期间生成的系列VariableEnvironment应该是这样的顺序:obj的,然后是with自己的,最后是global的。所以不管有没有声明变量,都会最先在obj的VariableEnvironment里找到a这个identifier。而如果有非同名的变量被声明,则是存储在with自己的VariableEnvironment里面。下面的例子可以说明:

var person = {
name: ‘Sun’,
person: ‘another person’
}
with(person) {
var name;
console.log(name); //Sun
var _inner = ‘_inner’;
console.log(_inner); //_inner
console.log(person); //another person
delete person;
console.log(person); //{ name: ‘Sun’ }
}

也许需要再进一步确认是不是scope chain是这样的。

2013年3月9日 21:26

这里的with主要是延长了块级作用域.

2013年3月12日 23:30

好像我就是raphealguo的样子。
感觉讲的有点不是很严谨。
例如,function(){ var a;} 你不能说a变量是属于某个对象的,它应该是属于这个函数运行时的作用域。
至于在寻找变量的过程,应该是混着作用域跟对象的“槽”(貌似原型语言把key:value当做一个槽)一起寻找,而有它自己的优先级,这个不了解,还得看js规范了。

2013年4月2日 1:38

bang哥,这个问题在最新的犀牛书上面有说的哦,就是淘宝翻译的那本

2013年4月2日 15:17

咁啊,没看过这货~

2014年3月6日 17:26

文中有几处理解错误:
1、首先with那个例子,根本原因是with 语句执行时,with里面的对象插入在当前作用域链的顶端,即with语句执行时的作用域链是obj(with)+with之前的作用域链,所以先在with的对象里面查找变量。浏览器上在全局作用域下本质上不等价于with那种情形;
2、
alert(document) //[object HTMLDocument]
var document

这个根本原因是,在全局执行上下文时,在变量对象VO(此时的VO就是全局对象)初始化之后,VO(global)已经存在了一个document的属性了,是浏览器初始化的,所以你在var document进行变量声明的时候,因为VO(global)已经存在了这个属性,就不会把这个声明的变量添加大VO中了,所以声明var document;就没什么效果;你可以测试
console.log(document,a);
//VO中已经存在了同名的属性,所以document声明无效
var document={a:2},a={a:2};
console.log(document,a);
//window.document 属性不可修改

不过可能document是全局对象window的不可写属性,所以修改document也是无效的。

2016年3月1日 15:52

业精于勤而荒于嬉,行成于思而毁于随。

Baidu
sogou