对象解构

对象解构语法在赋值语句的左侧使用了对象字面量,例如:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"

在此代码中,node.type 的值被存储到 type 本地变量中,node.name 的值则存储到 name 变量中。此语法相同于第四章介绍的简写的属性初始化器。typename 标识符既声明了本地变量,也读取了对象的相应属性值。

不要遗忘初始化器

当使用解构来配合 varletconst 来声明变量时,必须提供初始化器(即等号右边的值)。下面的代码都会因为缺失初始化器而抛出错误:

// 语法错误!
var { type, name };
// 语法错误!
let { type, name };
// 语法错误!
const { type, name };

const 总是要求有初始化器,即使没有使用解构的变量;而 varlet 则仅在使用解构时才作此要求。

解构赋值

以上对象解构示例都用于变量声明。不过,也可以在赋值的时候使用解构。例如,你可能想在变量声明之后改变它们的值,如下所示:

let node = {
        type: "Identifier",
        name: "foo"
    },
    type = "Literal",
    name = 5;

// assign different values using destructuring
({ type, name } = node);

console.log(type);      // "Identifier"
console.log(name);      // "foo"

在本例中,typename 属性在声明时被初始化,而两个同名变量也被声明并初始化为不同的值。接下来一行使用了解构表达式,通过读取 node 对象来更改这两个变量的值。注意你必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符(即等号)左侧出现。圆括号标示了里面的花括号并不是块语句、而应该被解释为表达式,从而允许完成赋值操作。

解构赋值表达式的值为表达式右侧(在 = 之后)的值。也就是说在任何期望有个值的位置都可以使用解构赋值表达式。例如,传递值给函数:

let node = {
        type: "Identifier",
        name: "foo"
    },
    type = "Literal",
    name = 5;

function outputInfo(value) {
    console.log(value === node);
}

outputInfo({ type, name } = node);        // true

console.log(type);      // "Identifier"
console.log(name);      // "foo"

outputInfo() 函数被使用一个解构赋值表达式进行了调用。该表达式计算结果为 node,因为这就是表达式右侧的值。对 typename 的赋值正常进行,同时 node 也被传入了 outputInfo() 函数。

当解构赋值表达式的右侧(= 后面的表达式)的计算结果为 nullundefined 时,会抛出错误。因为任何读取 nullundefined 的企图都会导致 “运行时” 错误(runtime error)。

默认值

当你使用解构赋值语句时,如果所指定的本地变量在对象中没有找到同名属性,那么该变量会被赋值为 undefined。例如:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name, value } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"
console.log(value);     // undefined

此代码定义了一个额外的本地变量 value,并试图对其赋值。然而,node 对象中不存在同名属性,因此 value 不出预料地被赋值为 undefined

你可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。若要这么做,需要在属性名后面添加一个等号并指定默认值,就像这样:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name, value = true } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"
console.log(value);     // true

在此例中,变量 value 被指定了一个默认值 true,只有在 node 的对应属性缺失、或对应的属性值为 undefined 的情况下,该默认值才会被使用。由于此处不存在 node.value 属性,变量 value 就使用了该默认值。这种工作方式很像函数参数的默认值(详见第三章)。

赋值给不同的本地变量名

至此的每个解构赋值示例都使用了对象中的属性名作为本地变量的名称,例如,把 node.type 的值存储到 type 变量上。若想使用相同名称,这么做就没问题,但若你不想呢?ES6 有一个扩展语法,允许你在给本地变量赋值时使用一个不同的名称,而且该语法看上去就像是使用对象字面量的非简写的属性初始化。这里有个示例:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type: localType, name: localName } = node;

console.log(localType);     // "Identifier"
console.log(localName);     // "foo"

此代码使用了解构赋值来声明 localTypelocalName 变量,分别获得了 node.typenode.name 属性的值。type: localType 这种语法表示要读取名为 type 的属性,并把它的值存储在变量 localType 上。该语法实际上与传统对象字面量语法相反,传统语法将名称放在冒号左边、值放在冒号右边;而在本例中,则是名称在右边,需要进行值读取的位置则被放在了左边。

你也可以给变量别名添加默认值,依然是在本地变量名称后添加等号与默认值,例如:

let node = {
        type: "Identifier"
    };

let { type: localType, name: localName = "bar" } = node;

console.log(localType);     // "Identifier"
console.log(localName);     // "bar"

此处的 localName 变量拥有一个默认值 "bar",该变量最终被赋予了默认值,因为 node.name 属性并不存在。

到此为止,你已经看到如何处理属性值为基本类型值的对象的解构,而对象解构也可被用于从嵌套的对象结构(即:对象的属性可能还是一个对象)中提取属性值。

嵌套的对象解构

使用类似于对象字面量的语法,可以深入到嵌套的对象结构中去提取你想要的数据。这里有个示例:

let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };

let { loc: { start }} = node;

console.log(start.line);        // 1
console.log(start.column);      // 1

本例中的解构模式使用了花括号,表示应当下行到 node 对象的 loc 属性内部去寻找 start 属性。记住上一节介绍过的,每当有一个冒号在解构模式中出现,就意味着冒号之前的标识符代表需要检查的位置,而冒号右侧则是赋值的目标。当冒号右侧存在花括号时,表示目标被嵌套在对象的更深一层中。

你还能更进一步,在对象的嵌套解构中同样能为本地变量使用不同的名称:

let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };

// extract node.loc.start
let { loc: { start: localStart }} = node;

console.log(localStart.line);   // 1
console.log(localStart.column); // 1

在此版本的代码中,node.loc.start 的值被存储在一个新的本地变量 localStart 上,解构模式可以被嵌套在任意深度的层级,并且在每个层级的功能都一样。

对象解构十分强大并有很多可用形式,而数组解构则提供了一些独特的能力,用于提取数组中的信息。

语法难点

使用嵌套的解构时需要小心,因为你可能无意中就创建了一个没有任何效果的语句。空白花括号在对象解构中是合法的,然而它不会做任何事。例如:

// 没有变量被声明!
let { loc: {} } = node;

在此语句中并未声明任何变量绑定。由于花括号在右侧,loc 被作为需检查的位置来使用,而不会创建变量绑定。这种情况仿佛是想用等号来定义一个默认值,但却被语法判断为想用冒号来定义一个位置。这种语法将来可能是非法的,然而现在它只是需要留意的一个疑难点。