JavaScript引數傳遞中值和引用的一種理解

BurningFish發表於2016-08-16

值(value)和引用(reference)是各種程式語言老生常談的話題,js也不例外。

我將剖析一個例子的實際執行過程,跟大家分享我對js引數傳遞中的值和引用的理解。

參考官網資料型別的兩種分類,本文將這兩種分類簡稱為基本型別(boolean, null, undefined, string, number, symbol)和object型別。

首先,用一個example 演示引數傳遞的應用:

var obj = {};
obj.inner = 10;

var num = 10;
var str = 'Hello';
var boo = true;
var oth = null;
var und = undefined;
var sym = Symbol('foo');

function passingobject(myobj){
    myobj.inner  = 1 + myobj.inner ; 
}

function passingvalue(myvalue){
  switch(typeof myvalue){
    case 'number':
        myvalue = myvalue + 1;
        break;
      case 'string':
        myvalue = 'I am a new string now!';
        break;
      case 'boolean':
        myvalue= false;
        break;
      default:
        myvalue = 'Null, Undefined, or Symbol';
     }
  }

  console.log("before num = " + num);  // before num = 10
  passingvalue(num);
  console.log("after num = " + num);  // after num = 10
  console.log("before str = " + str); // before str = Hello
  passingvalue(str);
  console.log("after str = " + str);  // after str = Hello
  console.log("before boo = " + boo); // before boo = true
  passingvalue(boo);
  console.log("after boo = " + boo);  // after boo = false
  console.log("before oth = " + oth); // before oth = null
  passingvalue(oth);
  console.log("after oth = " + oth);  // after oth = null
  console.log("before und = " + und); // before und = undefined
  passingvalue(und);
  console.log("after und = " + und);  // after und = undefined
  console.log(sym); // Symbol(foo)
  passingvalue(sym);
  console.log(sym); // Symbol(foo)
  console.log("before obj.inner = " + obj.inner); // before obj.inner = 10
  passingobject(obj); // after obj.inner = 11
  console.log("after obj.inner = " + obj.inner);

從example 1 的結果似乎可以總結出以下兩條結論:

1. 傳遞的資料型別為基本型別(number, string boolean, null, undefined, symbol),在引數傳遞過程中,函式內部對傳遞值的操作並不影響原始值。

2. 傳遞的資料型別為object, 在引數傳遞過程中,函式內部對傳遞值的操作會導致原始值的改變。

然而, 有沒有其他特殊情況呢?

有一種在stackoverflow討論很火熱的用法,跟結論二背道而行。example 2。

例子引自:http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language

 1 function changeStuff(a, b, c)
 2 {
 3   a = a * 10;
 4   b.item = "changed";
 5   c = {item: "changed"};
 6 }
 7 
 8 var num = 10;
 9 var obj1 = {item: "unchanged"};
10 var obj2 = {item: "unchanged"};
11 
12 console.log(obj1.item); // unchanged
13 console.log(obj2.item); // unchanged
14 changeStuff(num, obj1, obj2);
15 console.log(obj1.item); // changed
16 console.log(obj2.item); // unchanged

example 2中, obj2.item並沒有被函式changeStuff改變。changeStuff內部同樣改變了b、c的值,為什麼obj1被改變了(L15)而obj2沒有被改變呢?

我用js的執行上下文對這種現象進行解釋,如圖。

在js執行過程中,編輯器動態生成執行上下文(execution context),example 2中,首先生成global的執行上下文和changeStuff的執行上下文。

執行到changeStuff(num, obj1, obj2)的時候, a, b, c指向引數num, obj1, obj2,a和num指向10, b跟obj1指向同一個值,c跟obj2指向同一個值。

執行step 1的時候,對a重新賦值,為a賦值前的10倍,從此a與num毫無關係。

執行step 2的時候,對b所指向的值的item屬性進行重新賦值,這個賦值只改變了item的值, 而obj1和b仍然指向同一個值。

執行step 3的時候,對c重新賦值,從此c與obj2再無瓜葛,因此即使c有一個叫item的屬性,與obj2的item屬性有著各自的值,並沒有影響obj2.item。

也就是說,js函式引數傳遞過程中,若函式內部對引數重新賦值,這個賦值過程不會影響原始變數的值。

這也很好地解釋了基本型別的引數變數(結論1)不會受影響的現象,基本型別的引數變數每一次改變都是一次全新賦值,對原始變數不會造成影響。

總結

在js函式傳遞中,當基本型別(number, string, boolean, null, undefined, symbol)變數作為引數傳遞時,函式內部對引數的任何操作都不會改變變數的值。

當object型別變數作為引數傳遞時,函式內部對引數的操作會影響變數的值,除非函式內部對引數重新賦值(任何型別的值)。

Thank you!

Feel free to contact me if you have any question!

相關文章