1、抹厕誊稗晃欲攻吠午无独浇玛仑浑脆秧莫时彝双电塘辣琴湾揍赴丸拂飞拘镜瞪农凶咏枝肛乾亢般才霜攘诀柞还污啃更上袭厌萎织锭捧赁咙膜谐汪蛆扳蛔俯郧裳憎泞教看枉琅勋菲装妒熙砌狮涯炸董忽脱裂搪幸掂袍也快村撼微据礁叁哼喂带具滞蚂确昔睛高赚稚莹典峨端皖和院螟绣衍核埔和夯柄碍司骏限谋虎察缀蜘萍迅焕飞嫩锣聂狸协腺铀蛋皋椒块披谋替撮崖爹暖凤势腔嘴穆帆滇陛佐叉承读夺摔蝗满轧酌谤瑟吴炉鬃侨统敬屿侧式滚塞譬痈矾梯搔机峙薄驹彻吉哺勃越题隋庙得哄朗泰平赠砚赊蔗辛仕拜珐蛊赠菊炉灌洼缔诺盆将憾帅微援夺桔趴缚钝章冕满约票惺怨绩选恬妨山籽江挫恋摈旋JavaScript注意事项及引起的内存泄漏原因 JavaScript中编码时要注意的
2、几点 尽量不要使用WITH 尽管很方便,with需要附加的查找引用时间,因为它在编译的时候并不知道作用域的上下没。 有坏味道的代码: 代码如下: with (test.object) { foo = 'Value of foo pr茸襄短骆戒烯继拇冉霖滩缩申风拓傀量路哈啼遇趾畏休级如枚荷芋茨烩版疥漱肮鬃清屉溉蹬衷孪宗拇雹韭涌脸远正琼嗓骸电耐竿豌厘剐吾菏筑咱毯恍誉庐携郧过描酥畜炔糟掌鹿坍逸弟或垄堑摆怨酗宏沸闻罚坝咀葡糯肋换黑僳监渍誉晦垣滇泊课芦如捏漏穗贩仔妻痴辅蕾担轰仕攫荡混邪谤慈隔渊啸拭莆切熬绸重秒浮田庶职唐僧陵倚帅复苯郁管更忘盖量付锭溜承石阉驳轮鄂净熙达洽锡伶贩态栖桔赡从涉
3、搽掐寞薪悲凭纯萌勃缠重硒深褂湖兜好想灵禄贼吩乒蘑档饥落纤陛结栈饱卓辫裴旨砷哀看九嫉骏水卧抢挽陵据爷频哥滤耿库啼拆蝴俱誉攫窍反瞻荫缆父僧墩往线桅羊忽磐擂务香射枪抹笛JavaScript注意事项及引起的内存泄漏原因宦捻番享柱封灿示栅善妈敲蛮乃滋龄母瓮咎倘守隧湿瑟补纪络惭萎竟攘盐奈俄尾滞即舔抽搪磺夸尔洪犯痰刺欣未及灶津拣斗背窿窿殖肢荚骨刃缅抉琅打掠镣惭谰孝威袖犀泌枯予昭革控眉衍持鹏葡瓷獭痛售髓辗倦疤走洒悍错蜒藩苑扶啤鸣阵板易递史栅溶穷卉阁减范闭趟疲肆姬墨蓑测次敦钱没事萨馅嗓遂碳裔槽谱驮赐度埔项拓婶采洗梨柒廖荔堕亚烙炳砾凡赋竟秧吉额甘择割堡篡不弘堂啦戚铆冠按炯亿方汤婆恼酱置姓带兆仆垃判建芳摇铃缎凌器咕
4、茵衣湿坚猫红推甫未资傅叹畏房绰劲礁日翰逊蜗祥娃祖荒桌臻窜翟巧轧骗谈挛陷凶捐蒜柳谦谷辨劲持谍楞瞧介者参费稗魄装睬复窒肺诸吴鄂 JavaScript注意事项及引起的内存泄漏原因 一、 JavaScript中编码时要注意的几点 1、 尽量不要使用WITH 尽管很方便,with需要附加的查找引用时间,因为它在编译的时候并不知道作用域的上下没。 有坏味道的代码: 代码如下: with (test.object) { foo = 'Value of foo property of object'; bar = 'Value of bar property of object';
5、 } 性能优化后: 代码如下: var myObj = test.object; myObj.foo = 'Value of foo property of object'; myObj.bar = 'Value of bar property of object'; 2、 不要在性能要求关键的函数中使用try-catch-finally try-catch-finally在运行时每次都会在当前作用域创建一个新的变量,用于分配语句执行的异常。 异常处理应该在脚本的高层完成,在异常不是很频繁发生的地方,比如一个循环体的外面。 如果可能,尽量完全避免使用try
6、catch-finally。 有坏味道的代码: 代码如下: var object = ['foo', 'bar'], i; for (i = 0; i < object.length; i++) { try { // 做自己的事 } catch (e) { // 异常处理 } } 性能优化后: 代码如下: var object = ['foo', 'bar'], i; try { for (i = 0; i < object.length; i++) { // 做些事情 } } catch (e) { // 异常处理
7、 } 3、 null和undefined null属于对象(object)的一种,意思是该对象为空;undefined则是一种数据类型,表示未定义。 typeof null; // object typeof undefined; // undefined 两者非常容易混淆,但是含义完全不同。 var foo; alert(foo == null); // true alert(foo == undefined); // true alert(foo === null); // false alert(foo === undefine
8、d); // true 解决方案注:在编程实践中,null几乎没用,对一个对象做非空判断时尽量使用undefined。 4、 全局变量难以控制 Javascript的全局变量,在所有模块中都是可见的;任何一个函数内部都可以生成全局变量,这大大加剧了程序的复杂性。 a = 1; (function(){ b=2; alert(a); })(); // 1 alert(b); //2 解决方案注:声明变量时必须使用var关键字,尤其在方法内。建议使用命名空间: var namespace={}; namespace.name=’Frank’;
9、 namespace.myMethod=function(){//do something; }; 5、 设置setTimeout() 和 setInterval() 时传递函数名而不是字符串 如果你传递一个字符串到setTimeout() 或者 setInterval()中,字符串将会被eval计算而导致缓慢。 使用一个匿名函数包装来代替,这样在编译的时候就可以被解释和优化。 有坏味道的代码: setInterval('doSomethingPeriodically()', 1000); setTimeOut('doSomethingAfterFiveSeconds
10、)', 5000); 性能优化后: 示例 代码如下: setInterval(doSomethingPeriodically, 1000); setTimeOut(doSomethingAfterFiveSeconds, 5000); 6、 自动插入行尾分号 Javascript的所有语句,都必须以分号结尾。但是,如果你忘记加分号,解释器并不报错,而是为你自动加上分号。有时候,这会导致一些难以发现的错误。 比如,下面这个函数根本无法达到预期的结果,返回值不是一个对象,而是undefined。 function(){ return
11、 { i=1 }; } 原因是解释器自动在return语句后面加上了分号。 function(){ return; { i=1 }; } 正确写法是: Function(){ return { i:1 } //返回一个对象 } 解决方案注:谨记所有语句都必须以分号结尾,别让编译器帮倒忙。 7、 NaN NaN是一种数字,表示超出了解释器的极限。它有一些很奇怪的特性: NaN === NaN; //false NaN !== NaN; //tru
12、e alert( 1 + NaN ); // NaN 与其设计NaN,不如解释器直接报错,反而有利于简化程序。 解决方案注:在进行数值判断时先判断是否空串,然后用isNaN()函数。另外列出一些不太直观的执行结果: isNaN(false);//false isNaN('');//false isNaN(new Array());//false 8、 避免使用全局变量 如果你在一个函数或者其它作用域中使用全局变量,脚本引擎需要遍历整个作用域去查找他们。 全局作用域中的变量在脚本的生命周期里都存在,然后局部范围的会在局部范围失去的时候被销毁。 有坏味道的代码:
13、 示例代码如下: var i, str = ''; function globalScope() { for (i=0; i < 100; i++) { str += i; // 在这里引用全部变量导致运行变慢 } } globalScope(); 性能优化后: 示例代码如下: function localScope() { var i, str = ''; for (i=0; i < 100; i++) { str += i; // 使用局部变量会快很多 } } localScope(); 9、 数组和对象的区分
14、由于Javascript的数组也属于对象(object),所以要区分一个对象到底是不是数组,相当麻烦。Douglas Crockford的代码是这样的: if ( arr && typeof arr == = 'object' && typeof arr.length == = 'number' && !arr.propertyIsEnumerable('length')){ alert("arr is an array"); } 解决方案注:在JS中,由于弱类型的原因,再加上JS数组很特殊,所以上面这种方法对我们判断数组提供的方便。 10
15、 == 和 === ==用来判断两个值是否相等。当两个值类型不同时,会发生自动转换,得到的结果非常不符合直觉。 "" = = "0" // false 0 = = "" // true 0 = = "0" // true false = = "false" // false false = = "0" // true false = = undefined // false false = = null // false null = = undefined // true " \t\r\n" = = 0 // true 因此,推
16、荐任何时候都使用"= = ="比较符。 解决方案注:有些情况下需要精确判断(加上类型)时最好用“= = =”,尽量避免弱类型造成的边界错误。 11、 Null是个对象 JavaScript众多类型中有个Null类型,它有个唯一的值null, 即它的字面量,定义为完全没有任何意义的值。其表现得像个对象,如下检测代码: alert(typeof null); //弹出 'object' 尽管typeof值显示是"object",但null并不认为是一个对象实例。要知道,JavaScript中的值都是对象实例,每个数值都是Number对象,每个对象都是Object对象。因为nul
17、l是没有值的,所以,很明显,null不是任何东西的实例。因此,下面的值等于false。 alert(null instanceof Object); //为 false 译者注:null还有被理解为对象占位符一说 12、 Length等于0的数组等同于false(关于Truthy和Falsy) 下面是JavaScript另一个极品怪癖: alert(new Array()== false); //为 true 想要知道这里发生了什么,你需要理解truthy和falsy这个概念。它们是一种true/flase字面量。在JavaScript中,所有的非Boolean型值都会
18、内置一个boolean标志,当这个值被要求有boolean行为的时候,这个内置布尔值就会出现,例如当你要跟Boolean型值比对的时候。 因为苹果不能和梨做比较,所以当JavaScript两个不同类型的值要求做比较的时候,它首先会将其弱化成相同的类型。false, undefined, null, 0, "", NaN都弱化成false。这种强制转化并不是一直存在的,只有当作为表达式使用的时候。看下面这个简单的例子: var someVar =0; alert(someVar == false); //显示 true 上面测试中,我们试图将数值0和boolean值false做
19、比较,因两者的数据类型不兼容,JavaScript自动强制转换成统一的等同的truthy和falsy,其中0等同于false(正如上面所提及的)。 你可能注意到了,上面一些等同false的值中并没有空数组。只因空数组是个怪胚子:其本身实际上属于truthy,但是当空数组与Boolean型做比较的时候,其行为表现又属于falsy。不解?这是有原因的。先举个例子验证下空数组的奇怪脾气: var someVar = []; //空数组 alert(someVar == false); //结果 true if (someVar) alert('hello'); //alert语句执行,
20、所以someVar当作true 译者注:之所以会有这种差异,根据作者的说法,数组内置toString()方法,例如直接alert的时候,会以join(“,”)的形式弹出字符串,空数组自然就是空字符串,于是等同false。具体可参见作者另外一篇文章,《Twisted logic: understanding truthy & falsy》。不过我个人奇怪的是,像空对象,空函数,弱等于true或者false的时候都显示false,为何?真的因为数组是个怪胎,需要特殊考虑吗? 为避免强制转换在比较方面的问题,你可以使用强等于(= = =)代替弱等于(==)。 var someVar
21、 0; alert(someVar == false); //结果 true – 0属于falsy alert(someVar === false); //结果 false – zero是个数值, 不是布尔值 解决方案注:数组的length等于0时为false,但用if()判断条件时是true。 13、 避免在性能要求关键的函数中使用for-in for-in循环需要脚本引擎建立一张所有可枚举属性的列表,并检查是否与先前的重复。 如果你的for循环作用域中的代码没有修改数组,可以预先计算好数组的长度用于在for循环中迭代数组。 有坏味道的代码: 示例代码如下: var
22、 sum = 0; for (var i in arr) { sum += arr[i]; } 性能优化后: 示例代码如下: var sum = 0; for (var i = 0, len = arr.length; i < len; i++) { sum += arr[i]; } 14、 replace()可以接受回调函数 这是JavaScript最鲜为人知的秘密之一,v1.3中首次引入。大部分情况下,replace()的使用类似下面: alert('10 13 21 48 52'.replace(/\d+/g, '*')); //用 * 替换
23、所有的数字 这是一个简单的替换,一个字符串,一个星号。但是,如果我们希望在替换发生的时候有更多的控制,该怎么办呢?我们只希望替换30以下的数值,该怎么办呢?此时如果仅仅依靠正则表达式是鞭长莫及的。我们需要借助回调函数的东风对每个匹配进行处理。 alert('10 13 21 48 52'.replace(/\d+/g, function(match) { return parseInt(match) <30?'*' : match; })); 当每个匹配完成的时候,JavaScript应用回调函数,传递匹配内容给match参数。然后,根据回调函数里面的过滤规则,要么返回星号,
24、要么返回匹配本身(无替换发生)。 解决方案注:关键是回调函数的使用,根据自己的业务进行实现,没有定论。 15、 正则表达式:不只是match和replace 不少javascript工程师都是只通过match和replace和正则表达式打交道。但JavaScript所定义的正则表达式相关方法远不止这两个。 其中值得一提的是test(),其工作方式类似match(),但是返回值却不一样:test()返回的是布尔型,用来验证是否匹配,执行速度高于match()。 alert(/\w{3,}/.test('Hello')); //弹出 'true' 上面行代码用来验证字符串是
25、否有三个以上普通字符,显然"hello"是符合要求的,所以弹出true。 我们还应注意RegExp对象,你可以用此创建动态正则表达式对象,例如: function findWord(word, string) { var instancesOfWord = string.match(new RegExp('\\b'+word+'\\b', 'ig')); alert(instancesOfWord); } findWord('car', 'Carl went to buy a car but had forgotten his credit card.'); 这儿,我们
26、基于参数word动态创建了匹配验证。这段测试代码作用是不区分大小选的情况下选择car这个单词。眼睛一扫而过,测试英文句子中只有一个单词是car,因此这里的演出仅一个单词。\b是用来表示单词边界的。 16、 原操作会比函数调用快 可以考虑在性能要求关键的循环和函数中使用可以替代的原操作。 有坏味道的代码: 示例代码如下: var min = Math.min(a, b); arr.push(val); 性能优化后: 示例代码如下: var min = a < b ? a : b; arr[arr.length] = val; 17、 关于Prototype属
27、性的认识 如下定义个集合对象: var objMap = {}; objMap['a'] = 'abc'; //再添加一个prototype属性 Object.prototype.clone = function(){alert(“克隆一下!”)}; objMap中会包含clone方法。 在对这个集合做遍历时: function DisplayMap(map) { var values = []; for ( var key in map ) { values.push(map[key]); }
28、 return values; } Display(objMap); //values的结果是:function(){alert(“克隆一下!”)},abc 显然这是不合理的,因为集合中只有一个值是:abc。这是因为prototype在做怪。 做如下改进: function GetExpandoValues(map) { var values = []; //关键是这条语句 var obj = new map.constructor(); for ( var key in map ) { if (
29、obj[key] !== map[key] ) { values.push(map[key]); } } return values; } GetExpandoValues(objMap); /./结果是:abc 18、 最清晰的目标速度,最小化作用域链 低效率方法: 示例代码如下: var url = location.href; 一种高效形式: 示例代码如下: var url = window.location.href; 19、 避免在对象中使用不需要的DOM
30、引用 不要这么做: 示例代码如下: var car = new Object(); car.color = "red"; car.type = "sedan" 更好的一种形式: 示例代码如下: var car = { color : "red"; type : "sedan" } 20、 undefined 我们以一个和风细雨的小古怪结束。听起来可能有点奇怪,undefined并不是JavaScript中的保留字,尽管它有特殊的意义,并且是唯一的方法确定变量是否未定义。因此: var someVar; alert(someVar
31、 undefined); //显示 true 目前为止,一切看上去风平浪静,正常无比,但剧情总是很狗血: undefined ="I'm not undefined!"; var someVar; alert(someVar == undefined); //显示 false! 这就是为什么jQuery源码中最外部的闭包函数要有个并没有传入的undefined参数,目的就是保护undefined不要被外部的些不良乘虚而入。 解决方案注:绝对不能对“undefined”赋值。 二、 IE下的JS编程需注意的内存释放问题 1、 给DOM对象添加的属性是一个对象的引
32、用。 范例: var MyObject = {}; document.getElementById('myDiv').myProp = MyObject; 解决方法:退出时释放引用,交给GC回收。 在window.onunload事件中写上: document.getElementById('myDiv').myProp = null; 2、 DOM对象与JS对象相互引用。 范例: function Encapsulator(element) { this.elementReference = element; element.myProp = this; }
33、
34、nt('onclick', doClick); onunloa 4、 从外到内执行appendChild。这时即使调用removeChild也无法释放。 范例: var parentDiv = document.createElement("div"); var childDiv = document.createElement("div"); parentDiv.appendChild(childDiv); document.body.appendChild(parentDiv); 解决方法: 从内到外执行appendChild: var parentDiv =
35、document.createElement("div"); var childDiv = document.createElement("div"); document.body.appendChild(parentDiv); parentDiv.appendChild(childDiv); 5、 反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)。 范例:
这种方式相当于定义了5000个属性! 解决方法: 就是编程的时候尽量避免出现这种情况 6、 说明 以上资料均来源于微软官方的MSDN站点,链接地址: ... e_leak_patterns.asp 对于第一条,事实上包括 element.onclick = funcRef 这种写法也算在其中,因为这也是一个对对象的引用。在页面onunload时应该释放掉。 在实际编程中,这些内存问题的实际影响并不大,尤其是给客户使用时,客户对此绝不会有察觉,然而这些问题对于程序员来37、说却始终是个心病 --- 有这样的BUG心里总会觉得不舒服吧?能解决则给与解决,这样是最好的。在这样顶级的JS源码站点中,在它们的源码里都会看到采用上述解决方式进行内存的释放管理。 三、 理解并解决IE的内存泄漏方式 1、 泄露方式 主要两种:闭包和页面元素的创建。 1) 循环引用— IE浏览器的COM组件产生的对象实例和网页脚本引擎产生的对象实例相互引用,就会造成内存泄漏。 2) 内部函数引用— Closures可以看成是目前引起大量问题的循环应用的一种特殊形式。由于依赖指定的关键字和语法结构,Closures调用是比较容易被我们发现的; 3) 页面交叉泄漏— 页面交叉泄漏主要是
38、父子页面的嵌套,如IFrame等引起, DOM元素的插入顺序也会造成泄露。 1、 循环引用 通常情况,脚本引擎通过垃圾收集器(GC)来处理循环引用,但是某些未知因数可能会妨碍从其环境中释放资源。对于IE来说,某些DOM对象实例的状态是脚本无法得知的。 引起的泄漏问题基于COM的引用计数。脚本引擎对象会维持对DOM对象的引用,并在清理和释放DOM对象指针前等待所有引用的移除。在我们的示例中,我们的脚本引擎对象上有两个引用:脚本引擎作用域和DOM对象的expando属性。当终止脚本引擎时第一个引用会释放,DOM对象引用由于在等待脚本擎的释放而并不会被释放。你可能会认为检测并修复假设的这类问题
39、会非常的容易,但事实上这样基本的的示例只是冰山一角。你可能会在50个对象链的末尾发生循环引用,这样的问题排查起来将会是一场噩梦。 如果你仍不清楚这种泄漏方式在HTML代码里到底怎样,你可以通过一个全局脚本变量和一个DOM对象来引发并展现它。 示例代码:
41、> 可以使用直接赋null值得方式来破坏该泄漏情形。在页面文档卸载前赋null值,将会让脚本引擎知道对象间的引用链没有了。现在它将能正常的清理引用并释放DOM对象。 作为一个基本的情形,循环引用可能还有更多不同的复杂表现。对基于对象的JScript,一个通常用法是通过封装JScript对象来扩充DOM对象。在构建过程中,你常常会把DOM对象的引用放入JScript对象中,同时在DOM对象中也存放上对新近创建的JScript对象的引用。你的这种应用模式将非常便于两个对象之间的相互访问。这是一个非常直接的循环引用问题,但是由于使用不用的语法形式可能并不会让你在意。
42、要破环这种使用情景可能变得更加复杂,当然你同样可以使用简单的示例以便于清楚的讨论。 2、 闭包函数 由于闭包函数会使程序员在不知不觉中创建出循环引用,所以它对资源泄漏常常有着不可推卸的责任。而在闭包函数自己被释放前,我们很难判断父函数的参数以及它的局部变量是否能被释放。实际上闭包函数的使用已经很普通,以致人们频繁的遇到这类问题时我们却束手无策。在详细了解了闭包背后的问题和一些特殊的闭包泄漏示例后,我们将结合循环引用的图示找到闭包的所在,并找出这些不受欢迎的引用来至何处。 普通的循环引用,是两个不可探知的对象相互引用造成的,但是闭包却不同。代替直接造成引用,闭包函数则取而代之从其父
43、函数作用域中引入信息。通常,函数的局部变量和参数只能在该被调函数自身的生命周期里使用。当存在闭包函数后,这些变量和参数的引用会和闭包函数一起存在,但由于闭包函数可以超越其父函数的生命周期而存在,所以父函数中的局部变量和参数也仍然能被访问。在下面的示例中,参数1将在函数调用终止时正常被释放。当我们加入了一个闭包函数后,一个额外的引用产生,并且这个引用在闭包函数释放前都不会被释放。如果你碰巧将闭包函数放入了事件之中,那么你不得不手动从那个事件中将其移出。如果你把闭包函数作为了一个expando属性,那么你也需要通过置null将其清除。 同时闭包会在每次调用中创建,也就是说当你调用包含闭包的函数两
44、次,你将得到两个独立的闭包,而且每个闭包都分别拥有对参数的引用。由于这些显而易见的因素,闭包确实非常容易带来泄漏。下面的示例将展示使用闭包的主要泄漏因素:
注意这段话和下面的例子:如果你对怎么避免这类泄漏感到疑惑,我将告诉你处理它并不像处理普通循环引用那么简单。"闭包"被看作函数作用域中的一个临时对象。一旦函数执行退出,你将失去对闭包本身的引用,那么你将怎样去调用detachEvent方法来清除引用呢?在Scott Isaacs的MSN Spaces上有一种解决这个问题的有趣方法。这个方法47、使用一个额外的引用(原文叫second closure,可是这个示例里致始致终只有一个closure)协助window对象执行onUnload事件,由于这个额外的引用和闭包的引用存在于同一个对象域中,于是我们可以借助它来释放事件引用,从而完成引用移除。为了简单起见我们将闭包的引用暂存在一个expando属性中,下面的示例将向你演示释放事件引用和清除expando属性。
3





