内存问题是 JavaScript 比较底层的东西, 依葫芦画瓢学会了怎么使用变量, 但是对于内存的概念依然模糊, 今天让我们一起来了解一下内存在这门语言是怎么样的存在.
内存在不同类型的数值面前表现有很大的不同. 我们把值赋给一个变量, 解析器必须确定这个值是什么类型, 先来了解变量的两个类型:
基本类型: 简单的数据段: Undefined,Null,Boolean,Number,String, 按值访问, 可以直接操作实际的值.
引用类型: 保存在内存中的对象: Object,Array..JavaScript 赋值保存着对象的某个变量时, 操作的是对象的引用; 在为对象添加属性的时候, 操作的是实际的对象.
复制基本类型的数据时, 计算机会重新分配一个位置给新的变量; 但是复制引用类型的数据, 计算机只是复制了一个指针, 指向原有的对象. 所以改变其中一个引用类型数据的属性时, 访问另一个引用类型数据的属性能得到一样的结果, 比如:
复制基本类型并改变其中一个变量:
- let a = 20;
- let b = a;
- b = 30;
- console.log(a) // 20
复制引用类型并改变其中一个变量的属性:
- let m = {
- a:10, b:20
- };
- let n = m;
- n.a = 15;
- console.log(m.a) // 15
m,n 都指向一个引用类型的对象, 所以改变 n 的属性会导致 m 的属性改变. 上面表示的是变量之间基本的复制, 但是注意:** 在所有函数的参数传递中, 都是按值传递的, 不是按照引用传递的 **. 比如:
- function setName(obj){
- obj.name = "Nicholas";
- obj = new Object();
- obj.name = "Greg";
- }
- let person = new Object();
- setName(person);
- alert(person.name); // "Nicholas"
JS 内存空间分为栈 (stack), 堆 (heap), 池 (一般也会归类为栈中). 其中「栈」存放基本类型变量, 遵循后进先出的原则;「堆」存放引用类型, 堆存取数据的方式, 则与书架与书非常相似, 知道名字就能取出来用; 池存放常量.
检测一个变量是不是基本类型, 用 typeof 操作符就可以搞定, 但是这个操作符在遇到对象或者 null 时, 返回 Object, 我们不知道具体的类型. 这时候, 用 instanceof 来确认是什么类型的对象.
内存泄漏与回收
不再用到的内存, 没有及时释放, 就叫做内存泄露.
大多数语言提供自动内存管理, 减轻程序员的负担, 这被称为 "垃圾回收机制"(garbage collector). 原理很简单: 找出那些不再继续使用的变量, 然后释放其占用的内存.
JavaScript 具有自动垃圾收集机制, 不用程序员操太多心. 而不同的浏览器可能会采取不同的回收策略, 现代浏览器最常用的方式是标记清除, 其次是引用计数.
标记清除. 垃圾收集器会给内存中的所有变量都添加标记, 然后清除一些还会被使用的标记, 即凡是环境中还会用到的变量, 被其他变量引用的变量. 还有标记的变量就会被垃圾收集器删除, 完成内存的清除工作.
引用计数. 原理也很简单, 跟踪每个变量被引用的次数. 这里会产生一个棘手的问题, 就是遇到 "循环引用" 就没招了. 解决的方法是不再使用的对象, 我们把它设置成空对象 Null . 看下面的例子:
- function(){
- let a = new Object();
- let b = new Object();
- a.oneObject = b;
- b.anotherObject = a;
- }
上面的代码中, a 和 b 通过各自的属性实现相互引用, 两者的被引用次数都是 2 . 如果采用标记清楚策略, 由于函数执行结束, 这两个对象都离开了作用域, 都会被清除. 但是, 采用引用计数策略, a 和 b 都还继续存在, 因为他们的引用次数永远不会是 0. 此时, 只有手动断开引用.
- function(){
- let a = new Object();
- let b = new Object();
- a.oneObject = b;
- b.anotherObject = a;
- // 消除循环:
- a.oneObject = null
- b.anotherObject = null;
- }
来源: https://www.cnblogs.com/kurryluo/p/10050920.html