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

タグ: Advent Calendar 2017

分割代入や引数の「残り全部」を持ってくるやつ……。(現代的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までにはまだ存在していない未来仕様です。

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では対応してないっぽい。

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

その他

名前

引数の方、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” と呼称されているようです。

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

参考

分割代入、画期的な機能。(現代的JavaScriptおれおれアドベントカレンダー2017 – 14日目)

カテゴリー: JavaScript

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

概要

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

const data = {
  age: 99,
  id: id,
  name: 'Alice',
};

const { name, age } = data;
console.log(name);  // => "Alice"
console.log(age);  // => 99

配列もばらせます。別稿参照。

使い方

左辺ににょろ括弧 { … } で括って、欲しいプロパティの名前を書きます。

const data = { id: 123, name: 'Alice' };

const { id, age } = data;
console.log(id);  // => 123
console.log(age);  // => undefined

右辺のオブジェクトから合致する名前のプロパティの値を代入します。 (id)

右辺にないもの (age) を指定すると undefined になり、また右辺にあるもの (name) を必ずしも左辺に用意する必要はありません。

かんたん、かんたん。

初期値

= で、値が undefined だった場合の初期値を設定できます。

const data = { id: 123, name: undefined };

const { name = 'Anonymous', age = 0 } = data;
console.log(name, age);  // => "Anonymous", 0

null は普通に代入されます。

入れ子

入れ子になった情報を取り出すこともできます。その場合、途中のプロパティは値が取得されません。

const data = {
  detail: {
    phone: {
      number: '0120-000-000',
      type: 'free',
    },
  },
};

const { detail: { phone: { number, type } } } = data;
console.log(number);  // => "0120-000-000"
console.log(type);  // => "free"
console.log(detail);  // Exception: ReferenceError: detail is not defined

異なる名前で

APIで情報を取ってきたは良いけどなんかその命名違うんだよなーみたいな場合にも使えます。

{ original: renamed } のようにして original を renamed へ代入できます。

const data = { updated_at: '2017-12-14 00:00' };

const { updated_at: updatedAt } = data;
console.log(updatedAt);  // => "2017-12-14 00:00"

全部乗せ

初期値、入れ子、異名を全部組み合わせてもよろしい。

const data = { detail: { updated_at: '2017-12-14 00:00' } };

const {
  detail: {
    user_name: name = 'Anonymous',
    updated_at: updatedAt = 'NA',
  }
} = data;
console.log(name);  // => "Anonymous" (default value)
console.log(updatedAt);  // => "2017-12-14 00:00"

改行してもなお読みづらい感じはするね。うん、わかるよその気持ち。

関数の仮引数で

最初はちょっとぎょっとするかもしれないけど、仮引数の括弧の中でも使えます。

function sayHello({ message, delay = 1000 }) {
  setTimeout(() => console.log(message), delay);
}

sayHello({ message: 'Hello World!' });

必要なオプションが一目瞭然で良いかも。数が少ない間はね。

オプションオブジェクト option でざっくり受け取って、なんか中で処理を分けるとかすると良いかも?

function doGreatStuff(options) {
  doSomething(options);
  doAnything(options);
  doWhatever(options);
}

function doSomething({ foo }) {}
function doAnything({ bar }) {}
function doWhatever({ boo, hoge }) {}

その他

定義済み変数でSyntaxErrorになる

これはだめ。

const data = { foo: 'fufu' };

let foo;
{ foo } = data;  // Exception: SyntaxError: expected expression, got '='

行頭の { がブロックとみなされてしまうため。

括弧 ( … ) で括って式として判断させます。

const data = { foo: 'fufu' };

let foo;
({ foo } = data);
console.log(foo);  // => "fufu"

もちろん、普通にその場で変数宣言すれば問題ないです。

const data = { foo: 'fufu' };

const { foo } = data;
console.log(foo);  // => "fufu"

import

見た目は似てるけど違います。

import React, { Component } from 'react';
import { AppRegistry, Text, StyleSheet } from 'react-native';

雰囲気も似てるけど、動きも仕様も別物です。

destructuring assign

“destructure” は「破壊する」だそうです。あんまり一般的な語句ではないっぽい?

「分解代入」の方が意味的には適切なのかな。おれも分割代入って呼ぶけど。

デストラクタ destructor

じゃないです。コンストラクタの逆のやつ。

いや、まあこれはこれで欲しいことあるよね。いらない参照を明示的に切ったりとかイベント監視解除したりとかね。でも自動GCだからね。

参考

更新履歴

  • 2017-12-15 importは違うぞと言われてアアアーッッ!となって削除しました。たしかにいろいろ違います、すみません。

テンプレートを自作しよう。(現代的JavaScriptおれおれアドベントカレンダー2017 – 13日目)

カテゴリー: JavaScript

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

概要

昨日のやつでJavaScriptのテンプレート記法をご紹介したわけなんだけども、そのテンプレート記法を使った処理を「タグ関数」を作って独自に拡張することができます。

// 連結するだけの(非現実的な)タグ関数
function tagger(template, ...substitutions) {
  console.log(template[0]);  // => "hoge "
  console.log(template[1]);  // => " fuga "
  console.log(template[2]);  // => " piyo"
  console.log(substitutions[0]);  // => 10
  console.log(substitutions[1]);  // => 25

  return template[0] + substitutions[0] + template[1] + substitutions[1] + template[2];
}

const n = 10;
console.log(      `hoge ${n} fuga ${20 + 5} piyo`);  // => "hoge 10 fuga 25 piyo"
console.log(tagger`hoge ${n} fuga ${20 + 5} piyo`);  // => "hoge 10 fuga 25 piyo"

...substitutions は可変長引数的なやつです。別稿参照。

タグ関数

テンプレート記法を分解した結果を受け取り、処理して返す関数です。

デフォルトで String.raw() というものがあります。

console.log(String.raw`foo\nbar\n${"boo"}`);  // => "foo\\nbar\\nboo"

この関数を自作することができます。

例えば普通に繋げるだけの「何もしない」タグ関数を作る場合、こんな感じになります。

function plain(strings, ...values) {
  // strings[0] === "hoge "
  // values[0] === 10
  // strings[1] === " fuga "
  // values[1] === 25
  // strings[2] === " piyo"

  // 文字列を連結してゆく変数
  // (初期値として先頭のもの "hoge " を与えておく。)
  let result = strings[0];

  // 先頭は既に追加済みなので飛ばし、
  // 残りを順次追加
  for (var i = 1; i < strings.length; i++) {
    result += values[i - 1] + strings[i];
  }

  return result;
}

const n = 10;
console.log(     `hoge ${n} fuga ${20 + 5} piyo`);  // => "hoge 10 fuga 25 piyo"
console.log(plain`hoge ${n} fuga ${20 + 5} piyo`);  // => "hoge 10 fuga 25 piyo"

values には ${ … } の部分が、 strings にはそうではない普通の部分がそれぞれ配列で格納されます。

strings.length は values.length + 1 に必ずなります。なるはず。

例えば `A${99}Z` の場合、 strings は ["A", "Z"] 、 values は [99] となります。

普通の文字列部分がない場合

文字がない場所は空文字列になります。

`${99}` の場合、 ${ … } の前後に空文字列があるとみなされます。つまり strings は ["", ""] 、 values は [99] となります。

同様に `${-1}${99}` の場合、 strings は ["", "", ""] 、 values は [-1, 99] となります。

一行で普通に繋げるやつ(おまけ)

こんな感じでどっすかね。

// 普通に結合
function plain(strings, ...values) {
  return strings.slice(1).reduce((result, s, i) => result + values[i] + s, strings[0]);
}

console.log(plain`hoge ${1} fuga ${2} piyo`)

Array#reduce()らくちんちょうべんり。(読みやすいかは別だけど。)

与えられた数値の小数点以下を揃える

${ … } には数値を与えられる前提で、各値の小数点以下二桁まで表示する、というやつをやってみたいと思います。

// decimal-regularized text
function drt(strings, ...values) {
  let result = '';

  for (let i = 0; i < strings.length; i++) {
    result += strings[i];

    const value = values[i];
    if (typeof value === 'number') {
      const sValue = Math.floor(value * 100).toString();  // 3.1415 -> 314.15 -> 314 -> "314"
      const srValue = `${sValue.slice(0, -2)}.${sValue.slice(-2)}`;  // "314" -> "3" + "." + "14"
      result += srValue;
    }
  }

  return result;
}

console.log(drt`πは${Math.PI}です。`);  // => "πは3.14です。"
console.log(drt`私の身長は${167.3}cmです。`);  // => "私の身長は167.30cmです。"
console.log(drt`私の体重は${66}kgです。`);  // => "私の体重は66.00kgです。"

タグ関数を返す関数

前項の「与えられた数値の小数点以下を揃える」やつ、何桁で揃えたいかってあるじゃないですか。そこを引数で受け取れるようにすると便利かもしれません。

というわけで、タグ関数を(クロージャを使って生成して)返す関数を用意してみました。

// decimal-regularized text
function drt(digits) {
  // この中身は、digitsを使ってる以外はさっきのと一緒
  return function(strings, ...values) {
    let result = '';

    for (let i = 0; i < strings.length; i++) {
      result += strings[i];

      const value = values[i];
      if (typeof value === 'number') {
        const sValue = Math.floor(value * 10 ** digits).toString();  // 3.1415 -> 314.15 -> 314 -> "314"
        const srValue = `${sValue.slice(0, -digits)}.${sValue.slice(-digits)}`;  // "314" -> "3" + "." + "14"
        result += srValue;
      }
    }

    return result;
  }
}

const drt1 = drt(1);
console.log(drt1`πは${Math.PI}です。`);  // => "πは3.1です。"

const drt5 = drt(5);
console.log(drt5`πは${Math.PI}です。`);  // => "πは3.14159です。"

文字列以外を返す

例えばHTML文字列を受け取ったら、それからDOMツリーを構築して返す、なんてのもありだと思います。

function html(strings, ...values) {
  // 普通に結合
  const html = strings[0] + strings.reduce((result, s, i) => values[i-1] + s, '');

  // DOMツリー生成
  // (サボってjQuery使います。)
  const tree = jQuery(html)[0];

  return tree;
}

const title = 'Hello World!';
document.body.appendChild(html`
  <div class="main">
    <h1>${title}</h1>
  </div>`)

テンプレート関数

関数を返すタグ関数なんてのもいけます。

さっきのを拡張して、任意のデータを受け取ってDOMツリーを形成するやつにしました。

// データ
const items = [
  { name: 'Sugoi pen', price: '3000' },
  { name: 'Sugokunai pen', price: '130' },
];

// 「DOMを生成する関数」を返すタグ関数
function html(strings, ...keys) {
  // DOMを生成する関数
  return function(data) {
    // 結合
    let html = strings[0];
    for (var i = 1; i < strings.length; i++) {
      const key = keys[i - 1];
      const value = data[key];
      html += value + strings[i];
    }

    // DOMツリー生成
    // (サボってjQuery使います。)
    const tree = jQuery(html)[0];

    return tree;
  };
}

// テンプレート
const itemTemplate = html`
  <li class="sugoi-item">
    ${'name'}
    <span class="price">(${'price'}円)</span>
  </li>
  `;

// 実行
const elList = document.querySelector('ul#yabai-list');
items.forEach(data => {
  elList.appendChild(itemTemplate(data));
});

Mustacheで <h1>{{title}}</h1> と書いていたところを <h1>${'title'}</h1> と書くようにした感じです。

デモ:

普通の文字列を解析してあれこれしてオブジェクト生成して返す

昔作ったのを思い出しました。

Vue.jsでコンポーネントが上位コンポーネントから値を受け取る (props) とき、その値の妥当性を検証する仕組みがあります。こんな感じで諸々指定していく感じです。

const MyComponent = {
  props: {
    simpleString: { type: String },
    requiredNumber: { type: Number, required: true },
    defaultBoolean: { type: Boolean, default: true },
  }
}

これをもうちょっと簡単に書けないかなーと思って作りました。こういうふうに書けます。

const pt = require('vue-props-template')
 
const MyComponent = {
  props: pt`
    string simpleString
    required number requiredNumber
    boolean defaultBoolean = ${true}
  `
}

文字列を元にオブジェクトを返しているわけです。

その後全然触ってないや。年末にまたやろうかな。

うっ頭が

その他

raw

String.raw() を普通に関数呼び出しすると、こんな感じだとエラーになってしまう。

String.raw(['foo', 'bar'], 1);  // Exception: TypeError: can't convert undefined to object

どうも実は第一引数の配列に raw というプロパティが追加されていて、そちらを見ているみたい。ので、それを与えてやれば大丈夫。

String.raw({ raw: ['foo', 'bar'] }, 1);  // => "foo1bar"

この raw はエスケープをものともしないやつらが格納されてます。下記別稿「生文字列」の項を参照。

自作のタグ関数から strings.raw を見るのも可能。使い道はぱっと思いつかないけど、 String.raw() 的なことをしたい場面で便利なはずです。

function rawSomething(strings, ...values) {
  const rawStrings = strings.raw;
  // ...
}

テンプレート記法の解析とオブジェクト生成

“12.2.9.3 Runtime Semantics: GetTemplateObject ( templateLiteral )” がその仕様だと思うんだけど、正直理解し切れてません。

Realmってなんじゃ……。

参考