Quantcast
Channel: JavaScript Advent Calendarの記事 - Qiita

JavaScript Standard Styleのススメ

$
0
0

みなさんは、JavaScriptのコードを書くときに文字列は何で囲みますか?シングルクォート?ダブルクォート?
インデントに使用する文字はスペース?それともタブ?
JavaScript Standard Styleは、そのように千差万別なコーディングスタイルを統一するためのスタイルガイドの一つです。1

JavaScript Standard Styleのルール

JavaScript Standard Styleには、以下のようなルールがあります。

  • インデントはスペース2個
  • 文字列はシングルクォートで囲む
  • 未使用の変数は禁止
  • 文末のセミコロンは禁止
  • キーワードの後にスペースを入れる
  • 関数名の後にスペースを入れる
  • 値の比較に==ではなく===を使用
    • ただしobj == nullnull || undefinedをチェックするために許容される
  • 常にNode.jsのerr引数をハンドル
  • ファイルの先頭に/* global */コメントでブラウザのグローバルオブジェクトを宣言

これらは、100以上あるルールのうちのほんの一部に過ぎません。
すべてのルールを知りたい方は、公式ドキュメントを参照してください。

ルールは変更不可

JavaScript Standard Styleのルールは、基本的には変更できません。
あなたがタブインデント派だからといって、インデントに使用する文字をスペースからタブに変更した場合、それはもはやJavaScript Standard Styleとは呼べなくなります。
これには、「インデントにタブとスペースのどちらを使用すべきか」といった自転車置き場の議論を避ける意図があります。

セミコロン禁止について

JavaScript Standard Styleの最大の特徴はなんと言っても、文末のセミコロンが禁止なことです。
これは、セミコロンが必須な言語の経験者からすると奇妙に見えるかもしれません。
しかしながら、JavaScriptではセミコロンは必須ではないので、以下のようなコードも正しく動作します。

const message = 'Hello World'
console.log(message)

なお、セミコロン禁止のルールだけは受け入れられないという人のために、セミコロンありのJavaScript Semi-Standard Styleというスタイルガイドも存在します^^;

誰がJavaScript Standard Styleを使っているのか

JavaScript Standard Styleは、npmGitHubElasticExpressElectronなど、名だたる企業やプロジェクトで用いられています。

実際の例として、Electronのソースコードを見てみましょう。

index.js
var fs = require('fs')
var path = require('path')

var pathFile = path.join(__dirname, 'path.txt')

function getElectronPath () {
  if (fs.existsSync(pathFile)) {
    var executablePath = fs.readFileSync(pathFile, 'utf-8')
    if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
      return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath)
    }
    return path.join(__dirname, 'dist', executablePath)
  } else {
    throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')
  }
}

module.exports = getElectronPath()

出典:electron/index.js at master · electron/electron

  • インデントはスペース2個
  • 文字列はシングルクォートで囲む
  • 未使用の変数がない
  • 文末にセミコロンがない
  • キーワードの後にスペースがある
  • 関数名の後にスペースがある

など、JavaScript Standard Styleのルールに沿っていることが分かります。

JavaScript Standard Styleを使う方法

JavaScript Standard Styleを使うには、いくつかの方法があります。

公式サイトのデモページを使う

JavaScript Standard Styleをちょっと試してみたい場合、公式サイトのデモページが便利です。

82f46f4c-ee3a-9ed0-b297-f966ad6edb8e.gif

JavaScriptのコードを入力すると、JavaScript Standard Styleのルールに反している箇所をリアルタイムに指摘してくれます。
また、「Correct erros using --fix」というボタンを押すと、fixableなエラーが自動的に修正されます。
すべてのエラーを修正し結果がグリーンになれば、そのコードはJavaScript Standard Styleとしての仕様を満たしています:tada:

ESLintで使う

JavaScript Standard Styleは、JavaScriptのリントツールであるESLintのルールとして使用することができます。

package.jsonファイルを作成

ESLintはNode.js上で動作するため、package.jsonファイルが必要です。
この時点では、中身はnameぐらいでいいでしょう。

package.json
{
  "name": "javascript-standard-style-example"
}

パッケージをインストール

ESLint本体をインストールします。

$ npm i -D eslint

ESLintでJavaScript Standard Styleを使用するためのパッケージ群をインストールします。2

$ npm i -D eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node

この時点で、package.jsonファイルは以下のようになっているはずです。

package.json
{
  "name": "javascript-standard-style-example",
  "devDependencies": {
    "eslint": "^5.9.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-node": "^8.0.0",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-standard": "^4.0.0"
  }
}

ESLintの設定ファイルを作成

.eslintrc.jsonファイルを作成します。

.eslintrc.json
{
  "extends": "standard"
}

これで、ESLintでJavaScript Standard Styleのルールが適用されるようになりました。

リントを実行

ESLintはコマンドライン上で実行するか、AtomVisual Studio Codeなどのエディタ上で動かすことができます。

コマンドライン上で実行

package.jsonファイルに、ESLintによるリントを実行するためのコマンドを追加します。
コマンド名はなんでもいいですが、ここではわかりやすくlintとします。

package.json
{
  "name": "javascript-standard-style-example",
  "scripts": {
    "lint": "eslint --ext .js --ignore-path .gitignore ."
  },
  "devDependencies": {
    "eslint": "^5.9.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-node": "^8.0.0",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-standard": "^4.0.0"
  }
}

以下のコマンドを実行すると、ESLintによるリントが実行されます。

$ npm run lint

8b9af653-f597-7d72-86ac-59d0932eb4ef.png

fixableなルールを自動修正

一部のfixable(修正可能)なルールは、--fixオプションで自動修正することが可能です。
package.jsonファイルに、ESLintによる修正を実行するためのコマンドを追加します。
コマンド名はなんでもいいですが、ここではわかりやすくfixとします。

package.json
{
  "name": "javascript-standard-style-example",
  "scripts": {
    "lint": "eslint --ext .js --ignore-path .gitignore .",
    "fix": "eslint --fix --ext .js --ignore-path .gitignore ."
  },
  "devDependencies": {
    "eslint": "^5.9.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-node": "^8.0.0",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-standard": "^4.0.0"
  }
}

以下のコマンドを実行すると、ESLintによる修正が実行されます。

$ npm run fix

3a0b230d-dfd6-4047-5c50-4537f85b81a4.gif

Atomで動かす

linter-eslintをインストールします。

$ apm install linter-eslint

これだけで動きます。
うまく動かない場合は、ファイルを開き直したり、エディタを再起動したりしましょう。

f8b89aa6-5e63-f8eb-f30d-3831d7a5a023.png

Visual Studio Codeで動かす

ESLintをインストールします。
コマンドパレットを開いて以下のコマンドを入力してください。

ext install eslint

これだけで動きます。
うまく動かない場合は、ファイルを開き直したり、エディタを再起動したりしましょう。

a11772ec-8f61-f3a7-bda7-681b8e771760.png

使用例

GitHubでJavaScript Standard Styleの使用例を公開しています。

おわりに

僕はなるべくJavaScript Standard Styleを使用するようにしていますが、実に快適なものです。
あなたもぜひ、セミコロンのない世界を体感してみてください!

JavaScript Style Guide


  1. 有名なスタイルガイドには、他にGoogle JavaScript Style GuideAirbnb JavaScript Style Guideなどがあります。 

  2. eslint-config-standard - npm 


ESLintとPrettierをどう扱うべきかの考察(2018年12月版)

$
0
0

はじめに

Prettier が ver.1.15 で Vue.js に対応したことにより、Vue.js で開発していた私は ESLint とPrettier の併用である懸念が起きうることを知ることになりました。
あまり情報が広まっていないようなので、これから使おうと思っている開発者に向けてまとめてみました。

ESLintとPrettier

まず、ESLintPrettier の違いを知る必要があります。

ESLint とは、JavaScript の構文を静的解析し、問題のありそうな箇所を指摘・修正してくれるツールです。
使用していない変数だとかグローバル変数の宣言といった、不具合の原因になりそうな箇所を指摘してくれます。

次に、Prettier とは、JavaScript の構文を静的解析し、コードのフォーマット違反のある箇所を指摘・修正してくれるツールです。
ESLint と違ってフォーマットしか検証しませんが、統一されたきれいなコードをもたらしてくれます。
(ESLint もフォーマットをチェックしてくれますが、より細かくチェックしてくれます)

この通り、それぞれの検証範囲が違うので、併用するのが一般的とされます。

一方で、ESLint と Prettier を併用すると、それぞれのフォーマットチェックがバッティングしてしまうという問題がよく知られています。
それを回避するために、Prettier が eslint-plugin-prettiereslint-config-prettier というのを提供しています。

これらを使うと特定のルールを ESLint で無効化させて、代わりに Prettier がチェックすることになります。

それぞれの哲学

ここで、ESLint と Prettier の掲げている哲学に注目してみたいと思います。

ESLint の哲学として

Everything is pluggable
Every rule
Additionally
The project

の4つを掲げています。
平たく言うと、ルールは決めているけど、それを適用するかどうかはみんな自分で決めていってね、という方針ですね。

Prettier の哲学

By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles.

です。
すべての議論を止める、つまり Prettier がすべてのフォーマットルールを決めてくれるということです。

Prettier の哲学としては自分たちのフォーマットさえあれば大丈夫、ESLint としても他で適用したければしたらいいよ、というスタンスは一見噛み合っています。
が…

起きうる懸念

Prettier 開発者は三項演算子のフォーマットについて

We can't play the cat-and-mouse game of produce code that works with all editor plugins.

と言っています。

これ自体は、まあそうだよね。と思うんですが、他の Plugin のルールには合わせないという哲学は ESLint の Plugin でルールを組み上げるという哲学とバッティングするのです。

どういうことかというと、ESLint で Vue.js の構文をチェックする eslint-plugin-vue という Plugin は eslint-plugin-vue @4.7.1 の時点で Prettier の変更に 2018/12/9 時点では対応していません。
(Prettier の設定を優先するためのレイアウトルールをすべて無効にする issue は作られています)

一方で、eslint-plugin-vue のチェック内容を eslint-config-prettier @3.3.0 は無効化できていません。

そのため、Prettier がルールの範囲を広げていくことにより、他の Plugin のルールと重複してしまい、お互いが修正し合ってエラーが解消されない現象が発生してしまっています。
Prettier が他の Plugin のルールに合わせないつもりなら eslint-config-prettier でちゃんとルールを無効化するべきなのでしょうねぇ…。

どう適用していくべきか

個人的な意見ですが、Formatter と Linter の棲み分けは不可能なように思えます。
なので、以下の3つが現実的なのではないでしょうか。

  1. ESLint のみを使う
  2. Prettier のみを使う
  3. 併用するが、レイアウトルールはすべて Prettier に任せる

ESLint のみを使う場合、設定を個別に適用していくため設定ファイルが長くなる問題があります。が、自分が好きなスタイルを定義したい人は ESLint を選ぶべきでしょう。

Prettier のみを使う場合、個別に変更したいという気持ちは捨てたほうが良いです。Prettier に身を任せると幸せになれることでしょう。

併用する場合、Plugin のバージョンに注意を払う必要があります。が、現時点ではレイアウトルールを Prettier に任せさえすれば、それぞれのいいところを享受できるでしょう。

以上、みなさんのプロジェクト開発が幸せになれますように。

CoffeeScriptからJavaScriptに移行する

$
0
0

今年歴史あるRailsプロジェクトのCoffeeScriptをJavaScriptにひたすら直す作業をしてたので、CoffeeScriptを使っていたがJavaScriptに移したくなった際の流れをまとました。

大まかな流れ

  1. CoffeeScriptに前処理をして変換後のJavaScriptが極力キレイになるようにする
  2. decaffeinate コマンドで変換処理を行う
  3. 生成されたJavaScriptファイルに問題ないか確認(目視/実際に動かして)。一部修正を行う
  4. 古いCoffeeScriptのファイルを削除する

大まかな流れはこんな感じです。変換処理自体は手作業ではなくコマンドに任せます。が前処理をしておくと読みやすいJavaScriptになるので面倒でも手作業で下準備をします。

準備

decaffeinate をインストールします。npm install --save-dev decaffeinate なり yarn add --dev decaffeinateご自由に。
使い方ですがdecaffeinate <ファイル名/ディレクトリ名>でCoffeeScriptからJavaScriptへの変換できます。

前処理を行う

多分これで全部ではないですが、ここを気にするだけでだいぶ違います。

暗黙のreturnを明示的にする

CoffeeScriptはRubyと同様に関数最後の式を関数の返り値とする特徴があります。そのためreturnを付け加えないと望ましくない返り値を返している場合があります。

y = (x) ->
  x * x

はJavaScriptに直すと

y = function(x) {
  return x * x;
}

になりますし、多分こうなって欲しいかと思います。
ところが

y = (x) ->
  console.log(x)

もJavaScriptに直すと

y = function(x) {
  return console.log(x);
}

となってしまい、読み手を混乱させるかもしれません。
この必要なreturnか不要なreturnかを変換後に区別して直すのは非常に面倒なので変換前に処理します。
具体的にはreturnが必要な箇所にはreturn xのようにして何も返さない時には関数末にreturnを加えます。returnが必要なケースでは別にreturn付け加えなくても良いですが、一応確認をしたという意味でつけておくと無難です。
関数末のreturnは不要であれば変換時に消えるのであとで削除する必要はありません。

クラス内部の変数をインスタンス変数に直す

CoffeeScriptでは以下のような書き方が可能です。

class X
  x = 10 # メソッド外でも変数が定義できる
  getX: () =>
    return x

ところがこれをJavaScriptに変換すると結構読みにくいものになります。(どんなだったか忘れました)
そのため若干解釈が変わってしまいますが、インスタンス変数に直します。privateだよという意味を込めて_から変数名を始めるようにしてもいいでしょう

class X
  constructor: () =>
    @_x = 10 # インスタンス変数に直す

  getX: () =>
    return @_x

変換処理を行う

decaffeinateコマンドを実行します。ここは特に何もないので省略します。
ちなみに、let constの判断とかある程度アロー関数化とか変数展開とか分割代入とか結構よしなにやってくれます。

変換後の確認と修正

基本的に問題ないはずですが、一応動作確認しておきます。あとループ変数がvarで1まとめになっていた気がするので最後直しておきます。スルーしてもいいですが、結構気になります。個人的にな感想ですがループの変換は結構微妙だったイメージです。

// Before
var x;

for(x = 0; x < 10; ++x) {
  // 何か
}

for(x = 0; x < 10; ++x) {
  // 何か
}
// After
for(let x = 0; x < 10; ++x) {
  // 何か
}

for(let x = 0; x < 10; ++x) {
  // 何か
}

まとめ

CoffeeScriptからJavaScriptへの変換時には、多少手間暇かけることで見やすいJavaScriptにすることができました。

JavaScriptの概念たち (前編)

$
0
0

この記事は JavaScript Advent Calendar 2018 4日目の記事です。

昨日は@sasurai_usagi3さんで「CoffeeScriptからJavaScriptに移行する」でした。栄枯盛衰を感じます。「CoffeeScript」でググろうとしたらGoogleさんが「CoffeeScript オワコン」とサジェストしてきて悲しい気持ちになりました。
スクリーンショット 2018-12-03 13.21.35.png

明日は@todays-mitsuiさんで「Ramda とか?について」です。

はじめに

今回はGithubの33個のJavaScriptの概念という記事がかなり良記事だったので、その記事に乗っていたサイトたちを元にそれぞれの章の解説を書いてみました。

これらの概念を知らなくてもJavaScriptを書くことは多分出来ると思いますが、知っておくと何かと便利かと思います。
元サイトたちは参考文献に載せておくのでさらに詳しく知りたい人はそちらを見てください。

もう2018年も終わってしまうというこのタイミングでES5を念頭においたコードを書くということは無いと思うので、例や解説などは全てES2015(ES6)以降を想定して書いておきます。過去の遺産についてはあまり触れません。インツァァネッツォ✝️イクス✝️プローラー?

なお、文中では#00という表記を00章の意味で用いています。#0だと「0. ES2015以降の書き方についてのおさらい」を指します。

ライセンス&クレジット

クリエイティブ・コモンズ・ライセンス
この記事はCC BY 4.0ライセンスで提供されます。ご利用はご自由にどうぞ。

文責:わたせ
Twitter: @tsin1rou Qiita: @tsin1rou
誤字脱字の他、根本的な誤り、もっと良い実装や例の提案などがあればコメント欄もしくは上記twitterまでおしらせいただけると幸いです。


なんとJavaScriptの知識にかけてはこの人の右に出るものはいないと言われているazuさんにチェックしていただくことができました。週ごとにJavaScriptの最新情報を届けてくれるJSer.infoの運営やJavaScriptの入門書の執筆などをされていらっしゃる方です。本当にありがとうございます。

