摘要:数值类型引用类型有种通过复制数值传值的数据类型。我们称之为原始基本数据类型还有三种通过引用传值的数据类型。当等式运算符和用于引用型变量时,他们会检查引用。这是中的地方在内存中的映射包含了函数的引用,其他变量则包含基本数据类型的数据。
本文旨在了解如何复制对象、数组和函数以及如何将它们传递到函数中。知道引用类型复制的是什么。了解原始值是通过复制值来复制及传递的。
数值类型 & 引用类型JavaScript有5种通过复制数值传值的数据类型:Boolean, null, undefined, String, and Number。我们称之为原始/基本数据类型
JavaScript还有三种通过引用传值的数据类型:Array, Function, and Object。从专业角度讲,它们都是Objects, 故而统称为对象。
若为一个基本数据类型的变量赋值,我们可以认为变量包含了这个原始值。
var x = 10; var y = "abc"; var z = null;
这张图形象的展示了变量在内存中的存储情况:
Variables | Values |
---|---|
x | 10 |
y | "abc" |
z | null |
当我们用 = 将这些变量赋值给其他变量时,我们把这些值拷贝给了这些新变量。他们通过值复制的。
var x = 10; var y = "abc"; var a = x; var b = y; console.log(x, y, a, b); // -> 10, "abc", 10, "abc"
a 和 x 现在的值都是10. b 和 y 都拥有值 "abc"。他们各自独立,拥有相同的值,互不影响:
Variables | Values |
---|---|
x | 10 |
y | "abc" |
a | 10 |
b | "abc" |
改变其中一个值并不会影响另一个的值,彼此井水不犯河水,尽管后者曾经复制与它:
var x = 10; var y = "abc"; var a = x; var b = y; x = 5; y= "def"; console.log(x, y, a, b); // -> 5 "def" 10 "abc"对象
非基本数据类型的变量会保存对值的引用(地址)。该引用指向内存中对象的地址,变量实际不包含该实际值。
对象创建于计算机内存中。当我们写代码 arr = [], 我们在内存中创建了一个新数组, arr 中现在包含了新数组在内存中的地址。
假设address(地址)是一种新的传递数据的数据类型,就像数字和字符串。address指向通过引用传递的值的内存地址,就像字符串由"" 或 ""表示, address由 <> 表示。
当我们赋值引用一个引用型变量时,我们通常这样书写代码:
var arr = []; arr.push(1);
两步的操作分别是:
1.
Variables | Values | Address | Objects | |
---|---|---|---|---|
arr | <#001> | #001 | [] |
2.
Variables | Values | Address | Objects | |
---|---|---|---|---|
arr | <#001> | #001 | [1] |
值,地址以及 变量 arr 的包含的值 是静态不变的,仅仅是内存中的数组改变了。当我们对arr 进行操作时,例如添加新元素, JavaScript引擎会获取 arr 在内存中的地址 并操作该地址存储的数据。
引用赋值当一个引用型值即对象被用 = 赋值给另一个变量, 实际上复制过去的是那个引用型值的地址。对象通过引用赋值而不是直接传值。对象本身是静态不变的,唯一改变的 是对象的 引用 、地址。
var reference = [1]; var refCopy = reference;
内存变化:
Variables | Values | Address | Objects | |
---|---|---|---|---|
reference | <#001> | #001 | [1] | |
refCopy | <#001> |
现在每个变量都包含了同一个数组的引用,它们地址相同,这意味着如果我们改变了这个引用即改变reference, refCopy 也会随之改变,这一点与基本数据类型的值不一样。
reference.push(2); console.log(reference, refCopy); // -> [1, 2], [1, 2]
Variables | Values | Address | Objects | |
---|---|---|---|---|
reference | <#001> | #001 | [1,2] | |
refCopy | <#001> | [1,2] |
重新复制会覆盖旧值:
var obj = { first: "reference" };
内存变化:
Variables | Values | Address | Objects | |
---|---|---|---|---|
obj | <#234> | #234 | { first: "reference" } |
重新赋值:
var obj = { first: "reference" }; obj = { second: "ref2" }
Address存储了 obj 的变化 ,第一个对象仍在内存,第二个对象也在:
Variables | Values | Address | Objects | |
---|---|---|---|---|
obj | <#678> | #234 | { first: "reference" } | |
#678 | { second: "ref2" } |
当已经存在的对象没有被引用时,如上边的 #234 ,JavaScript会启动垃圾回收机制。这就意味着程序员失去了对该对象的所有引用,不能再使用这个对象,所以JavaScript可以安全地删除它。 这时,对象 { first: "reference" } 不能再被任何变量获取,内存会被回收。
== and ===当 等式运算符 == 和 === 用于引用型变量时, 他们会检查引用。 如果多个变量包含同一项目的引用时, 结果会返回 true
var arrRef = ["Hi!"]; var arrRef2 = arrRef; console.log(arrRef === arrRef2); // -> true
如果他们是不同的对象,即使它们包含相同的内容, 比较结果也会返回 false。
var arr1 = ["Hi!"]; var arr2 = ["Hi!"]; console.log(arr1 === arr2); // -> false对象的比较
如果想比较两个对象的属性是否一样,比较运算符会失去作用。我们必须编一个函数来检查对象的每一条属性和值是否相同。对于两个数组,我们需要一个函数遍历数组每项检查是否相同。
函数传参当我们传递基本数据类型的值给一个函数时,函数拷贝这个值作为自己的参数。效果和 = 相同:
var hundred = 100; var two = 2; function multiply(x, y) { // PAUSE return x * y; } var twoHundred = multiply(hundred, two);
上例中,我们将 hundred 赋值 100 。当我们把他传递给 multiply, 变量x 获得值 100 。如果用 = 赋值,值会被复制。 而且,hundred的值不会被影响。 这是multiply 中 //的地方在内存中的映射:
Variables | Values | Address | Objects | |
---|---|---|---|---|
hundred | 100 | #333 | function(x, y) {… } | |
two | 2 | |||
multiply | <#333> | |||
x | 100 | |||
y | 2 | |||
twoHundred | undefined |
multiply包含了函数的引用,其他变量则包含基本数据类型的数据。
twoHundred 是 undefined 因为我们还没有函数返回结果,在函数返回结果前,它等于
undefined。
纯函数是指不影响外部作用域的函数。只要一个函数只接受基本数据类型的值作为参数并且不适用任何外部范围的变量,他就是纯函数不会污染外部作用域。所有纯函数的变量在函数返回结果后会进入JavaScript的垃圾回收机制。
然而,接受一个对象(作为参数)的函数会改变他周围作用域的状态。如果函数接受一个数组的引用并改变了它指向的数组,可能是添加元素,引用这个数组的外部变量会见证这些变化。当函数返回结果后,产生的改变会影响外部作用域。这会导致很难追踪到的负面影响。
许多本地数组函数包含Array.map 和 Array.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 }
非纯函数接受了对象,改变了object的 age属性 为25,由于它对前面声明的引用直接起作用,直接改变了alex 对象。注意当返回person对象时,它返回与传递的相同的对象。alex 和 alexChanged 包含了对同一个对象的引用,既返回了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时,原对象并没有受到影响。这个函数就是纯洁纯净的。它没有影响任何外部作用域的对象,甚至传入函数的对象。新的对象需要被返回 并将其存储在一个新变量中否则一旦函数执行完毕就会被回收,该对象在作用于内就再也找不到了。
以下几个例子看看你是否理解了上述内容:
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); // -> { name: "Alex", age: 25 } console.log(personObj2); // -> { name: "John", age: 50 }
解析:
上述函数 等于:
var personObj1 = { name: "Alex", age: 30 }; var person = personObj1; person.age = 25; person = { name: "John", age: 50 }; var personObj2 = person; console.log(personObj1); // -> { name: "Alex", age: 25 } console.log(personObj2); // -> { name: "John", age: 50 }
唯一一点不同是前者person在函数结束后就被回收了。
再来几道题:
// 1 var obj = { innerObj: { x: 9 } }; var z = obj.innerObj; z.x = 25; console.log(obj.innerObj.x); // 2 var obj = { arr: [{ x: 17 }] }; var z = obj.arr; z = [{ x: 25 }]; console.log(obj.arr[0].x); // 3 var obj = {}; var arr = []; obj.arr = arr; arr.push(9); obj.arr[0] = 17; console.log(obj.arr === [17]); // 4 function fn(item1, item2) { if (item2 === undefined) { item2 = []; } item2[0] = item1; return item2; } var w = {}; var x = [w]; var y = fn(w); var z = fn(w, x); console.log(x === y);
结果:
25
17
false
false
3和4 需要注意一点: [5] === [5] ====> false
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89701.html
摘要:判断一个值是否是,只能用来判断如果两个都是字符串,每个位置的字符都一样,那么相等否则不相等。如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。对象转换成基础类型,利用它的或者方法。核心内置类,会尝试先于例外的是,利用的是转换。 javascript-- == vs === 高级语言层出不穷, 各个语言虽说思想一致,但仍有各自独特的设计理念和语法, js有许多容易让人迷惑的地方...
摘要:深拷贝浅拷贝本文主要对深拷贝浅拷贝的解释及实现做一下简单记录。之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。但注意,只能做一层属性的浅拷贝。 深拷贝VS浅拷贝 本文主要对深拷贝&浅拷贝的解释及实现做一下简单记录。原文链接,欢迎star。 之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。 堆和栈是计算机中划分出来用来存储的...
摘要:一数据类型基本类型引用类型类型判断返回结果未定义布尔值字符串数值对象或者函数拓展堆栈两种数据结构堆队列优先,先进先出由操作系统自动分配释放,存放函数的参数值,局部变量的值等。 一、数据类型 基本类型:`Null Boolean String Undefined Number(NB SUN)` 引用类型:`Array Function Object` 类型判断:typeof 返回结果...
摘要:字节码验证于是就写了以下的类,用来验证然后,然后,看字节码如下图。以上,就是整个关于引用传递和值传递的理解,有说的不对的,望指正。 写这个的原因主要是今天看到了知乎的一个问题,发现自己有些地方有点懵逼,写下来记录一下,知乎上排名第一的答案说的很清楚,不过看了以后依旧有点迷迷糊糊,所以自己写了个几行代码测试。首先上一个,感觉比较对的结论:**Horstmann的《java核心技术》(中文...
阅读 3594·2021-09-22 15:28
阅读 1249·2021-09-03 10:35
阅读 848·2021-09-02 15:21
阅读 3436·2019-08-30 15:53
阅读 3463·2019-08-29 17:25
阅读 537·2019-08-29 13:22
阅读 1506·2019-08-28 18:15
阅读 2256·2019-08-26 13:57