數值類型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

相關文章
相關標籤/搜索