首页 web前端 js教程 跟我学习JScript的Bug与内存管理_javascript技巧

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

May 16, 2016 pm 03:31 PM
bug jscript 内存管理

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

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

苹果iOS18bug汇总 苹果iOS18bug汇总 Jun 14, 2024 pm 01:48 PM

随着苹果WWDC发布会2024圆满落幕,不仅揭晓了macos15,其中最受关注的还是苹果iOS18新系统的更新,虽然有很多新功能出现,但是作为苹果iOS18首版不免让人纠结是否有必要升级苹果iOS18,在最新发布的苹果iOS18中又有哪些BUG存在呢?经过真实的使用测评,下面是苹果iOS18bug汇总,一起来看看吧。目前有许多iPhone用户都抢先升级到了iOS18.但各种系统Bug让人难受。有博主表示,升级iOS18要谨慎,因为“Bug多到飞起”。博主表示,如果你的iPhone是

C++对象布局与内存对齐,优化内存使用效率 C++对象布局与内存对齐,优化内存使用效率 Jun 05, 2024 pm 01:02 PM

C++对象布局和内存对齐优化内存使用效率:对象布局:数据成员按声明顺序存储,优化空间利用率。内存对齐:数据在内存中对齐,提升访问速度。alignas关键字指定自定义对齐,例如64字节对齐的CacheLine结构,提高缓存行访问效率。

C++ 函数内存分配和销毁在大型代码库中的最佳实践 C++ 函数内存分配和销毁在大型代码库中的最佳实践 Apr 22, 2024 am 11:09 AM

C++函数内存分配和销毁的最佳实践包括:使用局部变量进行静态内存分配。使用智能指针进行动态内存分配。在构造函数中分配内存,在析构函数中销毁内存。使用自定义内存管理器进行复杂内存场景。使用异常处理进行资源清理,确保异常时释放已分配内存。

C++ 函数内存分配和销毁的扩展与高级技术 C++ 函数内存分配和销毁的扩展与高级技术 Apr 22, 2024 pm 05:21 PM

C++函数内存管理提供了扩展和高级技术,包括:自定义分配器:允许用户定义自己的内存分配策略。placementnew和placementdelete:当需要将对象分配到特定内存位置时使用。高级技术:内存池、智能指针和RAII,用于减少内存泄漏、提高性能和简化代码。

C++ 内存管理:自定义内存分配器 C++ 内存管理:自定义内存分配器 May 03, 2024 pm 02:39 PM

C++中的自定义内存分配器可让开发者根据需求调整内存分配行为,创建自定义分配器需要继承std::allocator并重写allocate()和deallocate()函数。实战案例包括:提高性能、优化内存使用和实现特定行为。在使用时需要注意避免释放内存,管理内存对齐,并进行基准测试。

C++ 内存管理在多线程环境中的挑战和应对措施? C++ 内存管理在多线程环境中的挑战和应对措施? Jun 05, 2024 pm 01:08 PM

在多线程环境中,C++内存管理面临以下挑战:数据竞争、死锁和内存泄漏。应对措施包括:1.使用同步机制,如互斥锁和原子变量;2.使用无锁数据结构;3.使用智能指针;4.(可选)实现垃圾回收。

C++ 内存管理如何与操作系统和虚拟内存交互? C++ 内存管理如何与操作系统和虚拟内存交互? Jun 02, 2024 pm 09:03 PM

C++内存管理与操作系统交互,通过操作系统管理物理内存和虚拟内存,为程序高效分配和释放内存。操作系统将物理内存划分为页面,并按需从虚拟内存中调入应用程序请求的页面。C++使用new和delete运算符分配和释放内存,分别向操作系统请求内存页并将其返回。操作系统在释放物理内存时,将较少使用的内存页交换到虚拟内存中。

golang函数的内存管理最佳实践 golang函数的内存管理最佳实践 Apr 26, 2024 pm 05:33 PM

Go中的内存管理最佳实践包括:避免手动分配/释放内存(使用垃圾收集器);使用内存池提高经常创建/销毁对象时的性能;使用引用计数跟踪共享数据的引用数量;使用同步内存池sync.Pool在并发场景下安全管理对象。

See all articles