チェックしていただいた後に書き加えた部分もあるのでミスがあればだいたいその部分です。

後編について

あまりにも長くなったので前編と後編に分けました。後編の内容はこんな感じです。

  • 17. Prototype Chain
  • 18. Object.create & Object.assign
  • 19. Array.prototypeの便利な関数たち
  • 20. サイドエフェクトと純粋関数
  • 21. クロージャー
  • 22. 高階関数(HOF)
  • 23. 再帰
  • 24. コレクションとジェネレーター
  • 25. Promise
  • 26. async/await
  • 27. データ構造
  • 28. 計算時間
  • 29. アルゴリズム
  • 30. ポリモーフィズム
  • 31. デザインパターン
  • 32. カリー化と部分適用
  • 33. Clean Code

0. ES2015以降の書き方についてのおさらい

初心者の人でも読めるように簡単にES2015以降のJavaScriptの書き方についてまとめておきます。

なお、コード例中に出てくるhogehoge fugafuga piyopiyoなどは「どんな名前でも良いので適当に決めた例としての名前」です。特に意味はありません。海外だとfoo bar bazなどがよく使用されます。

変数の宣言

JavaScriptを学び始めた頃、変数宣言はvarを使うと覚えた人も多いと思います。ですが、ES2015以降はもっと良い変数宣言ができたのでvarを使うのはあまりおすすめできません。

ES2015以降はconst letを使います。constは再代入ができなくなるという特徴があります。

const hoge = 'hogehoge';
let fuga = 'fugafuga';

hoge = 'piyo'; // TypeError: Assignment to constant variable
fuga = 'piyo'; // OK

また、JavaScriptは上から順番に実行されていくのでまだ出てきていない変数などは使用できません。varは謎仕様によってこの原則に逆らうことがありましたが、varのことを忘れれば全て解決です。

console.log(hoge); // ReferenceError: hoge is not defined

const hoge = 'hogehoge';

関数の宣言

