跟我学习JScript的Bug与内存管理

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的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。

以上就是关于JScript的Bug与内存管理的全部介绍,希望对大家的学习有所帮助。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
在淘宝,京东等其他网页上我们能够看到,当鼠标移上去的时候,能够使其下面出现其它选项,现在就演示一下这种功能 主要是用到css里面的display,以及鼠标触发的事件onmouseover(),和onmouseout()方式,在加上css样式的设置和标签之间额使用就能实现。 具体如下: 1,为了很好的控制,采用了列表的样式,并且在设置css样式时很方便 2,采用了a标签的样式,能够让鼠标移上去对其他的有反应 具体的js函数写的功能: script type="text/javascript" functio
1、正则表达式test方法 var text = "cat, bat, sat, fat"; var pattern = /.at/; if (pattern.test(text)){ alert("The pattern was matched."); } 2、正则的toString()方法 var pattern = new RegExp("\\[bc\\]at", "gi"); alert(pattern.toString()); // /\[bc\]at/gi alert(pattern.toLo
用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了,最近看了一些 JavaScript高级程序设计,终于揭开了其神秘面纱。 每个函数都有一个prototype属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法。不像

JavaScript严格模式详解 - 2015-11-20 14:11:13

如我们所知,JavaScript 是一门灵活的语言。其灵活性同样也带来了很多坑,当然也有一些是 设计缺陷 。比如 一个变量没有声明就赋值,默认是全局变量,如 (function () { a = 3;})();console.log(window.a); // 输出3 对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。如 var obj = { c: 3, c: 4}// obj 为 {c:4} 还有什么 == , typeof 之类的坑,数不胜数。毕竟 JavaScript 之父设计这门语言只用了一
你是否遇到过这样的情况,写了个function,无参数。 function showUserInfo(){alert("你好!我是小明。");}function showUserInfo(){ alert("你好!我是小明。"); } 调用: showUserInfo();showUserInfo(); 后来,发现其他地方也需要这个function,但是有变量值已经在function里面写死了,怎么办?加个参数吧。 function showUserInfo(name){name=name||"小明";a
本文实例讲述了js操作table元素实现表格行列新增、删除的方法。分享给大家供大家参考,具体如下: /************ TableTool.js ****************************************************************************************************************** **************************************************************
大家知道Jquery中有serialize方法,可以将表单序列化为一个“”连接的字符串,但却没有提供序列化为Json的方法。不过,我们可以写一个插件实现。 我在网上看到有人用替换的方法,先用serialize序列化后,将替换成“:”、“‘”: /*** 重置form表单* @param formId form的id*/function resetQuery(formId){var fid = "#" + formId;var str = $(fid).serialize();//str= cardSele
nodejs获取网页内容绑定data事件,获取到的数据会分几次相应,如果想全局内容匹配,需要等待请求结束,在end结束事件里把累积起来的全局数据进行操作! 举个例子,比如要在页面中找有没有 www.baidu.com ,不多说了,直接放代码: //引入模块var http = require("http"),fs = require('fs'),url = require('url');//写入文件,把结果写入不同的文件var writeRes = function(p, r) { fs.appendFi
jQuery EasyUI v1.4.4 下载网址: http://www.jb51.net/codes/70218.html 本教程将展示如何使用easyui创建一个标签组件。标签可以动态地添加或删除多个面板,你可以使用标签在同一个页面上显示不同的实体。 标签一次只能显示一个面板,每个面板都有标题、图表和关闭按钮。当标签被选中之后,会显示相应面板的内容。 使用HTML标记创建标签,其中包括一个DIV容器和一些DIV面板。 div class="easyui-tabs" div title="First
在这篇文章里,我将深入研究JavaScript中最基本的部分——执行上下文(execution context)。读完本文后,你应该清楚了解释器做了什么,为什么函数和变量能在声明前使用以及他们的值是如何决定的。 1、EC—执行环境或者执行上下文 每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文(好高大上的概念啊)。 javascript中,EC分为三种: 全局级别的代码