闭包
- 闭包的定义:当内部的函数被保存到外部时,将会生成闭包,闭包会导致原有的作用域链不释放,造成内存泄漏。
- 闭包的好处:
- 变量长期驻扎在内存中
- 避免污染全局变量
- 私有成员的存在
- 闭包的坏处:
- 增大内存的使用量
- 容易造成内存泄漏
- 闭包的作用/使用场景
- 实现共有变量 =》 做累加器
- 代码实现
function add() { var count = 0; function demo() { count ++; console.log(count); } return demo; } var counter = add(); counter(); counter();
- 代码实现
- 可以做缓存
- 代码实现
function eater() { var food = ''; var obj = { eat: function() { console.log('i am eating' + ' ' + food); food = ''; }, push: function(myFood) { food = myFood; } } return obj; } var eater1 = eater(); eater1.push('banana'); eater1.eater();
- 代码实现
- 可以实现封装 属性私有化
- 代码实现
function Hang(name, wife) { var prepareWife = 'xiaozhang'; this.name = name; this.wife = wife; this.divorce = function() { this.wife = prepareWife; } this.changePrepareWife = function(target) { prepareWife = target; } this.sayPrepareWife = function() { console.log(prepareWife); } } var deng = new Hang('deng', 'xiaoliu'); deng.prepareWife; deng.sayPrepareWife();
- 代码实现
- 实现共有变量 =》 做累加器
- 闭包的防御
- 闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应该尽量防止这种情况发生。
- 解决闭包的方法
- 使用立即执行函数
立即执行函数
- 立即执行函数定义:此类函数没有声明,在一次执行过后释放,适合做初始化工作。
- 立即执行函数和普通函数的区别:立即执行函数执行完就被释放
内存泄漏
- 内存泄漏定义:应用程序不再需要占用的时候,由于某些原因,内存没有被操作系统或可用内存池回收。
- 内存泄漏的例子:
- 意外的全局变量
- 在函数内未声明的变量就赋值,这样会在全局对象创建一个新的变量。
function bar() { say = 'hehe'; } 即== function bar() { window.say ='hehe'; }
- 或者是使用this创建了全局的变量
function foo() { this.name = 'hehe'; } foo();
- 在函数内未声明的变量就赋值,这样会在全局对象创建一个新的变量。
- 被遗忘的计时器或回调函数
- 使用计时器setInterval()未清除,在老版本的IE6是无法处理循环引用的,会造成内存泄漏。
- 脱离DOM的引用的
- 闭包
- 意外的全局变量
- 如何识别的内存泄漏
- Chrome
- Timeline
- Profiles
- Node
- Chrome
- 怎么解决
- 手动释放
- 代码实现
var arr = [1, 2, 3]; arr = null;
- 代码实现
- 使用弱引用(weakset和weakmap)
- 优点:WeakMap里面对element的引用就是弱引用,不会被计入垃圾回收机制的。也就是说一旦消除对该节点的引用,它的占用内存就会被垃圾回收机制释放。WeakMap保存的这个键值对,也会自动消失。
- 代码实现
const vm = new WeakMap(); const element = document.getElementById('example'); vm.set(element, 'something'); vm.get(element);
- 手动释放
垃圾回收机制
- 定义:
- 找到内存空间中的垃圾。
- 回收垃圾,让程序员能再次利用这部分的空间。
- 常用的垃圾回收算法
- 引用计数法
- 定义:跟踪记录每个值被引用的次数。
- 优点:
- 可即刻回收:当被引用数值为0,对象马上会把自己作为空闲空间连到空闲链表上。也就是说,在变成垃圾的时候就立刻被回收。
- 因为是即使回收,那么程序不会暂停去单独使用很长一段时间的GC,那么最大暂停时间很短。
- 不用去遍历堆里面的所有活动对象和非活动对象。
- 缺点:
- 计数器需要占很大的位置:因为不能预估被引用的上限,打个比方,可能出现32位即2的32次方个对象同时引用一个对象,那么计时器就需要32位。
- 最大的劣势是无法解决循环引用无法回收的问题,这就是IE之前出现的问题。
- 代码示例
var a = new Object(); var b = a; a = null; b = null;
- 标记清除法(在V8引擎使用最多的)
- 定义:
- 标记阶段:把所有活动对象做上标记
- 清除阶段:把没有标记(也就是说非活动对象)销毁
- 优点:
- 实现简单,打标记用一位二进制就可以表示
- 解决了循环引用的问题
- 缺点:
- 造成碎片化(有点类似磁盘的碎片化)
- 再分配时遍历次数多,如果一直没有找到合适的内存块大小,那么会遍历空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端。
- 定义:
- 复制算法
- 将一块内存空间分为两部分,分别为From空间和To空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入From空间中,当From空间被占满的时,新生代GC就会启动了。算法就检查From空间中存活的对象复制到To空间中,如果有失活的对象就会销毁,当赋值完成后将From,空间和To空间互换,这样GC就结束了。
- 引用计数法
- 减少JavaScript中垃圾回收
- 对象优化
- 最大限度复用对象,不用尽可能设置为null,尽快被垃圾回收掉。
var obj = {}; for(var i = 0; i < 10; i++) { // var obj = {}; // 这样每次都会创建一个对象 obj.age = 19; obj.name = 'hehe'; console.log(obj); }
- 最大限度复用对象,不用尽可能设置为null,尽快被垃圾回收掉。
- 数组Array优化
- 将[]赋值给一个数组对象,是清空数组的捷径,但是这样又创建一个新的空对象,并且将原来的数组对象变成了一片小内存垃圾。
var arr = [1, 2, 3]; arr = [];
- 将数组长度赋值为0,也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生。
var arr = [1, 2, 3]; arr.length = 0;
- 将[]赋值给一个数组对象,是清空数组的捷径,但是这样又创建一个新的空对象,并且将原来的数组对象变成了一片小内存垃圾。
- 方法function优化
- 例如在游戏的主循环中,setTimout或requestAnimationFrame来调用一个成员方法是很常见的。每次调用都返回一个新的方法对象,这就导致了大量的方法对象垃圾。为了解决这个方法,可以将作为返回值的方法保存起来。
function say() { console.log('hehe'); } setTimeout((function(self) { return function() { self.say(); } })(this), 16) // 优化 this.sayFunc = (function(self) { return function() { self.say(); } })(this); setTimout(this.sayFunc, 16)
- 例如在游戏的主循环中,setTimout或requestAnimationFrame来调用一个成员方法是很常见的。每次调用都返回一个新的方法对象,这就导致了大量的方法对象垃圾。为了解决这个方法,可以将作为返回值的方法保存起来。
- 对象优化