出处:JavaScript 高级程序设计(第三版)第四章——变量、作用域和内存问题

一句话总结

JS 的方法参数是按值传递

前提

ES 变量分为两种数据类型的值:基本类型和引用类型

基本类型存储

基本类型的值在申请内存时是固定大小,所以保存在栈内存,故在复制基本类型变量时也是在栈内存中新开辟一份内存空间进行存储

引用类型存储

而引用类型变量的值大小不固定,且可任意改动,引用类型变量的本质是指向某一块内存区域的指针变量,故引用类型变量存储在内存自由分配的堆内存中,在对引用变量作直接复制(重新赋值)时也只是将新的变量指向相同的一片内存区域(即两个指针指向同一片堆内存空间)

引用类型探析

引用类型的值是存放在内存中的对象,但由于 JS 语言不允许直接访问内存中的位置(即不能直接操作对象的内存空间),故操作对象时实际是在操作对象的引用,而不是直接操作实际的对象本身(即引用类型的值是按引用访问的,这个引用可以理解为对象的句柄)

特别地

很多语言对于字符串是以对象形式进行表示,故为引用类型变量,但 ES 不是

详解

ES 中所有函数(方法)的参数都是按值传递的,即调用一个方法时,是将调用该方法时传入该方法的参数的复制给函数内部的参数(将实参的值复制给形参)

具体分类

JS 在访问变量时有按值和按引用两种方式,但参数只会按值传递

向参数传递基本类型的值

被传递的值会被复制给一个局部变量(这个局部变量就是形参,在 ES 中就是 arguments 对象的一个元素)

实例
function addTen(num) {
  num += 10;
  return num;
}
var count = 20;
var result = addTen(count);
// 看有没有影响到原变量
alert(count);
alert(result);

执行结果

20;
30;

向参数传递引用类型的值

JS 会把被传递的值的地址复制给一个局部变量,因为复制的是地址,所以在函数执行时,函数形参在函数内部改变时会影响到函数外部的该引用变量的值,因为两个地址指向同一片内存区域,但在函数执行结束,函数内部的局部变量被销毁,影响即会消失

实例
function setName(obj) {
  obj.name = "tom";
}
var person = new Object();
setName(person);
// 当把person传递给setName时,obj和person都指向相同的内存,所以对obj所指向的内存区域修改会影响到person
alert(person.name);

执行结果

tom;
分析

因为 person 指向的对象在堆内存中只存在一个,并且是全局对象

求证参数是按值传递而不是按引用传递

function setName(obj) {
  obj.name = "tom";
  obj = new Object();
  obj.name = "jerry";
}
var person = new Object();
setName(person);
// 当把person传递给setName时,obj和person都指向相同的内存,所以对obj所指向的内存区域修改会影响到person
alert(person.name);

执行结果

tom;
结论

函数内部重新生成的对象 obj,并对其新赋值 jerry 并没有改变函数外部 person 对应的属性值

如果向引用类型参数赋值是按引用赋值,那么 person 的 name 应该变为 jerry,因为假设形参 obj 拿到的是 person 的引用,而不是 person 引用的值,那么当函数内部生成新对象,并对 obj 进行重新指向时,形参 obj 的指向改变,外部的 person 的指向也应该改变,但是结果证明 alert(person.name)显示的依旧是 tom,所以即使函数参数是引用类型,也是按值传递

而实际上在函数内部重写 obj 时,这个变量引用的是一个局部对象变量,该局部对象会在函数执行完毕之时销毁

原文自 个人 github 博客,欢迎 star