10.11 解构算法

本节从不同的角度来看解构过程:一个递归的模式匹配算法。

这个不同的角度应该对理解默认值很有帮助。如果你感觉自己没有完全理解解构过程,那么继续读下去。

在最后,我将会使用这个算法解释下面两个函数声明的不同之处。

function move({x=0, y=0} = {})         { ··· }
function move({x, y} = { x: 0, y: 0 }) { ··· }

10.11.1 算法

解构赋值看起来像这样:

«pattern» = «value»

我们想使用 patternvalue 里面获取数据。我现在将会描述一种算法来做这件事,这在函数式编程中被称为模式匹配( pattern matching )(简称:匹配)。该算法指定操作符(“匹配”)用于表示解构赋值中给 pattern 匹配上一个 value 并赋值给变量:

«pattern» ← «value»

该算法通过递归的规则执行,这些递归规则在←操作符两侧的操作数都会发生。这个声明性的符号可能不好适应,但是它让该算法的说明更加简洁明了。每一个规则都有两部分:

  • 头部指明该规则应用到的操作数。
  • 主体部分指明接下来做什么。

我仅展示解构赋值的算法。解构变量声明和解构参数定义是类似的。

我也不会讲解高级的特性(计算属性键;属性值缩写;对象属性和数组元素作为赋值目标),仅仅讲解基础的东西。

10.11.1.1 模式

一个模式是下列的几种形式之一:

  • 一个变量: x
  • 一个对象模式: {«properties»}
  • 一个数组模式: [«elements»]

后面每节描述了这三种情况中的一种。

10.11.1.2 变量

  • (1) x ← value(包括 undefinednull
x = value

10.11.1.3 对象模式

  • (2a) {«properties»} ← undefined
throw new TypeError();
  • (2b) {«properties»} ← null
throw new TypeError();
  • (2c) {key: «pattern», «properties»} ← obj
«pattern» ← obj.key
{«properties»} ← obj
  • (2d) {key: «pattern» = default_value, «properties»} ← obj
let tmp = obj.key;
if (tmp !== undefined) {
  «pattern» ← tmp
} else {
  «pattern» ← default_value
}
{«properties»} ← obj
  • (2e) {} ← obj
// No properties left, nothing to do

10.11.1.4 数组模式

数组模式和迭代器。数组解构算法以数组模式和一个迭代器开始:

  • (3a) [«elements»] ← non_iterable
    assert(!isIterable(non_iterable))
throw new TypeError();
  • (3b) [«elements»] ← iterable
    assert(isIterable(iterable))
let iterator = iterable[Symbol.iterator]();
«elements» ← iterator

辅助函数:

function isIterable(value) {
    return (value !== null
        && typeof value === 'object'
        && typeof value[Symbol.iterator] === 'function');
}

数组元素和迭代器。算法以模式和迭代器的元素继续(从迭代器中取值)。

  • (3c) «pattern», «elements» ← iterator
«pattern» ← getNext(iterator) // undefined after last item
«elements» ← iterator
  • (3d) «pattern» = default_value, «elements» ← iterator
let tmp = getNext(iterator);  // undefined after last item
if (tmp !== undefined) {
  «pattern» ← tmp
} else {
  «pattern» ← default_value
}
«elements» ← iterator
  • (3e) , «elements» ← iterator (hole, elision)
getNext(iterator); // skip
«elements» ← iterator
  • (3f) ...«pattern» ← iterator (总是最后一部分!)
let tmp = [];
for (let elem of iterator) {
  tmp.push(elem);
}
«pattern» ← tmp
  • (3g) ← iterator

辅助函数:

function getNext(iterator) {
    let {done,value} = iterator.next();
    return (done ? undefined : value);
}

10.11.2 应用算法

下面的函数定义有命名的参数,一种有时称作可选对象( options object )的技术,在参数处理章节有解释。参数使用了解构和默认值,这样 xy 就可以省略不传了。但是对象参数也可以不传,就像下面代码中最后一行一样。此特性通过在函数声明头部的={}起作用。

function move1({x=0, y=0} = {}) {
    return [x, y];
}
move1({x: 3, y: 8}); // [3, 8]
move1({x: 3}); // [3, 0]
move1({}); // [0, 0]
move1(); // [0, 0]

但是为什么要像上述代码片段一样定义参数呢?为什么不是像下面这样的呢 - 这也是完全合法的 ES6 代码?

function move2({x, y} = { x: 0, y: 0 }) {
    return [x, y];
}

为了验证为什么 move1() 是正确的,让我们在两个例子中使用这两种函数。在做这件事之前,让我们看看传递的参数是如何匹配解析的。

10.11.2.1 背景:通过匹配传递参数

对于函数调用,形参(在函数定义里面)匹配实参(在函数调用里面)。举个例子,使用下面的函数定义和下面的函数调用:

function func(a=0, b=0) { ··· }
func(1, 2);

参数 ab 和下面的解构过程类似地被设置。

[a=0, b=0] ← [1, 2]

10.11.2.2 使用 move2()

让我们看一下 move2() 的解构过程是怎样的。

示例1。 move2() 会导致该解构过程:

[{x, y} = { x: 0, y: 0 }] ← []

左侧仅有的数组元素在右侧并没有匹配的内容,这就是为什么 {x, y} 匹配上了默认值,而不是来自于右侧的数据(规则 3b , 3d ):

{x, y} ← { x: 0, y: 0 }

该解构过程导致下面的两个赋值(规则 2c ,1):

x = 0;
y = 0;

这就是仅有的使用默认值的情况。

示例2。让我们看看函数调用 move2({z:3}) ,这会导致如下的解构过程:

[{x, y} = { x: 0, y: 0 }] ← [{z:3}]

在右侧数组的索引0处有一个数组元素。因此,默认值会被忽略,下一步是(规则 3d ):

{x, y} ← { z: 3 }

这会导致 xy 都被设置为 undefined ,这不是我们想要的。

10.11.2.3 使用 move1()

让我们尝试 move1()

示例1: move1()

[{x=0, y=0} = {}] ← []

在右侧数组的索引0处没有一个数组元素,所以使用默认值(规则 3d ):

{x=0, y=0} ← {}

左侧包含属性值缩写,解构过程相当于:

{x: x=0, y: y=0} ← {}

属性 x 和属性 y 在右侧都没有匹配的内容。因此,使用默认值,下一步是如下所示的解构过程(规则 2d ):

x ← 0
y ← 0

这会导致如下的赋值(规则1):

x = 0
y = 0

示例2: move1({z:3})

[{x=0, y=0} = {}] ← [{z:3}]

就像示例1一样,属性 xy 在右侧没有匹配的内容,所以使用默认值:

x = 0
y = 0

10.11.3 结论

这些例子展示了默认值是模式部分(对象属性或者数组元素)的特性。如果某个部分没有匹配上,或者匹配到了 undefined ,那么默认值就生效了。也就是说,模式匹配上了默认值。

results matching ""

    No results matching ""