変数の構造化代入

配列の代入を分割する

基本的な使い方

ES6 では、配列やオブジェクトから値を抽出し、特定のパターンに従って変数に値を割り当てることができます。これを構造化と呼びます。

以前は、変数に値を割り当てるには、値を直接指定することしかできませんでした。

a = 1 とします。
b = 2 とします。
c = 3 とします。

ES6では以下の記述が可能です。

[a, b, c] = [1, 2, 3]; とします。

上記のコードは、配列から値を抽出し、対応する位置に従って変数に割り当てることができることを示しています。

本質的に、この書き方は「パターン マッチング」です。等号の両側のパターンが同じである限り、左側の変数に対応する値が代入されます。ネストされた配列を使用した構造化の例をいくつか示します。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
バー // 2
バズ // 3

let [, , third] = ["foo", "bar", "baz"];
3番目 // "バズ"

[x, , y] = [1, 2, 3]; とします。
× // 1
y // 3

[頭, ...尾] = [1, 2, 3, 4]; とします。
頭 // 1// [2、3、4]

[x, y, ...z] = ['a']; とします。
x //「あ」
y // 未定義
z // []

構造化が失敗した場合、変数の値は「未定義」になります。

[foo] = []; にします。
[bar, foo] = [1] にします。

どちらの場合も、構造の分割は失敗し、「foo」の値は「unknown」と等しくなります。

もう 1 つのケースは不完全な構造化です。つまり、等号の左側のパターンが等号の右側の配列の一部とのみ一致します。この場合でも、分解は成功する可能性があります。

[x, y] = [1, 2, 3] とします。
× // 1
y // 2

[a, [b], d] = [1, [2, 3], 4] とします。
// 1
b // 2
d // 4

上の 2 つの例は不完全な分解ですが、成功する可能性があります。

等号の右側が配列でない場合 (厳密に言えば、走査可能な構造ではありません。「反復子」の章を参照)、エラーが報告されます。

// エラーを報告する
[foo] = 1 とします。
let [foo] = false;
let [foo] = NaN;
let [foo] = 未定義;
[foo] = null にします。
let [foo] = {};

上記のステートメントはすべてエラーを報告します。これは、等号の右側の値がオブジェクトに変換された後に Iterator インターフェイスを持たないか (最初の 5 つの式)、または Iterator インターフェイス自体が存在しないためです (最後の表現)。

Set 構造の場合、配列の分割代入も使用できます。

let [x, y, z] = new Set(['a', 'b', 'c']);
x //「あ」

実際、特定のデータ構造が Iterator インターフェイスを持っている限り、配列形式での分割代入が使用できます。

関数* fibs() {
  a = 0 とします。
  b = 1 とします。
  while (true) {
    収量a;
    [a, b] = [b, a + b];
  }
}

let [最初、二番目、三番目、四番目、五番目、六番目] = fibs();
6番目 // 5

上記のコードでは、fibs はジェネレータ関数 (「ジェネレータ関数」の章を参照) であり、ネイティブの Iterator インターフェイスを持っています。代入を分割すると、このインターフェイスから値が順番に取得されます。

デフォルト値

代入を分割すると、デフォルト値を指定できます。

[foo = true] = []; にしましょう。
foo // true

let [x, y = 'b'] = ['a'] // x='a', y='b';
let [x, y = 'b'] = ['a'、未定義] // x='a', y='b';

ES6 は内部で厳密等価演算子 (====) を使用して、位置に値があるかどうかを判断することに注意してください。したがって、デフォルト値は、配列メンバーが厳密に「unknown」と等しい場合にのみ有効になります。

[x = 1] = [未定義] とします。
× // 1

[x = 1] = [null] とします。
x // null

上記のコードでは、配列メンバーが null の場合、null は厳密には unknown と等しくないため、デフォルト値は有効になりません。

デフォルト値が式の場合、式は遅延評価されます。つまり、式は使用されたときにのみ評価されます。

関数 f() {
  console.log('aaa');
}

[x = f()] = [1] とします。

上記のコードでは、x が値を取得できるため、関数 f はまったく実行されません。上記のコードは、実際には次のコードと等価です。

x にしましょう。
if ([1][0] === 未定義) {
  x = f();
} それ以外 {
  x = [1][0];
}

デフォルト値は、構造化によって割り当てられた他の変数を参照できますが、変数は宣言されている必要があります。

let [x = 1, y = x] = []; // x=1;
let [x = 1, y = x] = [2] // x=2;
let [x = 1, y = x] = [1, 2]; // x=1;
let [x = y, y = 1] = []; // 参照エラー: y が定義されていません

上記の最後の式でエラーが報告される理由は、「x」がデフォルト値として「y」を使用する場合、「y」が宣言されていないためです。

##オブジェクトの割り当ての分割

導入

分割は配列だけでなくオブジェクトにも使用できます。

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // 「ああ」
バー // "bbb"

オブジェクトの構造化と配列の構造化には重要な違いがあります。配列の要素は順序どおりに配置され、変数の値はその位置によって決まりますが、オブジェクトの属性は順序どおりではないため、正しい値を取得するには変数の名前が属性と同じである必要があります。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // 「ああ」
バー // "bbb"

let { baz } = { foo: 'aaa'bar: 'bbb' };
baz // 未定義

上記のコードの最初の例では、等号の左側にある 2 つの変数の順序が、等号の右側にある同じ名前の 2 つのプロパティの順序と矛盾していますが、影響はありません。まったく価値観に関して。 2 番目の例の変数には、同じ名前の対応するプロパティがないため、値を取得できず、最終的には「未定義」と等しくなります。

構造化が失敗した場合、変数の値は「未定義」と等しくなります。

let {foo} = {bar: 'baz'};
foo // 未定義

上記のコードでは、等号の右側のオブジェクトは foo 属性を持たないため、変数 foo は値を取得できないため、unknown と等しくなります。

オブジェクトを構造化して割り当てると、既存のオブジェクトのメソッドを変数に簡単に割り当てることができます。

//例1
let {log、sin、cos} = 数学;

//例2
const { ログ } = コンソール;
log('hello') // こんにちは

上記のコードの例 1 では、Math オブジェクトの対数、サイン、およびコサインのメソッドを対応する変数に割り当てています。これは、より使いやすくなります。例 2 では、console.loglog 変数に割り当てます。

変数名が属性名と一致しない場合は、次のように記述する必要があります。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
バズ // 「ああ」

let obj = { 最初: 'こんにちは'、最後: '世界' };
let { 最初: f、最後: l } = obj;
f // 'こんにちは'
l // '世界'

これは実際、オブジェクトの構造化代入が次の形式の省略形であることを示しています (「オブジェクトの拡張」の章を参照)。

let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

つまり、オブジェクトの構造化と代入の内部メカニズムは、まず同じ名前の属性を見つけて、それを対応する変数に割り当てることです。実際に割り当てられるのは前者ではなく後者です。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
バズ // 「ああ」
foo // エラー: foo が定義されていません

上記のコードでは、foo が一致パターン、baz が変数です。実際に割り当てられるのは、パターン foo ではなく、変数 baz です。

配列と同様に、入れ子構造のオブジェクトに対しても構造の分割を使用できます。

obj = { にします
  p: [
    'こんにちは'、
    { y: '世界' }
  ]
};

let { p: [x, { y }] } = obj;
x // 「こんにちは」
y // 「ワールド」

このとき「p」は変数ではなくパターンなので値は代入されないことに注意してください。 p も変数として代入する必要がある場合は、次のように記述できます。

obj = { にします
  p: [
    'こんにちは'、
    { y: '世界' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // 「こんにちは」
y // 「ワールド」
p // ["こんにちは", {y: "世界"}]

別の例を示します。

const ノード = {
  位置: {
    始める: {
      行: 1、
      列: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = ノード;
行 // 1
loc // オブジェクト {開始: オブジェクト}
start // オブジェクト {行: 1、列: 5}

上記のコードには 3 つの構造化代入があり、それらは locstart、および line の 3 つの属性に対する構造化代入です。 line 属性の最後の分割割り当てでは、line だけが変数であり、locstart は変数ではなくパターンであることに注意してください。

以下はネストされた代入の例です。

obj = {} にします。
arr = []; とします。

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [真]

分割モードがネストされたオブジェクトで、子オブジェクトの親プロパティが存在しない場合、エラーが報告されます。

// エラーを報告する
let {foo: {bar}} = {baz: 'baz'};

上記のコードでは、等号の左側にあるオブジェクトの foo プロパティがサブオブジェクトに対応します。このサブオブジェクトの bar 属性は、構造化時にエラーを報告します。理由は非常に簡単です。この時点では foounknown に等しいため、サブプロパティを取得するとエラーが報告されます。

オブジェクトの割り当てを分割すると、継承されたプロパティにアクセスできることに注意してください。

const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);

const { foo } = obj1;
foo // "バー"

上記のコードでは、オブジェクト obj1 のプロトタイプ オブジェクトは obj2 です。 foo 属性は obj1 自体の属性ではなく、obj2 から継承された属性です。この属性は代入を分割することで取得できます。

デフォルト値

オブジェクトの分割では、デフォルト値を指定することもできます。

var {x = 3} = {};
× // 3

var {x, y = 5} = {x: 1};
× // 1
y // 5

var {x:y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { メッセージ: msg = '問題が発生しました' } = {};
msg // 「問題が発生しました」

デフォルト値が有効になる条件は、オブジェクトのプロパティ値が厳密に「unknown」に等しいことです。

var {x = 3} = {x: 未定義};
× // 3

var {x = 3} = {x: null};
x // null

上記のコードでは、属性 xnull と等しくなります。これは、nullunknown が厳密には等しくないため、これは有効な代入であり、デフォルト値 3 が有効になりません。

注記

(1) 宣言された変数を代入の構造化に使用する場合は、十分に注意する必要があります。

//書き方が間違っています
x にしましょう。
{x} = {x: 1};
// SyntaxError: 構文エラー

JavaScript エンジンは {x} をコード ブロックとして認識し、構文エラーが発生するため、上記のコードはエラーを報告します。この問題は、JavaScript がコードのブロックとして解釈しないように、行の先頭に中括弧を書かないことによってのみ解決できます。

// 正しい書き方
x にしましょう。
({x} = {x: 1});

上記のコードは、構造化代入ステートメント全体を括弧内に配置しており、正しく実行できます。括弧と構造化代入の関係については、以下を参照してください。

(2) 分割代入では、等号の左側のパターンに変数名を配置できません。したがって、非常に奇妙な代入式を作成することができます。

({} = [true, false]);
({} = 'abc');
({} = []);

上記の式は意味がありませんが、構文は正当であり、実行できます。

(3) 配列は本質的に特殊なオブジェクトであるため、配列のオブジェクト属性を分解できます。

arr = [1, 2, 3] とします。
{0 : 最初、[arr.length - 1] : 最後} = arr; とします。
最初の // 1
最後 // 3

上記のコードは、配列に対してオブジェクトの構造化を実行します。配列「arr」の「0」キーに対応する値は「1」、「[arr.length - 1]」は「2」キーで対応する値は「3」です。角括弧の記述方法は「属性名式」に属します(「オブジェクト拡張」の章を参照)。

文字列の代入を分割する

文字列を分解して割り当てることもできます。これは、この時点で文字列が配列のようなオブジェクトに変換されるためです。

const [a、b、c、d、e] = 'こんにちは';
a // "h"
なれ"
c // "l"
d //「l」
e //「お」

配列のようなオブジェクトには「length」プロパティがあるため、このプロパティを構造化して値を割り当てることもできます。

let {長さ: len} = 'こんにちは';
レン // 5

数値とブール値の構造化代入

代入を分割するとき、等号の右側が数値またはブール値の場合、最初にオブジェクトに変換されます。

{toString: s} = 123; とします。
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上記のコードでは、数値とブール値のラッピングオブジェクトに「toString」属性があるため、変数「s」で値を取得できます。

分割代入のルールは、等号の右側の値がオブジェクトまたは配列でない限り、最初にそれをオブジェクトに変換することです。 「undefine」や「null」はオブジェクト化できないため、分解して代入するとエラーとなります。

let { prop: x } = 未定義;
let { prop: y } = null;

関数パラメータの割り当ての構造化

関数パラメータは、構造化を使用して割り当てることもできます。

関数 add([x, y]){
  x + y を返します。
}

add([1, 2]); // 3

上記のコードでは、関数 add のパラメータは表面上の配列ですが、パラメータが渡された瞬間に、配列パラメータは変数 xy に分解されます。関数内のコードで感じられるパラメータは xy です。

別の例を示します。

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [3, 7]

関数パラメータの分割では、デフォルト値を使用することもできます。

関数 move({x = 0, y = 0} = {}) {
  [x, y] を返します。
}

move({x: 3, y: 8}); // [3, 8]
move({x:3}); // [3, 0]
move({}); // [0, 0]
移動(); // [0, 0]

上記のコードでは、関数moveのパラメータがオブジェクトとなっており、このオブジェクトを分解することで変数xyの値を取得しています。構造化が失敗した場合、xy はデフォルト値と等しくなります。

以下の書き方では結果が異なりますので注意してください。

関数 move({x, y} = { x: 0, y: 0 }) {
  [x, y] を返します。
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 未定義]
move({}); // [未定義、未定義]
移動(); // [0, 0]

上記のコードは、変数「x」と「y」にデフォルト値を指定するのではなく、関数「move」のパラメータにデフォルト値を指定しているため、前の書き方とは異なる結果が得られます。

unknown は関数パラメータのデフォルト値をトリガーします。

[1、未定義、3].map((x = 'はい') => x);
// [ 1, 'はい', 3 ]

括弧の問題

代入の構造化は便利ですが、解析するのは簡単ではありません。コンパイラにとって、式がパターンであるか式であるかを最初から知る方法はありません。それを知るには、等号を解析する必要があります (または解析に失敗する必要があります)。

発生する問題は、パターン内に括弧が出現した場合にどうするかということです。 ES6 のルールは、構造化につながる可能性のあるあいまいさがある場合には括弧を使用してはいけないということです。

ただし、このルールは実際には見分けるのがそれほど簡単ではなく、対処するのが非常に面倒になる可能性があります。したがって、可能な限りパターン内に括弧を配置しないことをお勧めします。

括弧が使用できない場合

次の 3 つの分割代入では括弧を使用してはなりません。

(1) 変数宣言文

// すべてのエラーを報告する
[(a)] = [1] とします。

{x: (c)} = {}; とします。
let ({x: c}) = {};
{(x: c)} = {}; とします。
{(x): c} = {}; とします。

let { o: ({ p: p }) } = { o: { p: 2 } };

上記の 6 つのステートメントはすべて変数宣言ステートメントであり、モードでは括弧を使用できないため、エラーが報告されます。

(2) 関数パラメータ

関数パラメータも変数宣言であるため、括弧を含めることはできません。

// エラーを報告する
関数 f([(z)]) { z を返す }
// エラーを報告する
関数 f([z,(x)]) { 戻り値 x; }

(3) 代入文のパターン

// すべてのエラーを報告する
({ p: a }) = { p: 42 };
([a]) = [5];

上記のコードではパターン全体が括弧で囲まれているため、エラーが発生します。

// エラーを報告する
[({ p: a }), { x: c }] = [{}, {}];

上記のコードではパターンの一部が括弧内に配置されているため、エラーが発生します。

括弧が使用できる場合

括弧を使用できる状況は 1 つだけです。代入ステートメントの非パターン部分で括弧を使用できます。

[(b)] = [3]; // 正しい
({ p: (d) } = {});
[(parseInt.prop)] = [3]; // 正しい

上記の 3 行のステートメントは、まず、宣言ステートメントではなく代入ステートメントであるため、正しく実行できます。次に、括弧がパターンの一部ではないためです。ステートメントの 1 行目では、括弧に関係なく、モードは配列の最初のメンバーを取得します。ステートメントの 3 行目では、モードは 'd' ではなく 'p' です。ステートメントの最初の行と同じプロパティが一貫していること。

目的

変数の構造化代入にはさまざまな用途があります。

(1) 変数の値を交換する

x = 1 とします。
y = 2 とします。

[x, y] = [y, x];

上記のコードは変数 xy の値を交換します。この記述方法は簡潔であるだけでなく、読みやすく、セマンティクスも非常に明確です。

(2) 関数から複数の値を返す

関数は 1 つの値のみを返すことができます。複数の値を返したい場合は、それらの値を配列またはオブジェクトに入れて返すことしかできません。代入を分割すると、これらの値を取得するのが非常に便利になります。

// 配列を返す

関数 example() {
  [123] を返します。
}
let [a, b, c] = example();

//オブジェクトを返す

関数 example() {
  戻る {
    フー: 1、
    バー: 2
  };
}
let { foo, bar } = example();

(3)関数パラメータの定義

構造化代入を行うと、パラメータのセットを変数名に簡単にマッピングできます。

// パラメータは順序付けられた値のセットです
関数 f([x, y, z]) { ... }
f([1, 2, 3]);

// パラメータは順序のない値のセットです
関数 f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

(4) JSONデータの抽出

代入の分割は、JSON オブジェクトからデータを抽出する場合に特に便利です。

let jsonData = {
  ID:42、
  ステータス: "OK"、
  データ: [867, 5309]
};

let { ID、ステータス、データ: 数値 } = jsonData;

console.log(id, ステータス, 番号);
// 42, "OK", [867, 5309]

上記のコードは、JSON データの値を迅速に抽出できます。

(5)関数パラメータのデフォルト値

jQuery.ajax = 関数 (url, {
  非同期 = true、
  beforeSend = function () {},
  キャッシュ = true、
  complete = function () {},
  クロスドメイン = false、
  グローバル = true// ...詳細な設定
} = {}) {
  // ... 何かをする
};

パラメータのデフォルト値を指定すると、関数本体内に var foo = config.foo || 'default foo'; のようなステートメントを記述する必要がなくなります。

(6) マップ構造をトラバースする

Iterator インターフェースを使用してデプロイされたオブジェクトはすべて、「for...of」 ループを使用して走査できます。 Map 構造は、Iterator インターフェイスをネイティブにサポートしており、変数の分割と代入により、キー名とキー値を取得するのに非常に便利です。

const マップ = 新しい Map();
map.set('first', 'hello');
map.set('秒', '世界');

for (マップの [キー, 値] にします) {
  console.log(キー + " は " + 値);
}
// まずはこんにちは
// 2 番目はワールドです

キー名のみ、またはキーの値のみを取得したい場合は、次のように記述できます。

// キー名を取得する
for (マップの [キー] を許可) {
  // ...
}

// キー値を取得する
for (map の [,value] にします) {
  // ...
}

(7) 入力モジュールの指定方法

モジュールをロードするとき、多くの場合、どのメソッドを入力するかを指定する必要があります。代入を分割すると、入力ステートメントが非常に明確になります。

const { SourceMapConsumer, SourceNode } = require("ソースマップ");

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0