原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值 到另一个变量时,原始值会被复制到新变量的位置。请看下面的例子:
let num1 = 5;
let num2 = num1;
这里,num1 包含数值 5。当把 num2 初始化为 num1 时,num2 也会得到数值 5。这个值跟存储在 num1 中的 5 是完全独立的,因为它是那个值的副本。
这两个变量可以独立使用,互不干扰。
在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区 别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际 上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来,如下面的例子所示:
let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // "Nicholas"
在这个例子中,变量 obj1 保存了一个新对象的实例。然后,这个值被复制到 obj2,此时两个变 3 量都指向了同一个对象。在给 obj1 创建属性 name 并赋值后,通过 obj2 也可以访问这个属性,因为它们都指向同一个对象。
1. 传递参数:
ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数 中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是 引用值,那么就跟引用值变量的复制一样。对很多开发者来说,这一块可能会不好理解,毕竟变量有按 值和按引用访问,而传参则只有按值传递。
在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用 ECMAScript 的话说, 就是 arguments 对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变 量,这意味着对本地变量的修改会反映到函数外部。
function addTen(num) {
num += 10;
return num;
}
let count = 20;
函数 addTen()有一个参数 num,它其实是一个局部变量。在调用时,变量 count 作为参数 传入。count 的值是 20,这个值被复制到参数 num 以便在 addTen()内部使用。在函数内部,参数 num 的值被加上了 10,但这不会影响函数外部的原始变量 count。参数 num 和变量 count 互不干扰,它们 只不过碰巧保存了一样的值。
如果 num 是按引用传递的,那么 count 的值也会被修改为 30。这个事实 在使用数值这样的原始值时是非常明显的。
function setName(obj) {
obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
创建了一个对象并把它保存在变量 person 中。然后,这个对象被传给 setName() 方法,并被复制到参数 obj 中。在函数内部,obj 和 person 都指向同一个对象。结果就是,即使对象 是按值传进函数的,obj 也会通过引用访问对象。
当函数内部给 obj 设置了 name 属性时,函数外部的 对象也会反映这个变化,因为 obj 指向的对象保存在全局作用域的堆内存上。很多开发者错误地认为, 当在局部作用域中修改对象而变化反映到全局时,就意味着参数是按引用传递的。为证明对象是按值传 递的。
function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
前后唯一的变化就是 setName()中多了两行代码,将 obj 重新定义为一个有着不同 name 的新对象。当 person 传入 setName()时,其 name 属性被设置为"Nicholas"。然后变量 obj 被设置 为一个新对象且 name 属性被设置为"Greg"。如果 person 是按引用传递的,那么 person 应该自动将 指针改为指向 name 为"Greg"的对象。
可是,当我们再次访问 person.name 时,它的值是"Nicholas", 这表明函数中参数的值改变之后,原始的引用仍然没变。当 obj 在函数内部被重写时,它变成了一个指 向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。