functionという言葉も基本的に使いません。(アロー関数とthisの挙動が異なるのでその場面だけ使います。詳しくは#15を参照)
const 関数名 = (引数argument)=>{ 処理 }と書きます。こうすることで関数名が被ってもエラーが出てお知らせしてくれるようになりました。ま、ESLintを使えという話はあるんですけどね。

const hoge = (arg) => {
  return arg * 2;
};

上のようにreturnの行しかないような関数は省略して(引数)=>(返り値)と書けます。

const hoge = (arg) => (arg * 2);
const hoge = arg => arg * 2; // さらに()を省略することも可能

時々謎の記法を見かけるかもしれませんが落ち着いて考えてみます。

const hoge = x => y => x + y; // 謎記法の例
const fuga = x => (y => x + y); // 上と同じ
const piyo = fuga(3); // piyo = y => 3 + y;
console.log(piyo(2)); // -> 5
console.log(fuga(3)(2)); // -> 5

1. コールスタック

JavaScriptでは関数を呼び出すとコールスタックと呼ばれる部分に呼び出された関数が積み上がっていきます。常に一番上の関数が実行されており、関数の実行が終わって値を返すとスタックから取り除かれ、上から2番目に載っていた関数へと戻っていきます。

例えばこんなコードがあったとします。

const hoge = x => x+5;
const fuga = x => hoge(x)*2;
console.log(fuga(3));

上の行から順番に実行され、console.log(fuga(3));の行まで来ました。

   const hoge = x => x + 5;
   const fuga = x => hoge(x * 2) * 3;
-> console.log(fuga(3));

このように関数を実行する命令があると、JavaScriptの実行エンジンはコールスタックと呼ばれる場所に関数を積み上げていきます。

コールスタック
console.log(fuga(3))
main()

そして常にコールスタックの一番上を実行します。今回はfuga(3)という関数を実行する命令が再び登場するのでさらに積み上げます。

コールスタック
fuga(3)
console.log(fuga(3))
main()

fugaを実行すると今度はhogeが登場しているので積み上げます。

   const hoge = x => x + 5;
-> const fuga = x => hoge(x * 2) * 3;
   console.log(fuga(3));
コールスタック
hoge(6)
fuga(3)
console.log(fuga(3))
main()

一番上にあるhoge(6)を実行します。

-> const hoge = x => x + 5;
   const fuga = x => hoge(x * 2) * 3;
   console.log(fuga(3));

11という返り値を得られたのでhoge(6)をコールスタックから取り除きます。

コールスタック
fuga(3)
console.log(fuga(3))
main()

一番上にあるfuga(3)の処理に戻ります。hoge(6)=11ということがわかったので33という返り値が求まります。そしてfuga(3)をコールスタックから取り除き、新たに一番上に現れたconsole.log(fuga(3))を実行してコンソールに33という表示が行われます。その後はmain()にしたがって次の行の処理へと進んでいきます。

このコールスタックはエラーを追跡する時に便利さを実感できると思います。例えばfugafuga関数がhogehoge関数を呼び出し、そこでエラーが起きたとします。

const hogehoge = a => { throw new Error('Something Wrong'); };
const fugafuga = a => hogehoge(a);
fugafuga(1);

コンソールなどに表示されるエラーにはコールスタックも表示されていると思います。

pen.js:4 Uncaught Error: Something Wrong
    at hogehoge (VM191 pen.js:4)
    at fugafuga (VM191 pen.js:7)
    at VM191 pen.js:9

at [関数名] (... [script名]:[何行目か])という情報が呼び出しに応じて積み上がっています。下の関数が上の関数を呼び出しているという関係です。

大抵の場合は一番上を見ればエラーの原因が特定できます。

2. プリミティブ型

基本データ型というのがわかりやすい表現で好きです。
-> JavaScript プリミティブ型 (基本データ型)

JavaScriptに組み込まれている型は全部で6個あります。

  • Boolean
    truefalse

  • String
    "abcde"みたいな文字列

  • Number
    普通の数字。100

  • null

  • undefined
    nullは積極的に「無い」事を示すものですがundefinedは「何も定義されていない」状態を表します。
    -> null と undefined の違い

  • Symbol
    ES2015で導入された新しい型。ユニークなIDが生成出来る。
    -> ECMAScript6にシンボルができた理由

3. 値型、参照型

2のプリミティブ型にあるものは全て値型です。値型というのは「変数をコピーした時に中身もコピーされる」ということです。

値型
let x = 10;
let y = 'abc';

const a = x;
const b = y;

x = 5;
y = 'def';

console.log(x, y, a, b); // -> 5 "def" 10 "abc"

xyを変えてもabは変わりません。これは当たり前という感じ。

変数
x 5
y "def"
a 10
b "abc"

参照型

オブジェクトとか配列とかは参照型です。例えば下のような配列を宣言したとします。

const a = [0, 1, 2];

すると変数aにはアドレスがセットされます。

変数
a <#001>
アドレス データ
#001 [0, 1, 2]

ここで下のように変数をコピーしてみると...

const b = a;

参照だけがコピーされます。

変数
a <#001>
b <#001>
アドレス データ
#001 [0, 1, 2]

つまり、aの中身を変えるとbの中身まで変わります。なるほどね🤔

a.push(99);
console.log(a, b); // -> [0, 1, 2, 99] [0, 1, 2, 99]

値渡ししたい場合は...スプレッド演算子を使って下のように書きます。

const a = [0, 1, 2];
const b = [...a];

a.push(99);

console.log(a, b); // -> [0, 1, 2, 99] [0, 1, 2]

そもそもconstなのに中身が書き換えられるなんてと思った人もいるかもしれませんが、constは変数それ自体の再代入を禁止しているだけなので中身はどうとでも書き換えることができます。
constが禁止するのは以下のような行為です。

const a = [0, 1, 2];
a = []; // -> Uncaught TypeError: Assignment to constant variable.
a = a; // -> Uncaught TypeError: Assignment to constant variable.

4. 型変換

JavaScriptの型変換は2.の型に合わせて

  1. Stringへの変換
  2. Booleanへの変換
  3. Numberへの変換

の3種類が存在します。

String

Stringへの変換は一番直感的な型変換だと思います。

String(123)           // '123'
String(-12.3)         // '-12.3'
String(null)          // 'null'
String(undefined)     // 'undefined'
String(true)          // 'true'
String(false)         // 'false'

また暗黙の型変換として+の片方がStringの場合はもう一方もStringに変換されます。

String(''+0+1)        // '01'
String(0+''+1)        // '01'
String(0+1+'')        // '1' <- 0+1が先に計算される

Boolean

'' 0 -0 NaN null undefined falseのみがfalseに変換されます。他は全部trueになります。

Boolean('')           // false
Boolean(0)            // false     
Boolean(-0)           // false
Boolean(NaN)          // false
Boolean(null)         // false
Boolean(undefined)    // false
Boolean(false)        // false
Boolean({})           // true
Boolean([])           // true

||or &&and !notの3つの論理演算子は暗黙のBoolean型変換を行います。

ただし、||&&は型変換を内部でのみ行い、返り値は元の値・型がそのまま返ってきます。
この仕様を利用して条件分岐を省略することができます。
-> JavaScriptの「&&」「||」について盛大に勘違いをしていた件 - Qiita

// player.nameが設定されていなければ'NoName'となる
const name = player.name || 'NoName';

// if文で書くとこうなる
let name;
if(player.name){
  name = player.name;
} else {
  name = 'NoName';
}

Number

一番色々あって面倒なのがNumberへの変換です。

Number(null)           // 0
Number(undefined)      // NaN
Number(true)           // 1
Number(false)          // 0
Number(" 12 ")         // 12
Number("-12.34")       // -12.34
Number("\n")           // 0
Number(" 12s ")        // NaN
Number(123)            // 123

以下の演算子はNumberへの暗黙の型変換を行います。

  • 比較演算子 > < <= >=
  • ビット演算子 | & ^ ~
  • 算術演算子 - + * / % (ただし+の片方がStringの場合を除く)
  • 単項の+演算子
  • 比較演算子 == != (ただし比較対象が両方ともStringの場合を除く)

また、==nullundefinedの型変換を行わないという特別ルールがあります。

null == 0               // false

5. ==と===の違い

結論から言うと可能な限り===を使うべきです。
4.でもみた通り、==は暗黙の型変換を行うので意図せぬ挙動の原因となります。
===は同じ型・同じ値の場合のみtrueを返すのでより厳密な比較ができます。

false == 0              // true
false === 0             // false
0 == ""                 // true
0 === ""                // false

nullundefined==においてそれぞれにのみ一致します。

null == undefined       // true
null === undefined      // false

NaNは自分自身を含めて何とも一致しないというルールがあります。NaNであるかどうか確認したい場合はNumber.isNaN()を使用します。

const a = NaN;
console.log(a === a);     // false
console.log(a === NaN);   // false
console.log(Number.isNaN(a));  // true

typeof

どの型なのか調べる場合にはtypeofが便利です。

typeof 3                 // "number"
typeof 'abc'             // "string"
typeof {}                // "object"
typeof true              // "boolean"
typeof undefined         // "undefined"
typeof (()=>{})          // "function"
使用例
if (typeof hogehoge === 'number'){...}

const fugafuga = value => {
  switch (typeof value) {
    case 'string':
      return 'this is string';
    case 'number':
    ...
  }
}

とはいえ、このtypeofはラッパーオブジェクト (new Number()とか) を全部"object"と判定してしまうなど一部使い勝手に問題があるので、実際はObject.prototype.toString.call()を使うことが多いと思います。詳しくは#30で。

6. スコープ

varはJavaScriptの闇の歴史の中に葬り去られたと信じているのでletおよびconstについてのみ書きます。
新しくJavaScriptを書くときにvarを使ってはいけません。
-> 本当にvarは駄目な子なのか? - Qiita

varを無視すればスコープについては非常に簡単で、{}で囲まれたブロックスコープが適用されます。ifでもforでも関数でも、{}で囲まれてさえいればスコープが発生すると考えればOKです。

const hoge = 'hogehoge';
{
  console.log(hoge); // Uncaught ReferenceError: hoge is not defined
  const hoge = 'fugafuga';
  console.log(hoge); // -> "fugafuga"
}
console.log(hoge); // -> "hogehoge"

上記のコード3行目でエラーが発生するのに違和感を覚える人も少なくないと思います。通常の上から解釈されていく言語ならhogeがそのスコープ内で未定義だった場合、上のスコープのものが自動で使用されるのが自然でしょう。もちろんJavaScriptでも基本的には上のスコープを見にいくのですが、同名の変数が同じスコープで定義されていると代わりにエラーが出ます。

const hoge = 'hogehoge';
{
  console.log(hoge); // -> "hogehoge"
}
{
  // JavaScriptでは同じスコープで同名の変数が宣言されているとエラーになる
  console.log(hoge); // ReferenceError
  const hoge = 'fugafuga';
}

これは「宣言の巻き上げ」というJavaScriptの仕様で、全ての変数はスコープの一番上で宣言されたのと同じ扱いになります。ただし、明示的に宣言した場合にはエラーが出ません。

{
  let hoge;
  console.log(hoge); // -> undefined
  hoge = 'hogehoge';
}
{
  console.log(hoge); // ReferenceError
  let hoge = 'fugafuga';
}

7. StatementExpression

JavaScriptのソースコードはStatementExpressionという2つの構文から成り立っています。StatementはJavaScriptにおける文の単位のことで、例えばif文だとif (式) 別の文という形ですね。Expressionは評価できて値が返る感じのやつです。

Statementをうまく説明できなくて申し訳ないのですが、基本的に「戻り値」がないものがStatementです。以下のような予約語を使用した構文と言えばいいですかね。StatementをExpressionが期待されている部分(関数呼び出しの際の引数など)に使用することはできません。

  • const let (var)
  • if (else) switch
  • for (do) while break continue
  • throw try catch (finally)
  • (function) functionにはStatementとしてのfunctionとExpressionとしてのfunctionがありますが、StatementとしてのfunctionはJavaScriptの黒歴史として葬り去られたので基本的に使用しません。 -> 関数宣言 vs 関数式 | ES2015+ - Qiita

StatementとStatementの間には;セミコロンを使います。ただし、{ }で囲まれたブロック文でStatementが終わる際には;を付けてはいけません。これがif(){}構文とかwhile(){}の後ろに;が付いていない理由です。

逆にif()hoge();みたいな{}を使わないifの後ろには通常通り;が必要です。

8. 即時関数、モジュール、名前空間

即時関数(IIFE, Immediately-Invoked Function Expressions)とは言ってみれば「使い捨ての関数」です。その場で使い捨てるのでグローバル空間を汚染しない他、外部からアクセスできる変数を制御することに使われてきました。

ただし、ES2015以降はブロックスコープの導入やモジュールの登場などにより上記の利点が一切失われ、ただ互換性を保つためだけに残っている書き方です。
新たにJavaScriptを書く際は即時関数を利用する代わりにモジュールを利用してください。

即時関数

即時関数とは関数を定義してすぐその場で実行する関数のことです。本当は下の例の一番上のように書きたいところなのですが、JavaScriptの構文上これはfunction expressionではなくfunction statementとして解釈されてしまうためエラーとなります。そこで( )を使用してfunction expressionであることを示しています。

function(){
  ...
}();  // -> Unexpected token

(function(){
  ...
})();  // -> OK

(function(){
  ...
}());  // -> これもOK

かつてJavaScriptにはvarという変数宣言がありました。これはlet constとは異なりブロックスコープではなくfunctionスコープだったため、varで宣言した変数をローカル化するにはfunctionで囲わなければいけませんでした。

(function() {
  var innerScope = 'hoge';
})();

console.log(innerScope); // ReferenceError

このとき活躍したのが上記のような即時関数で、function(){}構文で生成される関数をその場で実行することで外部からのアクセスを禁止できました。

いにしえの時代においてはJavaScriptはライブラリを導入する際、htmlに<script src=...></script>を依存関係に気をつけながら順番に並べて導入していたため、どこでどんなグローバル変数が使われているのか分かりませんから、謎の競合を起こさないようにするために自分でJavaScriptを書く際は基本的に即時関数を使用していました。

var hogehoge = 'hoge';

(function() {
  var hogehoge = 'fuga';
  console.log(hogehoge); // -> "fuga"
})();

console.log(hogehoge); // -> "hoge"

しかし今ではモジュールがありますし、またconstletはブロックスコープが適用されるので即時関数の出番はほぼ無くなったと言えるでしょう。

const hogehoge = 'hoge';

{
  const hogehoge = 'fuga';
  console.log(hogehoge); // -> "fuga"
}

console.log(hogehoge); // -> "hoge"

モジュール

この章の例はあくまでモジュールを紹介するためだけに書きました。多くの場合、これを参考にするのではなく#14で紹介するclassを使う方が妥当かと思われます。

モジュールは別ファイルに分けて作成します。

hogehoge.js
let cnt = 0;
export const inc = () => ++cnt;

別ファイルでexportしたものをimportで呼び出すことができます。

main.js
import { inc } from './hogehoge.js';
console.log(inc()); // -> 1
console.log(inc()); // -> 2

名前空間を分けたい場合は次のように書きます。

main.js
import * as counter from './hogehoge.js';
console.log(counter.inc()); // -> 1
console.log(counter.inc()); // -> 2
main.js
import { inc as hogehogeIncrement } from './hogehoge.js';
console.log(hogehogeIncrement()); // -> 1
console.log(hogehogeIncrement()); // -> 2

上記のようなhogehoge.jsの書き方をすると内部の変数は同じものになっていて、どこから呼び出しても同じ値が返ってきます。

hogehoge.js
let cnt = 0;
console.log('hogehoge');
export const inc = () => ++cnt;
foo.js
import { inc } from 'hogehoge.js';
console.log('foo');
inc();
bar.js
import { inc } from 'hogehoge.js';
console.log('bar');
inc();
main.js
import './foo.js';
import './bar.js';
// -> 'hogehoge'
// -> 'foo'
// -> 1
// -> 'bar'
// -> 2

インスタンスを切り分けたい場合は次の節に出てくるclassを利用します。

export default

モジュールが1つのオブジェクトしかexportしない場合はexport defaultを使うことが多いです。

hogehoge.js
class Hogehoge {
  constructor(){
    this.count = 0;
  }
  inc(){
    return ++this.count;
  }
}
export default Hogehoge;
main.js
import Hoge from './module';

const counter = new Hoge();
console.log(counter.inc()); // -> 1
console.log(counter.inc()); // -> 2

上記のようにclassのexportでよく使うように思います。

importとは違い、require()にはdefaultを標準で読み込んでくれるような機能はないので、何らかの事情でrequire()を使ってdefaultimportする場合はrequire().defaultとする必要があります。

ブラウザでのモジュール

ブラウザでも<script src="..." type="module"></script>のようにtypeを指定すればESModulesを使用できます。※ただしIEを除く

将来的にはwebpackでバンドルする必要も無くなるのかもしれませんが、現状ではnpmモジュールの多くがESModules形式に対応していないなどの問題があるためまだしばらくwebpackの天下は続くと思います。

あとファイルをダウンロードしてimport文を見つけて別のファイルをダウンロードして...というのは遅いですからね。http/2とかhttp/3ならまだしもhttp/1.1だとリクエスト数の制限のせいで複数ファイルのダウンロードが遅いので最悪です。

9. イベントループとメッセージキュー

JavaScriptは基本的にシングルスレッドで動作しているため、本来は全てのコードが同期的に動きます。つまり、1つの処理を行なっている間は他の処理を行うことができません。しかしそれでは不便だということでイベントループとメッセージキューという仕組みがあります。これらの仕組みによって擬似的に非同期処理を実現しています。

ここで重要なのですが、イベントループはJavaScriptエンジンの外部にあるものだということに注意してください。JavaScriptを制御しているのは実はイベントループの方であり、エンジンは渡された関数を実行するだけです。また、擬似的と書いたように実際には同期的に動作しているため、コールスタックに他の処理が入っている場合はイベントループは動きません。

イベントループの細かな設計は環境によって異なりますが、この章ではNode.jsの例を見ていきます。Node.jsのイベントループのページでは以下のような図で説明されています。

イベントループ
   ┌───────────────────────────┐
┌─>│           タイマー          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │      未解決のコールバック      │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

JavaScriptエンジンにコールスタックがあるのとは対称に、イベントループにスタックはなく、代わりに複数のキューから成り立っています。

いつイベントループが動作するのか

JavaScriptを実際に走らせると、まず自分の書いたコードが上から下まで実行されます。この実行が終わったら、つまりコールスタックが空になったらイベントループに入ります。

「入ります」と書きましたが、node.jsの場合はまずイベントループを動かす必要があるかどうかのチェックが行われます。イベントループで何も行われない場合はループに入らず、プロセスの終了処理を行います。

タイマーがセットされていたり、接続の待ち受けを行なっていたりするなど何らかの処理が残っている場合にはイベントループが動き始めます。

タイマーフェーズ

イベントループはこのフェーズから始まります。

名前の通り内部にタイマーを保持しており、指定した時間が経過したコールバックを実行します。この説明を読めば分かるように、setTimeoutとかのタイマー系関数は指定時間経過後にイベントループがタイマーフェーズに戻ってくるまで実行されません。

コールバックというのはまあ要するに関数の事で、関数の実行が終わった際に実行する別の関数の事を特別にそう呼びます。相手に電話して「〜〜のタイミングで〇〇に掛け直call backしてね」と言うイメージ。

例えば、A B C Dの4つのタイマーをセットした場合を考えてみます。

timer
setTimeout(A, 100);
setTimeout(B, 200);
setTimeout(C, 300);
setTimeout(D, 400);

タイマーがセットされると、登録されたコールバックはタイマーフェーズ用のキューに昇順で並べられます。

A B C D

さて、main.jsの他の処理が終わるまで時間がかかり、タイマーフェーズが始まる際にすでに250ms経過していたとします。すると今回のタイマーフェーズではA Bが実行され、Cの時間をチェックしてまだ到達していなければそこでタイマーフェーズが終了します。他にも、「Timerフェーズで使っていい時間」の制限もあるため、あまりにも長い処理を行なった場合は残りのタイマーは(時間がきていたとしても)スキップされ、次のタイマーフェーズまで先送りされます。

未解決のpendingコールバック

イベントループのpending queueに並んでいるコールバックを実行します。
pendingという名前の通り、1つのイベントループ内で処理しきるには遅すぎるものが複数のイベントループに渡って実行されます。代表例はファイルi/oやネットワーク接続。

キューが空になるか一定時間経過すると次のフェーズに移行します。

Idle & Prepareフェイズ

次のpollフェイズの準備を行います。
Node内部の処理も行われるらしいけどよく知りません。

Pollフェイズ

イベントループで一番大事だと思われるフェイズ。
JavaScriptで新しくファイルを読み込もうとしたり、外部からの接続を受け取る際にはそれらのタスクは一度watch_queueに入れられます。

このPollフェイズではwatch_queueに入っているタスクを順番に処理していきます。watch_queueが空になっても一定時間はPollフェイズのまま新しい接続を待ち受けます。

チェックフェイズ

setImmediate()のためのフェイズ。setImmediate()で追加されたコールバックたちがこのフェイズで実行されます。

setImmediate()node環境にしかない関数なのでブラウザでは使えません。

closeフェイズ

様々な処理のクリーンアップを行います。ソケットのクローズ処理(socket.on('close',()=>{}))もここで行われます。

closeフェイズが終わればもう一度イベントループを回す必要があるかどうかをチェックします。イベントループ内の各キューに何も処理が残っていなければプロセスを終了します。

なお、Promise.resolve()process.nextTick()はどのフェイズで呼び出されても次のフェイズに行く前に実行されます。同時の場合はnextTickが優先されます。

10. ブラウザで使用できるタイマー

setTimeout, setInterval

setTimeoutsetIntervalは共にブラウザで使用できるタイマーです。あまりにも古い関数なので引数の順番がキモい。

setTimeout(callback, delay)という感じで使います。delayミリ秒以降にcallbackが実行されます。
setInterval(callback, interval)の方はintervalミリ秒ごとにcallbackが実行されます。

index.js
console.log('startTime:', new Date());

setTimeout(()=>{
  console.log('timeout:', new Date());
}, 1000);
const timeout = setInterval(()=>{
  console.log('interval:', new Date());
}, 1000);

setTimeout(()=>{
  clearInterval(timeout);
}, 5000);
$ babel-node index.js
startTime: 2018-11-04T02:54:44.630Z
timeout: 2018-11-04T02:54:45.635Z
interval: 2018-11-04T02:54:45.635Z
interval: 2018-11-04T02:54:46.640Z
interval: 2018-11-04T02:54:47.643Z
interval: 2018-11-04T02:54:48.647Z
✨  Done in 5.68s.

callbackに引数を渡したい時は
setTimeout(callback, delay, 引数1, 引数2, ...)
とするか、
setTimeout(()=>{callback(引数1, 引数2, ...)}, delay)
とします。

イベントループはコールスタックが一度空になってから動き始めるのでエラーのデバッグが若干面倒です。

index.js
setTimeout(()=>{
  throw new Error('error');  
}, 1000);

どのエラーが発生したのかはわかりますが、コールスタックはタイマーのものとなっています。どこでタイマーに突っ込まれたのかは分かりません。

$ babel-node index.js
/Users/watace/qiita_repos/sandbox/index.js:4
  throw new Error('error');
  ^

Error: error
    at Timeout._onTimeout (/Users/watace/qiita_repos/sandbox/index.js:2:9)
    at ontimeout (timers.js:424:11)
    at tryOnTimeout (timers.js:288:5)
    at listOnTimeout (timers.js:251:5)
    at Timer.processTimers (timers.js:211:10)
error Command failed with exit code 1.

requestAnimationFrame

ブラウザでアニメーションというと「CSSだけでハロウィン気分の404ページ作った」のようにCSSでのアニメーションがメインとなっています。

でもCSSの機能だけでは満足できない!!!
もっとJavaScriptで卍最強卍のアニメーションを作りたい!!!!

という場合に用意されているのがrequestAnimationFrameです。
requestAnimationFrame(callback)
とすると次の描画の直前にcallbackが実行されます。つまりこれを再帰させて下のコードの様にすると描画ごとに実行される関数の完成です。

const animation = ()=>{
  requestAnimationFrame(animation);
  // ここで描画処理
};
requestAnimationFrame(animation);

アニメーションにsetInterval(animation, 1000/60)を使うのは避けましょう。フレームの更新と実行タイミングが合わない上に、ブラウザの実装によっては別のタブを開いていても実行され続けてしまう場合があります。

11. JavaScriptエンジン

世の中にはJavaで動く「Rhino」やSafariなどで使われている「JavaScriptCore」、Firefoxの「SpiderMonkey」、Google Chromeの「V8」など様々なJavaScriptエンジンが存在しています。

これらのエンジンはJavaScriptのコードを機械語に変換する役割を果たしています。エンジンごとに様々な方法でJavaScriptを機械語に変換しているのですが、ここでは「V8」について詳しくみていきます。

抽象構文木(AST)

V8エンジンはまずソースコードの構文解析を行い、抽象構文木を構築します。

code
var a;
AST
{
  "type": "Program",
  "start": 0,
  "end": 6,
  "range": [
    0,
    6
  ],
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 6,
      "range": [
        0,
        6
      ],
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 5,
          "range": [
            4,
            5
          ],
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "range": [
              4,
              5
            ],
            "name": "a"
          },
          "init": null
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

Ignition

V8ではバージョン5.9以降(⇒Chrome 59以降)、「Ignition」というインタプリタが使用されており、このIgnitionはASTをバイトコードに変換します。

バイトコードとは機械語を若干抽象化したようなものです。例えば下記のような関数があるとします。

const f = (a, b, c) => {
  const d = c - 100;
  return a + d * b;
}

これをバイトコードにするとこんな感じです。nodeを実行するときに--print-bytecodeをつけると出力してくれます。

LdaSmi #100 // accumulatorに100をコピー
Sub a2      // a2からaccumulatorを引く (c-100)
Star r0     // r0にaccumulatorをコピー (d=50)
Ldar a1     // a1からaccumulatorにコピー
Mul r0      // accumulatorにr0を掛ける (d*b)
Add a0      // accumulatorにa0を足す (a+(d*b))
Return      // accumulatorの値を返します

引数はレジスタに入れて渡します。例えばf(5, 2, 150)の時のレジスタはこんな感じ。

レジスタ
a0 5
a1 2
a2 150
r0 undefined
accumulator undefined

TurboFan

Ignitionによって生成されたバイトコードを機械語に翻訳してくれるのがTurboFanです。
機械語っていうとこんな感じのやつです。

movl rbx, [rax+0x1b]
REX.W movq r10,0x100000000
REX.W cmpq r10,rbx
...

12. ビット演算子

コンピューターは01で成り立っているのですが、その2進数表現で出てくる演算がビット演算です。

JavaScriptにもビット演算子が存在します。何に使うのか分かりませんが。競技プログラミングとか組み込み系とかをJavaScriptでやる人がいれば役立つかもしれません。

&and

ANDというのは2つの入力が共に1だったら1を出力する演算です。

入力1 入力2 出力
0 0 0
1 0 0
0 1 0
1 1 1

&演算子はこのAND演算を2進数のそれぞれの桁に対して行います。
例えば3&6を考えてみると、30011であり60110なので

8 4 2 1 10進数
入力1 0 0 1 1 3
入力2 0 1 1 0 6
出力 0 0 1 0 2

となります。

console.log(3 & 6);     // -> 2
console.log(12 & 15);   // -> 12

|or

ORは2つの入力のどちらかが1だったら1を出力する演算です。

ANDというのは2つの入力が共に1だったら1を出力する演算です。

入力1 入力2 出力
0 0 0
1 0 1
0 1 1
1 1 1

例えば3|6はこんな感じです。

8 4 2 1 10進数
入力1 0 0 1 1 3
入力2 0 1 1 0 6
出力 0 1 1 1 7
console.log(3 | 6);     // -> 7
console.log(12 | 15);   // -> 15

~not

NOTは入力を反転する演算です。マイクラで言うとレッドストーントーチです。

入力 出力
0 1
1 0
8 4 2 1 10進数
入力 0 0 1 1 3
出力 1 1 0 0 12?

上のように4bitでの演算であれば~312になるように思えます。しかし実はJavaScriptにおいては一番上のbitは負の数を表すという決まりがあります。したがって4bitでの1100-8+4+0+0なので答えは-4となります。

8 4 2 1 10進数
入力 0 0 1 1 3
出力 1 1 0 0 -4
console.log(~3);    // -> -4
console.log(~12);   // -> -13

^xor

XOR演算は2つの入力のうちどちらかが1であれば1を出力する演算です。両方が1の時は0になります。

入力1 入力2 出力
0 0 0
1 0 1
0 1 1
1 1 0

例えば3^6はこんな感じです。

8 4 2 1 10進数
入力1 0 0 1 1 3
入力2 0 1 1 0 6
出力 0 1 0 1 5
console.log(3 ^ 6);   // -> 5
console.log(12 ^ 15); // -> 3

シフト演算子

シフト演算子<< >> >>>を使えばビットを左右に動かすことができます。

例えば91001なので9<<2100100、つまり36となります。

console.log(9 << 2);  // -> 36

>>はビットを右にずらすのですが、その際左端のビットがコピーされます。

0000...00001001>>で2bit右にずらすと0000...00000010となり、
1111...11110111>>で2bit右にずらすと1111...11111101となります。
符号が維持できるというメリットがあります。

console.log(9 >> 2);  // -> 2
console.log(-9 >> 2); // -> -3

>>>は左端に0を追加します。

1111...11110111>>>で2bit右にずらすと0011...11111101となります。

console.log(9 >>> 2); // -> 2
console.log(-9 >>> 2);// -> 1073741821

使い方

フラグ管理

Linuxでパーミッション変えようとしたときに777とか謎の数字を打ち込むことになるのですが、これはフラグを表しています。

7というのは2進数だと111となるのですが、この一桁一桁がフラグになっています。Linuxのやつだと左からr w xですね。
r--だと100なので4rwxだと1117となります。

特定のフラグが立っているかどうかを調べるには&を使います。

const xFlag = 1;
const wFlag = 1 << 1;
const rFlag = 1 << 2;

if(flags & rFlag){  // flagsは7とか4とかの数字
  // rが立っている場合に処理が走る
}
if(flags & (rFlag | wFlag) === (rFlag | wFlag){
  // rとwが両方立っていれば走る
}

フラグのセットは|=なんかを使います。

let flags = 0;
// もちろん let flags = wFlag | rFlag; とかでも良い

flags |= rFlag;

部分和

競技プログラミングでは比較的bit演算が多用される気がします。そのうち一つだけ例を紹介します。

部分和というのは例えば

1 3 5という3つのカードを足してできる数字はどんなものがあるのか

という感じの問題です。答えとしては1 3 4 5 6 8 9となるのですが、これを求めるのにbit演算が使われます。

最初に0番地だけが1のビット列を用意します。

0000 0000 0001

そこにカード1の数字分だけ左ビットシフトを行い、元のビット列とorをとります。

0000 0000 0011

次のカード3の数字分だけ左ビットシフトを行い、元のビット列とorをとります。

0000 0001 1011

同じ操作を繰り返します。次は5なので5ビットシフトして...となります。

0011 0111 1011
BA98 7654 3210(番地・参考)

何番地が1なのかを調べればどんな部分和が現われるかを求められます。上のビット列では1番目、3番目、4番目、...、9番目が1になっています。

素朴な解法だとO(2^N)だったのがこの解法だとO(N)にできます。

13. DOMとレイアウトツリー

DOMはドキュメント・オブジェクト・モデルの略です。簡単にいうと要素のツリーです。

例えばこんな感じのHTMLがあるとします。

<!DOCTYPE html>
<html>
  <head>
    ...
  </head>
  <body>
    <div>
      ...
    </div>
    <script src="..."></script>
    <script src="..."></script>
  </body>
</html>

DOMは下のようになります。

DOCUMENT (always the root)
 └─HTML
   ├─HEAD
   │  └─...
   └─BODY
      ├─DIV
      │  └─...
      ├─SCRIPT
      └─SCRIPT

ブラウザはhtml文章をパースしてDOMを生成します。画面に描画されているのはDOMなので、これを適当に編集すれば画面の表示も変わります。

JavaScriptからDOMを編集する方法は色々あるのですが、代表的なものだけ紹介します。そもそも今時はReactとかVueとか使いますからね。直接編集するようなことはあまりないと思います。

要素を取得する

よく使用されるのはgetElementById()getElementsByClassName()querySelectorAll()などです。

// <... id="hoge">みたいにidが"hoge"の要素を取得
const element1 = document.getElementById('hoge');

// <... class="foo bar baz">みたいにクラスに含まれていればまとめて取得
// 返り値はリストになる。
const element2 = document.getElementsByClassName('foo');

// cssのセレクター形式で要素を取得。返り値はリスト。
const element3 = document.querySelectorAll('#hoge'); // id="hoge"の要素を取得
const element4 = document.querySelectorAll('div.highlighted > p'); // p要素のうち直接の親が'highlighted'クラスを持つものを取得

要素の編集

これもよく使うものだけ紹介します。

const element = document.getElementById('...');

// 要素を作成
const hogehoge = document.createElement('div');

// 要素を追加
element.appendChild(hogehoge);

// 要素を削除
element.removeChild(hogehoge);
// 子要素を全削除
while(element.firstChild){
  element.removeChild(element.firstChild);
}

// クラスを取得
const classList = element.classList;
if(classList.contains('fugafuga'){
  // 要素にfugafugaクラスが設定されていれば走る
}

// クラスを追加・削除・切り替え
classList.add('fugafuga');
classList.remove('fugafuga');
classList.toggle('fugafuga'); // 地味にこいつが便利で重宝する

イベント関連

addEventListener()removeEventListener()を使います。

element.addEventListener('click', (event)=>{
  // preventDefault()を呼ぶと通常の動作が行われなくなる。
  // 例えばaタグをクリックしても移動しなくなったり。
  event.preventDefault();
  console.log('要素がクリックされました');
});

// removeの方は両方の引数を同じにする必要がある
const clickHandler = (event)=>{...};
element.addEventListener('click', clickHandler);
element.removeEventListener('click', clickHandler);

click以外にどんなイベントがあるかはEvent reference | MDNで確認できます。

14. ClassとFactory

Class

ES2015以降は他の言語と同じようにClassを書くことができます。引数がなかった場合の初期値を決めるのには色々流派があるのですが僕は下のようにオブジェクトを利用する方法が好きです。事実上の名前付き引数ですね。ただしデフォルト値を設定する時、方法2の||を使う方法は''とかfalse null 0とかの代入に失敗するのでその点だけは注意してください。

class Hoge {
  // 引数を一つのオブジェクトにするという約束にして、argsで纏めて受け取る
  constructor(args){
    // 初期化処理

    /* 方法1
    this.state = Object.assign({
      name: 'NoName',
      age: 0
    }, args);
    */

    // 方法2
    const { name, age } = args || {};
    this.name = name || 'NoName';
    this.age = age || 0;
  }
  sayHello(){
    return `Hello! I am ${this.name}.`;
  }
  selfIntro(){
    return `${this.sayHello()} And I'm ${this.age} years old.`
  }
}

const hoge = new Hoge();
const fuga = new Hoge({name:'watace', age:23});

console.log(hoge.selfIntro()); // -> "Hello! I am NoName. And I'm 0 years old."
console.log(fuga.selfIntro()); // -> "Hello! I am watace. And I'm 23 years old."

extendsを使うと継承もできます。継承というのは簡単にいうと親のコピー+αを作成するということです。

class Piyo extends Hoge {
  constructor(args){
    // super()を使うと親のconstructorを呼び出せます
    super(args);

    const { nakigoe } = args || {};
    this.nakigoe = nakigoe || 'piyo';
    this.capital = this.nakigoe.charAt(0).toUpperCase()
      + this.nakigoe.slice(1); // 先頭を大文字にしてます
  }
  sayHello(){
    return `${this.capital}! ${this.name} ${this.nakigoe}!`
  }
}

const piyo = new Piyo({name:'watace'});
console.log(piyo.selfIntro()); // -> "Piyo! watace piyo! And I'm 0 years old."

ただしこのクラス構文はそのメンバーをEventListenerにセットするときに若干問題がおきます。

class Foo {
  constructor(args){
    const { name } = args || {};
    this.name = name || 'NoName';
  }
  say(){
    return `${this.name} here!`;
  }
  onClick(event){
    console.log(this.say());  // 上のsay()を呼び出したいが...
  }
}

const foo = new Foo({name:'pizza'});
const elem = document.getElementById('hoge');
elem.addEventListener('click', foo.onClick);
// クリックすると TypeError: this.say is not a function になる
// ↑この時のthisにはelemが入っているため

というのもイベントリスナーのハンドラーは呼び出されるときに呼び出し元の要素がthisになってしまうからです。

これはthisの問題なので解決方法は色々あります。が、今回はReact界隈で一番よく使用される「constructorでbind()する」方法を紹介します。

class Foo {
  constructor(args){
    ...
    // bind(this)を行うことで、呼び出された際のthisを今のthisのままにできる
    this.onClick = this.onClick.bind(this);
  }
  ...
}
...
// クリックすると目的通り"pizza here!"となる

ちなみにprivateなプロパティを持ちたい場合はSymbolを使うと良いようです。
(個人的な感想ですが、publicなプロパティでも直接編集するのはやめた方が良いと思います。インスタンスのプロパティを直接編集しているコードはどこで何をされたか分からず見た瞬間警戒心がMaxになるので。)

Symbolって何?
// 自分自身としか一致しないユニークなオブジェクトを生成する
const symbolA = Symbol();
console.log(symbolA === symbolA); // -> true
console.log(symbolA === Symbol()); // -> false
const __name__ = Symbol();

class Bar {
  constructor(args){
    const { name } = args || {};
    this[__name__] = name || 'NoName';  // Symbolの場合
    this.name = name || 'NoName';       // 通常のプロパティ
  }
  sayHello(){
    return `Hi, I'm ${this[__name__]}`;
  }
}
const bar = new Bar({name:'watace'});
bar.name = 'hoge';          // 直接プロパティを編集できる
console.log(bar.name);      // -> "hoge"😢
console.log(bar.sayHello());// -> "Hi, I'm watace"👍

// __name__を知っていれば直接アクセスできる
console.log(bar[__name__]); // -> "watace"

// これを避けるにはモジュール化してclassだけをexportすればOK
// インポート先では__name__は分からない
export default Bar;

ただしシンボルはReflect.ownKeys()で列挙されてしまうとのこと。
アクセスを完全に防ぐにはWeakMapを使うのが良いらしいです。(azuさんに教えていただきました。ありがとうございます)

const __name__ = new WeakMap();
class Baz {
  constructor(args){
    const { name } = args || {};
    __name__.set(this, name);
  }
  sayHello(){
    return `Hi, I'm ${__name__.get(this)}`;
  }
}
const baz = new Baz({name: 'watace'});
console.log(baz.sayHello());    // "Hi, I'm watace"

コラム: JSDocについて

上記のようなargsオブジェクトで全部の引数をまとめて受け取っちゃうような場合、どんなプロパティを設定すればいいのか分からなくなると思います。そこで活躍するのがドキュメントです。

class Hoge {
  /**
   * ここにclassの説明
   * @param {Object} args ここにargsの説明
   * @param {string} args.name what's your name?
   * @param {number} args.age how old are you?
   */
  constructor(args){
    ...

上記の形式でconstructorの前に書きます。するとエディタが下のような情報を出してくれます。
スクリーンショット 2018-11-11 19.09.14.png

補完も動作します。
スクリーンショット 2018-11-11 19.09.30.png

また、下のようにメソッドの前に書けば...

  /**
   * 挨拶します。
   * @returns {String} hello sentence
   */
  sayHello(){
    return `Hello! I am ${this.name}.`;
  }

エディタが情報を出してくれます。
スクリーンショット 2018-11-11 19.12.59.png

JSDocで良いJavaScript生活を送りましょう😎

Factory

Factoryという大層な名前がついていますが、ざっくり言うとオブジェクト版テンプレートのことです。複雑な引数を持ったクラスを簡単に作成するために噛ませたりします。

// 年齢を半分に詐称するfactory関数
const factory = (name, age) => {
  return new Hoge({name, age: age / 2});
}

決まった形式のオブジェクトを簡単に作れるようにするためにも使います。

const createIssue = (title, description)=>{
  return {
    title,
    description,
    tag: ['new'],
    date: new Date(),
    status: 'open'
  }
}

Factoryって全然馴染みがありませんでしたが、よく考えればReduxのActionの生成はこの形式ですね。

const receiveData = data => (
  {
    type: 'RECEIVE_DATA',
    data
  }
);

15. thisとcallとapplyとbind

this

function()=>{}で何がthisになるかが異なります。

functionにおいてのthis

thisは呼び出し方に応じて変化します。呼び出されるまで何がthisになるか分かりません。

function hoge(){
  console.log(this);
}

hoge();         // -> global

const obj = {
  hoge
};
obj.hoge();     // -> obj

const obj2 = {
  fuga: function(){
    hoge();
  }
};
obj2.fuga();    // -> global???!!!?!?!!?!??!!

アロー関数()=>{}においてのthis

コードを見れば何がthisになるかが分かります。これを「レキシカルなthis」と言います。
分かりやすいしミスも起きにくいので基本的にアロー関数を使用するようにしましょう。

アロー関数はどこからどうやって呼び出してもthisは常に同じものをさします。後述のapply call bindを使ってもこの制限を突破してしまうことはありません。

const hoge = () => {
  //この段階でthisがglobalなのでどこで呼び出してもglobalになる
  console.log(this);
};

hoge();         // -> global

const obj = {
  hoge
};
obj.hoge();     // -> global

const obj2 = {
  fuga: function(){
    hoge();
  }
};
obj2.fuga();    // -> global

callとapplyとbind

全部「何をthisにするか」を決めるための関数です。

callとapply

callapplyも呼び出す際に何がthisになるのかを明示的に決定するための関数です。
共に第一引数にthisにしたいものを入れます。callは第二引数以降に直接引数にしたいものを入れていきますが、applyは引数をまとめて配列にしたものを第二引数にするという違いがあります。

ただし共にアロー関数のthisを変えることはできません。

const hoge = (arg) => {
  console.log(arg);
  console.log(this);
};
function fuga(arg){
  console.log(arg);
  console.log(this);
}

const obj = {};
const args = ['arg1', 'arg2'];
hoge.call(obj, args);
// -> ["arg1", "arg2"]
// -> global

fuga.call(obj, args);
// -> ["arg1", "arg2"]
// -> Object {}

hoge.apply(obj, args);
// -> "arg1"  (配列が展開されて渡される)
// -> global

fuga.apply(obj, args);
// -> "arg1"
// -> Object {}

bind

bindも同じように何をthisにするかを決められます。こちらは一度決めれば永続的に効果を発揮しますが、アロー関数には使えません。

const hoge = () => {
  console.log(this);
};
function fuga(){
  console.log(this);
}

const obj = {};

// bindは新しい関数を返す
const hoge2 = hoge.bind(obj);
const fuga2 = fuga.bind(obj);

hoge();     // -> global (元の関数を変化させない)
fuga();     // -> global
hoge2();    // -> global (アロー関数には効かない)
fuga2();    // -> Object {}

16. newとインスタンスについて

newというのはClassの章に出てきたconst hoge = new Hoge()みたいな奴のことです。このnewは何を指示しているのでしょうか?

結論から言うとnewはインスタンスを作成するのに使用します。

class Hoge {
  constructor(args){
    console.log(this);
    const { name } = args || {};
    this.name = name || 'NoName';
  }
}
const Fuga1 = (args) => {
  console.log(this);
  const { name } = args || {};
  this.name = name || 'NoName';
}
function Fuga2(args) {
  console.log(this);
  const { name } = args || {};
  this.name = name || 'NoName';
}
                                // thisの中身は?
const hoge1 = Hoge();           // -> TypeError: Class constructor Hoge cannot be invoked without 'new'
const hoge2 = new Hoge();       // -> Object { name:"NoName" }
const fuga1_1 = Fuga1();        // -> global
const fuga1_2 = new Fuga1();    // -> TypeError: Fuga1 is not a constructor
const fuga2_1 = Fuga2();        // -> global
const fuga2_2 = new Fuga2();    // -> Object { name:"NoName" }

上の実験から、newがあればconstructorの呼び出し時に新たにオブジェクトが生成されて渡され、constructor内のthisがその新しいオブジェクトを指すようになることが分かります。
class()=>{}アロー関数がそれぞれどちらかしか行えないのに対してfunctionは両方行えてしまいます。newをつけるとfunctionをコンストラクタとして新しいオブジェクトを生成します。

個人的にはnewclassと常にセットで使用し、それ以外の場合は使用しないという取り決めが好きです。

instanceof

インスタンスがどんなクラスを継承してきたのかを確かめるにはinstanceofを使用します。たくさん継承を重ねていても先祖のどこかに存在すればtrueになります。

class Hoge {
  constructor(args){
    ...
  }
}
class Fuga extends Hoge {
  constructor(args){
    super(args);
    ...
  }
}
const hoge = new Hoge();
const fuga = new Fuga();
console.log(hoge instanceof Hoge);  // -> true
console.log(hoge instanceof Fuga);  // -> false
console.log(fuga instanceof Hoge);  // -> true
console.log(fuga instanceof Fuga);  // -> true

後編へ続く

-> JavaScriptの概念たち (後編)

参考文献(前編)

簡単な Webpack plugin を作成して Webpack と仲良くなる (ビルド時情報を console.log に表示する)

JavaScriptでスタイリッシュなCLIプロンプトを作成できるEnquirer

WebpackによるDynamic Import(実践編)

$
0
0

以前にWebpackでDynamic importを行ってみるという記事を書きましたが、実際に使ってみるとなると、文法以外に考える点がちょくちょく出てきました。

import()自体の書き方

まず、以前書いたように、実用的なimport()を行う上ではコメントを付けて細かな指示を出すことが必要となります。ということで、あちこちに直接import()を書いていては必要以上に煩雑になります。ということで、import()だけするファイルを作っておきましょう。

// Promise直出しの場合

export default import(/* webpackChunkName: "qiita" */'path/to/qiita');

// 関数を挟む場合

export default function importer(){
  return import(/* webpackChunkName: "qiita" */'path/to/qiita');
}

当然ながら、関数の中に入れた場合は関数を実行するまでimport()は実行されませんが、直接import()を書いた場合はファイル読み込みによってimport()がスタートします。

ファイル分割の粒度

HTTP/2での送信ならある程度マシになるかもしれませんが、ファイルを分けすぎてもファイル管理や通信コスト、オーバーヘッドなどを考えればマイナスとなりかねません。ある程度関連する機能をくくった上でまとめてexportするファイルを作って、それをimport()する形にするのがいいでしょう。

なお、import()の結果はPromiseresolveへ引数として渡されます。default importであればdefaultキーとして、named importであればその名前のキーとして取れます(静的なimport/exportで行われる名前の最適化は行われません)。

どのようにコードを分割するか

どのようにコードを分割するかは、どのようなコードを書いているかによって大きく違ってきます。

たとえばSPAでは、画面単位で切り分けるのがいいでしょう。Reactには16.6でDynamic importをサポートする機能が入っていますので、それに沿うのが合理的かと思います。

あとは、

  • クリックイベントでしか使われないルーチン
  • ページ表示時にいきなり出すわけではないモーダル
  • ファーストビューの際に通信しない場合の、通信用ライブラリ

など、ファーストビューで使わないものも遅延させてそこまで問題はありません。

30分でわかるJavaScriptプログラマのためのモナド入門


Hexoテーマ作成のためのデータ構造概説

$
0
0

この記事は JavaScript Advent Calendar の9日目です。
昨日は @stken2050 さんの30分でわかるJavaScriptプログラマのためのモナド入門でした。


最近とあるサイトをHexoで構築し直したので、オリジナルテーマを作成する際によく参照するデータ構造などを自分用メモとして公開しようと思います。

一昔前ならWordpressで構築するようなサイト構成ですが、Hexoなどの静的サイトジェネレーターで構築することで配信時には静的サイトの配信のみで済むというのがすごくイケてると感じました。

前提

  • Hexo 3.7.0

よく使うデータ構造

全ページ共通

データ 説明
config _config.ymlの情報を保持している
config.title _config.yml内でtitle: サイト名のように定義した値が取得できる
site サイト全体の情報を保持している
site.posts.data 全投稿記事の情報。順序は不定。Documentオブジェクトで格納
site.posts.length 全投稿記事数
site.categories カテゴリ情報? 使っていないのでよくわからず
site.tags タグ情報? 使っていないのでよくわからず

投稿記事共通(Documentオブジェクト)

Documentオブジェクトとして共通化されている。各記事ファイルの冒頭に以下のようにヘッダ情報として記載した値はすべて格納されている。

---
title: ブログ始めました
date: 2018-12-08 22:54:10
---
データ 説明
title ファイル冒頭部記載の記事タイトル
date ファイル冒頭部記載の投稿日。moment("記載の日付")のようにmoment.jsの利用を前提としている
source プロジェクトルートからのファイルパス
layout 適用されているレイアウト
raw ヘッダ部を含めたHTML化前の全文
_content ヘッダ部を除いたHTML化前の全文
content HTML化された全文
excerpt <!--more-->を利用したときにHTML化された前半部分。<!--more-->を使わない記事では空となる
more <!--more-->を利用したときにHTML化された後半部分。<!--more-->を使わない記事では全文が入る
canonical_path posts/20181208/ブログ始めました/index.htmlのようにルートからのファイル名を含むパス。正式なパスとしてはこれを使うべき?
path /posts/20181208/ブログ始めました/のようにルートからのパス
permalink https://suzukigu.me/posts/20181208/ブログ始めました/のように通信プロトコル指定からのフルパス
tags タグ情報? 使っていないのでわからず
categories カテゴリ情報? 使っていないのでわからず

index

  • /postsをインデックスとして構築
  • デフォルトのhexo-generator-indexは自由度が低いのでhexo-generator-index2を採用
    • ジェネレーターを使わないと「新しい順に5投稿分表示」といった部分を自前で実装しないといけないので面倒
データ 説明
page ページ情報。インデックスジェネレーターを利用しないとundefinedになる
page.total 全ページ数
page.current 現在ページ
page.posts.data ページ内の投稿記事情報。_config.ymlindex_generator.order_byで指定したソート順で格納される。Documentオブジェクトで格納
page.posts.length ページ内の投稿記事数。_config.ymlindex_generator.per_pageで指定した件数まで格納される
page.prev_link 前ページがあれば前ページのパス。なければ空
page.next_link 次ページがあれば次ページのパス。なければ空
page.canonical_path posts/index.htmlのようにルートからのパス

archive

  • hexo-generator-archiveを利用(デフォルト)
  • アーカイブ単位は, で設定
データ 説明
page ページ情報。indexで提供されていたデータはarchiveでも同様に含まれる
page.posts.data ページ内の投稿記事情報。_config.ymlarchive_generator.order_byで指定したソート順で格納される。Documentオブジェクトで格納
page.posts.length ページ内の投稿記事数。_config.ymlarchive_generator.per_pageで指定した件数まで格納される
page.year ページに含まれる記事の投稿年
page.month ページに含まれる記事の投稿月

投稿記事

  • https://suzukigu.me/posts/20181208/ブログ始めました/などにアクセスした場合
データ 説明
page ページ情報。ただし他と異なりpage = Documentオブジェクトとなっている
page.prev 前の記事のDocumentオブジェクト
page.next 次の記事のDocumentオブジェクト

テーマを作成する際にはこのあたりの情報が必須なのですが、公式ドキュメントにも詳しい記述を見つけられなかったため苦労しました。

Hexoのカスタムテーマ作ってみようかな、という方の参考になれば幸いです。

明日は @wonton14 さんの 「演算子の実行順」 です。


書き終えて思ったけどJavaScript要素がほとんどないな……。

演算子の実行順

$
0
0

JavaScript Advent Calendar 2018の10日目の記事です。

なんか今朝カレンダー見たら、空いてるじゃん!ラッk...
短めですが、結構悩んだので。

$(function () {
  var e;
  $(".show-hide").eq(0).show(), e = 1, setInterval(function () {
    var t;
    t = e - 1, 0 > t && (t = $(".show-hide").length - 1), $(".show-hide").eq(t).hide(), $(".show-hide").eq(e).show(), e++, $(".show-hide").length <= e && (e = 0)
  }, 1000)
})

https://jsfiddle.net/wonton0118/xpvt214o/987508/

今時jqueryかよとか言わずにお付き合い頂けますと...
まだ結構保守の現場だと普通なんですよね。

内容は show-hideクラスのついた要素を1000ms間隔で順番にshow/hideしていくというもの。

何が困ったかって、そう読めなかったのである。

問題部分は長ったらしいので置き換えて

A, B && C, D, E, F, G && H

とする。

ことの詳細は 演算子の優先順位 であり、

演算子の優先順位 (JavaScript)

こちらを見ていただければと思うが、,よりも&&が優先なのである。
決して頭から読んじゃいけない

つまり、
B && C 部分と G && H 部分が先に評価される。
なので例えば、 B = false, G = trueならば、

B && C → Bが返る
G && H → Hが返る

(&&演算子は左辺falseなら左辺をそうでないなら右辺を返すくらいの適当な説明にしておく)

よって、

A, B(=false), D, E, F, H

となり、この順で実行され(カンマ演算子は式をカンマで続けれるというだけ(全部実行される))、Hが評価された値が返る。

僕が読めるような形だとこんな感じ?

$(function () {
  var e;
  $(".show-hide").eq(0).show(), e = 1, setInterval(function () {
    var t;
    t = e - 1
    if (0 > t)
      (t = $(".show-hide").length - 1)
    $(".show-hide").eq(t).hide()
    $(".show-hide").eq(e).show()
    e++
    if ($(".show-hide").length <= e)
      (e = 0)
  }, 1000)
})

https://jsfiddle.net/wonton0118/xpvt214o/987512/

The End

読めるようになるとそうでもないけど、突然出るとしぬ...

四則演算では優先順位ちゃんとわかってるのに、なぜこうなるとわからなくなるのか...

何が大変だったって、(読めないって言う)後輩の前なのでいい格好しなきゃいけなかったこと。

※理解あってると信じたいですが、間違ってたら、そっと優しく編集リプください()

JavaScript: f( array ) よりも f( [...array] ) がいいとき?

$
0
0

単純なことなんですが、最近うっかり踏みそうになったプチ地雷からのクイズ?です。

突然ですが

何が表示されるでしょうか?

const array2Gen = xs =>
  function*(){ yield* xs; }();

const a = [0,1,2];
const g =array2Gen( a );

for(let i of a.keys() ){
  a[i] = undefined;
  console.log(g.next().value);
};

配列をジェネレータにして、順番に配列の要素の値を変更しながらジェネレータを呼んで表示させるということです。
こんなこと普通はしないでしょうけど。

答は:

undefined
undefined
undefined

です。
const で配列を宣言しても、配列の要素は変更できるのでしたね。ある意味、当然の結果です。
でも場合によっては、ジェネレータにした時点で中味が固定されて、もう変ってほしくないと思うんじゃないでしょうか?

0 1 2 が返ってきてほしいなあ...

うけとった配列をコピーして使う

これはどうでしょう。何が表示されるでしょうか?

const array2Gen = xs =>
  function*(){
    const ys = [...xs];
    yield* ys; 
  }();

const a = [0,1,2];
const g =array2Gen( a );

for(let i of a.keys() ){
  a[i] = undefined;
  console.log(g.next().value);
};

受け取った配列 xs を[...xs]で浅いコピーをしてからジェネレータにしています。
これで xs が変更されても大丈夫なはずです。
が、実行してみると...

undefined
1
2

あれ?一回目だけ変更されてる。なぜでしょう?
説明できます?
ともかく、このやりかたでは不十分です。別のやりかたを考えましょう。

コピーしてから渡す

今度はどうでしょう。何が表示されるでしょうか?

const array2Gen = xs =>
  function*(){ yield* xs; }();

const a = [0,1,2];
const g = array2Gen( [...a] );

for(let i of a.keys() ){
  a[i] = undefined;
  console.log(g.next().value);
};

今度は const g = array2Gen( [...a] );で浅いコピーをしてから関数に渡しています。
結果は?:

0
1
2

やったー!やっとできたー

まとめ

  1. 何でできたんだろう? 説明できる?
  2. いつもコピーしたほうがいい? どんなときにコピーするべき?
  3. 配列だけ? 他に気をつけたほうがいいモノは?
  4. 浅いコピーで大丈夫? どうやって見分ける?
  5. やった方がいいことは? 逆にやらない方がいいことは?
  6. ほかに気付いたこと? 何でも

...というようなクイズ? なんですが、何かお役に立つ内容でしたでしょうか?

JavascriptでIoTをやる方法

$
0
0

この記事はJavaScript Advent Calendar 2018の12日目です

@n0bisuke さんと合わせて2日連続でjavascriptのIoTに関するAdventCalendarになりました。
・・・と言っても、話が続いてるというわけでは無いです

前半戦なので、全体感ということで、JavascriptでIoTをやろうとしたときに
どんなやり方があるよという話をしたいと思います。
IoTやるとどんな事ができるよ!の話はこちらのAdventCalendarでも見ていただければと思います。

それぞれマイコンボードごとに分類してます。

Raspberry PiでIoT

javascriptのパワーユーザーならこれが一番違和感なく簡単なんじゃないでしょうか。
Linuxが手足のように使えるレベルの知識がある人向け。

computer_single_board.png

  • Raspberry Pi にnodejsをインストール・実行でIoTを行う
    Raspberry PiはただのLinux PCなので、node.jsがそのまま入ります。そのまま動かせばjavasriptで入出力ができます。
    Linuxの環境構築はとてもめんどいですが、パワーユーザーならきっと日常的にやってるはず。

  • CHIRIMENというミドルウェアを入れ、Raspberry Pi内のブラウザでjavascriptを実行
    CHIRIMENというのを入れると、電子工作に必須のio制御が内部ブラウザからの関数呼び出しでできるます。ブラウザカスタマイズしたのかな?

ESPでIoT

RaspberryPiとは逆に、電子工作バリバリできまっせ!という人はこちらがおすすめです。
なによりも安い

スクリーンショット 2018-12-12 19.42.45.png

  • ModdableをつかってJavascriptをコンパイルして書き込んで使う
    javascriptだってコンパイルすればただのアセンブラ。こういう系はjavascriptの機能が一部使えないことが多いですが、どこまで使えるのかはちょっと探しきれませんでした・・・。

  • Mongoose OSをつかってJavascriptをコンパイルして書き込んで使う
    こちらも同じで書き込んで使います。evalとか使えるのかな・・・?
    Moddableとの違いもよくわかってないです。

ArduinoでIoT

電子工作といえばarduino!というぐらい簡単にプログラムが書けます。C言語なら
JavaScriptで書こうとするとひと工夫必要です。

computer_one-board_microcomputer.png

  • Johnny-FiveというOSをarduinoに入れ、有線で接続したPCでnode.jsを動かしarduinoに指示を出す
    生のnode.jsを使うことができるので、なんでもやりたい放題です。そのかわり、有線でPCとArduinoをつないでおく必要があります。動作中ずっと。

obinzでIoT

クラウド上のAPIを叩くとマイコンの入出力が制御できるというマイコン。
他のボードとはインターネット越しの制御がよくも悪くも違う。
howobnizworks.png

  • obnizが用意しているクラウドのサーバー上のAPIをjavascriptで叩いて使う
    APIなのでブラウザから指示を送れます。ブラウザ上のボタンを押したらモーターが回る などのブラウザ連携ができるのが特徴ですね。

  • obnizが用意しているクラウドのサーバー上のAPIをnode.jsで叩いて使う
    こちらも同じですね。node.jsでも動くのでサーバーサイドからもいろいろできます。
    (むしろjavascriptじゃなくてもいい・・・?)
    VUIと組み合わせてgoogle home連携とかも簡単です。

まとめ

図にするとこんなのです。
自分のスキルと目的にあったマイコンボードを手に入れて、ハードウェア動かしましょう!
スクリーンショット 2018-12-12 20.02.19.png

WebUSBを使ってブラウザのJavaScriptからArduinoを制御してみよう!

$
0
0

WebUSBの動向を最近追えてなかったので久々に触ってみます。

そして思ったより簡単に出来ました。

こんなの作ってみました。

WebブラウザのカラーピッカーをグリグリやるとLEDの色が変わります。

WebUSB

"ブラウザの"JavaScriptからUSBデバイスへ"直接"アクセスできる技術です。

Chromeだとnavigator.usbが使えます。

結構前から注目はしていたけど実際にちゃんとArduinoで動かしてみるのは初めてです。

少し前に調べてた時よりも情報が増えてきてて嬉しい限りです。

参考: GoogleのエンジニアがUSBデバイスとウェブを直接接続できるAPIを作成
参考: Webブラウザからハードウェアにアクセス!WebUSB APIを使ってログイン認証を実装してみよう

環境

  • 母艦
    • Mac Book Pro 2015
    • macOS Mojave 10.14
    • Arduino IDE 1.8.7
    • Google Chrome 70
  • マイコン
    • Arduino Leonardo
      • Amazonで買うと安いかも

とりあえずWebUSBを試してみる

WebUSB/Arduinoを試してみた | Arduino | kosakalabの記事の内容を試してみました。

Arduino向けのWebUSBファームを書き込む

kimio-kosakaさんが公開してくれているファームを使います。感謝です。それにしてもすごい家系(わかる人はわかる)。

Arduino/WebUSBライブラリをインストール

https://github.com/kimio-kosaka/webUSB-arduino/releases

このzipファイルをDLして、Arduino IDEの.ZIP形式のライブラリをインクルードからインストールします。

サンプルコードを書き込み

  • ファイル>スケッチ例>WebUSB>consoleのサンプルコードを開きます

最初だけ写真のようにファイル>スケッチ例>互換性なし>WebUSB>consoleになってました。

ボードをArduino Leonardoにして書き込みます。Arduino LeonardoはUSB MicroBケーブルでPCと接続しましょう。

これでOKです。

WebサイトからLチカ操作してみる

  • https://kimio-kosaka.github.io/webUSB-arduino/console/ にアクセスします。
  • connectボタンを押すと接続しているUSBデバイスが表示されます。
  • Arduino Leonardoが表示されるので選択して接続するとconnectedに表示が変わります。

  • キーボードのHを押すとLEDが付いてLを押すとLEDが消えます。

内蔵のLEDでも確認出来ますが落ちてたLEDを13番に付けてみました。

すごい、さすがに反応が早い。

フルカラーLEDのサンプル

  • 同様にファイル>スケッチ例>WebUSB>rgbのサンプルを書き込む
  • フルカラーLEDを以下のように配線
    • ちなみにフルカラーLEDはオフィスに落ちてたやつなので型番とか分からないけど多分これでいけます。カソード顧問(左から3番目がマイナスなのでGNDに接続)。


引用: https://make.kosakalab.com/nodejs/webusb-arduino/

自前で作ってみた

今回はArduinoスケッチをいじらずにRGBカラーを変えるコードを作ってみます。

CodePenにあったRadial Color Picker - Vueのデモを元に改良してみます。イメージしてるカラーピッカーのサンプルを探したらVue.jsであったのでVue.js使っての実装です。

このライブラリの挙動に関してはradial-color-picker/vue-color-pickerを見ると良いです。

  • Arduinoのスケッチ

元のスケッチのままなのですが、localhostだったり自分の環境で試したいので3行目のhost指定の部分をこんな感じにしました。

rgb.ino
WebUSB WebUSBSerial(1, "localhost");
  • html
index.html
省略

    <div id="app">
        <button id="connect" @click="connectButtonAction" v-cloak>{{connectButtonText}}</button>
        <span id="status" v-cloak>{{statusText}}</span>

        <color-picker v-model="color" @input="onColorInput"></color-picker>
        <h1 v-text="msg"></h1>
        <pre v-html="color"></pre>

        <script src="https://jp.vuejs.org/js/vue.min.js"></script>
        <script src="https://unpkg.com/@radial-color-picker/vue-color-picker"></script>
        <script src="serial.js"></script>
        <script src="app.js"></script>
     </div>

省略        

CSSも入っているので試す場合は元のコードを見た方が良いです。

  • serial.js

こちらをそのまま利用させてもらってます。

  • app.js

利用したカラーピッカーがRGBじゃなくてHSL形式で値をとっているため、変換させる必要があってこちらのコードを入れ込んでます。
(変換ロジックが上手くいってないのか、なぜかちょっと色がずれてる感じがするんですが...)

app.js
var ColorPicker = VueColorPicker;

var app = new Vue({
    el: '#app',
    components: {
        ColorPicker: ColorPicker
    },
    data: {
        msg: 'WebUSB & Radial Color Picker - Vue',
        color: {
            hue: 50,
            saturation: 100,
            luminosity: 50,
            alpha: 1
        },
        statusText: '',
        connectButtonText: 'Connect',
        port: {},
    },

    methods: {
        // HSV(L)をRGBに変換
        hsv2rgb: function(hue, saturation, value) {
          // まるっとここの中身なので省略 http://shanabrian.com/web/javascript/color-code-convert-hsv-to-10rgb.php
        },

        //デバイスとの接続
        connect: async function() {
            try {
                await this.port.connect();
                console.log('connecting...');
                this.statusText = 'connected';
                this.connectButtonText = 'Disconnect';

                //デバイス側から値が送られてくるのを待ち受ける
                this.port.onReceive = data => {
                    let textDecoder = new TextDecoder();
                    console.log(textDecoder.decode(data));
                }

                this.port.onReceiveError = error => console.error(error);                
            } catch (error) {
                console.log(error);
                this.statusText = error;                
            }
        },

        //カラーピッカーの値が変わると反応
        onColorInput: function() {
            if (!this.port) return;
            let view = new Uint8Array(3);

            //HSV(L)の値をRGBに変換
            const color = this.hsv2rgb(this.color.hue,this.color.saturation,this.color.luminosity);

            view[0] = parseInt(color.red);
            view[1] = parseInt(color.green);
            view[2] = parseInt(color.blue);
            this.port.send(view); //データをUSBデバイスに送信

            console.log(this.color.hue,this.color.saturation,this.color.luminosity);
            console.log(this.hsv2rgb(this.color.hue,this.color.saturation,this.color.luminosity));
        },

        //connectボタンのトグル処理
        connectButtonAction: async function(){
            if (this.port) {
                this.port.disconnect();
                this.connectButtonText = 'Connect';
                this.statusText = '';
                this.port = null;
            } else {
                try {
                    const selectedPort = await serial.requestPort();
                    this.port = selectedPort;
                    this.connect();                    
                } catch (error) {
                    this.statusText = error;                    
                }
            }
        }
    },
    //ページ立ち上げ時
    mounted: async function() {
        const ports = await serial.getPorts();
        if (ports.length == 0) {
            this.statusText = 'No device found.';
        } else {
            this.statusText = 'Connecting...';
            this.port = ports[0];
            this.connect();
        }
    }
});

作ったコードはGitHubにも載せておきます。

https://github.com/n0bisuke/webusb_vue_sample/tree/master/colorpicker

Nuxt.jsとSkyWayで1時間でビデオチャットを作ってみるを書いた時もそうですが、navigatorにアクセスする初期起動はmountedに入れてあげて、その関数はmethodsに入れてあげればだいたい移植できそうですね。

参考にさせてもらってる元のrgb.jsと見比べてみてください。

所感

楽しい。

WebブラウザのJavaScriptが直接Arduinoにつながるってすごいですね。

今回はブラウザ -> ArduinoだったのでArduino -> ブラウザもそのうち試してみます。

WebBluetoothと同じような楽しさがあるんですけど@masciiくんの記事にも書いているようにブラウザが直接デバイスに繋がると色々と面白いことできそうですよね。

皆さんもお試しください!

実装してて詰まったところ

  • "DOMException: Unable to claim interface"

調べたら、こちらに当たりました。

Failed to claim interface 0: Device or resource busy

他のタブで開いてて、すでにUSB接続してる場合に怒られるみたいです。
他のタブやウィンドウなどで開いてないか確認して閉じてから再トライしましょう。
僕はこれが原因でした。

参考

めちゃ参考になりました!現状のWebUSBはとりあえずここ読んでおけばOKって感じがします。

スプレッド演算子と分割代入

$
0
0

この記事はJavaScript Advent Calendar 2018 14日目の記事です。

何かと役立ちそうなスプレッド演算子と分割代入についてです。
ブラウザで使用する際はBabelにかけた方が良いと思います。

スプレッド演算子

スプレッド演算子は配列やオブジェクトなどをその場に展開します。

let array = [1, 2, 3];
func(...array); // => func(1, 2, 3);
let array1 = [1, 2, 3];
let array2 = [...array1, 4, 5]; // => [1, 2, 3, 4, 5]

関数の引数にスプレッド演算子を使えば、配列のメソッドが使用できます。

function increment (...nums) {
  return nums.map(v => v + 1);
}

increment(1, 2, 3); // => [2, 3, 4]

関数の引数の情報をもったargumentsオブジェクトがありますが、こちらはArray-likeオブジェクトのため配列のメソッドは使用できません。

分割代入

配列やオブジェクトの値を別個の変数に値を代入できます。

let [a, b, c] = [1, 2, 3];
console.log(a); // => 1
console.log(b); // => 2
console.log(c); // => 3

let {x: foo, y: bar} = {x: 1, y: 2};
console.log(foo); // => 1
console.log(bar); // => 2

以下のようにすれば、変数の値を簡単に入れ替えられます。

[a, b] = [b, a];
[a, b, c] = [c, b, a];

組み合わせて使う

スプレッド演算子と分割代入を組み合わせて使うと便利です。
以下のようにすれば、配列の残りの部分を変数に格納できます。

let [a, b, ...c] = [1, 2, 3, 4, 5];
console.log(a); // => 1
console.log(b); // => 2
console.log(c); // => [3, 4, 5]

なお、配列の最初の方を取り出すことはできないようです。

let [...a, b, c] = [1, 2, 3, 4, 5]; // => エラー

文字列を.split('')のように分割できます。

let [...str] = 'hello';
console.log(str); // => ['h', 'e', 'l', 'l', 'o']
function upperEven (str) {
    return [...str].map((v, i) => (i + 1) % 2 === 0 ? v.toUpperCase() : v).join('');
}
upperEven('hello'); // => 'hElLo'

参考

スプレッド構文 | MDN

【JavaScript】スプレッド演算子の便利な使い方まとめ - Qiita

JSのスプレッド演算子を理解する - Qiita

分割代入 | MDN


明日は@OldBigBuddhaさんです。

ごめんなさい、URLミスってました


「人間はJavaScriptにいずれ負ける」ことを実感するツールを作った

$
0
0

はじめに

この記事は、JavaScript Advent Calendar 2018の18日目の記事です。

クソアプリ2 Advent Calendar 2018でも同じような内容の記事を書いています
(こちらは、よりクソな内容の記事となっています)

クソアプリ2 Advent Calendar 2018

ゲームを作りました

これはJavaScriptの速さを身をもって体験ができるアプリです。
アプリと言うかゲームですね。

JavaScriptよりも早く、ターゲットに到達するのを競うものです。

hvsj.gif

上の画像でなんとなく雰囲気わかる方もいるかも知れませんが、
緑の円よりも早く、もう一つの円に到達する(マウスオーバーする)ことを繰り返します。

プレイする

技術的な話

実装には、D3.jsともはや化石と化しそうなjQueryを使用しています。
(といっても、d3の要素はほぼないです)

このゲームは主に以下のクラスで動いています。

  • Targetクラス・・・みんなが追いかける丸
  • Trackerクラス・・・我々と戦う緑の丸
  • Navigatorクラス・・・今の現状を教えてくれるヤツ

GitHub - ソースコード

Targetクラスはただ動くだけ

みんなが追いかけるTargetクラスはただただ動けといわれれば、ランダムにうごくだけのクラスです。

    /**
     * Target クラス
     *
     * Navigatorクラスが操作をする。動かない丸。
     * Trackerクラスがこいつを追い続ける。
     *
     * @constructor
     */
    Target = function (svg, limitX, limitY) {
        // d3のプリセットカラーの中から適当に色を選ぶ
        var colors = d3.scale.category10().range();
        var color = colors[parseInt(Math.random() * colors.length)];

        // 活動限界を決める
        this.limitX = limitX;
        this.limitY = limitY;
        // d3のメソッドを使ってSVGを生成する
        this.circle = svg.append('circle')
            .attr('r', 10)
            .attr('cx', Util.randomX(limitX))
            .attr('cy', Util.randomY(limitY))
            .attr('fill', color)
            .attr('id', 'target');
    };
    // ランダムな場所に瞬間移動させる
    Target.prototype.move = function () {
        this.circle
            .attr('cx', Util.randomX(this.limitX))
            .attr('cy', Util.randomY(this.limitY));
    };
    // 座標を返す
    Target.prototype.getPoint = function () {
        return {
            x: this.circle.attr('cx'),
            y: this.circle.attr('cy')
        }
    }

Trackerクラス(緑の丸)は常に探しながら動く

じつは、この緑の丸は「ターゲットの位置が何処にあるかわかっていません」
人間と戦うに当たり、イーブンな土台に上げるためにあえてそうしています。

まず、相手との距離をスコア化します。
(近いと減る、遠いと増える、みたいな)

Trackerクラス全16方向(東西南北とその間、さらに北北西などの方位も)に試しに動いてみて、
Navigatorクラスに対して「どの方向に動いたやつが一番スコアが良かった?」と聞いて、それで一番スコアの良い(相手との距離が一番縮まる)方向に動きます。

それを定期的に続けることで
「何処にいるかは知らないけど、こっちにいったら近づいてる」
という情報のフィードバックをずっと得ながら移動していきます。

イメージで言うとドラゴンボールに出てくる「ドラゴンレーダー」のような動きでしょうか

仕組み図解

    /**
     * Trackerクラス
     *
     * ターゲットを追い続ける追跡者
     *
     * @param svg
     * @param navigator
     * @param limitX
     * @param limitY
     * @constructor
     */
    Tracker = function (svg, navigator, limitX, limitY) {

        // Targetより小さめの緑の円を生成
        this.circle = svg.append('circle')
            .attr('r', 5)
            .attr('cx', Util.randomX(limitX))
            .attr('cy', Util.randomY(limitY))
            .attr('fill', 'green')
            .attr('id', 'tracker');

        // 〜(略)〜

        // 移動する方向をxとyの成分で保持する
        this.forwardMaster = {
            // 上段 (北西・北・北東)
            7: {x: -1, y: -1},
            8: {x: 0, y: -1},
            9: {x: 1, y: -1},

            // 中段 (西・ニュートラル・東)
            4: {x: -1, y: 0},
            5: {x: 0, y: 0},
            6: {x: 1, y: 0},

            // 下段 (南西・南・南東)
            1: {x: -1, y: 1},
            2: {x: 0, y: 1},
            3: {x: 1, y: 1},

            // 北北西と北北東
            10: {x: -1, y: -2},
            11: {x: 1, y: -2},

            // 西北西と東北東
            12: {x: -2, y: -1},
            13: {x: 2, y: -1},

            // 西南西と東南東
            14: {x: -2, y: 1},
            15: {x: 2, y: 1},

            // 南南西と南南東
            16: {x: -1, y: 2},
            17: {x: 1, y: 2}

        };

        this.navigator = navigator;
    };


    /**
     * 判定
     *
     * @returns {number}
     */
    Tracker.prototype.judge = function () {

        var scoreArr = [];
        var maxIndex = 1;
        var minIndex = 1;

        // 各方向に向かって、一番近くなるスコアを集計する
        for (var i = 1; i <= this.maxForwardIndex; i++) {
            // 5は動かない(ニュートラル)なので飛ばす
            if (i != 5) {
                scoreArr[i] = this.mathScore(i);

                if (scoreArr[maxIndex] <= scoreArr[i]) {
                    maxIndex = i;
                }
                if (scoreArr[minIndex] >= scoreArr[i]) {
                    minIndex = i;
                }
            }
        }

        // ここに来た時点で、スコアが高いforward値が求められている
        return maxIndex;
    };


    /**
     * 移動スコアを計算する
     * @param forward
     * @returns {number}
     */
    Tracker.prototype.mathScore = function (forward) {
        var i;
        var count = 0;
        var sum = 0;

        while (this.history[forward].queue.length > this.step * 2) {
            this.history[forward].queue.shift();
        }

        // その方向の要素を全て洗う
        for (i in this.history[forward].queue) {
            sum += this.history[forward].queue[i].line;
            count++;
        }

        // 最後の要素だけをもう一回
        sum += this.history[forward].queue[i].line;
        count++;

        // 指数移動平均スコアを求める
        return (sum / count);
    };

実際には計算が早くなってるわけではない

ネタバレしてしまうと、setTimeoutの間隔がどんどん早くなっていっているだけです。

    Navigator.prototype.decrementDelay = function() {
        // 今の秒間隔(msec)の10分の1だけ間隔が短くなる
        // 最初の遅い時とだんだん早くなった時でも体感の変化率を揃えるため
        // (早いときは1msecの重みが違う)
        var substract = this.delay / 10;
        this.delay = this.delay - substract;
        return this.delay;
    }

そうです、「待ってもらっていただけ」です

筆者のスコア

作った人間はこれが限界でした。

スクリーンショット 2018-12-16 20.05.37.png

おわりに

本当は、強化学習機能を搭載して、
「このスコアの並びだったらだいたいこれぐらいの距離だな・・・」
という一気にジャンプする機能を付けたかったのですが、それはまたの機会に。

ていうか、Advent Calendarの記事がこんなのでいいのだろうか・・・

ES2019で追加される(かもな)最新機能ピックアップ

$
0
0

2015年に ES2015 がリリースされてから、個人的に EcmaScript の毎年の新機能のリリースがとても楽しみになりました。
そして来年には ES2019 のリリースが控えています。

そこでこの記事では、執筆時点で ECMAScript proposals で Stage 3 の段階にあるものの中で個人的に興味があるものをいくつかピックアップします。
( ES2019 に確実に含まれることを保証するものではありません)

BigInt

JavaScript では取り扱える整数値の最大が決まっています。
それは Number.MAX_SAFE_INTEGER で取得することができます。この値は 2の53乗 - 1 の値になります。

const x = Number.MAX_SAFE_INTEGER

console.log(x === 2 ** 53 - 1) // true
console.log(x) // 9007199254740991
console.log(x + 1) // 9007199254740992
console.log(x + 2) // 9007199254740992

見てわかる通り、 9007199254740991 よりも大きい整数は正しく扱うことができません。
それを扱えるようにしたのが BigInt です。

BigInt は整数の最後にnを加えるか、コンストラクタを呼び出すことで作成できます。

const x = 9007199254740991n
const y = BigInt(9007199254740991)
const z = BigInt('9007199254740991') // string も渡せる

console.log(x === y) // true
console.log(x === z) // true

BigIntNumber と同様に演算ができます。
しかし / だけは Number での計算とは違い、計算結果の小数点を丸めてしまいます。

const x = BigInt(Number.MAX_SAFE_INTEGER)

console.log(x) // 9007199254740991n
console.log(x + 2n) // 9007199254740993n
console.log(x - 2n) // 9007199254740989n
console.log(x * 2n) // 18014398509481982n
console.log(x % 10n) // 1n
console.log(x ** 2n) // 81129638414606663681390495662081n

console.log(x / 1000000000000000n) // 9n

Number と比較した際の挙動など、細かな仕様は以下で確認できます。
GitHub - tc39/proposal-bigint: Arbitrary precision integers in JavaScript

Array.prototype.{flat,flatMap}

Array のメソッドとして、 flatflatMap が追加されました。

flat

ネストされた配列を結合することができます。
どの深さまでのネストに対応するのかを引数で指定することができます。デフォルトは 1 です。

const arr1 = [1, 2, [3, 4]]
console.log(arr1.flat()) // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]]
console.log(arr2.flat()) // [1, 2, 3, 4, [5, 6]]

console.log(arr2.flat(2)) // [1, 2, 3, 4, 5, 6]

便利。

flatMap

配列に対して Array.prototype.map を行って返された配列に Array.prototype.flat を行うイメージです。
しかし flat を行う際のネストのレベルは1階層分のみです。

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

function callback(x) {
  return [x * 2]
}

console.log(arr.map(callback)) // [[2], [4], [6], [8]]
console.log(arr.flatMap(callback)) // [2, 4, 6, 8]

// flat と違い対応できるネストの深さは指定できず、1段階までです
console.log(arr.flatMap(x => [[x * 2]])) // [[2], [4], [6], [8]]

すごく便利。

String.prototype.{trimStart,trimEnd}

String のメソッドとして、 trimStarttrimEnd が追加されました。
以前からあった String.prototype.trim は文字列の前後の空白を削除するメソッドでしたが、今後は前を削除するのか後ろを削除するのかを選べるようになります。

const str = ' hoge '

console.log(str.trim()) // "hoge"
console.log(str.trimStart()) // "hoge "
console.log(str.trimEnd()) // " hoge"

あまり前のスペースだけ、もしくは後ろのスペースだけを削除したい実例が思い浮かばなかったのですが、なんにせよ対応できる幅が増えるのは良いことですね。

Object.fromEntries

Object.entries の逆バージョンです。
Object.entries が渡したオブジェクトをキーバリューの配列を返すのとは逆に、キーバリューの配列を渡すとそれをオブジェクトに変えてくれます。

const obj = Object.fromEntries([['a', 0], ['b', 1]])

console.log(obj) // { a: 0, b: 1 }

個人的に Object.entries は多用するので、きっと Object.fromEntries も多用するのでしょう、きっと・・・。

細かな仕様は以下で確認できます。
GitHub - tc39/proposal-object-from-entries: TC39 proposal for Object.fromEntries

import()

いわゆる dynamic import です。
外部モジュールを動的に読み込むことができるようになります。

Code Splitting などですでに使用している方は多いかと思いますが、それが ES2019 では標準で使えるようになるかもしれません。

この import 文は Promise を返します。

const filePath = 'modules/func'

import(`./${filePath}.js`)
  .then(module => {
    console.log(module)
  })
  .catch(err => {
    console.error(err)
  })

他の細かな仕様は以下で確認できます。
GitHub - tc39/proposal-dynamic-import: import() proposal for JavaScript

おわり

いくつか解説しましたが、現時点での ECMAScript proposals の Stage 3 は他にもまだまだあります。
全て見てみたいという方は tc39/proposals のリポジトリを見てみてください。

GitHub - tc39/proposals: Tracking ECMAScript Proposals

関数型プログラミングの世界からやってきた Pipeline Operator

$
0
0

Pipeline Operator

まずはこちらを

https://github.com/tc39/proposal-pipeline-operator

Elixirとかで見かける |> です。
現在はStage 1 Draftで、babelでもう導入可能です。

https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator


Usage

https://github.com/kashira2339/pipeline-operator-example

const hello = str => str + "hello";

const space = str => str + " ";

const world = str => str + "world";

const exclamationAsync = async str =>
  new Promise((resolve, reject) => setTimeout(() => resolve(str + "!"), 300));

const toUppercase = str => str.toUpperCase();

const str = "";

const msg1 = world(space(hello(str)));
const msg2 = str |> hello |> space |> world;
const msg3 = str |> hello |> space |> world |> exclamationAsync |> await |> toUppercase;

console.log("msg1:", msg1); // => msg1: hello world
console.log("msg2:", msg2); // => msg2: hello world
console.log("msg3:", msg3); // => msg3: HELLO WORLD!
// msg3のawaitは2018/12/14現在未実装

挙動は単純で、 x |> fとあるとき値xを関数fの第一引数として渡す」となります。

Example Use Cases

リポジトリのWikiにもいくつか載っており、それぞれが有用です。
https://github.com/tc39/proposal-pipeline-operator/wiki/Example-Use-Cases

validation

独立したバリデーション用の関数を組み合わせる

const required = () => value => {
  if (!value) throw Error('required');
  return value;
}
const format = regex => value => {
  if (!regex.test(value) ) throw Error('invalid format');
  return value;
}

value
  |> required()
  |> format(/https?:\/\//)
  |> console.log

React + Redux + react-redux + redux-form + react-router

また、react-reduxを採用したプロジェクトで散見される以下のようなコードも

const Component = () => {
  return ...
}

function mapStateToProps(state) {
  return {
    ...
  }
}

function mapDispatchToProps(dispatch) {
  return {
    ...
  }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(reduxForm({ ... })(Component)))

export部分で関数のネストがみられるので、pipeline operatorで以下のように改善できる

// before
// export default withRouter(connect(mapStateToProps, mapDispatchToProps)(reduxForm({ ... })(Component)))

export default Component
    |> reduxForm({ ... })
    |> connect(mapStateToProps, mapDispatchToProps)
    |> withRouter

まとめ

pipeline operatorが使えるようになると、
関数を連鎖させるときの可読性が従来と比べて大きく向上するので、
それぞれの関数をコンポーザブルに保ち、用途に応じて合成していくことで
機能を実現していく設計の有用性がより増してきます。

昨今のJavaScriptや周辺ライブラリを追っていると、
Array.prototype.flat,flatMapであったり
Partial Application Syntaxであったり
React hooksであったり
関数型言語にみられるような特徴を持たせるような提案がボチボチみられます。
開発環境やビジネス要求の上でも変化の激しいフロントエンド開発において
関数型プログラミングの思想を取り入れることで参照透過性を高め、
テストのしやすさ=変化への強さを担保していきたいといった時代の流れなのでしょうか。

そうなってくると、
elmのようなプログラミング言語の採用率が上がって行ったりするのか、
はたまた、関数型プログラミングにハードルを感じるプログラマが増え、
より簡単に素早くアプリケーションの作成ができるNuxtがユーザーを増やしていくのか、
いずれにせよ興味深いです。

Coroutine

JavaScriptの書き方をアップデート

$
0
0

なぜこの記事を書いたか

2018年からAngularを用いたSPAを開発するプロジェクトに参画しているが、
そこで他の開発者が書いたJavaScript(TypeScript)が自分の知らない記法で書かれていたので
自分のJavaScriptイメージをアップデート(最新の仕様を理解)する必要があると感じたからである。

Default parameters

関数に値が渡されない場合やundefinedが渡された場合にデフォルトの値で初期化される。

function add(num1, num2 = 1){
    console.log(num1 + num2);
}

add(3,2); // 5

add(3);   // 4

Rest parameters

不特定多数の引数を配列として受け取る

function showArray(...args){
    console.log(args);
}

function double(...args){

    // 配列のため、mapメソッドが使える
    const result = args.map(x => x*2);
    console.log(result);
}

showArray(3,2); // [3,2]

double(3,2);    // [6,4]

Spread syntax

  • 関数呼び出しでは0個以上の引数として扱われる
  • Arrayリテラルでは0個以上の要素として扱われる
  • Objectリテラルでは0個以上のkey-valueのペアとして扱われる
function add(a, b, c){
    console.log(a + b + c);
}

const numbers = [3,2,1];

// 関数呼び出し
add(...numbers); // 6

// Arrayリテラル
console.log([6,5,4,...numbers]); // [6,5,4,3,2,1]

// Objectリテラル
console.log({...numbers}); // {"0": 3, "1": 2, "2": 1}

for...of statement

iterableオブジェクトに対して反復的な処理をするループを作成する

function add(args){

    let result = 0;

    for(let x of args){
        result+=x
    }

    console.log(result);
}

const numbers = [3,2,1];

add(numbers); // 6

Template literals

let placeholder = "プレースホルダー";

let str = `テンプレートリテラルを使用すると
改
行
や
${placeholder}を埋め込むことができます`;

console.log(str);

// テンプレートリテラルを使用すると
// 改
// 行
// や
// プレースホルダーを埋め込むことができます

const

再代入や再宣言が不可(定数)

const number = 100;
number = 200; // TypeError: Assignment to constant variable.

let

ブロックスコープの局所変数を宣言できる。任意で値を代入することができる。

let num = 10;

if(10 === num){
    let num = 20;

    console.log("if scope:" + num); // if scope:20

}

// if文内で宣言されているnumとはスコープが異なるため代入されない
console.log(num); // 10

Arrow functions

無名関数を=>で記述できる。

// 従来の無名関数
const function1 = function(x){
    return x * 2;
}

// アロー関数を用いた書き方
const function2 = x => x * 2;

console.log(function1(3)); // 6
console.log(function2(5)); // 10

Map

Mapはkey-valueで保持するオブジェクト。
keyとしてオブジェクトやプリミティブ値を使用することができる。

let myMap = new Map();

let keyStr = '文字';
let keyObj = {};
let keyFunc = () => {};

myMap.set(keyStr, '値(str)');
myMap.set(keyObj, '値(object)');
myMap.set(keyFunc, '値(function)');

console.log(myMap.get(keyStr));  // 値(str)
console.log(myMap.get(keyObj));  // 値(object)
console.log(myMap.get(keyFunc)); // 値(function)

Set

どんな型でも一意の値を格納する。値を重複して保持することができない。

let mySet = new Set();
const myObj = {"1":100};

mySet.add(1);
console.log(mySet); // {1}
mySet.add(5);
console.log(mySet); // {1, 5}
mySet.add(1);
console.log(mySet); // {1, 5}
mySet.add(myObj);
console.log(mySet); // {1, 5, {"1":100}}
mySet.add(myObj);
console.log(mySet); // {1, 5, {"1":100}}

Array.prototype.includes

特定の要素が配列に含まれているかどうかをbooleanで返却してくれる。

  • 含まれている場合はtrue
  • 含まれていない場合はfalse
const array = ["北海道", "青森", "秋田"];

console.log(array.includes("北海道")); // true
console.log(array.includes("東京")); // false

Exponentiation operator

べき乗の計算

console.log(2 ** 10); // 1024

Object.entries

引数で渡されたobjectを[key,value]の形で返却する

const object1 = { foo: 'bar', baz: 42 };
console.log(Object.entries(object1));
// [ ['foo', 'bar'], ['baz', 42] ]

Object.values

引数で渡されたobjectのプロパティの値を配列で返却する

const object1 = { foo: 'bar', baz: 42 };
console.log(Object.values(object1));
// ['bar', 42]

String padding

指定した長さの文字列になるように指定した文字列で埋めてくれる

const str = '100';
console.log(str.padStart(5, '0'));
// 00100

Object.getOwnPropertyDescriptors

指定したオブジェクトのすべてのプロパティディスクリプターを返却する

const obj = {
    hoge: "hogehoge"
};

const descriptor = Object.getOwnPropertyDescriptors(obj);
console.log(descriptor.hoge.value); // "hogehoge"

Trailing commas in function parameter lists and calls

関数の引数に末尾のカンマを使用できる

function hoge (a, b,){}

Async functions

非同期の処理を同期的に書くことができる

function hoge(){
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('resolved');
        }, 2000);
    });
}

async function asyncCall() {
  console.log('calling');
  var result = await hoge();
  console.log(result);
  console.log('done')
  // calling
  // resolved
  // done の順で出力
}

asyncCall();

Babel 7 の主な変更点まとめ

$
0
0

2019/06/21 追記

記事内容を Babel 7.4.0 に対応したものに更新しました。

主に追記した箇所は以下に関してです。

はじめに

今更ですが、Babel 7 の主な変更点をまとめた備忘録です。

ほとんどの内容は公式ドキュメントである「Upgrade to Babel 7 」と「Usage Guide」を参考にしているため、既にこちらを読んで理解した方々には不要な記事だと思います。

また、普段 Babel と webpack を併用しているため、Babel CLI や.babelrcなどに関しては触れません。

記事本文でも紹介しますが、Babel 7 にマイグレーションしたサンプル(webpack と併用)は GitHub に置いてあります。

hira777/webpack-with-babel7

本記事での「プロポーザル」の定義

以下のような意味があるが

  • ECMAScript の新たな仕様として提案された機能
  • ECMAScript の新たな仕様として提案され、策定中の機能
  • ECMAScript の新たな仕様として追加する機能の提案書

本記事では「(ECMAScript の新たな仕様として提案され、)策定中の機能」ぐらいな認識で問題ない。

そのため、以下の言葉の意味は大体同じ。

  • 「Stage 4 未満のプロポーザル」 = 「Stage 4 未満の策定中の機能」

プロポーザルや Stage などをより詳しく知りたい方は以下を参照。

目次

yearly presets は非推奨になった

以下のような yearly presets は非推奨になった(Babel 6 から非推奨だった気がするが、一応記載)。

  • babel-preset-es2015
  • babel-preset-es2016
  • babel-preset-es2017
  • babel-preset-latest

そのため preset は@babel/preset-envを利用する。

Babel 6

babel-preset-es2015を利用する場合。

npm install --save-dev babel-preset-es2015

Babel 7

npm install --save-dev @babel/preset-env

stage-x presets は非推奨になった

preset-stage-0などの stage-x presets は非推奨になった。

Babel 6

preset-stage-1を利用する場合。

npm install --save-dev babel-preset-stage-1

Babel 7

個別にプラグインをインストールする必要がある(以下は 2018-10-11 時点での Stege 1 のプラグイン)。

npm install --save-dev @babel/plugin-proposal-export-default-from @babel/plugin-proposal-logical-assignment-operators @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-pipeline-operator @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-do-expressions

babel-upgrade を利用して Babel 7 へのアップグレードを自動で行える(環境によっては完全にアップグレードできるわけではない)

babel-upgradeを利用すれば、Babel 7 へのアップグレードに伴う依存関係、設定ファイルや JavaScript ファイルをに自動的に更新できる。

例えば、以下のようなpackage.jsonのある階層でnpx babel-upgrade --writeを実行すると

{
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-1": "^6.24.1"
  }
}

以下のようにpackage.jsonが更新される。

{
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-do-expressions": "^7.0.0",
    "@babel/plugin-proposal-export-default-from": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@babel/plugin-proposal-function-sent": "^7.0.0",
    "@babel/plugin-proposal-json-strings": "^7.0.0",
    "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/plugin-syntax-import-meta": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "babel-loader": "^8.0.0"
  }
}

