先來看兩個個來自於 《JavaScript 高級程序設計》P70-P71 的兩個例子。node
function addTen(num) { num += 10; return num; } var count = 20; var result = addTen(count); alert(count); // 20, 沒有變化 alert(result); // 30
書上解釋說,JavaScript 參數傳遞都是按值傳參。git
因此傳遞給 addTen
函數的值是 20
這個值,因此函數執行結束原始變量 count
並不會改變。github
function setName(obj) { obj.name = 'Nicholas'; obj = new Object(); obj.name = 'Greg'; } var person = new Object(); setName(person); alert(person.name); // Nicholas
爲何結果是 Nicholas
呢?ecmascript
疑問:若是是傳值,那應該是把 person 變量的值(也就是一個指向堆內存中對象的指針)傳遞到函數中,
obj.name = 'Greg';
改變了堆內存中對象的屬性,爲何person.name
仍是Nicholas
?函數
讓咱們再將上面兩個例子綜合爲下面的例子:lua
function changeStuff(a, b, c) { a = a * 10; b.item = "changed"; c = {item: "changed"}; } var num = 10; var obj1 = {item: "unchanged"}; var obj2 = {item: "unchanged"}; changeStuff(num, obj1, obj2); console.log(num); console.log(obj1.item); console.log(obj2.item);
最終的輸出結果是:spa
10 changed unchanged
因此 JS 究竟是傳值調用仍是傳引用調用呢?要弄清楚這個問題,首先咱們要明白到底什麼是傳值調用(Call-ny-value)和傳引用調用(Call-by-reference)。設計
在傳值調用中,傳遞給函數參數是函數被調用時所傳實參的拷貝。在傳值調用中實際參數被求值,其值被綁定到函數中對應的變量上(一般是把值複製到新內存區域)。指針
即 changeStuff
的參數 a
b
c
是 num1
obj1
obj2
的拷貝。因此不管 a
b
c
怎麼變化,num1
obj1
obj2
都保持不變。code
問題就在於 obj1
變了。
在傳引用調用調用中,傳遞給函數的是它的實際參數的隱式引用而不是實參的拷貝。一般函數可以修改這些參數(好比賦值),並且改變對於調用者是可見的。
也就是說 changeStuff
函數內的 a
b
c
都分別與 num
obj1
obj2
指向同一塊內存,但不是其拷貝。函數內對 a
b
c
所作的任何修改,都將反映到 num
obj1
obj2
上 。
問題就在於 num
和 obj2
沒變。
從上面的代碼能夠看出,JavaScript 中函數參數的傳遞方式既不是傳值,也不是傳引用。主要問題出在 JS 的引用類型上面。
JS 引用類型變量的值是一個指針,指向堆內存中的實際對象。
還有一種求值策略叫作傳共享調用(Call-by-sharing/Call by object/Call by object-sharing)。
傳共享調用和傳引用調用的不一樣之處是,該求值策略傳遞給函數的參數是對象的引用的拷貝,即對象變量指針的拷貝。
也就是說, a
b
c
三個變量的值是 num
obj1
obj2
的指針的拷貝。 a
b
c
的值分別與 num
obj1
obj2
的值指向同一個對象。函數內部能夠對 a
b
c
進行修改可從新賦值。
function changeStuff(a, b, c) { a = a * 10; // 對 a 賦值,修改 a 的指向,新的值是 a * 10 b.item = "changed"; // 由於 b 與 obj1 指向同一個對象,因此這裏會修改原始對象 obj1.item 的內容 c = {item: "changed"}; // 對 c 從新賦值,修改 c 的指向,其指向的對象內容是 {item: "changed"} }
接下來讓咱們再來分析一下代碼。
var num = 10; var obj1 = {item: "unchanged"}; var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
能夠看到,變量 a
的值就是 num
值的拷貝,變量 b
c
分別是 obj1
obj2
的指針的拷貝。
函數的參數其實就是函數做用域內部的變量,函數執行完以後就會銷燬。
a = a * 10; b.item = "changed"; c = {item: "changed"};
如圖所示,變量 a
的值的改變,並不會影響變量 num
。
而 b
由於和 obj1
是指向同一個對象,因此使用 b.item = "changed";
修改對象的值,會形成 obj1
的值也隨之改變。
因爲是對 c
從新賦值了,因此修改 c
的對象的值,並不會影響到 obj2
。
從上面的例子能夠看出,對於 JS 來講:
基本類型是傳值調用
引用類型傳共享調用
傳值調用本質上傳遞的是變量的值的拷貝。
傳共享調用本質上是傳遞對象的指針的拷貝,其指針也是變量的值。因此傳共享調用也能夠說是傳值調用。
因此《JavaScript 高級程序設計》說 JavaScript 參數傳遞都是按值傳參 也是有道理的。
本文同步於個人博客 https://github.com/nodejh/nodejh.github.io/issues/32