JS中的值类型和引用类型

如果将原始类型的值赋给变量,我们可以将该变量视为包含原始值。例如

const x = 10;
const y = 'abc';
const z = null;

x包含10. y包含'abc'。为了巩固这个想法,我们设想保留这些变量及其各自值在内存中的样子,如下:

在这里插入图片描述

当我们使用=将这些变量分配给其他变量时,我们将值复制到新变量。它们按值复制。

const x = 10;
const y = 'abc';
const a = x;
const b = y;
console.log(x, y, a, b); // -> 10, 'abc', 10, 'abc'

ax现在都包含10. by现在都包含'abc'。它们是分开的,因为原始值本身被复制了。
在这里插入图片描述
改变一个不会改变另一个,彼此是无关联的。

var x = 10;
var y = 'abc';
var a = x;
var b = y;
a = 5;
b = 'def';
console.log(x, y, a, b); // -> 10, 'abc', 5, 'def'

对象

分配了非原始值的变量将被赋予对该值的引用。该引用指向对象在内存中的位置。变量实际上不包含该值。

在计算机内存中的某个位置创建对象。当我们写arr = []时,我们在内存中创建了一个数组。变量arr接收的是该数组的地址和位置。

让我们假装地址是一个按值传递的新数据类型,就像数字或字符串一样。地址指向内存中通过引用传递的值的位置。就像字符串用引号(’'或“”)表示一样,地址将用箭头括号<>表示。 当我们分配和使用引用类型变量时,我们编写和看到如下图所示:

var arr = [];
arr.push(1);

在这里插入图片描述
在这里插入图片描述
请注意,变量arr包含的值(地址)是静态的。内存中的数组是变化的。

当我们使用arr做某事时,例如push一个值,Javascript引擎会转到内存中的arr位置并使用存储在那里的信息。

按引用分配

当使用=将引用类型值(一个对象)复制到另一个变量时,复制的其实是该对象在内存中的地址。

var reference = [1];
var refCopy = reference;

内存中的分配,如下图所示
在这里插入图片描述
现在,每个变量都包含对同一数组的引用(<#001>)。这意味着如果我们改变referencerefCopy也将看到这些更改:

reference.push(2);
console.log(reference, refCopy); // -> [1, 2], [1, 2]

上述代码, 如下图所示
在这里插入图片描述

我们已经将2推入内存中的数组。当我们使用referencerefCopy时,我们指向同一个数组。

重新分配引用

重新分配引用变量将替换旧引用。

var obj = { first: 'reference' };

假设内存图如下
在这里插入图片描述
现在我们进行重新分配引用

var obj = { first: 'reference' };
obj = { second: 'ref2' }

存储在obj中的地址发生了变化。第一个对象仍然存在于内存中,下一个对象也是如此:
在这里插入图片描述
当没有对剩余对象的引用时,正如我们在上面的地址#234中看到的那样,Javascript引擎可以执行垃圾收集。这只是意味着程序员已经丢失了对该对象的所有引用,并且不能再使用该对象,因此引擎可以继续并安全地从内存中删除它。在这种情况下,对象{first:'reference'}不再可访问,并且可供引擎用于垃圾收集。

扩充

当在引用类型变量上使用相等运算符=====时,如果变量包含对同一对象的引用(内存地址Addresses相同),则比较结果为true。

var arrRef = [’Hi!];
var arrRef2 = arrRef;
console.log(arrRef === arrRef2); // -> true

var arr1 = ['Hi!'];
var arr2 = ['Hi!'];
console.log(arr1 === arr2); // -> false

如果我们有两个不同的对象并且想要查看它们的属性是否相同,那么最简单的方法是将它们都转换为字符串然后比较字符串。当相等运算符比较基元时,它们只是检查值是否相同。

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);
console.log(arr1str === arr2str); // true

另一种选择是递归循环遍历对象并确保每个属性都相同。

通过函数传递参数

当我们将原始值传递给函数时,该函数将值复制到其参数中。它实际上与使用=相同。

var hundred = 100;
var two = 2;
function multiply(x, y) {
    // PAUSE
    return x * y;
}
var twoHundred = multiply(hundred, two);

在上面的例子中,我们给出hundred 的值100.当我们将它传递给multiply时,变量x得到该值。该值被复制,好像我们使用了=赋原始值一样。下面是PAUSE注释行中内存的快照。
在这里插入图片描述

纯函数

我们将不影响外部范围内任何内容的函数称为纯函数。(简单点说就是给函数传递的参数不是Object对象)

只要函数只将原始值作为参数并且不在其周围范围内使用任何变量,它就会自动变纯,因为它不会影响外部范围内的任何内容。

函数返回后,内部创建的所有变量都会被垃圾收集。

但是,接受Object的函数可以改变其周围范围的状态。

如果函数接受数组引用并改变它指向的数组,引用该数组的周围范围中的变量也会看到该更改。

函数返回后,它所做的更改将在外部作用域中保留。

这可能导致难以追踪的不期望的副作用。

因此,许多数组函数(包括Array.mapArray.filter)都被编写为纯函数。
它们接受数组引用并在内部,它们复制数组并使用副本而不是原始副本。

这使得原始版本不受影响,外部范围不受影响,我们返回对全新数组的引用。 让我们来看一个纯粹与不纯的函数的例子。

function changeAgeImpure(person) {
    person.age = 25;
    return person;
}
var alex = {
    name: 'Alex',
    age: 30
};
var changedAlex = changeAgeImpure(alex);
console.log(alex); // -> { name: 'Alex', age: 25 }
console.log(changedAlex); // -> { name: 'Alex', age: 25 }

这个不纯的函数接受一个对象,并将该对象的属性年龄更改为25.因为它作用于给定的引用,它直接更改对象alex

请注意,当它返回person对象时,它将返回传入的完全相同的对象。alexalexChanged包含相同的引用。返回person变量并将引用存储在新变量中是多余的。

让我们看一下纯函数。

function changeAgePure(person) {
    var newPersonObj = JSON.parse(JSON.stringify(person));
    newPersonObj.age = 25;
    return newPersonObj;
}
var alex = {
    name: 'Alex',
    age: 30
};
var alexChanged = changeAgePure(alex);
console.log(alex); // -> { name: 'Alex', age: 30 }
console.log(alexChanged); // -> { name: 'Alex', age: 25 }

在这个函数中,我们使用JSON.stringify将我们传递给的对象转换为字符串,然后使用JSON.parse将其解析回一个对象。通过执行此转换并将结果存储在新变量中,我们创建了一个新对象。还有其他方法可以做同样的事情,例如循环遍历原始对象并将其每个属性分配给新对象,但这种方式最简单。

新对象具有与原始对象相同的属性,但它是内存中明显独立的对象。 当我们更改此新对象的age属性时,原始对象不受影响。这个功能现在很纯粹。它不会影响其自身范围之外的任何对象,甚至不会影响传入的对象。

新对象需要返回并存储在新变量中,否则在函数完成后会被JS引擎被垃圾收集。

自测

function changeAgeAndReference(person) {
    person.age = 25;
    person = {
        name: 'John',
        age: 50
    };
    
    return person;
}
var personObj1 = {
    name: 'Alex',
    age: 30
};
var personObj2 = changeAgeAndReference(personObj1);
console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

领红包,小赞赏一下吧
本文翻译自 Arnav Aggarwal 的 Explaining Value vs. Reference in Javascript

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值