現時点では、webpack.config.jsなどの設定ファイルは更新されないため、開発環境によっては babel-upgrade を利用してもアップグレードを完全に行えない可能性もあるため注意。

webpack.config.jsの更新は今後実装予定である。他にどのような機能を実装する予定なのかはこちらを参照。

babylon がリネームされた

Babel で利用されている JavaScript のパーサーであるbabylon@babel/parserにリネームされた。

多くのパッケージの提供が Scoped Packages に変更された

多くのパッケージは scoped packages として提供されるようになった。

つまり、@babel/preset-envのように@babel/(スコープ)がついて提供されるようになった。

そのため、様々なパッケージがリネームされており、babel-cli -> @babel/cliのようにbabel-がついていたパッケージは、基本的にbabel-@babel/に置き換わっている。

自分が利用しているパッケージがリネームされていないか確認した方が良い(大体リネームされているらしい)。

リネームに伴い、コンフィグの記述も変更

以下はその例

Babel 6

module.exports = {
  presets: ['preset-env'],
  plugins: ['plugin-transform-arrow-functions']
};

以下のようにショートハンド(preset-plugin-の短縮)も利用できた。

module.exports = {
  presets: ['env'],
  plugins: ['transform-arrow-functions']
};

Babel 7

module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ['@babel/plugin-transform-arrow-functions']
};

ショートハンド(preset-plugin-の短縮)は引き続き利用できるが、@babel/の指定は必須。

module.exports = {
  presets: ['@babel/env'],
  plugins: ['@babel/transform-arrow-functions']
};

ショートハンドは便利だが、パッケージ名をフルで書いた方が何のパッケージを利用しているのかを確実且つ即座に理解できる。

全員がショートハンドを利用できることを知っているわけではないため、チーム開発などの時は認識合わせをした方が良いかもしれない。

Stage 4 未満のプロポーザルのパッケージがリネームされた

Stage 4 未満のプロポーザルのパッケージは以下のように-proposal-が付いたものにリネームされた。

  • @babel/plugin-transform-function-bind(Stage 0) -> @babel/plugin-proposal-function-bind
  • @babel/plugin-transform-class-properties(Stage 3) -> @babel/plugin-proposal-class-properties

プロポーザルの Stage が 4 に移行したら、パッケージ名がリネームされるので留意しておく。

(と書いておきながら、Stage 4 であるObject Rest/Spread Properties@babel/plugin-proposal-object-rest-spreadで提供されているので、ドキュメントを読み間違えているかもしれない。)

パッケージ名から ECMASCript の Edition は削除された

いくつかのプラグインは名前に-es3-、または-es2015-が付いていたが、以下のように削除された。

  • @babel/plugin-transform-es2015-classes -> @babel/plugin-transform-classes

