写在前面的话

注:本文是拜读了 深入理解JavaScript 之后深有感悟,故做次笔记方便之后查看。

感觉这章的内容有点深奥....略难懂啊。

先坐下笔记,加深一下印象吧。

我主要记一下自己感觉有用的东西...哈哈

函数表达式和函数声明

在ECMAScript中,创建函数的最常用的两个方法是函数表达式和函数声明,两者期间的区别是有点晕,因为ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符:

  函数声明:

  function 函数名称 (参数:可选){ 函数体 }

  函数表达式:

  function 函数名称(可选)(参数:可选){ 函数体 }

所以,可以看出,如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

function foo(){} // 声明,因为它是程序的一部分
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function(){
function bar(){} // 声明,因为它是函数体的一部分
})();

表达式和声明存在着十分微妙的差别,首先,函数声明会在任何表达式被解析和求值之前先被解析和求值,即使你的声明在代码的最后一行,它也会在同作用域内第一个表达式之前被解析/求值,参考如下例子,函数fn是在alert之后声明的,但是在alert执行的时候,fn已经有定义了:

alert(fn());

  function fn() {
return 'Hello world!';
}

另外,还有一点需要提醒一下,函数声明在条件语句内虽然可以用,但是没有被标准化,也就是说不同的环境可能有不同的执行结果,所以这样情况下,最好使用函数表达式:

// 千万别这样做!
// 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个 if (true) {
function foo() {
return 'first';
}
}
else {
function foo() {
return 'second';
}
}
foo(); // 相反,这样情况,我们要用函数表达式
var foo;
if (true) {
foo = function() {
return 'first';
};
}
else {
foo = function() {
return 'second';
};
}
foo();

函数声明的实际规则如下:

函数声明只能出现在程序函数体内。从句法上讲,它们 不能出现在Block(块)({ ... })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement语句, 而不能包含函数声明这样的源元素。另一方面,仔细看一看规则也会发现,唯一可能让表达式出现在Block(块)中情形,就是让它作为表达式语句的一部分。但是,规范明确规定了表达式语句不能以关键字function开头。而这实际上就是说,函数表达式同样也不能出现在Statement语句或Block(块)中(因为Block(块)就是由Statement语句构成的)。

JScript的Bug

比较恶的是,IE的ECMAScript实现JScript严重混淆了命名函数表达式,搞得现很多人都出来反对命名函数表达式,而且即便是最新的一版(IE8中使用的5.8版)仍然存在下列问题。

下面我们就来看看IE在实现中究竟犯了那些错误,俗话说知已知彼,才能百战不殆。我们来看看如下几个例子:

例1:函数表达式的标示符泄露到外部作用域

  var f = function g(){};
typeof g; // "function"

上面我们说过,命名函数表达式的标示符在外部作用域是无效的,但JScript明显是违反了这一规范,上面例子中的标示符g被解析成函数对象,这就乱了套了,很多难以发现的bug都是因为这个原因导致的。

注:IE9貌似已经修复了这个问题

例2:将命名函数表达式同时当作函数声明和函数表达式

 typeof g; // "function"
var f = function g(){};

特性环境下,函数声明会优先于任何表达式被解析,上面的例子展示的是JScript实际上是把命名函数表达式当成函数声明了,因为它在实际声明之前就解析了g。

这个例子引出了下一个例子。
例3:命名函数表达式会创建两个截然不同的函数对象!

 var f = function g(){};
f === g; // false f.expando = 'foo';
g.expando; // undefined

看到这里,大家会觉得问题严重了,因为修改任何一个对象,另外一个没有什么改变,这太恶了。通过这个例子可以发现,创建2个不同的对象,也就是说如果你想修改f的属性中保存某个信息,然后想当然地通过引用相同对象的g的同名属性来使用,那问题就大了,因为根本就不可能。

再来看一个稍微复杂的例子:

例4:仅仅顺序解析函数声明而忽略条件语句块

  var f = function g() {
return 1;
};
if (false) {
f = function g(){
return 2;
};
}
g(); //

