论坛首页 Web前端技术论坛

对[<<javascript征途>>-第四章]中一个结论错误的论证

浏览 5094 次
该帖已经被评为隐藏帖
作者 正文
   发表时间:2010-11-04  

[<<javascript征途>>-第四章 J存在这样的一句话【javaScript 解释器会在预编译期就把函数处理了,而对于匿名函数却视而不见,直到执行期才按表达式逐行进行解释。】

我认为该结论是错误的应该解释为

JavaScript 解释器会在预编译期就把函数处理了及变量声明处理,直到执行期才完成对变量的赋值及表达式的处理

并不是对匿名函数视而不见

  

示例

<script Language="JavaScript">
	 test();//1
	 function test(){
	 		alert( "test1" );
	 }
	  test();//2
	 function test(){
	 		alert( "test2" );
	 }
	  test();//3
</script>

 

按我们以前说的JavaScript是从上到下执行,这样理解的话1的话应该是报错,2的话应该为test1,3的话应该test2。

但实际上我们看到的结果三处都为test2。

为什么会这样呢

第一:JavaScript时按<script></script>编译,并执行的。

 第二:JavaScript编译时完成对变量的声明,及函数处理。

 第三:JavaScript后声明的函数将会覆盖前声明的函数

从上面三条我们可以看出在编译时 第二个test函数将覆盖第一test函数。当执行test时,对于<script>中只存在第二个test的定义,所以三处消息都为test2

     请看如下示例

<script Language="JavaScript">
	 test();//1
	 var test = function( ){
	 		alert( "test1" );
	 }
	  test();//2
	 function test(){
	 		alert( "test2" );
	 }
	  test();//3
</script>

   这样结果会是什么呢。

  结果可能也让你意外,1为test2,2为test1,3为test1。

  这个也可以用上面的第二条解释,对应变量test解释器只会完成相应的声明,而不会完成相应的赋值。所以test 的实际为第二函数test,这也是为什么1弹出test2,当代码执行到var test时,这个时候才完成对变量test赋 值,及将匿名函数赋值给test这样函数2和3为什么会是结果test1呢。

    各位看到这里也没说明<<javascript征途>>中理论的错误,不着急大家可以看如下示例

<script Language="JavaScript">
	 test();//1
	 var test1 = function test ( ){
	 		alert( "test1" );
	 }
	  test1();//2
	 function test(){
	 		alert( "test2" );
	 }
	  test();//3
</script>

 

各位想想test1()会是什么结果,按照<<javascript征途>>中的说明将有名函数test赋值给test1,并在解释阶段完成对test函数表达式的处理。

结果1为test2,2也为test2,3也为test2

但结果确与<<javascript征途>>结论的结果不一致,

真实的结果为 1为test2,2为test1,3为test2

第一个test函数并未在解释阶段完成处理,而是在执行阶段完成处理,并且不能覆盖解释阶段的test的函数定义。所以才有了如上结果。

 

以上是我个人的一点看法,对于JavaScript底层机制还不时挺熟悉,只能结合测试的代码说明这个问题。请各位大虾结合自己的看法发表下意见。

 

   发表时间:2010-11-04   最后修改:2010-11-04
翻了翻ECMAScript edition3 ( 发现这玩艺越读越深,每次都有小惊喜……)

函数定义有两类,函数声明与函数表达式:
FunctionDeclaration :
   function Identifier ( FormalParameterListopt ) { FunctionBody }
FunctionExpression :
   function Identifieropt ( FormalParameterListopt ) { FunctionBody }

可以看到, function A(){} 与 var a = function A(){}是完全不同的,一个是声明,一个是表达式。
(而与var a = function(){}的区别请看附1)

而JS程序的组成为:(附2有分析如何区分声明与表达式)
Program :
   SourceElements
SourceElements :
   SourceElement
   SourceElements SourceElement
SourceElement :
   Statement
   FunctionDeclaration

执行过程为:
1. Process SourceElements for function declarations.
2. Evaluate SourceElements.
3. Return Result(2).

即:先处理函数声明,再处理代码。
var a = function A(){}不属于函数声明,它属于函数表达式(FunctionExpression)

so, 现在回到那句有争议的话,它的问题在于这两点
1. "javaScript 解释器会在预编译期就把函数处理了"  -->  应为"函数声明"
2. 漏掉了既非函数声明,也非匿名函数的表达式: var a = function A(){}



------------------------------------
附1
var a = function A(){} 这类函数表达式的解释执行过程
它并不是函数声明,试试这段代码就知道了:
var a = function Fun(){};
alert(Fun); // 会报错,并无Fun这个变量

ECMA中描述的解释执行过程:
The production  FunctionExpression  :  function  Identifier  ( FormalParameterListopt  )  {  FunctionBody  } is evaluated as follows:
1.  Create a new object as if by the expression new Object().
2.  Add Result(1) to the front of the scope chain.
3.  Create a new Function object as specified in section 13.2 with parameters specified by FormalParameterListopt
and body specified by FunctionBody. Pass in the scope chain of the running execution context as the Scope.
4.  Create a property in the object Result(1). The property's name is Identifier, value is Result(3), and attributes are
{ DontDelete, ReadOnly }.
5.  Remove Result(1) from the front of the scope chain.
6. Return Result(3).
看起来头晕么?我也是到今天才突然明白过来,简单转述一下:
它与不带Identifier的函数表达式的区别在于,1,2,4,5步,临时将scope chain增加了一层(有点类似with语句),只不过这一层是空白对象,然后将Identifier定义在这层scope上。(注:ECMAScript 3rd中是说new Object(),这使得它会受Object.prototype影响,会产生很多问题,很多浏览器并没按这个来做。ECMAScript 5th中已经改掉了)
然后,这个函数存储的[[scope]]能访问到自身的Identifier。
最后,创建函数完毕后,又将增加的这层scope移掉了,对外界来说,这段代码和没有Identifier一样,但对函数内部来说,它能通过Identifier访问自身。

模拟执行:
test(); // statement 1
var test1 = function test ( ){
	alert( "test1" );
}; // statement 2
test1();//statement 3
function test(){
	alert( "test2" );
} // declaration
test();//statement 4

1. 初始状态
scope chain: {global:[]}
2. 处理函数声明
scope chain: {global:[test1:undefined, test:function(){alert("test2")}]}
3. 执行第一句
scope chain: 同上
运行global.test // test2
4. 执行第二句{
   1. 新增对象插入到scope chain
   scope chain: {empty_object:[]}  -> {global:[test1:undefined, test:function(){alert("test2")}]}
   2. 创建函数,以F来称呼它
   F.[[scope]] = {empty_object:[]}  -> {global:...}
   3. 在4.1步中新增的对象上增加test属性
   F.[[scope]] = {empty_object:[test:function(){alert("test1")}]}  -> {global:...}
   scope chain: 同上
   4. 从scope chain中移除4.1插入的对象
   scope chain: {global: ...}
},并赋值给test1
scope chain: {global:[test1:function(){alert("test1")}, test:function(){alert("test2")}]}
5. 第三句
运行global.test1 // test1
6. 第四句
运行global.test // test2

附2
由上面的文法定义得知,程序(或函数体)内容为SourceElements,内含多个并行的SourceElement。
而一个SourceElement由语句(Statement)或函数声明(FunctionDeclaration)组成
注意!再往下没有FunctionDeclaration的递归了,也就是说,只有在程序或函数体根层(因为是并列的SourceElement,没有包含)才算是FunctionDeclaration。
也就是说,如果在if块或其它块中的function xxx(){},并不算是声明,只是表达式罢了。
当然。。。IE总是出人意料,更详细资料看这里:
http://kangax.github.com/nfe/

以上分析涉及到了scope chain相关知识,不懂的请自行查阅资料(google, ECMAScript)
2 请登录后投票
   发表时间:2010-11-04  
clue 写道
翻了翻ECMAScript edition3 ( 发现这玩艺越读越深,每次都有小惊喜……)

函数定义有两类,函数声明与函数表达式:
FunctionDeclaration :
   function Identifier ( FormalParameterListopt ) { FunctionBody }
FunctionExpression :
   function Identifieropt ( FormalParameterListopt ) { FunctionBody }

可以看到, function A(){} 与 var a = function A(){}是完全不同的,一个是声明,一个是表达式。
(而与var a = function(){}的区别请看附1)

而JS程序的组成为:
Program :
   SourceElements
SourceElements :
   SourceElement
   SourceElements SourceElement
SourceElement :
   Statement
   FunctionDeclaration

执行过程为:
1. Process SourceElements for function declarations.
2. Evaluate SourceElements.
3. Return Result(2).

即:先处理函数声明,再处理代码。
var a = function A(){}不属于函数声明,它属于函数表达式(FunctionExpression)

so, 现在回到那句有争议的话,它的问题在于这两点
1. "javaScript 解释器会在预编译期就把函数处理了"  -->  应为"函数声明"
2. 漏掉了既非函数声明,也非匿名函数的表达式: var a = function A(){}



------------------------------------
附1
var a = function A(){} 这类函数表达式的解释执行过程
它并不是函数声明,试试这段代码就知道了:
var a = function Fun(){};
alert(Fun); // 会报错,并无Fun这个变量

ECMA中描述的解释执行过程:
The production  FunctionExpression  :  function  Identifier  ( FormalParameterListopt  )  {  FunctionBody  } is evaluated as follows:
1.  Create a new object as if by the expression new Object().
2.  Add Result(1) to the front of the scope chain.
3.  Create a new Function object as specified in section 13.2 with parameters specified by FormalParameterListopt
and body specified by FunctionBody. Pass in the scope chain of the running execution context as the Scope.
4.  Create a property in the object Result(1). The property's name is Identifier, value is Result(3), and attributes are
{ DontDelete, ReadOnly }.
5.  Remove Result(1) from the front of the scope chain.
6. Return Result(3).
看起来头晕么?我也是到今天才突然明白过来,简单转述一下:
它与不带Identifier的函数表达式的区别在于,1,2,4,5步,临时将scope chain增加了一层(有点类似with语句),只不过这一层是空白对象,然后将Identifier定义在这层scope上。
然后,这个函数存储的[[scope]]能访问到自身的Identifier。
最后,创建函数完毕后,又将增加的这层scope移掉了,对外界来说,这段代码和没有Identifier一样,但对函数内部来说,它能通过Identifier访问自身。

模拟执行:
test(); // statement 1
var test1 = function test ( ){
	alert( "test1" );
}; // statement 2
test1();//statement 3
function test(){
	alert( "test2" );
} // declaration
test();//statement 4

1. 初始状态
scope chain: {global:[]}
2. 处理函数声明
scope chain: {global:[test1:undefined, test:function(){alert("test2")}]}
3. 执行第一句
scope chain: 同上
运行global.test // test2
4. 执行第二句{
   1. 新增对象插入到scope chain
   scope chain: {empty_object:[]}  -> {global:[test1:undefined, test:function(){alert("test2")}]}
   2. 创建函数,以F来称呼它
   F.[[scope]] = {empty_object:[]}  -> {global:...}
   3. 在4.1步中新增的对象上增加test属性
   F.[[scope]] = {empty_object:[test:function(){alert("test1")}]}  -> {global:...}
   scope chain: 同上
   4. 从scope chain中移除4.1插入的对象
   scope chain: {global: ...}
},并赋值给test1
scope chain: {global:[test1:function(){alert("test1")}, test:function(){alert("test2")}]}
5. 第三句
运行global.test1 // test1
6. 第四句
运行global.test // test2

以上分析涉及到了scope chain相关知识,不懂的请自行查阅资料(google, ECMAScript)

 

很感谢clue的解释

尤其对于函数表达式及函数定义概念和对于函数表达式作用域链的解释。

不过一下代码在IE与Firefox存在差异,

<script Language="JavaScript">
	 var test1 = function test ( ){
	 		alert( "test1" );
	 }
	 alert( test );
	  test();
         window.test();
</script>

 这个代码在IE中并未提示错误,alert() 与test函数,window.test(),运行正常,

在Firefox存在错误也就是未定义。

我在IE下调试的这样一直以为test作用域处于widow下,所以对于

test(); // statement 1
var test1 = function test ( ){
	alert( "test1" );
}; // statement 2
test1();//statement 3
function test(){
	alert( "test2" );
} // declaration
test();//statement 4

这个的执行结果有点迷惑,尤其《JavaScript征途》那句话的更让我迷惑。

看到clue的解释尤其英文中关于函数表达式的作用域的说明才让我明白。

 

0 请登录后投票
   发表时间:2010-11-04  
80197675 写道

很感谢clue的解释

尤其对于函数表达式及函数定义概念和对于函数表达式作用域链的解释。

不过一下代码在IE与Firefox存在差异,

<script Language="JavaScript">
	 var test1 = function test ( ){
	 		alert( "test1" );
	 }
	 alert( test );
	  test();
         window.test();
</script>

 这个代码在IE中并未提示错误,alert() 与test函数,window.test(),运行正常,

在Firefox存在错误也就是未定义。

我在IE下调试的这样一直以为test作用域处于widow下,所以对于

test(); // statement 1
var test1 = function test ( ){
	alert( "test1" );
}; // statement 2
test1();//statement 3
function test(){
	alert( "test2" );
} // declaration
test();//statement 4

这个的执行结果有点迷惑,尤其《JavaScript征途》那句话的更让我迷惑。

看到clue的解释尤其英文中关于函数表达式的作用域的说明才让我明白。

 

如果IE下能访问到非匿名函数表达式的Identifier,那表明IE对这种表达式(var a = function Fun(){})的解释执行与ECMAScript标准不符,这里找到了一篇文章非常详细地分析了这玩艺:

Named function expressions demystified (JScript bugs讲述了IE的bug)

 

摘选这篇分析的结果:

IE下,所有function xxx(){}这类表达式,都会进行预定义(与标准严重不符,包括赋值语句及其它块中的也处理了)

如果按ECMA标准应该算是FunctionExpression的带Identifier的函数语句,IE即当声明又当表达式,并且执行双份:

var a = function b(){
   alert("test");
};
// 按标准,此处应出错,因为b未定义。
alert(a === b); // IE下为false... IE sucks

 它可以看作类似于以下代码:

function b(){
   alert("test");
}
var a = function(){
   alert("test");
};
alert(a === b);
 

 

对此。。。我的看法是:这些东西太低层了,各个浏览器差异还真不小,理解就好了,不用太过于专注。

尽量不要用带名称的函数表达式,函数声明放在最外层。

 

最后再来一句: IE sucks

2 请登录后投票
   发表时间:2010-11-04  
clue 写道

如果IE下能访问到非匿名函数表达式的Identifier,那表明IE对这种表达式(var a = function Fun(){})的解释执行与ECMAScript标准不符,这里找到了一篇文章非常详细地分析了这玩艺:

Named function expressions demystified (JScript bugs讲述了IE的bug)

 

摘选这篇分析的结果:

IE下,所有function xxx(){}这类表达式,都会进行预定义(与标准严重不符,包括赋值语句及其它块中的也处理了)

如果按ECMA标准应该算是FunctionExpression的带Identifier的函数语句,IE即当声明又当表达式,并且执行双份:

var a = function b(){
   alert("test");
};
// 按标准,此处应出错,因为b未定义。
alert(a === b); // IE下为false... IE sucks

 它可以看作类似于以下代码:

function b(){
   alert("test");
}
var a = function(){
   alert("test");
};
alert(a === b);
 

 

对此。。。我的看法是:这些东西太低层了,各个浏览器差异还真不小,理解就好了,不用太过于专注。

尽量不要用带名称的函数表达式,函数声明放在最外层。

 

最后再来一句: IE sucks


  clue 你也太厉害呢,全是英文材料,膜拜中!!!!!!!!!!!!!!!!!!!!!

  现在在整理JavaScript,发现很多的概念【很多的概念在网上解释的也不正确】及代码书写模式以前都没见过。

  以后有问题向clue请教。

0 请登录后投票
   发表时间:2010-11-05  
非常好的讨论,受益匪浅,特别是对es3的解读,感谢楼主和clue。
0 请登录后投票
   发表时间:2010-11-05  
好帖就应该是这样的
0 请登录后投票
   发表时间:2010-11-06  
我想书的作者应该看到了吧  上来也讨论下子呢 哈哈
0 请登录后投票
   发表时间:2010-11-08  
都是高人啊。该书的作者在51js上争论的很激烈啊!不承认自己有问题,大家可以去搜一下,真是很激烈哦。
0 请登录后投票
   发表时间:2010-11-09  
还真是卧虎藏龙,牛人不要老潜水啊
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics