※スマホ対応はしてません。

データ保持ならObjectよりMapの方が良いの?(現代的JavaScriptおれおれアドベントカレンダー2017 – 19日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 19日目

良いか???

概要

Array#map() じゃなくて Map というビルトインコンストラクタです。

辞書 (dictionary) みたいなノリで使えます。

const map = new Map();

map.set(100, 'Number 100');
console.log(map.get(100));  // => "Number 100"

for (let [key, value] of map) {
    console.log(`[${key}] = [${value}]`);
}

使い方

new で作って名前と値の組み合わせの情報を格納します。

情報の追加と取得

set() で追加(と上書き)、 get() で取得。

const map = new Map();
const key = 100;

// set
map.set(key, { message: 'Hello World!' });

// get
const value = map.get(key);

concole.log(key, value);

set() は map オブジェクト自体を返すので、メソッドチェインみたいに書けます。

map
  .set(100, 'Hello')
  .set(101, 'World')
  .set(102, '!')

あんまり書かない方が良いと思うけど。

有無の確認

const map = new Map();

console.log(map.has(1));  // => false

map.set(1, 'one');
console.log(map.has(1));  // => true

削除

delete() でキーを指定して消します。削除できた場合は true が、もともと値を持っていなかった場合は false が帰ります。 delete 演算子と違うね。

あと clear() で全部消します。こちらは常に undefined を返す。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3
console.log('#101', map.get(101));  // => #101 Goodmorning Universe!

// ひとつ削除
deleted = map.delete(101);
console.log('Size:', map.size);  // => Size: 2
console.log('#101', map.get(101));  // => #101 undefined
console.log('Delete successfully?', deleted);  // => true

// 削除済みのものを削除
deleted = map.delete(101);
console.log('Delete successfully?', deleted);  // => false

// 全部削除
map.clear();
console.log('Size:', map.size);  // => Size: 0

キーは何でも

オブジェクトの場合は基本的に toString() で文字列に変換されるんだけど、 Map の場合は型も含めてそのままキーになります。 1 と "1" は別物扱いです。

const map = new Map();

// 数値の1と文字列の"1"
map.set(1, 'Number 1');
map.set('1', 'String 1');

console.log(map.get(1));  // => "Number 1"
console.log(map.get('1'));  // => "String 1"

各種オブジェクトをそのまま突っ込むこともできます。

繰り返す

forEach() あるいは for-of ループでぐるぐるできます。

あと size という、配列の length みたいなプロパティがあります。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3

map.forEach((value, key) => {
    console.log('forEach', key, value);
})

for (let [key, value] of map) {
    console.log('for-of', key, value);
}

forEach() 以外の、 Array.prototype 系のメソッドはないです。

キー、値をまとめて取得

keys() でキーだけ、 values() で値だけ、さらに entries() でキーと値の組のイテレータを得られます。

const map = new Map();

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

for (let key of map.keys()) {
    console.log('key', key);
}

for (let value of map.values()) {
    console.log('value', value);
}

for (let [key, value] of map.entries()) {
    console.log('key-value', key, value);
}

[] アクセスはだめ

マップじゃなくて普通のインスタンスプロパティへのアクセスになります。

const map = new Map();

map.set(100, 'Hello World!');
map[101] = 'Goodmorning Universe!';

console.log(map.size);  // => 1
console.log(map.get(100));  // => "Hello World!"
console.log(map.get(101));  // => undefined
console.log(map['101']);  // => "Goodmorning Universe!"

オブジェクトに追加情報を持たせる

他に独自の情報を持ちたいけど元のオブジェクトに触りたくないなーというとき、そのオブジェクト自体をキーにして、追加情報を別途置いておくことができます。

const map = new Map();

const el = document.querySelector('#foo');
const someDataForTheElement = {};
map.set(el, someDataForTheElement);

でも別インスタンスになると別物っていう扱いになっちゃうので、永続的に使える識別子があればそっちの方が良さそう。

使い道

と、ここまで書いてきたけど正直これっていう使い道が思いつかない……。

オブジェクト Object (ないし {} )でも同じようなことはできます。今までずっとこっちでやってきましたね。

const map = {};
map[100] = 'A hundred';
map[101] = 'A hundred and one';

console.log(100, map[100]);  // => "A hundred"

Object.keys(map)
    .forEach((key) => {
        const value = map[key];
        console.log(key, value);
    });

反復処理とかちょっとアレだけどまあどうにかなるし、まだあんまりよくわかんない。

Object と Map の違い

  • [] の代わりに set() 、 get() を使う
  • キーに文字列以外も使える( 1 と "1" が別物扱い)
  • 反復処理 for-of できる
  • size で数を取れる

MDNには

こう載ってる。

『オブジェクトとマップの比較』より。

これは Map をいつも使えばいいということではありません。オブジェクトはまだ多くの場面で使えます。Map インスタンスはコレクションとして使う場合のみに役に立ちます。以前オブジェクトをこのように使っていたコードに Map を使うことを考えてみるべきです。オブジェクトはメンバ変数とメソッドを備えたレコードとして使われます。もしまだどちらを使えばいいかわからないなら、以下の質問に答えてみてください。

  • キーがいつも実行時までわからない、またはキーを直接調べる必要がありますか?
  • すべての値が同じ型で、交換して使用できますか?
  • 文字列でないキーが必要ですか?
  • キーと値のペアを時々、追加または削除しますか?
  • 簡単に量が変わるキーと値のペアがありますか?
  • そのコレクションをイテレートしますか?

これらはあなたが Map をコレクションとして使いたいときのサインです。もし対照的に、固定された量のキーがあり、それら個々に操作し、Mapの使い方と区別する場合、オブジェクトを使いましょう。

その他

NaN が合致する

JavaScriptの細かい話で良く出てくるやつなんですが、 NaN “Not a Number” は特殊な値で、自身と等値になりません。

const n = NaN;
console.log(n === n);  // false (wow)

が、マップのキーとして使う場合、(内部で)同じ値とみなしてくれます。

const map = new Map();

map.set(NaN, 'Not a Number');
console.log(map.get(NaN));  // => "Not a Number"
map.set(NaN, '!');
console.log(map.get(NaN));  // => "!"

もちろん NaN と文字列 "NaN" は合致しません。やったね。

オブジェクト Object のキー

上の方で「 obj[key] の key は基本的に文字列に」みたいに言ったけど、 Symbol てのも使えるようになりました。

参考

  • ECMAScript® 2017 Language Specification
    • 23.1 Map Objects
    • 23.1.3 Properties of the Map Prototype Object … 各種メソッドとプロパティ
    • 7.2.10 SameValueZero ( x, y ) … get() でキーの合致を確認する内部処理
    • 12.3.2.1 Runtime Semantics: Evaluation … obj[key] の解釈
    • 7.1.14 ToPropertyKey ( argument ) … obj[key] の key の解釈
  • Map – JavaScript | MDN
  • Symbol – JavaScript | MDN

配列を「開いて」使うスプレッド演算子。(現代的JavaScriptおれおれアドベントカレンダー2017 – 18日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 18日目

概要

配列をばらばらの配列要素だったことにして使えるやつです。

const arr = [1, 2, 3];

const arr2 = [0, ...arr];  // => [0, 1, 2, 3]

使い方

カンマ , 区切りでいろいろ書くところへ、 ...arr のようにすると、その配列の中身をばらばらに書いたかのような状態に展開してくれます。

配列とか、

const arr0 = [1, 2, 3];

const arr1 = [0, ...arr];  // => [0, 1, 2, 3]

関数とか。

function foo(a, b, c) {
  console.log(a, b, c);
}

const arr = [1, 2, 3];
foo(...arr);  // 1, 2, 3

途中でも、いくつでも

残余引数とかは末尾でしか使えなかったんですが、展開する分には十分明瞭なので、どこでもいくつでも使えます。

const arr1 = [1, 2, 3];
const arr2 = [4, 5];
const arr3 = [];

const all = ['|', ...arr1, '|', ...arr2, '|', ...arr3, '|'];  // => [ '|', 1, 2, 3, '|', 4, 5, '|', '|' ]

配列の中の配列

... を付けないとただの入れ子になります。そりゃそうじゃ。

const arr0 = [1, 2, 3];

const arr1 = [0, arr0];  // => [0, [1, 2, 3]]
const arr2 = [0, ...arr0];  // => [0, 1, 2, 3]

配列の複製

残余代入の方でも同じようなことやったけど、変数に格納する必要がない分こっちの方が良いかも。

const arr1 = [1, 2, 3];

function checkCloned(arr2) {
  console.log(arr1);
  console.log(arr2);
  console.log(arr1 === arr2);
}

checkCloned([...arr1]);
[ 1, 2, 3 ]
[ 1, 2, 3 ]
false

配列の結合

Array#conat() の代わりに使えそうです。

const arr1 = [1, 2, 3];
const arr2 = [4, 5];

const all1 = arr1.concat(arr2);  // => [1, 2, 3, 4, 5]
const all2 = [...arr1, ...arr2];  // => [1, 2, 3, 4, 5]

先頭項目を入れ替える

const arr = [1,2,3,4,5];

const [, ...secondAndLater] = arr;
const arr2 = [99, ...secondAndLater];  // => [99, 2, 3, 4, 5];

書いてはみたけど何に使えるかな。

オブジェクトのスプレッド

オブジェクトの残余代入と同様、ES2017までにはまだ存在していない未来仕様です。ES2018に入りそう。

const obj1 = { a: 1 };
const obj2 = { ...obj1 };

執筆時点でChrome、Firefoxとあと手元のNode.js v8.5で動いてます。

一方babel-preset-envのv1.6.1では対応してないっぽい。

SyntaxError: /path/to/project/main.js: Unexpected token (2:15)
  1 | const obj1 = { a: 1 };
> 2 | const obj2 = { ...obj1 };
    |                ^

(細かいバージョンは調べてません。)

Reactで使う

JSXの仕様なのでESのものではないんだけど、似たようなものなので紹介しておきます。あ、これオブジェクトの展開してるね。

you can use ... as a “spread” operator

const Button = props => {
  const { kind, ...other } = props;
  const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  return <button className={className} {...other} />;
};

Reactは普段使いしてないです。なんか違ってたらごめんなさい、教えてください。

その他

名前

みんな「スプレッド演算子 (spread operator)」と呼んでるけど、仕様書にそういう表現ないね。

あと配列の方は “SpreadElement” という名前が出てきてるんだけど、関数引数の方は特に名前が付いてもいない。

参考

  • ECMAScript® 2017 Language Specification
    • 12.2.5 Array Initializer … “SpreadElement” 配列のスプレッド演算子の記法
    • 12.2.5.2 Runtime Semantics: ArrayAccumulation … スプレッド演算子の処理
    • 12.3 Left-Hand-Side Expressions … 関数呼び出しの ArgumentList
    • 12.3.4 Function Calls
    • 12.3.6.1 Runtime Semantics: ArgumentListEvaluation … “…AssignmentExpression” とかの解釈
    • 9.4.4 Arguments Exotic Objects … arguments
  • スプレッド演算子 – JavaScript | MDN

分割代入や引数の「残り全部」を持ってくるやつ……。(現代的JavaScriptおれおれアドベントカレンダー2017 – 17日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 17日目

概要

...foo のようにして、「残り」を配列に突っ込めます。

function foo(a, b, ...rest) {}

あと配列の分割代入でも使えます。

const [a, b, ...rest] = arr;

加えて、まだ未確定の仕様ではオブジェクトの方でも使えるようになるっぽい。

const { a, b, ...rest } = obj;

使い方

分割代入でも関数の仮引数で使えます。

const arr = [1, 2, 3, 4, 5];

const [a, b, ...rest] = arr;
console.log(a, b, rest);  // => 1, 2, [3, 4, 5]
function foo(a, b, ...rest) {
  console.log(a, b, rest);  // => 1, 2, [3, 4, 5]
}

foo(1, 2, 3, 4, 5);

全部を受け取る

他を書かなければまるごと全部もらえます。

arr.concat() の代わりに浅いクローンで使えるかも。

const arr = [1, 2, 3, 4, 5];

const [...arr2] = arr;
console.log(arr);  // => [ 1, 2, 3, 4, 5 ]
console.log(arr2);  // => [ 1, 2, 3, 4, 5 ]
console.log(arr === arr2);  // => false

... で arguments を置き換える

arguments は引数が格納された配列風オブジェクトです。関数内で this みたいなノリでいきなり使えます。

function arrrrrg() {
  console.log(arguments.length, arguments[0]);
}

arrrrrg();  // 0, undefined
arrrrrg(100, 200, 300);  // 3, 100

で、これを ... で置き換えることができます。

function arrrrrg(...args) {
  console.log(args.length, args[0]);
}

arrrrrg();  // 0, undefined
arrrrrg(100, 200, 300);  // 3, 100

arguments には他にも( ... では置き換えられない)機能があるので、チラ見しとくと何かの役に立つかもです。

fn.apply()

他の関数を我が物顔で実行するのに使える Function#apply() というのがありまして。

arguments と組み合わせて使うと、他の関数をそのまま呼びなおすことができました。これも当然残余引数 ... で置き換えることができます。

const obj = {
  xxx: 'Hehehe',

  main(a, b, c) {
    console.log(this.xxx, a, `c is ${c}`);
  },
};

// 他の関数を呼ぶだけ
function justCallMain(...args) {
  obj.main.apply({ xxx: 'Yo!', }, args);
}

obj.main(11, 22, 33);  // "Hehehe", 11, "c is 33"
justCallMain(11, 22, 33);  // "Yo!", 11, "c is 33"

大昔にそんな話もしましたね。

引数の先頭に応じて別の処理を呼ぶ

(伝われ)

なんかこういうのあるよね。デザインパターンで名前付いてるのかな。

const robot = {
  command(type, ...args) {
    // typeに応じてメソッドを呼ぶ
    switch (type) {
      case 'walk':
        this.move(4, ...args);
        break;

      case 'run':
        this.move(16, ...args);
        break;

      case 'move':
        this.move(...args);
        break;

      case 'sing':
        this.sing(...args);
        break;

      // ...
    }
  },

  // ...
};

// 北へ向かって歩く
robot.command('walk', 'north');

// 東へ向かって走る
robot.command('run', 'east');

// 北へ8 km/hで移動
robot.command('move', 8, 'north');

// 突然歌いだす
robot.command('sing', 'あわてんぼうのサンタクロース');

仮引数じゃなくて関数呼び出しの引数で ... 使ってるのはスプレッド演算子です。別稿参照。

あと配列の分割代入でも同じような組み合わせやりましたねー。

オブジェクトの残余代入

ES2017までにはまだ存在していない未来仕様です。ES2018に入りそう。

const obj = { a: 1, b: 2, c: 3, d: 4 };

const { a, b, ...rest } = obj;
console.log(a, b, rest);  // => 1, 2, { c: 3, d: 4 }

執筆時点でChrome、Firefoxとあと手元のNode.js v8.5で動いてます。

一方babel-preset-envのv1.6.1では対応してないっぽい。

SyntaxError: /path/to/project/main.js: Unexpected token (2:14)
  1 | const obj = { a: 1, b: 2, c: 3 };
> 2 | const { a, b, ...rest } = obj;
    |               ^

(細かいバージョンは調べてません。)

その他

名前

引数の方、MDNだと “rest parameters” になってて、日本語訳で「残余引数」なんて呼んでるひとが多いみたいです。

変数の方はどう呼ぶんだろ。とりあえず「残余代入」なんて書いてはみたけれど。再びMDN(の英語版)を見ると “rest in arrays” 、 “rest in objects” という名前で “Browser compatibility” の表に記載されています。

可変長引数

他の言語だとこう呼ぶ場合が多いと思う。(引数の場合だけだけど。)

英語は “variadic” と呼ぶっぽい? (Wikipedia英語版を見た。)

参考

更新履歴

  • 2017-12-17 fn.apply() の話を追加
  • 2017-12-17 arguments の話を追加

分割代入と逆に、プロパティを短く定義するやつ。(現代的JavaScriptおれおれアドベントカレンダー2017 – 16日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 16日目

概要

{ foo: foo } を { foo } のように省略できます。

const name = 'Alice';

const obj = { name };
console.log(obj.name);  // => "Alice"

使い方

値が入っている変数の名前とプロパティの名前が一致する場合、省略して書くことができます。

const name = 'Alice';

const obj1 = { name: name };
console.log(obj1.name);  // => "Alice"

const obj2 = { name };
console.log(obj2.name);  // => "Alice"

混ぜて書く

コロン : と値とを書く普通のやり方と一緒に使えます。

const data;

const result = {
  data,
  date: Date.now(),
};

関数の引数で

頑張って作った結果を突っ込んだ変数とか、仮引数でもらったやつをそのまま突っ込むこともできます。

function foo({ message }) {
  const date = Date.now();
  bar({ data, message });
}

ちなみに関数の仮引数( foo() のやつ)の方は「分割代入」の類です。別稿参照。

Vue.jsで

components でよく使ってます。

const ThemeItem = require('./ThemeItem.vue')

module.exports = {
  components: {
    ThemeItem,
  },

  // ...
};

その他

混ぜてかくと気持ち悪いような

ひとつのオブジェクトを作る場合はどちらかに統一して、 : 使う場合は省略できるものもしない方が良いかなあ。

const id = nanika.id;
const status = { ... };  // なんか複雑なアレ

const obj = {
  date: Date.now(),
  id,
  name: regularize(user.name),
  status,
};

違うオブジェクトならファイル内で両方あって良いんだけど。

参考

分割代入、配列も画期的。(現代的JavaScriptおれおれアドベントカレンダー2017 – 15日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 15日目

概要

オブジェクトとかでまとまってもらえる情報を、最初からバラバラにしちゃうやつです。配列でも使えます。

const arr = ['https:', 'ginpen.com', '/about'/];

const [protocol, host, path] = arr;
console.log(protocol);  // => "https:"
console.log(host);  // => "ginpen.com"
console.log(path);  // => "/about/"

String#match() で猛威を振るいそう。(?)

使い方

左辺にしかく括弧 [ … ] で括って、初期化なり代入なりする変数の名前を書きます。

const arr = [11, 22];

const [a, b] = arr;
console.log(a);  // => 11
console.log(b);  // => 22

順序左側の順序と右側の順序が一致します。

いらない値を飛ばす

カンマ , だけ書いて変数名を省略すると、そこの位置の値を飛ばします。

const arr = [11, 22, 33];

const [a, , b] = arr;
console.log(a);  // => 11
console.log(b);  // => 33

残りの値をまとめる

... を使って「残り」を全てひとつの新しい配列に突っ込むことができます。

const arr = [11, 22, 33, 44];

const [first, ...rest] = arr;
console.log(first);  // => 11
console.log(rest);  // => [22, 33, 44]

この ... は関数の仮引数でも使えます。別稿参照。

初期値

オブジェクトのやつと同様、 = で初期値を設定できます。

const arr = [-1, undefined, -3];

const [a = 11, b = 22, c = 33, d = 44] = arr;
console.log(a);  // => -1
console.log(b);  // => 22
console.log(c);  // => -2
console.log(d);  // => 44

入れ子

これもオブジェクトのやつと同様。

consr arrrr = ['1', ['2-1', ['2-2-1']], 3];

const [a, [, [x]], c] = arrrr;
console.log(a);  // => '1'
console.log(x);  // => '2-2-1'

もちろんオブジェクトと組み合わせて使うこともできます。

const data = { names: ['Martin', 'Luther', 'King'] };

const { names: [first, , family] } = data
console.log(first);  // => 'Martin'
console.log(family);  // => 'King'

String#match() と組み合わせる

諸々便利かなと思います。

例えば日付を分解する。

const rxDate = /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;

const sDate = '2017-12-15 10:05:00';

const [, year, month, date, hours, minutes, seconds] = sDate.match(rxDate);
console.log(year);  // => "2017"
console.log(hours);  // => "10"

結果の先頭には条件に合致した全体が入ってくるけど、今回いらないので、値を捨てます。

変数の中身を入れ替える

MDNに載ってたけどあんまり使わない気がする。

let a = 'A';
let b = 'B';

[a, b] = [b, a];
console.log(a);  // => "B"
console.log(b);  // => "A"

んー何かのアルゴリズムとかでこういうことしたっけかな。

引数とか?の先頭とそれ以外を分ける

例はNode.jsの方ですけども、なんかこういうのすることもあるかもしれない。

const { spawn } = require('child_process');

const input = 'ls -a -l /var/log';

const [command, ...args] = input.split(' ');
spawn(command, args)
    .stdout.on('data', (data) => {
        console.log(data.toString());
    });

その他

省略のカンマ ,

仕様書では “elision” と呼称されているようです。

主な意味: (母音・音節などの)省略

参考