这个bug查找就难多了,但导致bug的原因却非常简单。首先,g被当作函数声明解析,由于JScript中的函数声明不受条件代码块约束,所以在这个很恶的if分支中,g被当作另一个函数function g(){ return 2 },也就是又被声明了一次。然后,所有“常规的”表达式被求值,而此时f被赋予了另一个新创建的对象的引用。由于在对表达式求值的时候,永远不会进入“这个可恶if分支,因此f就会继续引用第一个函数function g(){ return 1 }。分析到这里,问题就很清楚了:假如你不够细心,在f中调用了g,那么将会调用一个毫不相干的g函数对象。

你可能会文,将不同的对象和arguments.callee相比较时,有什么样的区别呢?我们来看看:

 var f = function g(){
return [
arguments.callee == f,
arguments.callee == g
];
};
f(); // [true, false]
g(); // [false, true]

可以看到,arguments.callee的引用一直是被调用的函数,实际上这也是好事,稍后会解释。

还有一个有趣的例子,那就是在不包含声明的赋值语句中使用命名函数表达式:

(function(){
f = function f(){};
})();

按照代码的分析,我们原本是想创建一个全局属性f(注意不要和一般的匿名函数混淆了,里面用的是带名字的生命),JScript在这里捣乱了一把,首先他把表达式当成函数声明解析了,所以左边的f被声明为局部变量了(和一般的匿名函数里的声明一样),然后在函数执行的时候,f已经是定义过的了,右边的function f(){}则直接就赋值给局部变量f了,所以f根本就不是全局属性。

了解了JScript这么变态以后,我们就要及时预防这些问题了,首先防范标识符泄漏带外部作用域,其次,应该永远不引用被用作函数名称的标识符;还记得前面例子中那个讨人厌的标识符g吗?——如果我们能够当g不存在,可以避免多少不必要的麻烦哪。因此,关键就在于始终要通过f或者arguments.callee来引用函数。如果你使用了命名函数表达式,那么应该只在调试的时候利用那个名字。最后,还要记住一点,一定要把命名函数表达式声明期间错误创建的函数清理干净

对于,上面最后一点,我们还得再解释一下。

这章的东西确实有点偏,我们只简单了解下就可以。其他的个人感觉用处不大,不做笔记了。

最新文章

  1. 扩:new and override
  2. 安装redis入门
  3. (01)javascript 数据类型
  4. JS 特殊字符的魅力
  5. phpmailer 参数使用说明
  6. 异常详细信息: System.Data.SqlClient.SqlException:用户 'IIS APPPOOL\DefaultAppPool' 登录失败解决办法
  7. Android开发的进阶之路
  8. Go语言并发与并行学习笔记(一)
  9. 【数论+技巧】神奇的Noip模拟试题第二试 T1 素数统计
  10. 第四次作业——WORDSEARCH小游戏
  11. win8 iis安装及网站发布(转)
  12. Eclipse下执行main函数报java.lang.NoClassDefFoundError的解决
  13. 201521123045 《Java程序设计》第7周学习总结
  14. 【CJOJ1603】【洛谷1220】关路灯
  15. django中图片的上传和显示
  16. 树莓派0 ubuntu无显示器ssh登录终端
  17. [Swift]LeetCode454. 四数相加 II | 4Sum II
  18. Java虚拟机详解----JVM内存结构
  19. WPF:TreeView绑定
  20. vue中使用refs定位dom出现undefined?

热门文章

  1. LEFT JOIN条件写在where里是不会多查出数据来的
  2. rpm 卸载
  3. GPS坐标转百度地图坐标
  4. python 爬虫 黑科技
  5. Jupyter notebook用法
  6. 丢用lamp手动安装apache php mysql
  7. Machine learning 第7周编程作业 SVM
  8. php 下载文件/直接下载数据内容
  9. #.NET# DataGrid显示大量数据——DataGridView虚模式
  10. P4177 [CEOI2008]order