摘要:數值類型引用類型有種通過復制數值傳值的數據類型。我們稱之為原始基本數據類型還有三種通過引用傳值的數據類型。當等式運算符和用于引用型變量時,他們會檢查引用。這是中的地方在內存中的映射包含了函數的引用,其他變量則包含基本數據類型的數據。
本文旨在了解如何復制對象、數組和函數以及如何將它們傳遞到函數中。知道引用類型復制的是什么。了解原始值是通過復制值來復制及傳遞的。
數值類型 & 引用類型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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89701.html
摘要:判斷一個值是否是,只能用來判斷如果兩個都是字符串,每個位置的字符都一樣,那么相等否則不相等。如果一個是字符串,一個是數值,把字符串轉換成數值再進行比較。對象轉換成基礎類型,利用它的或者方法。核心內置類,會嘗試先于例外的是,利用的是轉換。 javascript-- == vs === 高級語言層出不窮, 各個語言雖說思想一致,但仍有各自獨特的設計理念和語法, js有許多容易讓人迷惑的地方...
摘要:深拷貝淺拷貝本文主要對深拷貝淺拷貝的解釋及實現做一下簡單記錄。之所以會有深拷貝與淺拷貝之分,是因為不同數據類型的數據在內存中的存儲區域不一樣。但注意,只能做一層屬性的淺拷貝。 深拷貝VS淺拷貝 本文主要對深拷貝&淺拷貝的解釋及實現做一下簡單記錄。原文鏈接,歡迎star。 之所以會有深拷貝與淺拷貝之分,是因為不同數據類型的數據在內存中的存儲區域不一樣。 堆和棧是計算機中劃分出來用來存儲的...
摘要:一數據類型基本類型引用類型類型判斷返回結果未定義布爾值字符串數值對象或者函數拓展堆棧兩種數據結構堆隊列優先,先進先出由操作系統自動分配釋放,存放函數的參數值,局部變量的值等。 一、數據類型 基本類型:`Null Boolean String Undefined Number(NB SUN)` 引用類型:`Array Function Object` 類型判斷:typeof 返回結果...
摘要:字節碼驗證于是就寫了以下的類,用來驗證然后,然后,看字節碼如下圖。以上,就是整個關于引用傳遞和值傳遞的理解,有說的不對的,望指正。 寫這個的原因主要是今天看到了知乎的一個問題,發現自己有些地方有點懵逼,寫下來記錄一下,知乎上排名第一的答案說的很清楚,不過看了以后依舊有點迷迷糊糊,所以自己寫了個幾行代碼測試。首先上一個,感覺比較對的結論:**Horstmann的《java核心技術》(中文...
閱讀 3693·2021-09-22 15:28
閱讀 1307·2021-09-03 10:35
閱讀 889·2021-09-02 15:21
閱讀 3493·2019-08-30 15:53
閱讀 3504·2019-08-29 17:25
閱讀 581·2019-08-29 13:22
閱讀 1568·2019-08-28 18:15
閱讀 2298·2019-08-26 13:57