`
hax
  • 浏览: 962622 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

JScript的函数声明和函数表达式

    博客分类:
  • JS
阅读更多
JScript在函数声明和函数表达式方面不合ECMA标准,这已经是一个老生常谈的话题了。最近aimingoo谈到eval问题的时候再一次触及到这个问题。

大体就是 eval('(function (){})') 不返回函数对象,但是 eval('(0, function (){})') 却返回函数对象。此外,还有 eval('(function f(){})') 会在当前执行环境中产生 f 指向一个函数(当然,按照标准是不应该有的)。

从现象上看,这表示JScript并没有把孤立的“(function (){})”作为function表达式。按照规范,(function(){})就是一个表达式,但是遗憾的是对于JScript来说,括号还不够,一定得有运算符介入或者语句expect一个表达式时,比如简单的逗号运算符、return语句之类的,才会被认为是表达式。

此外“(function f(){})”按照规范应该也被认为是一个function表达式,但是JScript的行为却好像是一个函数声明。

这里就有有趣的问题了。

0,function f1(){}

这样一个语句中,究竟是函数表达式还是函数声明呢?

答案是:两者皆是。

当然这仅是从效果上说,因为本来这样的行为就不符合标准的定义。

不过更微妙的问题是,这个表达式返回的函数对象和所声明的f1函数,其实并非同一个函数!

对此,你可以在IE浏览器的地址栏里执行:javascript:alert(eval('0,function f1(){}')==f1),结果是false。

也就是说function表达式产生了一个不同于function声明的结果——这里产生了两个不同的函数对象!

回想到我以前对于with语句行为的测试,可以确认,JScript 5.0到5.5的时候,对function声明和function表达式的行为做了很大的修订,这可能是上述奇怪表现的源头。不过我手头没有JScript 5.0,所以无法做进一步的确认。

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

注意,这边的不相等,并非COM对象包装所造成的假象(比如两次读取event包装对象不相等,或者跨window/frame所造成的问题),而真的是两个函数,其实际执行效果其实也是不同的(虽然这里这个例子无法表现出来,但是如果加上with语句就会有差别了)。

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

function timer(f) {
	var start = new Date().getTime()
	for (var i = 0; i < MAX; i++) {
		f()
	}
	var end = new Date().getTime()
	if (typeof alert != 'undefined')
		alert(end - start)
	else
		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
	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可能是这样处理的:任何函数结构一律按照函数声明提前处理(所以总是产生变量绑定),函数表达式则被替换为函数对象的引用。

后来JScript 5.5为了改善对于with的处理而加入了真正的函数表达式实现,本来它可以完全fix这个问题。但是为了向前兼容而保持对于具名函数一律产生变量绑定,并且也保持了其with作用域的行为(就with语句中的function声明而言,按照规范实际应可视为语法错误——不过SpiderMonkey等引擎都按照function表达式等同处理,而JScript则选择忽略with)。最后得到的就是一个语句产生两份函数对象这样微妙的结果。

进一步我们可以大胆猜想,JScript 5.5大体上是这样:JScript在解析代码时,如果函数有具名,就会产生一份函数对象绑定到对应该名字的变量上,函数声明和绑定会被提前到当前执行上下文的最前端,因此总是会忽略with语句。而如果是一个函数表达式,则到实际运行到包含函数表达式的语句时,如果这个函数表达式有被用到(如有运算符时),就会产生一份函数对象,这个过程是在运行时的,所以会纳入with语句对于scope的修改。这两个步骤是共存的,所以可能出现一个语句最终产生出两份有微妙差异的函数对象的现象(实际上并不一定是两份,因为函数表达式是每运行到该语句时就产生一个函数对象,所以是n+1份)。

最后我们来看单独一句“(function (){})”,它既不是声明语句也不是表达式(严格的说是既没有函数声明的效果,也没有函数表达式的效果),实际上这里什么也没执行,这个代码被 JScript抛弃掉了(或者说这里是一个被优化掉了的函数表达式)。这一点可以通过类似我上面的性能测试证明之(即包含这个语句的函数其执行时间与一个空函数是一样的)。

之所以单独的括号不会触发函数表达式,也许是因为括号只是用于产生AST的结构而不像运算符等会产生独立的节点,JScript没有对此作特别处理,所以单独括号和没有括号并无差别。当然这也只是我的臆测而已。
5
0
分享到:
评论

相关推荐

    JS 有名函数表达式全面解析

    此例展示了 JScript 如何将有名函数表达式同时视为函数声明和函数表达式。按照 JavaScript 规范,函数声明会在执行上下文中优先于其他表达式进行解析,而 JScript 的实现则让 `g` 作为一个函数声明提前被解析了。 *...

    JScript 函数大全

    - **声明函数**:`function 函数名(参数列表) { 函数体 }`,如`function add(a, b) { return a + b; }`。 - **匿名函数**:没有名称的函数,常用于回调或立即执行,如`(function() { console.log('Hello, World!')...

    命名函数表达式探秘.pdf

    在JavaScript中,函数表达式和函数声明是两种创建函数的基本方式。两者的主要区别在于它们的语法结构和执行上下文。 - **函数声明**:这是一种较为常见的定义函数的方式。其基本语法结构如下: ```javascript ...

    JScript中文参考手册

    9. **函数表达式与函数声明**:了解函数表达式(匿名函数、立即调用的函数表达式)和函数声明的区别,以及作用域规则。 10. **闭包**:理解闭包的概念及其在内存管理、模块化和私有变量中的应用。 11. **严格模式*...

    javascript 函数声明与函数表达式的区别介绍

    还是一样,先上代码: 代码如下: [removed] var f = function g() { return 1; }; if (false) { f = function g...包括在if的条件语句中,也只是定义了函数表达式,没有去声明函数。 那么这样直接访问肯定是会出错的。

    Javascript与Jscript从入门到精通

    JavaScript支持函数表达式(匿名函数和箭头函数)以及函数声明。 4. **数组和对象**:数组是有序的数据集合,可以存储多个值。对象则是一种键值对的集合,用于组织复杂的数据结构。 5. **事件处理**:JavaScript常...

    JScript中文手册(完全版)

    函数声明和函数表达式是两种定义函数的方式。作用域决定了变量的可见性和生命周期,全局变量在整个脚本中都可访问,而局部变量仅在其定义的函数或块内部有效。 **对象与原型** JScript采用基于原型的面向对象编程...

    JScript中文參考手冊

    JScript的基本语法与JavaScript类似,包括变量声明(var)、数据类型(如字符串、数字、布尔、null、undefined等)、运算符(算术、比较、逻辑、赋值等)、流程控制(if...else、switch、for、while、do...while等)...

    JScript帮助文档

    2. **语法细节**:JScript 在某些语法上与JavaScript有所区别,例如JScript不支持函数表达式,而是使用函数声明。 3. **错误处理**:JScript 使用try...catch...finally进行异常处理,而JavaScript也有相同结构,但...

    JScript 语言参考.chm

    2. **函数与作用域**:函数定义、参数传递、局部与全局变量、闭包,以及在JScript中的函数表达式。 3. **对象与原型**:理解JavaScript中的对象模型,原型链,以及如何通过构造函数创建自定义对象。 4. **数组与...

    JScript 语言参考

    1. **正则表达式**:JScript 支持正则表达式操作,用于字符串匹配和替换。 2. **错误处理**:通过try...catch...finally语句进行异常处理,可以捕获并处理运行时错误。 3. **闭包**:函数可以访问并修改其外部作用...

    JScript中文帮助手册

    1. **基础语法**:JScript的基础语法包括变量声明(var关键字)、数据类型(如字符串、数字、布尔值等)、运算符(算术、比较、逻辑等)、控制流(if语句、for循环、while循环)以及函数定义和调用。 2. **对象和...

    《JScript 语言参考》中文版.chm

    8. **正则表达式**:JScript 支持正则表达式,用于模式匹配和文本处理。 9. **错误处理**:通过`try...catch`语句块进行错误捕获和处理。 **JScript 在Web开发中的应用** 1. **DOM操作**:JScript 可以通过`...

    JScript 语言参考(CHM)

    JScript 用户指南部分是为初学者设计的,它提供了JScript的基础知识,包括变量声明、数据类型、运算符、控制结构(如条件语句和循环)、函数定义以及事件处理。这部分内容会帮助开发者了解如何编写和运行简单的...

    JScript中文帮助

    JScript支持两种类型的函数:函数声明和函数表达式。函数可以有参数,并返回值。匿名函数(即没有名字的函数,通常作为回调使用)也是常见的形式。 **JScript事件处理** 在Windows环境下,JScript常用于编写与用户...

    jscript中文帮助

    JScript 支持声明函数和匿名函数。 4. **事件处理**:在Web环境中,JScript通常用于处理用户与页面的交互,例如点击按钮、改变表单元素等事件。 5. **DOM操作**:Document Object Model (DOM) 是HTML和XML文档的...

Global site tag (gtag.js) - Google Analytics