@babel/polyfill が非推奨になった(Babel 7.4.0 から)

以下は@babel/polyfillのページから抜粋したもの。

As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features) and regenerator-runtime/runtime (needed to use transpiled generator functions):

そのため、@babel/polyfillの代わりに、core-jsregenerator-runtime/runtimeを利用する。

Babel 7

import '@babel/polyfill';

Babel 7.4.0

import 'core-js/stable';
import 'regenerator-runtime/runtime';

core-js@babel/polyfillも利用している polyfill。利用が推奨されているのは v3 で@babel/polyfillが利用しているのは v2。

regenerator-runtimeは async/await を利用するために必要な polyfill。

@babel/preset-env の useBuiltIns を利用して、core-js@3 から必要な polyfill のみを import できるようになった(Babel 7.4.0 から)

以下のようにcore-jsなどを import すると、利用していない不要な polyfill も import されてしまい、トランスパイル後のファイルサイズが非常に大きくなってしまう。

import 'core-js/stable';
import 'regenerator-runtime/runtime';

Babel 7.4.0 では@babel/preset-envuseBuiltInsという設定を利用し、必要な polyfill のみを import できる。

以下はそのサンプル(webpack と併用)。

hira777/webpack-with-babel7

サンプルの babel.config.js で指定している Babel の設定は以下の通り。

