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


Posted in Javascript onNovember 18, 2015

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与内存管理的全部介绍,希望对大家的学习有所帮助。

Javascript 相关文章推荐
jquery让返回的内容显示在特定div里(代码少而精悍)
Jun 23 Javascript
基于js与flash实现的网站flv视频播放插件代码
Oct 14 Javascript
将页面table内容与样式另存成excel文件的方法
Aug 05 Javascript
JavaScript必知必会(六) delete in instanceof
Jun 08 Javascript
js 打开新页面在屏幕中间的实现方法
Nov 02 Javascript
JS填写银行卡号每隔4位数字加一个空格
Dec 19 Javascript
详解基于vue-router的动态权限控制实现方案
Sep 28 Javascript
浅谈Vue.js中ref ($refs)用法举例总结
Dec 19 Javascript
vue指令只能输入正数并且只能输入一个小数点的方法
Jun 08 Javascript
解决包含在label标签下的checkbox在ie8及以下版本点击事件无效果兼容的问题
Oct 27 Javascript
微信公众号H5之微信分享常见错误和问题(小结)
Nov 14 Javascript
Vue获取页面元素的相对位置的方法示例
Feb 05 Javascript
跟我学习javascript的循环
Nov 18 #Javascript
js操作table元素实现表格行列新增、删除技巧总结
Nov 18 #Javascript
跟我学习javascript的for循环和for...in循环
Nov 18 #Javascript
js实现模拟银行卡账号输入显示效果
Nov 18 #Javascript
跟我学习javascript的prototype原型和原型链
Nov 18 #Javascript
超精准的javascript验证身份证号的具体实现方法
Nov 18 #Javascript
javascript中checkbox使用方法简单实例演示
Nov 17 #Javascript
You might like
法压式咖啡之制作法
2021/03/03 冲泡冲煮
php面向对象中的魔术方法中文说明
2014/03/04 PHP
CodeIgniter针对lighttpd服务器URL重写的方法
2015/06/10 PHP
Zend Framework实现具有基本功能的留言本(附demo源码下载)
2016/03/22 PHP
PHP创建XML的方法示例【基于DOMDocument类及SimpleXMLElement类】
2019/09/10 PHP
JavaScript实现Sleep函数的代码
2007/03/04 Javascript
W3C Group的JavaScript1.8 新特性介绍
2009/05/19 Javascript
JS操作JSON要领详细总结
2013/08/25 Javascript
javascript使用smipleChart实现简单图表
2015/01/02 Javascript
jquery简单实现图片切换效果的方法
2015/05/12 Javascript
JavaScript中的getTime()方法使用详解
2015/06/10 Javascript
js绘制圆形和矩形的方法
2015/08/05 Javascript
第九篇Bootstrap导航菜单创建步骤详解
2016/06/21 Javascript
浅谈在js传递参数中含加号(+)的处理方式
2016/10/11 Javascript
jQuery旋转插件jqueryrotate用法详解
2016/10/13 Javascript
简单的vue-resourse获取json并应用到模板示例
2017/02/10 Javascript
用nodeJS搭建本地文件服务器的几种方法小结
2017/03/16 NodeJs
React Native 环境搭建的教程
2017/08/19 Javascript
js中json对象和字符串的理解及相互转化操作实现方法
2017/09/22 Javascript
浏览器调试动态js脚本的方法(图解)
2018/01/19 Javascript
浅析微信小程序自定义日历组件及flex布局最后一行对齐问题
2020/10/29 Javascript
python自动zip压缩目录的方法
2015/06/28 Python
Python的Flask站点中集成xhEditor文本编辑器的教程
2016/06/13 Python
python中多个装饰器的执行顺序详解
2018/10/08 Python
python3 打印输出字典中特定的某个key的方法示例
2019/07/06 Python
Python Gluon参数和模块命名操作教程
2019/12/18 Python
pandas to_excel 添加颜色操作
2020/07/14 Python
基于python判断字符串括号是否闭合{}[]()
2020/09/21 Python
ghd澳大利亚官方网站:英国最受欢迎的美发工具品牌
2018/05/21 全球购物
初中数学教学反思
2014/01/16 职场文书
《骑牛比赛》教后反思
2014/04/22 职场文书
物流管理系毕业生求职信
2014/06/03 职场文书
产品委托授权书范本
2014/09/16 职场文书
讲座新闻稿
2015/07/18 职场文书
公司车辆管理制度
2015/08/04 职场文书
pytest实现多进程与多线程运行超好用的插件
2022/07/15 Python