ES 基础 —— 函数参数的求值策略

published

什么是求值策略

函数参数的求值策略(Evalution Strategy)解决函数的参数如何求值的问题。

三种求值策略

按值传递(传值)

按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。

修改函数形参的值并不会影响实参。

按引用传递(传引用)

按引用传递(call by reference):函数的形参接受实参的隐式引用,而不再是副本,形参和实参指向相同的值。

修改函数形参的值,实参也会被修改。

按共享传递(传引用的副本)

按共享传递(call by sharing),最早由Barbara Liskov 在 1974 年的 GLU 语言中提出。该求值策略还被用于 Python、Java、Ruby、Scheme 等多种语言。 该策略的要点是:函数的形参是被调用时所传实参的引用的副本。

                                      +-----+
                 +--------------------+ bar |
                 |                    +-----+
                 v                       ^
          +------------+
          |   Object   |                 | COPY VALUE
          +------------+
                 ^                       |
                 |                    +-----+
                 +--------------------+ foo |
                                      +-----+
          图中 foo 和 bar 的都是 Object 的引用,foo 是从 bar 复制而来的。

它的特性:

  • 对函数形参的修改,会影响实参。
  • 对函数形参赋值,不会影响到实参。

ES 中函数的求值策略

  • 基本数据类型:Undefined, Null, Boolean, Number, String,Symbol
  • 引用数据:Object

基本数据类型

ES 中的基本数据类型,是按值传递的。

const bar = 0
function foo(i) {
  i = 1
}

foo(bar)
console.log(bar)  // 输出 0,没有受到 i = 1 的影响。

引用数据类型

用一个修改 Object 的操作来当作示例:

const bar = {
  x: 0
}

function foo(obj) {
  obj.x = 1
}

foo(bar)
console.log(bar.x)  // 输出1,说明值被修改了。

这说明,objbar 引用了同一个对象。这好像是按引用传递,但到底是不是呢?

再看一例:

const bar = {
  x: 0
}

function foo(obj) {
  obj = 100000
}

foo(bar)
console.log(bar.x)  // 输出 0,说明值没被修改。

如果是按引用传递,修改形参 obj 的值,应该影响到实参 bar 才对,可是 obj 的修改并没有影响到 bar。 第一个例子和第二个例子存在矛盾,所以,ES 中的引用类型肯定不是按引用传递的。

再通过按共享传递的策略来解释上一节的两个例子。

const bar = {
  x: 0
}

function foo(obj) {
  obj.x = 1
}

foo(bar)
console.log(bar.x) // 输出1,说明值被修改了。

objbar 的一个副本,它们引用相同的对象,所以通过修改形参 obj 的属性值,能够影响到实参的属性值。

const bar = {
  x: 0
}

function foo(obj) {
  obj = 100000
}

foo(bar)
console.log(bar.x)  // 输出 0,说明值没被修改。

objbar 的一个副本,它们引用相同的对象,对 obj 重新赋值只是切断了它和对象之间的联系,并不会影响对象。

之前的矛盾解释通了。所以,ES 中的引用类型是按共享传递的。

结论

ES 中的函数求值策略:

  • 基本数据类型按值传递。
  • 引用数据类型按共享传递。

也有另一种说法:“ES 中,不管基本数据类型,还是引用数据类型,都是按值传递的。” 这样,也可以解释得通 —— 引用数据类型按值传递了内存指针的副本。