内存生命周期
不管什么程序语言,内存生命周期基本是一致的:
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
在 JavaScript 中,最后一步是隐藏的、透明的。
JavaScript 的内存分配
- JavaScript 在定义变量时就完成了内存分配。
var a = 123; // 给数值变量分配内存
var b = "azerty"; // 给字符串分配内存
- 通过函数调用的内存分配
var c = new Date(); // 分配一个 Date 对象
var d = document.createElement('div'); // 分配一个 DOM 元素
- 有些方法分配新变量或者新对象
var e = "azerty";
var f = e.substr(0, 3); // f 是一个新的字符串
// 因为字符串是不变量,JavaScript 可能决定不分配内存,只是存储了 [0-3] 的范围。
值的使用
使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
当内存不再需要使用时释放(垃圾回收)
引用计数
此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
标记清除
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
当变量进入环境时,我们会标记为“进入环境,并且给它一个标示。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要进入到了相应的环境,那么我们的浏览器就有可能用到它们,而当变量离开环境时。则将其标记为离开环境;
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量及被环境中的变量引用的变量的标记。而在此之后再被加上离开环境时标记的变量讲被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了,最后,垃圾收集器完成内存的清除工作。销毁哪些带标记的值,并回收他们所占用的内存空间。
内存空间
- JavaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象,JavaScript的基础数据类型往往都会保存在变量对象中。
var a = 20;
var b = a;
b = 30;
// 这时a的值是20
- JS的引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 这时m.a的值是15
按值传递
按值传递就是把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
当传递 value 到函数 foo 中,相当于拷贝了一份 value,假设拷贝的这份叫 _value,函数中修改的都是 _value 的值,而不会影响原来的 value 值。
按引用的副本传递(按值传递)
按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。
var obj = {
value: 1
};
function foo(o) {
o.value = 2;
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
按引用传递是传递对象的引用,而函数中参数是传递对象的引用的副本!
var obj = {
value: 1
};
function foo(o) {
o = 2;
console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1
修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。
结论
参数如果是基本类型是按值传递,如果是引用类型按引用的副本传递。
但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递
所以ECMAScript中所有函数的参数都是按值传递的。