babel.config.js
module.exports = function(api) {
  api.cache(true);

  const presets = [
    [
      // プリセットに @babel/preset-env を指定する
      '@babel/preset-env',
      {
        // サポートするブラウザ、この設定に応じて、必要な polyfill のみが import される
        targets: {
          edge: '13'
        },
        // 必要な polyfill のみを import させたい場合、'usage'を指定する(必須)
        useBuiltIns: 'usage',
        // polyfill を利用する core-js のバージョンを指定する(指定しないとバージョン2が利用され警告が出力される)
        corejs: 3,
        // trueにすると利用しているポリフィルなどの情報が出力される
        // polyfill が import されているかどうかを確認するためのものなので必須ではない
        debug: true
      }
    ]
  ];

  return {
    presets
  };
};

targetsで指定したブラウザをサポートするために必要な polyfill が import される。useBuiltIns: 'usage'corejs: 3の指定は必須。

また、エントリーポイントである src/app.js は以下の通り。

src/app.js
[1, 2, 3].includes(2);

利用されているArray.prototype.includes()は、Edge 14 からサポートされている。

今回、targetsedge: '13'を指定しているため、Edge 13 をサポートするためにArray.prototype.includes()の polyfill が import される。

上記のように Babel の設定をすれば、import '@babel/polyfill';import 'regenerator-runtime/runtime';を記述せずに polyfill を import できるが、core-jsregenerator-runtime自体はインストールしておく必要があるので注意する。

