JavaScript中的求值策略

最近在研究 lambda 演算中的 η-變換 在 JavaScript 中的應用,偶然在 stackoverflow 上看到一個比較有意思的問題。關於 JavaScript 的求值策略,問JS中函數的參數傳遞是按值傳遞仍是按引用傳遞?回答很經典。javascript

一慄以蔽之

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);         // 10
console.log(obj1.item);   // changed
console.log(obj2.item);   // unchanged
  • 若是說JS中函數的參數傳遞是按值傳遞,那麼在函數changeStuff內部改變b.item的值將不會影響外部的obj1對象的值。
  • 若是說JS中函數的參數傳遞是按引入傳遞,那函數changeStuff內部所作的改變將會影響到函數外部全部的變量定義,num將會變成100、obj2.item將會變成changed。很顯然實際不是這樣子的。

因此不能說JS中函數的參數傳遞嚴格按值傳遞按引入傳遞。總的來講函數的參數都是按值傳遞的。JS中還採用一種參數傳遞策略,叫按共享傳遞。這要取決於參數的類型。java

  • 若是參數是基本類型,那麼是按值傳遞的;
  • 若是參數是引用類型,那麼是按共享傳遞的。

參數傳遞

ECMAScript 中全部函數的參數都是 按值傳遞的。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣。 基本類型值的傳遞如同基本類型變量的複製同樣,而 引用類型值的傳遞,則如同引用類型變量的複製同樣。-- 《JavaScript高級程序設計》

紅寶書上講全部函數的參數都是按值傳遞的,究竟是不是呢?讓咱們分析下上面的栗子:es6

按值傳遞

JavaScript中基本類型做爲參數的策略爲 按值傳遞(call by value):函數

function foo(a) {
  a = a * 10;
}

var num = 10;

foo(num);

console.log(num); // 10 沒有變化

這裏看到函數內部參數的改變並無影響到外部變量。按值傳遞沒錯。設計

按共享傳遞

JavaScript中對象做爲參數傳遞的策略爲 按共享傳遞(call by sharing):code

  • 修改參數的屬性將會影響到外部對象
  • 從新賦值將不會影響到外部對象

按上面栗子函數內部修改了參數b的屬性item,會影響到函數外部對象,於是obj1的屬性item也變了。對象

function bar(b) {
  b.item = "changed";
  console.log(b === obj1) // true
}

var obj1 = {item: "unchanged"};

bar(obj1);

console.log(obj1.item);   // changed 修改參數的屬性將會影響到外部對象

b === obj1打印結果爲true能夠看出,函數內部修改了參數的屬性並無影響到參數的引用。bobj1共享一個對象地址,因此修改參數的屬性將會影響到外部對象。ip

而將參數c從新賦值一個新對象,將不會影響到外部對象。get

function baz(c) {
  c = {item: "changed"};
  console.log(c === obj2) // false
}

var obj2 = {item: "unchanged"};

baz(obj2);

console.log(obj2.item);   // unchanged 從新賦值將不會影響到外部對象

將參數c從新賦值一個新對象,那麼c就綁定到了一個新的對象地址,c === obj2打印結果爲false,判斷他們再也不共享同一個對象地址。它們各自有獨立的對象地址。因此從新賦值將不會影響到外部對象。it

總結

能夠說 按共享傳遞按值傳遞 的特例,傳遞的是引用地址的拷貝。因此紅寶書上說的也沒錯。

能夠把 ECMAScript 函數的參數想象成局部變量。-- 《JavaScript高級程序設計》

延伸 - 惰性求值

前面瞭解到了全部函數的參數都是按值傳遞的。JavaScript 中參數是必須先求值再做爲實參傳入函數的。可是在ES6中有一個特例。

參數默認值不是傳值的,而是每次都從新計算默認值表達式的值。也就是說,參數默認值是 惰性求值的。 -- 《ECMAScript 6 入門》
let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代碼中,參數p的默認值是x + 1。這時,每次調用函數foo,都會從新計算x + 1,而不是默認p等於 100。

參考

求值策略

Is JavaScript a pass-by-reference or pass-by-value language?

ES6 中函數參數的默認值

相關文章
相關標籤/搜索