JavaScript注意事项及引起的内存泄漏原因_第1页
JavaScript注意事项及引起的内存泄漏原因_第2页
JavaScript注意事项及引起的内存泄漏原因_第3页
JavaScript注意事项及引起的内存泄漏原因_第4页
JavaScript注意事项及引起的内存泄漏原因_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

JavaScript 注意事项及引起的内存泄漏注意事项及引起的内存泄漏原原 因因 一 一 JavaScript 中编码时要注意的几点中编码时要注意的几点 1 尽量不要使用尽量不要使用 WITH 尽管很方便 with 需要附加的查找引用时间 因为它在编译的时候并不知 道作用域的上下没 有坏味道的代码 代码如下 with test object foo Value of foo property of object bar Value of bar property of object 性能优化后 代码如下 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 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 异常处理 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 undefined true 解决方案注 在编程实践中 null 几乎没用 对一个对象做非空判断时尽 量使用 undefined 4 全局变量难以控制全局变量难以控制 Javascript 的全局变量 在所有模块中都是可见的 任何一个函数内部都可 以生成全局变量 这大大加剧了程序的复杂性 a 1 function b 2 alert a 1 alert b 2 解决方案注 声明变量时必须使用 var 关键字 尤其在方法内 建议使用 命名空间 var namespace namespace name Frank namespace myMethod function do something 5 设置设置 setTimeout 和和 setInterval 时传递函数名而不是字符串时传递函数名而不是字符串 如果你传递一个字符串到 setTimeout 或者 setInterval 中 字符串将会被 eval 计算而导致缓慢 使用一个匿名函数包装来代替 这样在编译的时候就可以被解释和优化 有坏味道的代码 setInterval doSomethingPeriodically 1000 setTimeOut doSomethingAfterFiveSeconds 5000 性能优化后 示例 代码如下 setInterval doSomethingPeriodically 1000 setTimeOut doSomethingAfterFiveSeconds 5000 6 自动插入行尾分号自动插入行尾分号 Javascript 的所有语句 都必须以分号结尾 但是 如果你忘记加分号 解 释器并不报错 而是为你自动加上分号 有时候 这会导致一些难以发现的错 误 比如 下面这个函数根本无法达到预期的结果 返回值不是一个对象 而 是 undefined function return i 1 原因是解释器自动在 return 语句后面加上了分号 function return i 1 正确写法是 Function return i 1 返回一个对象 解决方案注 谨记所有语句都必须以分号结尾 别让编译器帮倒忙 7 NaN NaN 是一种数字 表示超出了解释器的极限 它有一些很奇怪的特性 NaN NaN false NaN NaN true alert 1 NaN NaN 与其设计 NaN 不如解释器直接报错 反而有利于简化程序 解决方案注 在进行数值判断时先判断是否空串 然后用 isNaN 函数 另 外列出一些不太直观的执行结果 isNaN false false isNaN false isNaN new Array false 8 避免使用全局变量 如果你在一个函数或者其它作用域中使用全局变量 脚本引擎需要遍历整 个作用域去查找他们 全局作用域中的变量在脚本的生命周期里都存在 然后局部范围的会在局 部范围失去的时候被销毁 有坏味道的代码 示例代码如下 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 数组和对象的区分数组和对象的区分 由于 Javascript 的数组也属于对象 object 所以要区分一个对象到底是 不是数组 相当麻烦 Douglas Crockford 的代码是这样的 if arr 解决方案注 在 JS 中 由于弱类型的原因 再加上 JS 数组很特殊 所以上 面这种方法对我们判断数组提供的方便 10 和和 用来判断两个值是否相等 当两个值类型不同时 会发生自动转换 得 到的结果非常不符合直觉 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 因此 推荐任何时候都使用 比较符 解决方案注 有些情况下需要精确判断 加上类型 时最好用 尽量避免弱类型造成的边界错误 11 Null 是个对象是个对象 JavaScript 众多类型中有个 Null 类型 它有个唯一的值 null 即它的字面量 定义为完全没有任何意义的值 其表现得像个对象 如下检测代码 alert typeof null 弹出 object 尽管 typeof 值显示是 object 但 null 并不认为是一个对象实例 要知道 JavaScript 中的值都是对象实例 每个数值都是 Number 对象 每个对象都是 Object 对象 因为 null 是没有值的 所以 很明显 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 型值都会内置一个 boolean 标志 当这个值被要求有 boolean 行为的时候 这个内置布尔值就会出 现 例如当你要跟 Boolean 型值比对的时候 因为苹果不能和梨做比较 所以当 JavaScript 两个不同类型的值要求做比较 的时候 它首先会将其弱化成相同的类型 false undefined null 0 NaN 都弱 化成 false 这种强制转化并不是一直存在的 只有当作为表达式使用的时候 看下面这个简单的例子 var someVar 0 alert someVar false 显示 true 上面测试中 我们试图将数值 0 和 boolean 值 false 做比较 因两者的数据 类型不兼容 JavaScript 自动强制转换成统一的等同的 truthy 和 falsy 其中 0 等 同于 false 正如上面所提及的 你可能注意到了 上面一些等同 false 的值中并没有空数组 只因空数组是 个怪胚子 其本身实际上属于 truthy 但是当空数组与 Boolean 型做比较的时候 其行为表现又属于 falsy 不解 这是有原因的 先举个例子验证下空数组的奇 怪脾气 var someVar 空数组 alert someVar false 结果 true if someVar alert hello alert 语句执行 所以 someVar 当作 true 译者注 之所以会有这种差异 根据作者的说法 数组内置 toString 方法 例如直接 alert 的时候 会以 join 的形式弹出字符串 空数组自然就是空 字符串 于是等同 false 具体可参见作者另外一篇文章 Twisted logic understanding truthy alert someVar false 结果 true 0 属于 falsy alert someVar false 结果 false zero 是个数值 不是布尔值 解决方案注 数组的 length 等于 0 时为 false 但用 if 判断条件时是 true 13 避免在性能要求关键的函数中使用 for in for in 循环需要脚本引擎建立一张所有可枚举属性的列表 并检查是否与先 前的重复 如果你的 for 循环作用域中的代码没有修改数组 可以预先计算好数组的 长度用于在 for 循环中迭代数组 有坏味道的代码 示例代码如下 var 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 用 替换所有的数字 这是一个简单的替换 一个字符串 一个星号 但是 如果我们希望在替 换发生的时候有更多的控制 该怎么办呢 我们只希望替换 30 以下的数值 该 怎么办呢 此时如果仅仅依靠正则表达式是鞭长莫及的 我们需要借助回调函 数的东风对每个匹配进行处理 alert 10 13 21 48 52 replace d g function match return parseInt match 30 match 当每个匹配完成的时候 JavaScript 应用回调函数 传递匹配内容给 match 参数 然后 根据回调函数里面的过滤规则 要么返回星号 要么返回匹配本 身 无替换发生 解决方案注 关键是回调函数的使用 根据自己的业务进行实现 没有定论 15 正则表达式 不只是正则表达式 不只是 match 和和 replace 不少 javascript 工程师都是只通过 match 和 replace 和正则表达式打交道 但 JavaScript 所定义的正则表达式相关方法远不止这两个 其中值得一提的是 test 其工作方式类似 match 但是返回值却不一样 test 返回的是布尔型 用来验证是否匹配 执行速度高于 match alert w 3 test Hello 弹出 true 上面行代码用来验证字符串是否有三个以上普通字符 显然 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 这儿 我们基于参数 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 属性的认识属性的认识 如下定义个集合对象 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 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 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 引用 不要这么做 示例代码如下 var car new Object car color red car type sedan 更好的一种形式 示例代码如下 var car color red type sedan 20 undefined 我们以一个和风细雨的小古怪结束 听起来可能有点奇怪 undefined 并不 是 JavaScript 中的保留字 尽管它有特殊的意义 并且是唯一的方法确定变量是 否未定义 因此 var someVar alert someVar undefined 显示 true 目前为止 一切看上去风平浪静 正常无比 但剧情总是很狗血 undefined I m not undefined var someVar alert someVar undefined 显示 false 这就是为什么 jQuery 源码中最外部的闭包函数要有个并没有传入的 undefined 参数 目的就是保护 undefined 不要被外部的些不良乘虚而入 解决方案注 绝对不能对 undefined 赋值 二 二 IE 下的下的 JS 编程需注意的内存释放问题编程需注意的内存释放问题 1 给给 DOM 对象添加的属性是一个对象的引用 对象添加的属性是一个对象的引用 范例 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 new Encapsulator document getElementById myDiv 解决方法 解决方法 在 onunload 事件中写上 document getElementById myDiv myProp null 3 给给 DOM 对象用对象用 attachEvent 绑定事件 绑定事件 范例 function doClick element attachEvent onclick doClick 解决方法 解决方法 在 onunload 事件中写上 element detachEvent 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 document createElement div var childDiv document createElement div document body appendChild parentDiv parentDiv appendChild childDiv 5 反复重写同一个属性会造成内存大量占用反复重写同一个属性会造成内存大量占用 但关闭但关闭 IE 后内存会被释后内存会被释 放放 范例 function foo for i 0 i 5000 i hostElement text function 这种方式相当于定义了 5000 个属性 解决方法 解决方法 就是编程的时候尽量避免出现这种情况 6 说明说明 以上资料均来源于微软官方的 MSDN 站点 链接地址 e leak patterns asp 对于第一条 事实上包括 element onclick funcRef 这种写法也算在其中 因为这也是一个对对象的引用 在页面 onunload 时应该释放掉 在实际编程中 这些内存问题的实际影响并不大 尤其是给客户使用时 客户对此绝不会有察觉 然而这些问题对于程序员来说却始终是个心病 有 这样的 BUG 心里总会觉得不舒服吧 能解决则给与解决 这样是最好的 在 这样顶级的 JS 源码站点中 在它们的源码里都会看到采用上述解 决方式进行内存的释放管理 三 三 理解并解决理解并解决 IE 的内存泄漏方式的内存泄漏方式 1 泄露方式泄露方式 主要两种 闭包和页面元素的创建 1 循环引用 IE 浏览器的 COM 组件产生的对象实例和网页脚本引擎产生的对 象实例相互引用 就会造成内存泄漏 2 内部函数引用 Closures 可以看成是目前引起大量问题的循环应用的一种 特殊形式 由于依赖指定的关键字和语法结构 Closures 调用是比较容易被 我们发现的 3 页面交叉泄漏 页面交叉泄漏主要是父子页面的嵌套 如 IFrame 等引起 DOM 元素的插入顺序也会造成泄露 1 循环引用 通常情况 脚本引擎通过垃圾收集器 GC 来处理循环引用 但是某些未 知因数可能会妨碍从其环境中释放资源 对于 IE 来说 某些 DOM 对象实 例的状态是脚本无法得知的 引起的泄漏问题基于 COM 的引用计数 脚本引擎对象会维持对 DOM 对象的引用 并在清理和释放 DOM 对象指针前等待所有引用的移除 在我 们的示例中 我们的脚本引擎对象上有两个引用 脚本引擎作用域和 DOM 对象的 expando 属性 当终止脚本引擎时第一个引用会释放 DOM 对象引 用由于在等待脚本擎的释放而并不会被释放 你可能会认为检测并修复假 设的这类问题会非常的容易 但事实上这样基本的的示例只是冰山一角 你可能会在 50 个对象链的末尾发生循环引用 这样的问题排查起来将会是 一场噩梦 如果你仍不清楚这种泄漏方式在 HTML 代码里到底怎样 你可以通过 一个全局脚本变量和一个 DOM 对象来引发并展现它 示例代码 var myGlobalObject function SetupLeak myGlobalObject document getElementById MyDiv document getElementById MyDiv expandoProperty myGlobalObject function BreakLeak document getElementById MyDiv expandoProperty null 可以使用直接赋 null 值得方式来破坏该泄漏情形 在页面文档卸载前赋 null 值 将会让脚本引擎知道对象间的引用链没有了 现在它将能正常的清理 引用并释放 DOM 对象 作为一个基本的情形 循环引用可能还有更多不同的复杂表现 对基 于对象的 JScript 一个通常用法是通过封装 JScript 对象来扩充 DOM 对象 在构建过程中 你常常会把 DOM 对象的引用放入 JScript 对象中 同时在 DOM 对象中也存放上对新近创建的 JScript 对象的引用 你的这种应用模式 将非常便于两个对象之间的相互访问 这是一个非常直接的循环引用问题 但是由于使用不用的语法形式可能并不会让你在意 要破环这种使用情景 可能变得更加复杂 当然你同样可以使用简单的示例以便于清楚的讨论 2 闭包函数 由于闭包函数会使程序员在不知不觉中创建出循环引用 所以它对资源泄 漏常常有着不可推卸的责任 而在闭包函数自己被释放前 我们很难判断 父函数的参数以及它的局部变量是否能被释放 实际上闭包函数的使用已 经很普通 以致人们频繁的遇到这类问题时我们却束手无策 在详细了解 了闭包背后的问题和一些特殊的闭包泄漏示例后 我们将结合循环引用的 图示找到闭包的所在 并找出这些不受欢迎的引用来至何处 普通的循环引用 是两个不可探知的对象相互引用造成的 但是闭包 却不同 代替直接造成引用 闭包函数则取而代之从其父函数作用域中引 入信息 通常 函数的局部变量和参数只能在该被调函数自身的生命周期 里使用 当存在闭包函数后 这些变量和参数的引用会和闭包函数一起存 在 但由于闭包函数可以超越其父函数的生命周期而存在 所以父函数中 的局部变量和参数也仍然能被访问 在下面的示例中 参数 1 将在函数调 用终止时正常被释放 当我们加入了一个闭包函数后 一个额外的引用产 生 并且这个引用在闭包函数释放前都不会被释放 如果你碰巧将闭包函 数放入了事件之中 那么你不得不手动从那个事件中将其移出 如果你把 闭包函数作为了一个 expando 属性 那么你也需要通过置 null 将其清除 同时闭包会在每次调用中创建 也就是说当你调用包含闭包的函数两 次 你将得到两个独立的闭包 而且每个闭包都分别拥有对参数的引用 由于这些显而易见的因素 闭包确实非常容易带来泄漏 下面的示例将展 示使用闭包的主要泄漏因素 function AttachEvents element This structure causes element to ClickEventHandler elment attachEvent onclick ClickEventHandler function ClickEventHandler This closure refs elment function SetupLeak 每次执行总会产生泄露 AttachEvents document getElementById MyDiv function BreakLeak 下一个例子解决闭包造成的问题 由于ClickEventHandler 是个内部对象 所以在这个例子中无 法去释放它 所以 下个例子做了正确的处理 注意这段话和下面的例子注意这段话和下面的例子 如果你对怎么避免这类泄漏感到疑惑 我将 告诉你处理它并不像处理普通循环引用那么简单 闭包 被看作函数作用域 中的一个临时对象 一旦函数执行退出 你将失去对闭包本身的引用 那 么你将怎样去调用 detachEvent 方法来清除引用呢 在 Scott Isaacs 的 MSN Spaces 上有一种解决这个问题的有趣方法 这个方法使用一个额外的引用 原文叫 second closure 可是这个示例里致始致终只有一个 closure 协助 window 对象执行 onUnload 事件 由于这个额外的引用和闭包的引用存在 于同一个对象域中 于是我们可以借助它来释放事件引用 从而完成引用 移除 为了简单起见我们将闭包的引用暂存在一个 expando 属性中 下面 的示例将向你演示释放事件引用和清除 expando 属性 function AttachEvents element in order to remove this we need to put it somewhere Creates another ref element expandoClick ClickeEventHandler This structure causes element to ref ClickEventHandler elment attachEvent onclick element expandoClick function ClickEventHandler This closure refs elment function SetupLeak The leak hanppens all at once AttachEvents document getElementById MyDiv function BreakLeak ClickeEventHandler是AttachEvents函数内部对象 让div元素的 expandoClick属性引用它才能在外部对其使用 document getElementById MyDiv detachEvent onclick document getElement ById MyDiv expandoClick document getElementById MyDiv expandoClick null 3 交叉泄漏 这种基于插入顺序而常常引起的泄漏问题 主要是由于对象创建过程中 的临时对象未能被及时清理和释放造成的 它一般在动态创建页面元素 并将其添加到页面 DOM 中时发生 一个最简单的示例场景是我们动态创建 两个对象 并创建一个子元素和父元素间的临时域 注 页面元素之间层次 结构关系由

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论