Stage 4 未満のプロポーザルの polyfill も import する(Babel 7.4.0 から)

Babel 7.4.0 から、@babel/preset-envuseBuiltInsで、Stage 4 未満のプロポーザルの polyfill も import できるようになった。

以下のようにcorejs: { version: 3, proposals: true }を指定すれば Stage 4 未満のプロポーザルの polyfill も import される。

babel.config.js
module.exports = function(api) {
  api.cache(true);

  const presets = [
    [
      // プリセットに @babel/preset-env を指定する
      '@babel/preset-env',
      {
        // サポートするブラウザ、この設定に応じて、必要な polyfill のみが import される
        targets: {
          edge: '14'
        },
        // 必要な polyfill のみを import させたい場合、'usage'を指定する(必須)
        useBuiltIns: 'usage',
        // core-js のバージョンを指定する(指定しないとバージョン2が利用され警告が出力される)
        // corejs: 3,
        // Stage 4 未満のプロポーザルの polyfill も import される
        corejs: { version: 3, proposals: true },
        // trueにすると利用しているポリフィルなどの情報が出力される
        // polyfill が import されているかどうかを確認するためのものなので必須ではない
        debug: true
      }
    ]
  ];

  return {
    presets
  };
};

上記の状態で、以下のような Stage 2(2019/06/20 時点)のString.prototype.replaceAllを利用したコードをトランスパイルすると、自動で polyfill が import される。

const queryString = 'q=query+string+parameters';
const withSpaces = queryString.replaceAll('+', ' ');
console.log(withSpaces); // => q=query string parameters

(補足)@babel/polyfill と core-js@3 で、「どのブラウザでどの polyfill を import する必要があるかを判別するために利用するデータ」が異なる

それぞれが利用するデータは以下の通り。

そのため、@babel/preset-env@babel/polyfillを利用した場合と@babel/preset-envcore-js@3を利用した場合で import される polyfill が異なる時がある

たとえば、targetsedge: '18'を指定して、以下のようなPromise.prototype.finally()が含まれるコードをトランスパイルすると

Promise.resolve().finally();

@babel/preset-env@babel/polyfillを利用した場合はPromise.prototype.finally()の polyfill が import されないが、@babel/preset-envcore-js@3core-js-compatが v3.1.4 の時点)を利用した場合は Promise.prototype.finally()の polyfill が import される。

そのため、「targetsの指定はそのままで@babel/preset-envで利用する polyfill を@babel/polyfillからcore-js@3に変更してトランスパイルしたら、import される polyfill が無茶苦茶変わった...!!なぜ...!?」といった状況が発生する可能性はあるが、バグではない。

なぜ core-js@3 では core-js-compat を利用しているのか?

compat-tableだと細かい点で不備があり、完全に正しいわけではないから(らしい)。

くわしくはこちらを参照。

core-js-compat は常に最新のものを利用した方が良い(かもしれない)

現時点では core-js-compat が頻繁に更新されているため、バーションが上がると polyfill の import のされ方が結構異なる気がした。

そのため、常に最新の core-js-compat を利用した方が良い(かもしれない)。

TypeScript のトランスパイルが可能になった

@babel/preset-typescriptを利用して、TypeScript のトランスパイルが可能になった(型チェックはできないため、型チェックするためには TypeScript が必要)。

※トランスパイルの仕方は別の記事に書きましたので、こちらを参考にしてください。

webpack 4 + Babel 7 + TypeScript + TypeScript EsLint + Prettier の開発環境を構築する

終わり

本記事で記載した変更点は、ざっくり分類すれば以下の通りです。

  • 様々なパッケージがリネームされた
  • @babel/polyfillの変更点
  • @babel/preset-envuseBuiltInsの利用方法

上記を理解すれば、とりあえず Babel 7 へのマイグレーションはできると思います。

変更点をより詳しく知りたい方は以下をご参照ください。

JavaScriptプログラマのための2019年の機械学習と関数型プログラミング


特定のユーザーのQiita新規投稿を定時でチャットに流す

$
0
0

部内で Qiita に記事投稿したら教えてね、とのお願いがありました。
メールやチャットでの連絡だと忘れがちなので定期的にチャット(Teams)に POST してくれる JavaScript を書いてみます。

利用するサービス群は以下です:

Firebase は事前にアカウント登録し、プロジェクトを作成、Functions を作成します。
Functions は無料プランだと外部 API 叩けないので、有料の Blaze プランにします。
無料枠内でなんとかなるのでなんとかする1
cron-job.org も事前にアカウントを登録します。こちらは無料。
Qiita は 1 時間 60 回までなら認証なしで API 叩けるので特に何もいりません。

各サービスの API を調べる

Qiita

Qiita:Teams には Webhook はあるのですが、通常の Qiita にはありません。
Qiita の API に Organization 単位の投稿情報を取得するものも用意されていませんでした。

Qiita API Docs

用意されている API からできそうな方法を考えてみます。
Organization 名からユーザーを限定することは難しそうです。
代替案として以下は使えるかもしれません。

アカウント集約用アカウント(tdcsoft)を作成して2、Organization のメンバーをフォローします。
この状態で API 叩いてみましょう。

// GET https://qiita.com/api/v2/users/tdcsoft/followees
;[
  {
    description: null,
    facebook_id: null,
    followees_count: 3,
    followers_count: 5,
    github_login_name: null,
    id: 'ukiuki',
    items_count: 2,
    linkedin_id: null,
    location: null,
    name: '',
    organization: null,
    permanent_id: 94278,
    profile_image_url:
      'https://qiita-image-store.s3.amazonaws.com/0/94278/profile-images/1473706223',
    team_only: false,
    twitter_screen_name: null,
    website_url: null
  },
  {
    description: null,
    facebook_id: null,
    followees_count: 13,
    followers_count: 44,
    github_login_name: 'TakahiRoyte',
    id: 'TakahiRoyte',
    items_count: 13,
    linkedin_id: null,
    location: null,
    name: '',
    organization: null,
    permanent_id: 89911,
    profile_image_url:
      'https://qiita-image-store.s3.amazonaws.com/0/89911/profile-images/1518364681',
    team_only: false,
    twitter_screen_name: 'TakahiRoyte',
    website_url: null
  }
]

配列で私と ukiuki さんのユーザー情報取れますね。
あとはユーザーごとに最新の投稿を取得できれば良さそうです。
どうやら投稿記事の API がクエリ対応してそうです。

  • GET /api/v2/items
    • 記事の一覧を作成日時の降順で返します。
    • query
      • 検索クエリ
      • Example: "qiita user:yaotti"
      • Type: string

対応しているクエリはQiita 検索画面に用意されてるものと同じですね。
作成するスクリプトを午前 00:05 に流すとして、「指定のユーザー名 + 機能より新しい記事」を取れれば OK なはず。
API を叩いてみます。

// GET https://qiita.com/api/v2/items?query=user%3ATakahiRoyte+created%3A%3E2018-12-01
;[
  {
    rendered_body: '',
    body: '',
    coediting: false,
    comments_count: 0,
    created_at: '2018-12-15T18:12:00+09:00',
    group: null,
    id: '855b6116c8132b0c9286',
    likes_count: 7,
    private: false,
    reactions_count: 0,
    tags: [
      {
        name: 'アジャイル',
        versions: []
      },
      {
        name: 'スクラム',
        versions: []
      },
      {
        name: 'ソフトウェア開発',
        versions: []
      }
    ],
    title: 'スクラムを失敗させる51のアンチパターン',
    updated_at: '2018-12-15T18:12:00+09:00',
    url: 'https://qiita.com/TakahiRoyte/items/855b6116c8132b0c9286',
    user: {
      description: null,
      facebook_id: null,
      followees_count: 13,
      followers_count: 44,
      github_login_name: 'TakahiRoyte',
      id: 'TakahiRoyte',
      items_count: 13,
      linkedin_id: null,
      location: null,
      name: '',
      organization: null,
      permanent_id: 89911,
      profile_image_url:
        'https://qiita-image-store.s3.amazonaws.com/0/89911/profile-images/1518364681',
      team_only: false,
      twitter_screen_name: 'TakahiRoyte',
      website_url: null
    },
    page_views_count: null
  }
]

取れますね!

Microsoft Teams

Teams は Connectors という機能を有効にして Incoming Webhook を受け取ることができます。
Connector を作って Incoming Webhook の URL を生成しておきましょう。

Teams Document - Using connectors

Connector では Message Card というメッセージのフォーマット機能が使えます。
Microsoft が最近推しているAdaptive Cardsは残念なことに対応していません。
以下のカードを自由に作れるサイトで色々試して良い感じのレイアウトを作りましょう。
左上のテンプレートから下の方の Legacy Message Card の好きなのを選んでカスタマイズします。

Cards Playground

最終的に下の形にしてみました。

{
  "@type": "MessageCard",
  "@context": "http://schema.org/extensions",
  "themeColor": "55C500",
  "summary": "Qiita新着投稿",
  "sections": [
    {
      "activityTitle": "[スクラムを失敗させる51のアンチパターン](https://qiita.com/TakahiRoyte/items/855b6116c8132b0c9286)",
      "activitySubtitle": "TakahiRoyte",
      "activityImage": "https://qiita-image-store.s3.amazonaws.com/0/89911/profile-images/1518364681",
      "markdown": true
    },
    {
      "activityTitle": "[スクラムは問題を可視化するフレームワーク](https://qiita.com/TakahiRoyte/items/93ef9b29b2e61937fb14)",
      "activitySubtitle": "TakahiRoyte",
      "activityImage": "https://qiita-image-store.s3.amazonaws.com/0/89911/profile-images/1518364681",
      "markdown": true
    }
  ],
  "potentialAction": [
    {
      "@type": "OpenUri",
      "name": "Organizationページを開く",
      "targets": [
        {
          "os": "default",
          "uri": "https://qiita.com/organizations/tdc-soft"
        }
      ]
    }
  ]
}

行けそうなので実装の流れは以下の感じになりそうです。

  1. 集約用アカウントのフォロワー一覧を取得する。
  2. 各フォロワーの昨日より新しい投稿記事を取得する。
  3. 取得したデータを元に文字列を整形し Teams に POST する。

実装

プロジェクトセットアップ

firebase-tools を利用してプロジェクトをセットアップします。
追加でインストールする npm モジュールは HTTP リクエストを送るaxiosのみ。

$ npm install -g firebase-tools

$ firebase login
# Firebaseプロジェクトを作成したアカウントでログイン

$ mkdir qiita-new-post

$ cd qiita-new-post

$ firebase init functions
# 1. Yes
# 2. Select FirebaseProject -> 作成したFirebase Project
# 2. JavaScript
# 3. Use ESLint -> No
# 4. npm install now -> Yes

$ cd functions

$ npm install --save axios

Functions がちゃんとデプロイできるか確認します。
functions/index.jsのコメントアウトを外しましょう。

functions/index.js
const functions = require('firebase-functions')

// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions

exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send('Hello from Firebase!')
})

外したらデプロイコマンド叩きます。

$ firebase deploy --only functions

デプロイ後、表示された URL を直にブラウザで開くとHello from Firebase!が表示されます。

コーディング

設定と大まかな処理の流れを生成されたindex.jsに書いていきます。
API 叩く=非同期処理なのでメイン処理は async な関数にします。

functions/index.js
const functions = require('firebase-functions')
const axios = require('axios')
const groupAccount = 'tdcsoft'
const qiitaApiBaseUrl = 'https://qiita.com/api/v2'
const teamsWebhookUrl = 'https://outlook.office.com/webhook/123.../IncomingWebhook/123...' // 略

exports.qiitaNewPost = functions.https.onRequest(async (request, response) => {
  // ユーザーリスト取得
  const users = await getUsers()

  // ユーザーリストに紐づく新着記事リスト取得
  const newPosts = await getUsersNewPosts(users)

  // 新規投稿がある場合、Teamsに投稿する
  if (newPosts.length) {
    await postNewPostsToTeams(newPosts)
  }
  response.send('Post Success')
})

2018/12/23 現在の Cloud Functions のデフォルトは Node 6 でasync/awaitが使えません。
package.jsonに以下の設定を追加して Node 8 で動くようにします。

package.json
"engines": {"node": "8"}

各メソッドの実装は 1 つ 1 つ説明してもあれなので、リポジトリを参照していただければと思います。

qiita-new-post

定期実行の仕組みを作る

Firebase Cloud Function へデプロイ

デプロイコマンド叩きます。

$ firebase deploy --only functions

完了。
試しに URL 叩くともう動くはずです。

image.png

動いてる!

cron-job.org で定期実行セットアップ

Create New Cron で以下のように設定するだけです。

image.png

後は待つだけ。
完成です。

あとがき

以上で動くはずですが cron-job.org からの動作確認はこの記事で試します。
もし動かなかったら修正します。

12/24 00:05 追記

動きました!
image.png


  1. 個人的に既に有料プラン使ってたのでそこに生やしました。 

  2. 利用規約を確認した上でユーザー作成しています。 

円と円の当たり判定を使った簡単なシューティング 

$
0
0

はじめに

今回は簡単なゲーム作りを通して円と円の当たり判定を学んでいきましょう。
前提
if,for,変数などが分かる

数学的な話は苦手なのでググってください。

作るゲームはこんな感じです。
図1.jpg

使う画像
EDGE1.png

数学的な話(少し)

円1: 半径 r1 , 中心 C1(Cx1,Cy1)
円2: 半径 r2 , 中心 C2(Cx2,Cy2)

使う公式
(Cx1-Cx2)^2 + (Cy1-Cy2)^2 ≦ (r1+r2)^2

以上の公式が成り立っていれば当たっています。

これオブジェクトの数だけ繰り返せばどれが当たっているか分かります。
簡単ですね。ここまで読んだならもうできる。

当たり判定の実装

main.js
enemys.forEach(enemy => {
        let sc = 0
        if (enemy.isDead) {
            return
        }
        bullets.forEach(bullet => {
            //敵
            const er = enemy.width / 2
            const enemyCenterX = enemy.x + enemy.width / 2
            const enemyCenterY = enemy.y + enemy.height / 2
            //弾
            const br = bullet.r
            const bulletCenterX = bullet.x + enemy.width / 2
            const bulletCenterY = bullet.y + enemy.height / 2

            const x = (enemyCenterX - bulletCenterX) ** 2
            const y = (enemyCenterY - bulletCenterY) ** 2
            const r = (er + br) ** 2
            //成り立っていれば敵と弾が消える
            if (x + y <= r) {
                enemy.isDead = true
                bullets.splice(sc, 1)
            }
            sc++
        })
    })

実装結果
Page Title - Google Chrome 2018-12-24 07.47.31.png
動くものはこちら
aが左移動
dが右移動
eで弾を打つ

終わりに

全体の実装はここに置いておきます。
今回は大学1・2年生の少しプログラムが書ける人向けに書いてみました。
あまり難しくないが2Ⅾゲームを作る時多用するので覚えておくといいと思います。

参考

2D衝突 その3 点と円、円と円
http://marupeke296.com/COL_2D_No3_PointToCircle.html

Yearly Node.js 2018