关闭

JavaScript学习--Item18 JScript的Bug与内存管理

212人阅读 评论(0) 收藏 举报
分类:


1、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(); // 2

这个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来引用函数。如果你使用了命名函数表达式,那么应该只在调试的时候利用那个名字。最后,还要记住一点,一定要把命名函数表达式声明期间错误创建的函数清理干净。

2、JScript的内存管理

知道了这些不符合规范的代码解析bug以后,我们如果用它的话,就会发现内存方面其实是有问题的,来看一个例子:

var f = (function(){
  if (true) {
    return function g(){};
  }
  return function g(){};
})();

我们知道,这个匿名函数调用返回的函数(带有标识符g的函数),然后赋值给了外部的f。我们也知道,命名函数表达式会导致产生多余的函数对象,而该对象与返回的函数对象不是一回事。所以这个多余的g函数就死在了返回函数的闭包中了,因此内存问题就出现了。这是因为if语句内部的函数与g是在同一个作用域中被声明的。这种情况下 ,除非我们显式断开对g函数的引用,否则它一直占着内存不放。

var f = (function(){
  var f, g;
  if (true) {
    f = function g(){};
  }
  else {
    f = function g(){};
  }
  // 设置g为null以后它就不会再占内存了
  g = null;
  return f;
})();

通过设置g为null,垃圾回收器就把g引用的那个隐式函数给回收掉了,为了验证我们的代码,我们来做一些测试,以确保我们的内存被回收了。

测试

测试很简单,就是命名函数表达式创建10000个函数,然后把它们保存在一个数组中。等一会儿以后再看这些函数到底占用了多少内存。然后,再断开这些引用并重复这一过程。下面是测试代码:

function createFn(){
  return (function(){
    var f;
    if (true) {
      f = function F(){
        return 'standard';
      };
    }
    else if (false) {
      f = function F(){
        return 'alternative';
      };
    }
    else {
      f = function F(){
        return 'fallback';
      };
    }
    // var F = null;
    return f;
  })();
}

var arr = [ ];
for (var i=0; i < 10000; i++) {
  arr[i] = createFn();
}

通过运行在Windows XP SP2中的任务管理器可以看到如下结果:

IE7:

  without `null`:   7.6K -> 20.3K
  with `null`:      7.6K -> 18K

IE8:

  without `null`:   14K -> 29.7K
  with `null`:      14K -> 27K

如我们所料,显示断开引用可以释放内存,但是释放的内存不是很多,10000个函数对象才释放大约3M的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。


0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

你不知道的JavaScript--Item18 JScript的Bug与内存管理

1、JScript的BugIE的ECMAScript实现JScript严重混淆了命名函数表达式,搞得现很多人都出来反对命名函数表达式,而且即便是现在还一直在用的一版(IE8中使用的5.8版)仍然存在下...
  • i10630226
  • i10630226
  • 2015-10-22 21:43
  • 21636

spark性能调优与BUG修正

做了一年延云YDB的开发,这一年在使用spark上真心踩了不少坑,总结一下,希望对大家有所帮助。 spark 内存泄露 1.高并发情况下的内存泄露的具体表现 很遗憾,spark的设计...
  • qq_33160722
  • qq_33160722
  • 2017-03-06 17:14
  • 495

linux下内存管理学习心得(一)

最近在学习内存管理的时候,发现对linux下的所谓内存如何管理如何分配都不熟悉,通过最近的查阅资料可总结如下,如有不妥之处欢迎大家批评与指正。 总的的来说linux的内存管理其实主要难理解的是以下几...
  • yuanzhangmei1
  • yuanzhangmei1
  • 2013-03-19 10:42
  • 1562

柠檬BUG管理-最简单的在线BUG管理工具

柠檬BUG管理-最简单的在线BUG管理工具一.概述柠檬BUG管理(www.lemonpm.com),由雒灵科技(北京)开发的一款在线的BUG管理工具。这是一款面向中小IT企业或分布式团队推出的一款在线...
  • gao_zhen_yu
  • gao_zhen_yu
  • 2015-03-05 14:40
  • 1630

javascript的垃圾回收机制与内存管理

一、垃圾回收机制—GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。 原理:垃圾收集器会...
  • OLiver_web
  • OLiver_web
  • 2016-12-31 17:05
  • 2704

说说我对 javascript 浮点运算 BUG 的看法

对于 javascript 浮点运算 BUG 大家都应该有所耳闻,在网上也会收到很多解决办法,在这里我引用两篇博文。 Javascript 浮点运算问题分析与解决 Javascript优化后的加减...
  • wooddy1982
  • wooddy1982
  • 2016-11-19 09:34
  • 301

最受欢迎的五大bug管理平台

五大最受欢迎的BUG管理系统     Google在中国大*陆遭遇变故做出暂时性的退出大*陆市场,也使很多忠实的用户受到小小的挫折,以本公司为例,原本的BUG都是记录在google的EXCEL在线文...
  • u012138153
  • u012138153
  • 2015-05-27 10:09
  • 2225

五大最受欢迎的BUG管理系统

Google在中国大陆遭遇变故做出暂时性的退出大陆市场,也使很多忠实的用户受到小小的挫折,以本公司为例,原本的BUG都是记录在google的 EXCEL在线文档中,因为经常性的打不开,测试和开发组在线...
  • ygc973797893
  • ygc973797893
  • 2014-10-29 17:05
  • 2982

学会蒲公英Bug管理云的使用

【前言】 一个Bug管理系统是软件公司重要的组成部分,它不但协助开发人员发现程序中的问题点,还能帮助大家积累发现同类问题和解决问题的经验。 选择“蒲公英Bug管理云”是出于下面几个原因: ...
  • th_gsb
  • th_gsb
  • 2017-01-10 01:08
  • 1980

五款轻量型bug管理工具横向测评

最近正在使用的本地bug管理软件又出问题了,已经记不清这是第几次了,每次出现问题都要耗费大量的时间精力去网上寻找解决方案,劳心劳力。
  • ameetones
  • ameetones
  • 2017-04-26 16:21
  • 2425
    个人资料
    • 访问:345994次
    • 积分:5800
    • 等级:
    • 排名:第5186名
    • 原创:289篇
    • 转载:19篇
    • 译文:0篇
    • 评论:19条
    文章分类
    最新评论