aimingoo的专栏

.F{color:red}aimingoo

周爱民ID:aimingoo
386762次访问,排名131好友24人,关注者68
aimingoo的文章
原创 116 篇
翻译 0 篇
转载 0 篇
评论 666 篇
aimingoo的公告
新书出版:
china-pub在线购买
相关评论和文章

其它:
 相关评论和文章
 相关评论和文章
最近评论
popcoder:哇,赞
aimingoo:fenggood,谢谢提醒。我已经传了一份在csdn download上了:

http://download.csdn.net/source/591452
fenggood:您给的上面 behind_the_curtain.zip
这个文件的地址下载不了,能不能上传到 CSDN 上来呀?
fenggood:
您给的下面的地址下不了,能不能传到 CSDN到上来呀?

《大道至简》幕后故事的全文PDF下载地址:
http://aimingoo.delphibbs.com/aimingoo/behind_the_curtain.zip
 或
http://groups.google.com/group/qomo/web/behind_the……
hax:可能我表达有问题。我重新总结了一篇:http://hax.javaeye.com/blog/230182

函数声明虽然是语法解析期处理,但是函数对象的生成却是在运行时的。即使是一个函数声明,函数对象也是在进入execution context时产生的,与函数表达式的差别是,它仅在execution context的一开始时生成一次。性能测试当然无法验证非运行时的问题,但……
文章分类
收藏
    相册
    旅游
    我、joy与朋友们
    其它
    Hello World!
    ZDNet China软件技术专区
    我的链接
    aimingoo's 网上空地
    我的Delphi项目资源
    麦秸的垛
    我的朋友们
    kiki-玩java的国际游人
    Margaret
    叶卡-Online
    左左-网行者
    老孟-孟岩的孟
    存档
    订阅我的博客
    XML聚合  FeedSky

    原创 对JavaScript的eval()中使用函数的进一步讨论~收藏

    新一篇: JavaScript全局优化带来的负面效果…… | 旧一篇: 标题党的进步:道字大旗不再扯,美为号召又开张

    《JavaScript语言精髓与编程实践》的读者I22141提出了一问题:为什么下面这段代码在JScript
    和SpiderMonkey中表现不一样:
    --------
    var func = eval("(function(){})");
    alert(typeof func);
    --------
    更进一步的问题是,书中对匿名和具名函数在JScript与SpiderMonkey中的表现解释得不够
    清楚。好的,这篇文章就这个问题深入讨论,不单涉及书中的内容,也更深入地讲述一
    下JS的解释与执行过程——其实所有的内容在书中都有涉及,但过于分散,不便于专门
    地来分析一个具体问题。

    首先,应该明确表达式与语句。对于JS来说,eval()总是试图执行一个语句,因此它必须
    先将执行文本理解为语句。如下:
    --------
    eval("1")
    --------
    在JS看来,由于eval()必须执行语句,因此"1"不再是直接量表达式,而是直接量表达式语
    句,也就是相当于“1;”。这些内容,在“5.2.2  动态执行过程中的语句、表达式与值”
    中有详细解释。

    所以,eval()的返回值,其实是语句最后一个(有效的)子句的返回值。接下来,我们需
    要了解“声明语句”和“表达式”。例如:
    --------
    function x() {
      //....
    }
    --------
    很明显,这是一个具名函数的“声明语句”。注意的是,“声明语句”是不返回值的。也
    就是说,声明语句是在语法解释期,由预编译器处理的,而在执行期它是没意义的——没
    有值,也没有返回值。例如单纯的“var X”,是一个声明语句,它就不会返回值,而对于
    “var X=100”来说,JS就处理成一个声明语句,和一个在执行期的赋值语句,它就有返回
    值(后者的值)。

    上面的规则对于JScript和SpiderMonkey来说都是一样的,这没有区别。有区别的是接下来的
    内容。首先,SpiderMonkey承认“函数表达式(function expression)”,为了直接这样一种
    特性,它约在“函数表达式”中出现的“函数名”是无效的。因为“函数名”是“声明语
    句”来陈述的,而“表达式”是比语句更小(或更低级)的一个级别,因此不可能在“表
    达式”中出现“语句声明”,所以只好在表达式中忽略函数名。这样一来,SpiderMonkey中
    下面语句:
    --------
    x = "1234" + (function X() {});
    ------
    中函数X就没有标识符的效果,它对表达式之外的、或者全局的“标识符”都不会构成影
    响。更进一步地说:
    --------
    var X = 100;
    x = "1234" + (function X() {});
    ------
    在这样两行代码中,变量X不会被重写,因为第二行中的函数名X是无效的。关于这些内
    容,在书中“5.4.2.1  语法声明与语句含义不一致的问题”有详细解释。

    正是在上面这个小节中,还讨论到了MS JScript对这个问题的处理。JScript承认在代码内文
    的任意位置出现的函数标识符声明。也就是说,由于上面的标识符是有效的,所以全局变
    量中的“X”就会被重写。但是,正是由于这个缘故,JScript就必须对下面这个问题做解释:
    ------
    eval("(function(){})");
    eval("(function X(){})");
    ------
    请问:这两行代码在语义上有没有不同?由于SpiderMonkey承认函数表达式,因此把两个
    都解释为表达式的运算元;而JScript要承认第二行代码中的变量名X,因此只好两个都解释
    为语句。也就是说,在缺省情况下,JScript认为第一行是匿名函数“声明语句”,第二行则
    是具名函数声明语句。因此,如同前面所说的,“声明语句”不返回值,所以在JScript中两
    行代码都返回undefined,而且第二行代码声明了一个变量名X。

    关于这个问题,我在脚注中说“函数声明的语句含义”的确是有些含糊的。无论如何,只需
    要简单地理解为JScript认为这里是“函数语句声明”,而SpiderMonkey认为这里是“函数表达
    式声明”就可以了。

    好。到这里为止,我们大概只解释清楚了:
    ------
    eval("(function(){})");
    eval("(function X(){})");
    ------
    这两行语句的效果,以及产生这种效果的原因。但是,对于我在书上的例子和脚注说明,仍
    然是有疑问的。这来源于这段文字和代码:
    ------
    不过在JScript中存在一个例外:函数直接量(这里指匿名函数)不能通过这种方式来获得。例如下面的代码:
    var func = eval("(function() { })");
    // 输出"undefined"
    alert(typeof func);
    这种情况下,可以具名函数来得到它(*)。例如:………………
    ------
    先留意这段文字是上下文相关的。我只是想说明如何能向变量"func"赋一个有效的值。这涉
    及到两种方法:
    ------
    // 方法1,用匿名函数
    var func = eval("(function() { })");
    alert(typeof func);

    // 方法2,用具名函数
    var func;
    eval("function func() { }");
    alert(typeof func);
    ------
    这段话的意思是“在JScript中使用方法1(用匿名函数的方法)是不行的,需要使用第二种”,
    而在脚注中,说SpiderMonkey正好相反,也仅指这个例子而言——在SpiderMonkey中,第
    二种方法是无效的,而第一种是有效的。

    这里与前述的内容稍有差异的是,方法2并没有使用“返回值”,而只是通过eval()语句中
    利用函数名声明的效果,来影响全局变量func。而正是由于SpiderMonkey不承认这个声明的
    标识符,所以是无效的。

    同样的原因,读者I22141说修改成下面这样:
    ------
    var func = eval("function func() { }");
    ------
    在SpiderMonkey中则是利用了返回值,与上面这个示例已经不是同一个问题了。所以I22141
    所问“(那么你所说的在SpiderMonkey中可以用eval()返回一个匿名函数,而对具名函数却只
    能返回undefined是什么含义?)”,也是脱离了这个示例的一个设问。

    最后再来讲述一个细节问题,这在书上也未有提及,其实也是一个很怪异的事件。首先,在
    上面我说,在JScript中:
    --------
    // 第一种情况
    var func = eval("(function(){})");
    alert(typeof func); // 显示"undefined"
    --------
    这里显示undefined是因为JScript将后面的函数解释为匿名函数声明,所以没有值。其实是相对
    要牵强一些的。因为我们修改一下:
    --------
    // 第二种情况
    var func = eval("(1, function(){})");
    alert(typeof func);  // 显示"function"
    --------
    就不同了。那么到底为什么第一种情况下,JScript就一定是理解为“声明语句”而不是表达
    式呢?我不得确知。我只是从:
    --------
    // 第三种情况
    var X;
    eval("(function X(){})");
    alert(typeof X); // 显示"function"
    --------
    这种情况下存在“语句声明”的效果来推断第一种情况的。更为奇特的是,我不清楚为什
    么JScript容许在一个表达式运算符“(...)”中存在一个“语句”——因为理论上说,这种情
    况是在语法分析中难于解释的。我甚至在“5.4.2.1  语法声明与语句含义不一致的问题”提
    到下面的代码:
    --------
    // 示例5:语法声明阶段重写
    // 重写
    (function Object(){
    }).prototype.value = 100;

    // 显示值undefined
    var obj = new Object();
    alert(obj.value);
    --------
    是因为执行期“(X).prototype.value”中的那个函数X,与语句分析期覆盖了全局的Object标识
    符的函数,不是同一个。但,我仍然不明白,为什么JScript允许在表达式中存在声明语句。
    与此更为相悖的是,如果要承认这样的假设,那么为什么下面的语句不能执行:
    --------
    (var x=100);
    --------
    然而如果不承认,那么下面的代码却又能正常执行:
    --------
    (function X() {});
    --------
    OH... 在这个问题的最终答案上,我仍然是迷惑的。

    发表于 @ 2008年07月22日 20:29:00|评论(loading...)|编辑|收藏

    新一篇: JavaScript全局优化带来的负面效果…… | 旧一篇: 标题党的进步:道字大旗不再扯,美为号召又开张

    评论

    #hotplum 发表于2008-07-23 09:38:30  IP: 221.221.144.*
    呵呵!
    我感觉JavaScript就没有一般意义上的"声明"~
    比如Delphi的声明在语法分析和编译阶段都是有意义的!
    而JavaScript对var 或function 处理的很简单:
    比如var a;
    其实相当于 var a = undifined;
    在执行对象的上下文中初始化了一个变量a,类型是undifined,值也是undifined
    而 function b() {};
    也是在执行对象的上下文中初始化了一个变量b,只是变量b的类型是function,值就是function b() {}
    比如上面的:
    var X = (function X() {});
    在JScript中 相当于:
    先初始化了一个变量X是一个function 然后就是
    var X= X; 了
    这样 var a=(function X() {}); //括号可以忽略
    就是 var a = X; 了

    至于var a = (var b = 100); //括号可以忽略
    var a = var b = 100; 语法解析器认为你是想
    var a = b =100; 显然 第二个var是多于的!
    上面 a 和 b 都是number
    如果a和b是function呢?
    就是这样
    var a = b = function (){};
    是不是可以得出这样的结论
    var 关键字是在运行上下文初始化一个变量(六种类型)
    而function x (){};
    等价于
    var x = function x(){};
    初始化一个function类型的变量,值为function x(){};
    这样看下面的例子
    var a = b = function c(){alert("do")};
    alert(typeof a);
    alert(typeof b);
    alert(typeof c);
    在IE中,先是按照 var c = function c(){}; 理解
    然后是var a = b =c;

    而mozilla中认为 是这样的 var b = function c(){alert("do")};
    在b作为一个function类型效果上 b到底是"function c(){alert("do")}"
    还是"function (){alert("do")}" 没什么区别的(只是toString不同)!
    IE和Firefox谁的理解对,好像也没有什么影响!

    正在拜读爱民的大作,胡乱瞎扯几句....









    #hotplum 发表于2008-07-23 10:01:07  IP: 221.221.144.*
    另外请教个问题:
    在书中P31页的例子中(关于引用还是值传递)
    var str = 'abcde';
    var obj = new String(str);

    function newToString() {
    return 'hello,world!';
    }

    function func(val){
    val.toString = newToString;
    }

    func(str);
    alert(str);
    func(obj);
    alert(obj);

    如果改成alert(obj+"") ;
    如何理解,是不是在
    alert(obj);
    相当于alert(obj.toString());
    alert(obj+"");
    相对于 alert(obj.valueOf() +"");








    #aimingoo 发表于2008-07-23 13:08:19  IP: 221.220.187.*
    先说后面这个问题。对于“+”这个运算符,JS有些特殊的地方。这条规则在“5.3.7.1 运算导致的类型转换”中有讲:
    ----
    仅对两个运算元的“+”运算符来说,JavaScript设定的规则是:当运算元中有任意一个是字符串时,运算符视为“字符串连接”。
    ----

    由于上一规则的存在,而你的示例的第二个远算元是字符串直接量,所以它变成了“字符串连接”。由此,JS
    实际调用的代码是这样:

    if (isStrType(v1) || isStrType(v2)) {
    return getTypePrototype(v1).toString.apply(v1) + getTypePrototype(v2).toString.apply(v2)
    }

    上面的getTypePrototype(X).toString.apply()是表
    明用系统对指定运算元类型的、原型上的toString()方法来转换。
    #aimingoo 发表于2008-07-23 13:10:55  IP: 221.220.187.*
    对于第一个问题,涉及到你对VAR这个语法关键词的理解。这个问题,在“2.4.1.2 赋值语句与变量声明语句”有详细地讲述,是分语法分析期和执行期两个部分来实现的。
    #ljlsunny 发表于2008-07-23 15:08:46  IP: 219.142.61.*
    eval()函数还有一个功能就是将一个xml文档,转换为一个对象
    #trarck 发表于2008-07-24 11:20:16  IP: 116.228.27.*
    var temp=function mytemp(){
    alert("temp");
    }
    temp.prototype.vv=123;
    mytemp.prototype.vv=100;

    alert(typeof(temp));
    alert(typeof(mytemp));

    var oo=new temp();
    var myo=new mytemp();

    alert(oo.vv);
    alert(myo.vv);

    temp();
    mytemp();
    #trarck 发表于2008-07-24 11:22:03  IP: 116.228.27.*
    对与这个
    // 示例5:语法声明阶段重写
    // 重写
    (function Object(){
    }).prototype.value = 100;

    // 显示值undefined
    var obj = new Object();
    alert(obj.value);

    我是这么理解的
    var temp=function mytemp(){
    alert("temp");
    }
    temp.prototype.vv=123;
    mytemp.prototype.vv=100;

    alert(typeof(temp));
    alert(typeof(mytemp));

    var oo=new temp();
    var myo=new mytemp();

    alert(oo.vv);
    alert(myo.vv);

    temp();
    mytemp();
    #tantaiyizu 发表于2008-07-25 14:21:29  IP: 125.34.41.*
    你们都在说些什么呢?
    #hax 发表于2008-08-19 14:01:04  IP: 207.179.28.*
    这个eval中function的问题,归根到底,是JScript如何识别一个function表达式。按照规范,单独的(function(){})就是一个表达式,但是遗憾的是对于JScript来说,括号还不够,一定得在语法分析期望一个表达式时,比如简单的逗号运算符,才会被认为是表达式。

    尤其值得注意一点,JScript 5.0到5.5的时候,对function声明和function表达式的行为做了很大的修订,这可能是上述奇怪表现的源头。我手头没有JScript 5.0,所以无法做进一步的确认。

    不过可以试着执行 javascript:alert(eval('0,function f1(){}')==f1) ,你会发现返回 false。。。也就是说function表达式产生了一个不同于function声明的结果,换言之这里产生了两个不同的函数(在JScript 5.5之前则应该是一个函数)。

    另一个例子是:javascript:alert(eval('(function f1(){return arguments.callee==f1})()'));alert(f1())
    返回结果false和true。

    注意这里并不是同一个函数绑定到两个symbol,而真的是两个函数,其实际执行效果其实也是不同的(虽然这里这个例子无法表现出来,但是如果加上with语句就会有差别了)。

    产生这个用于函数表达式的函数,和函数声明所产生的函数一样,需要不少资源。对此,我写了一个测试:

    var MAX = 1000000

    function timer(f) {
    var start = new Date().getTime()
    for (var i = 0; i < MAX; i++) {
    f()
    }
    var end = new Date().getTime()
    WScript.Echo(end - start)
    }

    timer(test0)
    timer(test1)
    timer(test2)
    timer(test3)
    timer(test4)
    timer(test5)
    timer(test6)
    timer(test7)

    function test0 () {
    // do nothing
    }

    function test1 () {
    // func decl
    function f() {}
    }

    function test2 () {
    // func decl or exp?
    (function f() {})
    }

    function test3 () {
    // two functions
    function f1() {}
    (function f2() {})
    }

    function test4 () {
    // two references point to one function
    #hax 发表于2008-08-19 14:02:10  IP: 207.179.28.*
    function test4 () {
    // two references point to one function
    var f1, f2
    f1 = f2 = function () {}
    }

    function test5 () {
    // still one function
    var f1 = f2
    function f2() {}
    }

    function test6 () {
    // actually two different functions!
    var f2 = function f1() {}
    }

    function test7 () {
    // two functions
    var f1 = function () {}
    function f2() {}
    }

    执行结果大体如下:
    D:\>cscript test-func-benchmark.js
    Microsoft (R) Windows Script Host Version 5.7
    Copyright (C) Microsoft Corporation. All rights reserved.

    2704
    7593
    7563
    10047
    7828
    7735
    10079
    10078

    这个测试从侧面再一次证明了诸如var x=function f(){}这样的语句,实际会产生两个不同的函数。

    因此可以大胆猜想,在JScript 5.5做出这一改动的时候,JScript团队可能认为,从性能优化的角度考虑,应该避免在无必要的时候产生这一额外的用于函数表达式的函数。所以,对于没有任何变量来接收函数表达式的时候,函数表达式的结果就被抛弃了,不幸的是,这也包括仅仅有括号的时候。
    #hax 发表于2008-08-19 14:30:57  IP: 207.179.28.*
    从另一个方面说,这也解释了你对于何时才是声明语句的疑惑。因为对于function的解析JScript其实并不是遵循标准来的,所以不能按照标准对于函数声明/函数表达式的划分来解释JScript的问题。
    JScript现在的表现实际上是最初不符合标准的设计加上JScript 5.5所打的patch的结合体。
    按照我的猜测,最初的JScript可能是这样处理的:任何函数结构一律按照函数声明提前处理(所以总是产生变量绑定),函数表达式则被替换为函数对象的引用。
    后来JScript 5.5为了改善对于with的处理而加入了真正的函数表达式实现,本来它可以完全fix这个问题。但是估计是为了向前兼容,而保留了原先一律产生变量绑定的做法。结果就是一个语句产生两份函数对象这样微妙的结果。
    #hax 发表于2008-08-19 14:48:37  IP: 207.179.28.*
    进一步,“那么到底为什么第一种情况下,JScript就一定是理解为“声明语句”而不是表达式呢?”

    其实第一种情况(“(function (){})”)既不是声明语句也不是表达式(严格的说是既没有函数声明的效果,也没有函数表达式的效果),实际上这里什么也没执行,这个代码被JScript抛弃掉了(或者说这里是一个被优化掉了的函数表达式)。这一点可以通过类似我上面的性能测试证明(即包含这个语句的函数其执行时间与一个空函数是一样的)。
    #aimingoo 发表于2008-08-19 18:15:05  IP: 125.33.149.*
    很努力地了解你的想法。但最后一个,你的性能测试并不能表明这个函数声明被抛弃掉。因为函数声明是语法解析期处理的,正好不占用执行时间。
    #hax 发表于2008-08-20 16:15:09  IP: 207.179.28.*
    可能我表达有问题。我重新总结了一篇:http://hax.javaeye.com/blog/230182

    函数声明虽然是语法解析期处理,但是函数对象的生成却是在运行时的。即使是一个函数声明,函数对象也是在进入execution context时产生的,与函数表达式的差别是,它仅在execution context的一开始时生成一次。性能测试当然无法验证非运行时的问题,但是可以表明(function (){...})语句并没有产生对应的函数对象。相反的,如果是 void function () {...} ,就会产生函数对象,(function f(){...})也会,而且两者花费的时间是差不多的。这个时间也就是实例化一个函数对象的时间吧。

    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © aimingoo