本文旨在了解如何复制对象、数组和函数以及如何将它们传递到函数中。知道引用类型复制的是什么。了解原始值是经过复制值来复制及传递的。程序员
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 |
当咱们用 =
将这些变量赋值给其余变量时,咱们把这些值拷贝给了这些新变量。他们经过值复制的。code
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' |
改变其中一个值并不会影响另外一个的值,彼此井水不犯河水,尽管后者曾经复制与它:ip
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' }
不能再被任何变量获取,内存会被回收。
当 等式运算符 ==
和 ===
用于引用型变量时, 他们会检查引用。 若是多个变量包含同一项目的引用时, 结果会返回 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