数值类型vs引用类型

本文旨在了解如何复制对象、数组和函数以及如何将它们传递到函数中。知道引用类型复制的是什么。了解原始值是经过复制值来复制及传递的。程序员

数值类型 & 引用类型

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'

ax 如今的值都是10. by 都拥有值 '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>

如今每一个变量都包含了同一个数组的引用,它们地址相同,这意味着若是咱们改变了这个引用即改变referencerefCopy 也会随之改变,这一点与基本数据类型的值不同。

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包含了函数的引用,其余变量则包含基本数据类型的数据。
twoHundredundefined 由于咱们尚未函数返回结果,在函数返回结果前,它等于
undefined

纯函数

纯函数是指不影响外部做用域的函数。只要一个函数只接受基本数据类型的值做为参数而且不适用任何外部范围的变量,他就是纯函数不会污染外部做用域。全部纯函数的变量在函数返回结果后会进入JavaScript的垃圾回收机制。
然而,接受一个对象(做为参数)的函数会改变他周围做用域的状态。若是函数接受一个数组的引用并改变了它指向的数组,多是添加元素,引用这个数组的外部变量会见证这些变化。当函数返回结果后,产生的改变会影响外部做用域。这会致使很难追踪到的负面影响。
许多本地数组函数包含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 }

非纯函数接受了对象,改变了objectage属性 为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时,原对象并无受到影响。这个函数就是纯洁纯净的。它没有影响任何外部做用域的对象,甚至传入函数的对象。新的对象须要被返回 并将其存储在一个新变量中不然一旦函数执行完毕就会被回收,该对象在做用于内就再也找不到了。
如下几个例子看看你是否理解了上述内容:

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);

结果:

  1. 25

  2. 17

  3. false

  4. false

3和4 须要注意一点: [5] === [5] ====> false

相关文章
相关标签/搜索