首先咱们须要理解,内存是什么。简单来说,内存存储了计算机运行过程的须要的所有数据,也就是计算机正在使用的所有数据。咱们须要合理的使用内存,防止内存被大量无用数据占用,同时也要防止访问和修改与当前程序无关的内存区域。内存主要包括如下几个部分: 内核数据区域,栈区,共享库映像,堆区,可读写区域,只读区域。学习javascript,咱们不须要理解内存和cache,内存和I/O之间具体工做原理,但咱们须要了解掌握如何合理的使用内存,合理的分配释放内存。javascript
Javascript 是那些被称做垃圾回收语言当中的一员。垃圾回收语言经过周期性地检查那些以前被分配出去的内存是否能够从应用的其余部分访问来帮助开发者管理内存。换句话说,当计算机发现有的内存已经不能被访问到了,就会把它们标记为垃圾。开发者只须要知道一块已分配的内存是否会在未来被使用,而不可访问的内存能够经过算法肯定并标记以便返还给操做系统。java
js中的变量除了6个基本类型之外,其他的都是对象。也就说基本类型在赋值是传递的是值,也就是原来数据的一份拷贝。基本类型包括number、string、boolean、symbol、null、undefined.
用2个例子来理解一下:算法
var a = 10; //基本类型 var b = a; //a把10拷贝一份,把这个拷贝给b a = 20; //修改了a,不影响a的拷贝 console.log(a); //20 console.log(b); //10
var a = {num: 20}; //不是基本类型 var b = a; //这里没有任何拷贝工做,b指向和a彻底一致的同一块内存 b.num = 15; //因为b和a指向同一块内存,因此b.num修改了等同于a.num修改了 console.log(a.num); //15 console.log(b.num); //15 //进一步理解 b = {age: 10}; //等号右边定义了一个新的对象,产生的新的内存分配,此时b指向了这块新的内存,a仍是指向原来那块内存 console.log(a); //{num: 15} console.log(b); //{age: 10}
值得强调的是:在函数参数传递的时候和返回值时同样遵照这个传递规则,这是构成闭包的基础条件数组
//一个反例 var num1 = 10; var num2 = 20; function swap(a, b){ var temp = a; a = b; b = temp; } swap(num1, num2); console.log(num1); //10 console.log(num2); //20
以上代码不能如愿的把2个传入变量的值交换,由于基本类型在参数传递时也是值传递,及a,b是num1,num2的拷贝,不是num1和num2自己。固然实现交换的方法不少,在不引入第三个变量状况下,不用单独写一个函数。浏览器
//实现交换a,b //方法1: var temp = a; a = b; b =temp; //方法2: a = a + b; b = a - b; a = a - b; //方法3: a = [b, b = a][0]; //方法4(仅适用于整数交换): a = a ^ b; // ^表示异或运算 b = a ^ b; a = a ^ b; //方法5: [a, b] = [b, a]; //解构赋值
var inc = function(){ var x = 0; return function(){ //返回一个非基本类型 console.log(x++); }; }; inc1 = inc(); //inc1是闭包内匿名函数的引用,因为该引用存在,匿名函数引用计数不为0,因此inc做用域对应的内存不能释放,闭包造成 inc1(); //0 inc1(); //1
当对象的属性是对象的时候,简单地赋值致使改属性传递的是另外一个对象属性的引用,这样的拷贝是浅拷贝,存在安全风险。咱们应该递归的拷贝对象属性的每一个对象,造成深拷贝。方法以下:安全
//浅拷贝与深拷贝 var o = { name: "Lily", age: 10, addr:{ city: "Shenzheng", province: "Guangdong" }, schools: ["primaryS", "middleS", "heightS"] }; var newOne = copy(o); console.log(o); //Object {name: "Lily", age: 10, addr: Object} console.log(newOne); //Object {name: "Lily", age: 10, addr: Object} newOne.name = "Bob"; console.log(newOne.name); //"Bob" console.log(o.name); //"Lily" newOne.addr.city = "Foshan"; console.log(newOne.addr.city); //"Foshan" console.log(o.addr.city); //"Foshan" function copy(obj){ var obj = obj || {}; var newObj = {}; for(prop in obj){ if(!obj.hasOwnProperty(prop)) continue; newObj[prop] = obj[prop]; //当obj[prop]不是基本类型的时候,这里传的时引用 } return newObj; } var newOne = deepCopy(o); console.log(o); //Object {name: "Lily", age: 10, addr: Object} console.log(newOne); //Object {name: "Lily", age: 10, addr: Object} newOne.name = "Bob"; console.log(newOne.name); //"Bob" console.log(o.name); //"Lily" newOne.addr.city = "Foshan"; console.log(newOne.addr.city); //"Foshan" console.log(o.addr.city); //"Shenzheng" newOne.schools[0] = "primatrySchool"; console.log(newOne.schools[0]); //"primatrySchool" console.log(o.schools[0]); //"primatryS" function deepCopy(obj){ var obj = obj || {}; var newObj = {}; deeply(obj, newObj); function deeply(oldOne, newOne){ for(var prop in oldOne){ if(!oldOne.hasOwnProperty(prop)) continue; if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){ newOne[prop] = oldOne[prop].constructor === Array ? [] : {}; deeply(oldOne[prop], newOne[prop]); } else newOne[prop] = oldOne[prop]; } } return newObj; }
不一样的变量定义方式,会致使变量不能被删除,内存没法释放。闭包
// 定义三个全局变量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }()); // 试图删除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 测试该删除 typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
很明显,经过var定义的变量没法被释放。app
垃圾回收(Garbage Collection),简称GC。简单来说,GC就是把内存中不须要的数据释放了,这样这部份内存就能够存放其余东西了。在javascript中,若是一个对象再也不被引用,那么这个对象就会被GC回收。具体回收策略包括如下3种:dom
当从window节点遍历DOM树不能遍历到某个对象,那么这个对象就会被标记为没用的对象。因为回收机制是周期性执行的,这样,当下一个回收周期到来时,这个对象对应的内存就会被释放。函数
当系统中定义了一个对象后,对于这一块内存,javascript会记录有多少个引用指向个部份内存,若是这个数为零,则这部份内存会在下一个回收周期被释放。
就比如上一个例子中,利用delete关键字删除变量或属性,达到释放内存的目的。分一下几种状况:
//释放一个对象 obj = null; //释放是个对象属性 delete obj.propertyName; delete globalVariable; //没有用var声明的变量是window的属性,用delete释放。 //释放数组 array.length = 0; //释放数组元素 array.splice(2,2); //删除并释放第三个元素起的2个元素
不过须要注意的是, 这几个GC策略是同时做用的:
var o1 = {}; //开辟一块内存放置对象,并用o1指向它 var o2 = o1; //o2指向与o1同一个内存区域 console.log(o1); //{} console.log(o2); //{} o2 = null; //标记o2为没用的对象 console.log(o2); //null console.log(o1); //{} 因为还有o1指向这个内存区域,引用计数不为零,因此内存并无被释放 o1 = null; //引用计数为0, 内存释放
若是你访问了已经被回收了的内存,会发生不可预计的严重后果。好比一段内存被释放了,可能里面的值就不是原来的值了,你还要拿来用那不是本身找错误吗?更严重的就是你修改了其余程序的数据!!!咱们将这样的变量叫作野指针(wild pointer)。为了不这样的也只能出现,也为了节省计算机资源,咱们须要防止内存泄露(memory leak)。
内存泄漏也称做存储渗漏,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果致使一直占据该内存单元,直到程序结束。简单来讲就是该内存空间使用完毕以后未回收。
内存泄露是每一个开发者最终都不得不面对的问题。即使使用自动内存管理的语言,你仍是会碰到一些内存泄漏的状况。内存泄露会致使一系列问题,好比:运行缓慢,崩溃,高延迟,甚至一些与其余应用相关的问题。
//反例 var parent = document.getElementById("parent"); var first = parent.firstChild(); while(first){ //循环屡次触发reflow,效率过低 parent.removeChild(first); //在旧的浏览器上会致使内存泄漏 first = parent.firstChild(); } //正解 document.getElementById("parent").innerHTML = “”;
var ele = document.getElementById("eleID"); ele.onclick = function fun(){ //Do stuff here } //... ele.onclick = null; //删除元素前取消全部事件,jQuery中也是在删除节点前利用removeEventListen去除了对应事件 ele.parentNode.removeChild(ele);
在严格模式下编写代码能够避免这个问题
//状况一: 函数中没有用var声明的变量 function fun1(){ name = "Mary"; //全局变量 } fun1(); //状况二: 构造函数没用new关键字调用 function Person(name){ this.name = name; } Person("Mary"); //函数内定义全局变量
//反例 setInterval(function(){ var ele = document.getElementById("eleID"); //改代码毎100毫秒会重复定义该引用 //Do stuff }, 100); //正解 setInterval(function(){ var ele = document.getElementById("eleID"); //改代码毎100毫秒会重复定义该引用 //Do stuff ele = null; }, 100);
//反例 //不妨认为这里的上下文是window function init(){ var el = document.getElementById('MyElement'); //这是一个DOM元素的引用,非基本类型 el.onclick = function(){ //el.onclick是function匿名函数的引用 alert(el.innerHTML); //funciton中访问了这个做用域之外的DOM元素引用el,致使el不能被释放 } } init(); //正解 function init(){ var el = document.getElementById('MyElement'); //这是一个DOM元素的引用,是非基本类型 var text = el.innerHTML; //字符串,是基本类型,解决alert(el.innerHTML)不能正常工做问题 el.onclick = function(){ //el.onclick是function匿名函数的引用 alert(text); //var声明的text是基本类型,没必要释放 } el = null; //手动释放,但会致使alert(el.innerHTML)不能正常工做 } init(); //若是函数结尾要return el,用如下方法释放el //正解 function init(){ var el = document.getElementById('MyElement'); //这是一个DOM元素的引用,是非基本类型 var text = el.innerHTML; //字符串,是基本类型,解决alert(el.innerHTML)不能正常工做问题 el.onclick = function(){ //el.onclick是function匿名函数的引用 alert(text); //var声明的text是基本类型,没必要释放 } try{ return el; } finally { el = null; //手动释放,但会致使alert(el.innerHTML)不能正常工做 } } init();
function create() { var parent = document.getElementById('parent'); for (var i = 0; i < 5000; i++) { var el = document.createElement('div'); el.innerHTML = "test"; gc.appendChild(el); //这里释放了内存 } }
//构成一个循环引用 var o1 = {name: "o1"}; var o2 = {name: "o2"}; o1.pro = o2; o2.pro = o1; //这种状况须要手动清理内存,在不须要的时候把对象置为null或删除pro属性