[聚合文章] JavaScript 中的求值策略

JavaScript 2018-01-13 23 阅读

最近在研究 lambda 演算中的 η-变换 在 JavaScript 中的应用,偶然在 stackoverflow 上看到一个比较有意思的问题。关于 JavaScript 的求值策略,问JS中函数的参数传递是 按值传递 还是 按引用传递 ?回答很经典。

一栗以蔽之

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中还采用一种参数传递策略,叫 按共享传递 。这要取决于参数的类型。

  • 如果参数是 基本类型 ,那么是 按值传递 的;
  • 如果参数是 引用类型 ,那么是 按共享传递 的。

参数传递

ECMAScript 中所有函数的参数都是 按值传递 的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。 基本类型 值的传递如同基本类型变量的复制一样,而 引用类型 值的传递,则如同引用类型变量的复制一样。-- 《JavaScript高级程序设计》

红宝书 上讲所有函数的参数都是 按值传递 的,到底是不是呢?让我们分析下上面的栗子:

按值传递

JavaScript中基本类型作为参数的策略为 按值传递 (call by value):

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

var num = 10;

foo(num);

console.log(num); // 10 没有变化

这里看到函数内部参数的改变并没有影响到外部变量。 按值传递 没错。

按共享传递

JavaScript中对象作为参数传递的策略为 按共享传递 (call by sharing):

  • 修改参数的属性将会影响到外部对象
  • 重新赋值将不会影响到外部对象

按上面栗子函数内部修改了参数 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 共享一个对象地址,所以修改参数的属性将会影响到外部对象。

而将参数 c 重新赋值一个新对象,将不会影响到外部对象。

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 ,判断他们不再共享同一个对象地址。它们各自有独立的对象地址。所以重新赋值将不会影响到外部对象。

总结

可以说 按共享传递按值传递 的特例,传递的是引用地址的拷贝。所以红宝书上说的也没错。

可以把 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 中函数参数的默认值

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。