20190811のJavaScriptに関する記事は24件です。

JavaScriptでクォートをエスケープしないですむ方法を紹介

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

JavaScriptでクォートをエスケープしないですむ方法を紹介

ES6までは、シングルクォートやダブルクォートをエスケープする必要が生じる場合もありました。

しかし。ES6からはバッククォートを使うことでこの問題を解決できます。

例:

`'Hello' "World!"`

この記事が参考になれば幸いです。

終わりに

Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RPGアツマールのグローバルシグナルでレイドバトルを実装する

概要

RPGアツマールには非同期ネットゲームを想定したAPIが存在します
「グローバルシグナル」を使ってレイドバトルを作ってみたのでその解説になります

グローバルシグナルについて

作ったもの

仕様

図1.png

高HPなボスが出現して、他のプレイヤーも戦闘に参加し、協力してそのボスを倒す
というのを実現します

具体的な仕様は下記のとおりです

  • ボスについて
    • ユーザー1人につきボスが1体出現する
    • 出現から24時間経過で消滅する
    • ボスを倒した、もしくは、消滅したら、翌日に次のボスが出現する
    • 曜日によって出現するボスが変わる
  • 他のプレイヤー
    • 出現中のボスを選んで戦闘に参加してダメージを与えることができる(これによりあるプレイヤーが他のプレイヤーのボスに対して攻撃することができる)

実装について

送信するシグナルの情報

戦闘終了時に、以下の情報を入れてグローバルシグナルを送信します
これにより、シグナルを受け取ったプレイヤーは別のプレイヤーのボスの存在を知れる状態になります

  • 誰のどのボスかを識別する情報
    • ユーザーID
    • セーブ番号
    • ボスのレベル(倒せた場合に次回出現時に1上がる)
  • 戦闘結果
    • ボスに今回与えたダメージ
    • ボスの残りHP
  • ボスの情報
    • 敵グループID
    • 出現した時間(UnixTimeから「時」を算出する)
{"id":101,"sig":[00000,1,14,999865792,134208,196,434706]}

戦闘結果に応じて配列に詰めてこのようなjsonを送信しています
シグナルは100byteの制限があるので収まるように注意してデータの内容を決めます

誰のどのボスかを識別する

誰の(ユーザーID)どのセーブの(セーブ番号)どのボス(ボスのレベル)か、
によって、誰のどのボスかを一意に識別しています

「戦闘結果」のところに書いたHPの計算をするために、シグナルが来たときにどのボスに対してダメージを与える計算をするかに使います

1つのゲームで複数のセーブデータを許す場合、ユーザーIDだけだと一意に識別することができなくなります。ゲームの仕様次第ですが、ゲーム開始時に現在時刻などからハッシュを作ってセーブデータを一意に判別できるようにするなどをやっておくとよいです。今回実装したゲームでは、セーブは複数できるけど途中でセーブ番号が変えられないような仕様にしています。
ただ、この辺ややこしくなりそうならセーブデータは1個しか保存できないようにした方がシンプルになるかと思います。

戦闘結果

シグナルの受け取り側がボスの状況を再現するのに必要な情報を入れます

そのために必要な情報が
「誰が何ダメージ与えたか」「合計何ダメージ与えられて残りHPがいくつか」です
必要な理由はそれぞれ以下の通りです

  • 「誰が何だダメージ与えたか」

    • シグナルを受ける側は与えたダメージの情報を単純に引くことで、残りHPを計算します。同じボスに対して2人3人4人など複数が同時に攻撃をしかけた場合にはこれの合計値が使われることになります
  • 「残りHPがいくつか」

    • すべてのグローバルシグナルは受け取れない可能性があることを考慮しなければならないため、1個のシグナルで1体分のボスを表現できるようにします。そのため「残りHP」の情報も含めています

後は、
シグナルを受け取る度に、以下の2つの計算結果から残りHPが小さくなる方を採用して、自分の手元にあるボスの情報を更新します

  • 手元のボスの残りHPから与えたダメージを引く
  • シグナルに含まれた残りHP

これにより、ダメージがより進んでいる方が採用されます

ボスの情報

  • 敵グループID

    • 曜日ごとにボスを変えるため、敵グループIDを入れておきます
  • 出現した時間

    • ボスが出現した時点でUnixTimeの年月日時分秒...の内、「時」を記録しておきます。出現から24時間経った時点で倒せていなかったら消滅した判定をします

まとめ

他プレイヤー間のボスを表現するために必要な情報をこのようにしてグローバルシグナルで実装できました
これによりレイドバトルをグローバルシグナルで実現しています

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FireStoreでページングを試してみたメモ

概要

FireBaseのバージョンは6.3.5。
FireStoreを使ったページングについて試したメモ。
最初に件数を取得する以外に何か方法がないか探している。

ソース

保存の頻度は高くなく、createdAtにnanosecondsまで同じデータはなく、ユニークな値となっている想定。
nextPageTokenが空白のときは次のページはないという仕様にしている。
以下のソースだと、limitと、保存されているデータの総数が同じときに、
次のデータがないのに「次へ」というボタンが表示されるバグがある。

import * as firebase from 'firebase';
const { Timestamp } = firebase.firestore;

export async function readEnemies(storeUserId, limit, pageToken) {
  const db = firebase.firestore();
  let query = db.collection("users").doc(storeUserId).collection('enemies').orderBy('createdAt', 'desc').limit(limit);
  const splitter = ':';

  if (pageToken !== "") {
    const [seconds, nanoseconds] = pageToken.split(splitter);
    const timestamp = new Timestamp(seconds, nanoseconds);
    query = query.startAfter(timestamp);
  }

  const querySnapshot = await query.get();
  const enemies: any[] = [];
  await querySnapshot.forEach((doc) => {
    const enemy = doc.data();
    enemies.push(enemy);
  });

  if (querySnapshot.docs.length < limit) {
    // limitより少なければ、次のデータはないとする ... limitと同値の時は次へが表示されてしまう
    return { enemies, nextPageToken: "" };
  }

  const last = querySnapshot.docs[querySnapshot.docs.length - 1];
  const lastData = last.data();
  const time = lastData.createdAt;
  let nextToken = `${time.seconds}${splitter}${time.nanoseconds}`;

  return { enemies, "nextPageToken": nextToken };
}

参考ソース

ts
index

参考

Firestore で いいね順(Score順)Sort + Paging するポイント
クエリカーソルを使用したデータのページ設定
firebase. firestore. Timestamp
Firestoreを試してみた
Firebase Cloud Firestoreの使い方
TypeScriptからFirestoreを使いやすくするfirestore-simple v4をリリースしました
Firestoreお役立ちリンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase Functions + TypeScriptをparcelで事前ビルドしてやりすごす

Firebase functionsは現在標準でTypeScriptに対応している。
しかしHostingなど他の機能も使いたい場合や、それらとモジュールを共有したい場合、tsconfigやpackage.jsonが複数のディレクトリに割れたりしてちょっとしんどくなる。
この場合lernaなどでmonorepo構成を取るのが良いのだろうが、TypeScriptが絡んだりするとなかなかまだハマりどころが少なく無く面倒になりがちだ。

そこで以前AWS Lambda向けのファイルをparcelでビルドしたのと似たようなことをやってみたら思ったより悪くなかったのでまとめておく。

1. 準備

まずは適当にディレクトリを作るとこから

$ mkdir some-firebase-project
$ cd some-firebase-project
$ yarn init -y
$ touch firebase.json

parcelとtypescriptも入れてしまおう

$ yarn add parcel typescript
$ yarn tsc --init

あと今回はfirebase initは使わないのでそこは自前で行う

$ mkdir functions
$ touch functions/package.json

2. package install

次にプロジェクト直下でパッケージインストール。トップディレクトリでfunctionsに必要なパッケージを入れる。最終的にbundleしたものをアップするのでpeerDependencyも入れる

$ yarn add firebase-admin @firebase/database @firebase/app @firebase/app-types @firebase/database-types firebase-tools

3. functionファイルの用意

function本体となるファイルを配置する。

$ mkdir src
$ mkdir src/functions
// src/functions/index.ts
import * as functions from "firebase-functions" // 全インポートにしないとひっかかるっぽい

exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!")
})

4. スクリプト設定(本題)

package.jsonをこんな感じで設定

{
  "scripts": {
    "build:functions": "parcel build src/functions/index.ts --target=node -d functions -o index.js --bundle-node-modules --no-source-maps --no-minify",

だいたいAWS Lamdaの場合と一緒で、--target=node--bundle-node-modulesにしている。
またfirebase-admin関連がsourcemap関連のwarningを吐き出してくるので--no-source-mapsを付けている。
また吐き出し先を-d functionsでfunctions下にしている。

--no-minifyをしてるのはビルド速度を上げたい&サーバーサイドのスクリプトなのでminifyする意味あんまり感じない&デバッグ楽そう ぐらいな気持ちなので好みで決めていい

これでyarn build:functionsが出来た。

4.5. Cannot resolve dependency 'http2' の対策をする

本当はここまでで通るはずなのだが、そのまま実行するとCannot resolve dependency 'http2'のエラーが出る事がある。
どうもparcelの問題として、http2モジュールをうまく読み込めずビルドが失敗する状態にあるようだ

https://github.com/parcel-bundler/parcel/issues/2921

ということでその場合は下記のようにhttp2パッケージをインストールしてしまえば解決する

$ yarn add http2

5. firebase.jsonの準備

次にfirebase.jsonを記述する。だいたいこんな感じ。predeployで指定を与える部分が重要

{
  "functions": {
    "source": "functions",
    "predeploy": "yarn build:functions"
  },
  "hosting": {
    ... 
  }
}

6. functions/package.jsonをいじる

AWS Lambdaと違ってpackage.jsonが無いとdeploy時に失敗してしまうので、これを回避するためにpackage.jsonを置いとく。
バージョンは10にしておく。今回はparcel側で--bundle-node-modulesするやり方をとっているのでdependenciesは不要だ。

functions/package.json
{
  "name": "functions",
  "engines": {
    "node": "10"
  },
  "main": "index.js",
  "private": true
}

7. gitignoreする(オプション)

細かいがgitignoreとしてfunctions/index.jsを追加しておくと良い。

functions/index.js
dist

好みでなければ吐き出し先をfunctions/lib/などディレクトリ先にしてそこごとignoreするのも良いだろう。

8. deploy

レッツデプロイ。

$ firebase deploy --only functions

おまけ: hostingがここに入った場合

hositngが入るとこんな具合のディレクトリ構成になる。

.
├── dist # hostingの出力先
│   ├── hosting.09bd9689.js
│   └── index.html
├── functions # functionsの出力先
│   ├── index.js
│   └── package.json
├── src
│   ├── functions # functionsのソース
│   │   └── index.ts
│   ├── hosting # hostingのソース
│   │   ├── index.html
│   │   └── index.tsx
│   └── lib # 共通部分
│       └── database-client.ts
├── firebase.json
├── package.json
├── renovate.json
├── tsconfig.json
└── yarn.lock

firebase.jsonとpackage.jsonはこんな感じになる

package.json
  "script": {
    "build": "parcel build src/index.html",
      ...

firebase.json
  "hosting": {
    "public": "dist",
    "predeploy": "yarn build",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】Vue CLIでVue.jsを動かす〜プロジェクト作成まで

Vue.jsとは

  • JavaScriptのフレームワーク。
  • どのような規模・段階のアプリケーションにも対応することができる(プログレッシブフレームワーク)
  • ページの一部で部分的に使用することもできるし、大規模なSPA(シングルページアプリケーション)を構築することもできる。

プログレッシブフレームワークとは、Vue.jsの生みの親であるEvan You氏の提唱する概念です。

参考記事

Vue.jsについて学習してみた <基礎編>
Vue.js概要?

Vue CLIとは

  • 迅速なVue.js開発を支援するためのコマンドラインインターフェイス。
  • Vue.jsを利用した開発には、様々なパッケージやツールについての知識・複雑な設定が必要になる。それを自動化し、開発環境の構築を補助してくれる。
  • 個別に公開されているパッケージ(ビルドツール、コンパイラなど) の集合体でもある。Vue CLIを利用することにより、そこから必要な機能だけを使うことができる。

参考記事

公式サイト(英語)

インストール

あらかじめディレクトリを作成し、初期化しておきます。(今回初期化の必要はないかもしれませんが、念の為。)

$ mkdir vue_cli_test
$ cd vue_cli_test
$ npm init

npmによるインストール

グローバルインストールで行っています。

$ npm install -g @vue/cli

加えて、@vue/cli-initもインストールします。

$ npm install -g @vue/cli-init

@vue/cli-initのインストールがされていない場合、プロジェクト作成のコマンドnpm init実行時に以下のエラーが発生します。

Command vue init requires a global addon to be installed.
Please run npm install -g @vue/cli-init and try again.

グローバルインストールの場合、パッケージはnpm list -gで確認できるディレクトリ配下のnode_modulesディレクトリにインストールされます。

$ npm list -g
/Users/yuki/.nodebrew/node/v12.7.0/lib

パッケージ名を指定して確認することもできます。

$ npm list -g @vue/cli
/Users/yuki/.nodebrew/node/v12.7.0/lib
└── @vue/cli@3.10.0 

環境変数の設定

このままだとvueコマンドが使えないため、.bash_profileにパスを設定します。
まず、コマンドがどこに存在するのか確認します。

$ npm bin -g
/Users/yuki/.nodebrew/node/v12.7.0/bin

.bash_profileに追記します。

$ vi ~/.bash_profile
export PATH="$HOME/.nodebrew/node/v12.7.0/bin:$PATH"

反映させます。

$ source ~/.bash_profile

Vue.jsのバージョン確認

vueコマンドが通るようになったので、Vue.jsのバージョンを表示してみます。
確認ができれば、Vue CLIのインストールは成功です。

$ vue -V
3.10.0

プロジェクト作成

npm init テンプレート名 プロジェクト名でプロジェクトを作成します。

今回、テンプレートはwebpackを指定します。

テンプレートの種類については、こちらの記事が参考になります。
Vue-cli(webpack)解剖 ーディレクトリ構成ー

$ vue init webpack my-app

いくつか質問をされます。
今は余分な機能を入れたくなかったので、以下の質問はNoにしました。

// SPA構築に必要なVue.js公式ルータ
? Install vue-router? No
// 構文チェックツール
? Use ESLint to lint your code? No
// テストツール
? Set up unit tests No
// テストツール
? Setup e2e tests with Nightwatch? No

プロジェクト名であるmy-appディレクトリが作成され、必要な機能がインストールされます。
最後の方にあるメッセージの通り、my-appディレクトリに移動します。

To get started:

  cd my-app
  npm run dev
$ cd my-app

次にnpm run devでサーバを起動します。
devというエイリアスコマンドは、プロジェクト名のディレクトリ/package.jsonscriptで設定されています。

デフォルトでは以下の設定になっています。
npm startnpm run startでもサーバを起動することができるようです。

myapp/package.json
"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "build": "node build/build.js"
},
$ npm run dev

表示されるメッセージの通りhttp://localhost:8080にアクセスして、Vue.jsのデモ画面が確認できれば成功です。

Your application is running here: http://localhost:8080
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】動的なカテゴリーボックスの実装方法

はじめに

動的なセレクトボックスを実装するにあたり、非同期通信を用いたので備忘録としてここ記す。

テーブル

categoriesテーブル

Column Type Options
category-name string null false
ancestry string null false

ancestryのGemを使い多階層としている。
親、子、孫が一つのカラムの中に存在している。

実装

category.js
  $(function(){
  function appendOption(category){ // optionの作成
    var html = `<option value="${category.id}">${category.name}</option>`;
    return html;
  }
  function appendChidrenBox(insertHTML){ // 子セレクトボックスのhtml作成
    var childSelectHtml = '';
      childSelectHtml = `<div class='product-select-wrapper' id= 'children_wrapper'>
                        <div class='product_category-select'>
                        <select class="category_select-box" id="child_category" name="category_id">
                        <option value="---">---</option>
                        ${insertHTML}
                        </select>
                        <i class='fa fa-chevron-down'></i>
                        </div>
                        <div class= 'product_select-grandchildren'>
                        </div>
                        </div>`;
    $('.product_select-children').append(childSelectHtml);
  }
  function appendgrandChidrenBox(insertHTML){ // 孫セレクトボックスのhtml作成
    var grandchildrenSelectHtml = '';
    grandchildrenSelectHtml = `<div class='product-select-wrapper' id= 'grandchildren_wrapper'>
                              <div class='product_category-select'>
                              <select class="category_select-box" id="grandchild_category" name="category_id">
                              <option value="---">---</option>
                              ${insertHTML} 
                              </select>
                              <i class='fa fa-chevron-down'></i>
                              </div>
                              <div class= 'product_select-grandchildren'>
                              </div>
                              </div>`;
    $('.product_select-grandchildren').append(grandchildrenSelectHtml);
  }



  $(document).on('change', '#category_select', function(){  // 親セレクトボックスの選択肢を変えたらイベント発火
    var productcategory = document.getElementById('category_select').value; 
  // ↑ productcategoryに選択した親のvalueを代入
    if (productcategory != ''){
 // ↑ productcategoryが空ではない場合のみAjax通信を行う。選択肢を初期選択肢'---'に変えると、通信失敗となってしまうため。
      $.ajax({
        url: 'category_children',
        type: 'GET',
        data: { productcategory: productcategory },
        dataType: 'json'
      })
      .done(function(children){  // 送られてきたデータをchildrenに代入
        var insertHTML = '';
        children.forEach(function(child){  
  // forEachでchildに一つずつデータを代入。子のoptionが一つずつ作成される。
          insertHTML += appendOption(child); 
        });
        appendChidrenBox(insertHTML); 
        $(document).on('change', '#category_select', function(){
  // 通信成功時に親の選択肢を変えたらイベント発火。子と孫を削除。selectのidにかけるのではなく、親要素にかけないと残ってしまう
          $('#children_wrapper').remove(); 
          $('#grandchildren_wrapper').remove();
        })
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');

      })
    }
  });


  // documentにしないとリロードしなければイベントが発火しない
  $(document).on('change', '#child_category', function(){
    var productcategory = document.getElementById('child_category').value;
    if (productcategory != ''){
    $.ajax ({
      url: 'category_grandchildren',
      type: 'GET',
      data: { productcategory: productcategory },
      dataType: 'json'
    })
    .done(function(grandchildren){
      var insertHTML = '';
      grandchildren.forEach(function(grandchild){
        insertHTML += appendOption(grandchild);
        });
        appendgrandChidrenBox(insertHTML);  
        $(document).on('change', '#child_category',function(){
          $('#grandchildren_wrapper').remove();
          })
        })  
        .fail(function(){
          alert('カテゴリー取得に失敗しました');
        })
    }
  });
});

routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "items#index" 
  # ここから itemsコントローラのAjax通信でのアクション先指定
  resources :items do 
    collection do
      get 'category_children' 
      get 'category_grandchildren'
    end
  end
  # ここまで
end
items_controller.rb
  def new
    @category = Category.all.order("id ASC").limit(13) # categoryの親を取得
  end

  def category_children  
    @category_children = Category.find(params[:productcategory]).children 
    end
  # Ajax通信で送られてきたデータをparamsで受け取り、childrenで子を取得

  def category_grandchildren
    @category_grandchildren = Category.find(params[:productcategory]).children
    end
  # Ajax通信で送られてきたデータをparamsで受け取り、childrenで孫を取得。(実際には子カテゴリーの子になる。childrenは子を取得するメソッド)
category_children.json.jbuilder
json.array! @category_children do |child|
  json.id child.id
  json.name child.category_name
end
ategory_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.category_name
end
new.html.haml
.product_select-details
  .product_select-group
    .product_header
      カテゴリー
    .product_require
      必須
  .product_category-select
    = f.collection_select :category, @category, :id, :category_name, { prompt: "---" }, { class: "category_select-box", id: "category_select" }
    %i.fa.fa-chevron-down
  .product_select-children

さいごに

間違っているところがあればご指摘下さい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【読切】非同期処理の沼~Vue.js Django編~

あらすじ

2019年8月10日。私はVue.jsとDjango REST Frameworkを使い、ある業務システムの画面開発を担当していた。
フレームワークなんて代物はまだ触り始めて2,3か月。とは言えどこを触れば何が変わるか多少は理解してきている自負もあった。
そんな良くも悪くも弾みのついていた私を戒めるかのように、単体テスト中、そいつは突然現れた。

出会い

『更新中の…ステータスが…… エラーコード:409』

「(動作確認では何度も正常に作動していた機能開発...そんなはずは...)」
NG判定とエラー内容をテスト記録として記述する。

"""
HTTP 409 Conflict はリクエストが現在のサーバーの状態と競合したことを示すステータスコード。

競合は PUT メソッドを使用したリクエストのレスポンスで最も発生しやすい。例えば、サーバーにすでに存在しているファイルよりも古いバージョンのファイルをアップロードした際に409の応答が返され、バージョン管理システムの競合が発生する可能性がある。
"""
409 Conflict

困った困った。なけなしの知識と経験であれやこれやと思考した。
そして画面の再描画後、再度そいつは現れた。

『更新中の…ステータスが…… エラーコード:409』

非同期処理の沼

「ここか。」
答えに辿り着くのに長くはかからなかった。

heavyHoge.ts
 // post(url, data, loading)
 // url : 呼び出し先のURL
 // data : REST側に送りたいデータ
 // loading : 処理終了までローディング画面を表示

 // 重ための処理
  private async heavyProcess(): Promise<void> {
    const payload = '画面から送る何かのデータ群';
    this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
    this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
    // 画面の更新処理
    this.onShown();
  }

ボタンを押下して、この処理が走っている間に他の作業も進められるようにしたい。数十秒~数分なんて流石に待っていられない。
そのような声は当然にあり、この処理は上記の通り非同期処理で実装していた。
ここでの2つのhttp.post(以下、処理①と処理②)は類似処理を行っているが、具体的には下記の処理を行っている。

1.画面内のデータを取得しREST側へPOST
2.取得したデータの条件が合えば、外部APIへ有料の関連データを取得
3.種々のCRUD処理を行う

更にエラーの発生パターンについて調べたところ、大きく下記の2つであった。

1.処理①が上記1~3を終えて、戻り値を返して処理②を行おうとした直後
2.処理①のみor処理②のみが走るように画面側で予め条件設定し、1度目の処理を終えた直後に同じ処理のトリガーを引いた直後

つまり、処理量の多さにCRUD処理が実際には完了しきいっていない状態の時に再び処理①②を行うことでエラーが起きていた。
それを示すように awaitを付記するとloading画面は処理終了まで続くがエラーは消える。(当然っちゃ当然だが)

heavyHoge.ts
 // post(url, data, loading)
 // url :
 // data : 何かのデータ群
 // loading : 処理終了までローディング画面を表示

 // 重ための処理
  private async heavyProcess(): Promise<void> {
    const payload = '画面から送る何かのデータ群';
    await this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
    await this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
    // 画面の更新処理
    this.onShown();
  }

とにかく、今回のエラーの発生要因やその背景なんかを整理すると
・外部APIが有料ということもあり、連続or多数の本チャンデータ取得パターンの確認を怠った。
・1度のボタン押下で行う非同期処理を2つに分けてしまった。
 (※片方は他画面で使用しているViewに飛んでいた為、このような実装になった)
・REST側で各処理を行うViewを呼び出す為の、共通クラスを用意しなかった。
 (※やってることは2個目と同じ)

しかし、上記の課題を全てクリアして当初の設計通り実装しようとしても、まだエラーの発生パターン2が解決しない。
そう、ユーザーが短時間に連続で同じ処理を行おうとした時だ。完全に非同期処理の沼に嵌まってしまったのである。

2019年8月11日。27歳の誕生日を迎えた今日、未だ解決策は見つかっていない。

To Be Continued

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptを一行使用するだけで、プルダウンメニューで選択した瞬間に画面遷移

プルダウンメニューから選択した瞬間に画面を移動する機能の実装に躓いたのでメモします。
以下のソースをhtmlファイルに記述します。

<select onChange="location.href=value;">
    <option class="selected">並び替え</option>
    <option value="URL1">新しい順</option>
    <option value="URL2">参考になった順</option>
</select>

以下の写真のようにブラウザに表示されます。
515f9a9a4eed683fbe048261b938a309.png

これで、新しい順を選択するとURL1、参考になった順を押すとURL2に遷移するようになります。 onChange="location.href=value;"の一文をselectタグに追加するだけで機能するので便利だと感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript ES6 【forEach/map/filter/find/every/some/reduce】

ES6を勉強したので自分のためにメモする

UdemyでES6を結構しっかり学んだけど、関数系がごっちゃになりがちなので自分用にメモします。
全部便利そうなので今後しっかり使っていきたい!

forEach

オブジェクトに繰り返し処理を行いたいときに使用する。

let images = [
  { height: 10, width: 20 },
  { height: 20, width: 30 },
  { height: 30, width: 40 },
];

let areas = [];
images.forEach(function(image) {
  areas.push(image.height*image.width);
});

areas;

----- result ------
[200,600,1200]

// アロー関数使用バージョン
images.forEach(image => areas.push(image.height*image.width));

map

オブジェクトから特定のキーオブジェクトを抜き出したいときに使用する。

let colors = [
  { jp: '', eng: 'red' },
  { jp: '', eng: 'blue' },
  { jp: '', eng: 'yellow' },
];

let engColor = colors.map(function(color) {
    return color.eng;
});

engColor;

----- result ------
["red","blue","yellow"]

// アロー関数使用バージョン
let engColor = colors.map(color => color.eng);

filter

条件に合致するものだけ抜き出したいときに使用する。

let includeCaffeine = [
  { name: 'コーヒー', caffeine: true },
  { name: '麦茶', caffeine: false },
  { name: '緑茶', caffeine: true },
];

includeCaffeine.filter(function(drink) {
  return drink.caffeine;
});

----- result ------
[{"name":"コーヒー","caffeine":true},{"name":"緑茶","caffeine":true}]

// アロー関数使用バージョン
includeCaffeine.filter(drink => drink.caffeine);

find

条件に合致する一番最初の要素を見つけたいときに使う。

let dogsOkCafe = [
  { name: 'CafeA', dogsOK: false },
  { name: 'CafeB', dogsOK: true },
  { name: 'CafeC', dogsOK: true },
];

dogsOkCafe.find(function(cafe) {
    return cafe.dogsOK;
});

----- result ------
{"name":"CafeB","dogsOK":true}
// ↑最初のひとつだけなので、CafeCははいらない

// アロー関数使用バージョン
dogsOkCafe.find(cafe => cafe.dogsOK);

every

すべてのオブジェクトが条件に合致しているかどうか判定するときに使用する

let places = [
  { name: '北海道', visited: true },
  { name: '沖縄', visited: true },
  { name: '群馬', visited: true },
];

places.every(function(place) {
  return place.visited;
});

----- result ------
True

// アロー関数使用バージョン
places.every(place => place.visited);

some

ひとつでも条件に合致しているものがあるか判定するときに使用する

let places = [
  { name: '北海道', visited: true },
  { name: '沖縄', visited: false },
  { name: '群馬', visited: false },
];

places.some(function(place) {
  return place.visited;
});

----- result ------
True

// アロー関数使用バージョン
places.some(place => place.visited);

reduce

オブジェクトを違う型にしたりするときに使用する

var fruits = [
  { type: 'Apple' },
  { type: 'Strawberry' },
  { type: 'Strawberry' },
  { type: 'Strawberry' },
  { type: 'Apple' }
];

fruits.reduce(function(prev, fruit) {
    if (fruit.type === 'Apple') { prev.apple += 1; return prev; }
    if (fruit.type === 'Strawberry') { prev.strawberry += 1; return prev; }
}, { apple: 0, strawberry: 0 });

----- result ------
{"apple":2,"strawberry":3}

// アロー関数使用バージョン
fruits.reduce((prev, fruit) => {
    if (fruit.type === 'Apple') { prev.apple += 1; return prev; }
    if (fruit.type === 'Strawberry') { prev.strawberry += 1; return prev; }
}, { apple: 0, strawberry: 0 });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js超基本の使い方まとめ

最近やっているプロジェクトでjQueryを使ってたのですが、jQueryの面倒さに発狂したくなってしまい、なんとかならないものかと思っていたところ、Vue.jsに出会いましたので学習をはじめました。

参考

今回udemyを一通り受講し、振り返りをしているため非常に参考とさせていただきました。非常に分かりやすい構成になっているため、初めて学習を始める人はまず受講してみることをオススメ致します。

Vue.jsの基本構文

データバインディング

Mustache構文

Vue.jsではMustache構文を使うことで、簡単にVueインスタンスデータとHTMLを結びつけることができます。
このデータの結びつけのことをデータバインディングといいます。

下記の例を表示すると {{ message }} にはHello Vue.js!が表示されます。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Vue Sample</title>
  </head>
  <body>
    <div id="example">
        {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  </body>
</html>
main.js
var app = new 
Vue({
  el: "#example",
  data: {
    message: "Hello Vue.js!"
  }
});

v-bind

簡単なテキストのデータバインディングであればMustache構文を使えましたが、DOM要素の属性をデータバインディングする場合にはMustache構文はエラーとなり使えません。

※以降Vueに関わる部分のみHMTLを記載

index.html
<div id="example-error">
  <a href="{{ url }}">Google</a>
</div>
main.js
var app = new Vue({
  el: "#example-error",
  data: {
    url: "https://google.com"
  }
});

そこで使用するのが v-bindディレクティブ です。
v-bindディレクティブ を使えばDOM要素の属性を動的に切り替えることができます。

例1:href属性のデータバインディング
下記例だとhref属性のurlにGoogleのURLがデータバインディングされます。

index.html
<div id="example1">
  <a v-bind:href="url">Google</a>
</div>
main.js
var app = new Vue({
  el: "#example1",
  data: {
    url: "https://google.com"
  }
});

例2:value属性のデータバインディング
下記例ではinputタグのvalue属性がデフォルトでおはよう!と表示されます。

index.html
<div id="example2">
  <input v-bind:value="message">
  <p>{{ message }}</p>
</div>
main.js
var app = new Vue({
  el: "#example2",
  data: {
    message: "おはよう!"
  }
});

しかし初期値のおはよう!は正しく表示されていましたが、文字を書き換えた時にpタグが書き換わっていなかったことに気づくと思います。

これはv-bindによるデータバインディングは

 ・jsで更新したdataはテンプレートに更新されるが、
 ・テンプレート側で更新してもdataが更新されない

という性質があるためです。そこで登場するのが、v-on メソッドです。

v-on

v-onメソッドはイベント処理をするためのディレクティブです。
input要素のテキストエリアに入力する度にinputイベントが発生するので、v-bindに追加でv-onを追加してdataを更新する処理を追加します。

index.html
<div id="example">
  <input 
    v-bind:value="message"
    v-on:input="message = $event.target.value"
  >
  <p>{{ message }}</p>
</div>
main.js
var app = new Vue({
  el: "#example",
  data: {
    message: "おはよう!"
  }
});
  1. 初期値でmessageにおはよう!が入力されています
  2. そのためMustasche構文でかいているpタグにもおはよう!が表示されます
  3. 入力欄におはよう!の!を削除するとinputイベントが発生し、messageを書き換えます
  4. するとMustasche構文でかいているpタグにもおはように更新されます

このようにjsからテンプレートのデータを更新するだけでなくく、テンプレートからもデータを更新することを 双方向データバインディングといいます。下記の2行は全く同じ表現で、どちらもmessageに対して双方向データバインディングすることができます。

v-model

v-bindとv-onを組み合わせた双方向データバインディングの例を示しましたが、これはv-modelというディレクティブで表現することができます。

  // どちらも同じ
  <input v-model="message">
  <input v-bind:value="message" v-on:input="message = $event.target.value">

v-bindとv-onの省略記法

v-bindとv-onは頻繁に使用するため省略記法があります。

ディレクティブ 完全な構文 省略記法
:v-bind <a v-bind:href="url"> ... </a> <a :href="url"> ... </a>
:v-on <a v-on:click="func"> ... </a> <a @click="func"> ... </a>

繰り返し処理

v-for

v-forを使えば繰り返し文を使うことができます。
またv-for="(user, index) in users"とすることで配列のキーを取り出すことも可能です。

リストの例
<ul id="example">
  <li
   v-for="(user, index) in users"
  >
    {{ member }}
  </li>
</ul>
var app = new Vue({
  el: "#example",
  data: {
    users: ["佐藤", "鈴木", "山田"]
  }
});

表示・非表示の切り替え

v-if, v-if-else, v-else

v-ifディレクティブではブロックを条件に応じて描画することができます。

<div id="app">
 <div v-if="toggle">
    <p>Hello</p>
  <div v-else>
    <p>World</p>
  </div>
</div>
var app = new Vue({
  el: '#app',
  data: {
    toggle: true // 切り替える
  }
})

v-show

v-showディレクティブは属性値がtrueとなる場合に要素を表示します。

<div id="app">
 <div v-show="toggle">
    <p>Hello</p>
  </div>
</div>
var app = new Vue({
  el: '#app',
  data: {
    toggle: true // 切り替える
  }
})

v-ifとv-showの比較と使い分け

比較

v-ifとv-showは表示・非表示の方法に明らかな違いがあります。

v-if v-show
切り替え方法 ブロックごと作成・削除 ブロックを残したまま表示・非表示を切り替え
初期描画コスト 低い 高い
描画切り替えコスト 高い 低い

v-ifは非表示にする時にブロックごと削除します。
スクリーンショット 2019-08-11 12.45.09.png

一方でv-showは非表示にする時にブロックに対してCSSのdisplay:noneを使って切り替えます。
スクリーンショット 2019-08-11 12.44.03.png

使い分け

v-if
 ・ 初期描画コストが低く、描画切り替えのコストが高い
 ・ そのため切り替えの頻度が低いなら、初期描画コストの低いv-ifを使用する
v-show
 ・ 初期描画コストが高く、描画切り替えのコストが低い
 ・ そのため切り替えの頻度が多いなら、描画切り替えコストの低いv-ifを使用する

算出プロパティ

computed

算出プロパティは関数で算出したデータを返すことができるプロパティのことです。要はgetterやsetterの役割だと思ってもらえれば良いかと思います。

<div id="app">
  {{ reverseMessage }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: "Hello world!"
  },
  computed: {
    // data:messageの文字列を反転させる
    reverseMessage: function() {
      return this.message.split('').reverse().join('') 
    }
  } 
})

算出プロパティとメソッドの比較

先ほどの例だと、メソッドを使っても同じことが実現できます。

<div id="app">
  {{ reverseMessage }}
  {{ reverseMessageMethod() }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: "Hello world!"
  },
  computed: {
    reverseMessage: function() {
      return this.message.split('').reverse().join('') 
    }
  },
  methods: {
    reverseMessageMethod: function() {
      return this.message.split('').reverse().join('') 
    }
  } 
})

そこで算出プロパティとメソッドの違いをまとめると3点の違いがあります。

算出プロパティ メソッド
記法 ()不要 ()必要
使い方 getter, setter getter
キャッシュ される されない

算出プロパティのキャッシュ

算出プロパティとメソッドの使い分けとしてキャッシュの有無があります。
Mathのランダム関数を使ったときの例を用いて説明します。

<div id="example">
  メソッド
  <ol>
    <li> {{ getDate() }}</li>
    <li> {{ getDate() }}</li>
    <li> {{ getDate() }}</li>  
  </ol>
  算出プロパティ
  <ol>
    <li>{{ date }}</li>
    <li>{{ date }}</li>
    <li>{{ date }}</li>
  </ol>
</div>
var app = new Vue({
  el: "#example",
  computed: {
    date: function () {
     console.log("computed");
      return Math.random();
    }
  },
  methods: {
    getDate: function () {
     console.log("methods");
      return Math.random();
    }
  }
});
出力結果
メソッド
0.523744236259859
0.29754866940841374
0.3024997149055251

算出プロパティ
0.19494595412700444
0.19494595412700444
0.1949459541270044

このように同じMath.random()を使っても、算出プロパティは全て同じ値に対してメソッドは全てバラバラの値になりました。

スクリーンショット 2019-08-11 14.42.04.png
また算出プロパティは描画は3度していますが、コンソールログには1度しかログが出ていません。

算出プロパティとメソッドの使い分け

算出プロパティ
 ・ getter以外にもsetterを定義したい場合
 ・ キャッシュを効かせて、処理を高速化させたい場合

メソッド
 ・ あえて処理を高速化させたくない場合

さいごに

今回初めてVue.jsを使ってみましたが、jQueryのように直接DOM操作をするわけでなく、少ない量でかつわかりやすくDOMの操作をすることができ非常に期待が高まりました。
使い分けに関する情報など、初学者に少しでも役に立てば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでサクサク!SVG内に線を引いてみる

はじめに

前回の記事続きです。
SVGとVue.jsを組み合わせて今回は、Pathを使って線を引いてみるという
処理を実装して見たいと思います。

前回の記事と組み合わせることで、
マインドマップのようなWebアプリも作れるかもしれないです。
(その場合はpath要素と図形要素を保持して、線を設定する形になると思います。)

aperture-video-95d3487f-079b-43c8-bca2-7f1eda3f892e (1).gif

早速コードを見ていきましょう!

Path要素を入れる

まず、線を表現するのに必要なPath要素を、
SVGタグ内に記載いたします。
v-model内にリストで表現し、線でつないだ分だけ可変的に生成できるようにします。

線を引いている時(確定前要素)と、線を引いた後(確定後要素)で、
Pathを分けております。
今回オプション要素で、線の太さや、色を全体に可変的に適用する設定もおまけで入れてます。

SVGDemo.vue
<template>
  <div class="container">
    <div class="setting-box">
      <!-- 色を変えるとこ -->
      <p></p>
      <input type="color" v-model="strokeFill" />
      <!-- 線の太さとか -->
      <p>線の太さ</p>
      <input type="range" v-model="strokeWidth" />
    </div>
    <!-- SVG定義 -->
    <svg :width="width" :height="height" :viewBox="viewport" @wheel="zoompan" @mousedown="lineNode($event, idx)"> 
      <!-- 線を引いた後 -->
      <path :d="path"
            fill="none"
            :stroke="strokeFill"
            :opacity="strokeOpacity"
            :stroke-width="strokeWidth"
            stroke-linecap="round"
            v-for="(path, idx) in pathList" :key="idx"
            v-if="pathList.length > 0"/>
      <!-- 線を引いている最中 -->
      <path :d="pathTemp"
            fill="none"
            :stroke="strokeFill"
            :opacity="strokeOpacity"
            :stroke-width="strokeWidth"
            stroke-linecap="round"
            v-if="isLineNode"/>
    </svg>
  </div>
</template>

イベントハンドラを実装

今回は図形からドラッグしていくと
線が生成され、図形同士で繋げた場合に
生成されるようにしていきます。

SVGDemo.vue
<script>
export default {
  name: 'SVGDemo',
  data () {
    return {
      width: 500,
      height: 500,
      ratio: 1,
      dx: 0,
      dy: 0,
      viewport: '0 0 500 500',
      isLineNode: false,
      beforeMouseX: null,
      beforeMouseY: null,
      selectIdx: 0,
      pathList: [],
      pathTemp: '',
      strokeFill: '#525',
      strokeOpacity: 1,
      strokeWidth: '5',
      moveLineX: 0,
      moveLineY: 0,
      startLineX: 0,
      startLineY: 0,
    } 
  },
  // マウス操作関連
  mounted () {
    console.log('MOUNT LISTENER ON')
    document.addEventListener('mouseup', this.mouseUp)
    document.addEventListener('mousemove', this.mouseMakeLine)
  },
  beforeDestroy () {
    console.log('MOUNT LISTENER OFF')
    document.removeEventListener('mouseup', this.mouseUp)
    document.removeEventListener('mousemove', this.mouseMakeLine)
  },
  methods: {
    zoompan (e) {
      var [x, y, w, h] = this.viewport.split(' ').map(v => parseFloat(v))
      if (e.ctrlKey) {
        // 拡大(Y軸が上がる場合) 縮小(Y軸が下がる場合)
        if (e.deltaY > 0) {
           w = w * 1.01
           h = h * 1.01
        } else {
          w = w * 0.99
          h = h * 0.99
        }
        this.makeViewBox(x, y, w, h)
        this.ratio = w / this.width
        e.preventDefault()
      } else {
        // 移動
        if ((this.dx + e.deltaX > -this.width && this.dy + e.deltaY > -this.width) &&
            (this.dx + e.deltaX < this.width * 2 && this.dy + e.deltaY < this.width * 2)) {
          this.makeViewBox(x + e.deltaX, y + e.deltaY, w, h)
          this.dx += e.deltaX
          this.dy += e.deltaY
        }
      }
    },
    // viewboxを作成
    makeViewBox (x, y, w, h) {
      this.viewport = [x, y, w, h].join(' ')
    },
    // マウスのクリックが終わった段階で初期化
    mouseUp (e) {
      this.isLineNode = false
      this.beforeMouseX = null
      this.beforeMouseY = null
      this.pathList.push(this.pathTemp)
      this.pathTemp = null
      e.preventDefault()
    },
    // move中は前回まで動かした差分を取りながら座標を変化させていく
    mouseMove (e) {
      if (!this.isMove) return
      var mouseX = e.offsetX * this.ratio + this.dx
      var mouseY = e.offsetY * this.ratio + this.dy
      var dx = 0
      var dy = 0
      if (this.beforeMouseX && this.beforeMouseY) {
          dx = mouseX - this.beforeMouseX
          dy = mouseY - this.beforeMouseY
      }
      this.beforeMouseX = mouseX
      this.beforeMouseY = mouseY
      var tempX = dx + Number(this.rects[this.selectIdx].x)
      var tempY = dy + Number(this.rects[this.selectIdx].y)
      if (tempX > 0) this.rects[this.selectIdx].x = tempX
      if (tempY > 0) this.rects[this.selectIdx].y = tempY
      e.preventDefault()
    },
    lineNode (e, i) {
      this.startLineX = e.offsetX * this.ratio + this.dx
      this.startLineY = e.offsetY * this.ratio + this.dy
      this.moveLineX = e.offsetX * this.ratio + this.dx
      this.moveLineY = e.offsetY * this.ratio + this.dy
      this.isLineNode = true
      e.preventDefault()
    },
    mouseMakeLine (e) {
      if (!this.isLineNode) return
      var mouseX = e.offsetX * this.ratio + this.dx
      var mouseY = e.offsetY * this.ratio + this.dy
      var dx = 0
      var dy = 0
      if (this.beforeMouseX && this.beforeMouseY) {
          dx = mouseX - this.beforeMouseX
          dy = mouseY - this.beforeMouseY
      }
      this.beforeMouseX = mouseX
      this.beforeMouseY = mouseY
      var tempX = this.moveLineX
      var tempY = this.moveLineY
      tempX += dx
      tempY += dy
      if (tempX > 0) this.moveLineX = tempX
      if (tempY > 0) this.moveLineY = tempY
      this.pathTemp = 'M' + this.startLineX + ',' + this.startLineY + ' L' + this.moveLineX + ',' + this.moveLineY
    }
  }
}
</script>

完成

ここまでロジックを記載していくと
このような形になるかと思います。
aperture-video-65ade8d9-c974-4505-9dfc-b07807b73ed1.gif
ベジェ曲線など組み込んでカーブさせても
面白そうですね!

最後に

いかがでしたでしょうか?
Vue.jsとSVGを使うと色々なことができるので、
また色々記事を書いていけたら、と思います。

現在弊社では、HRモンスターと呼ばれる
採用の新しいスタイルを提供するサービスをローンチいたしました。

ローンチ後のさらなる機能追加、改善などのPDCAサイクルを回すべく、
エンジニアを募集しております。
https://www.wantedly.com/projects/341182

Kubernetes、Vue.js(Javascript)、Django(Python)といったモダンな技術を使って、
開発しておりますので、もしご興味がある方はぜひ、ご応募お待ちしております。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Programming News】Qiitaまとめ記事 Weekly August 2nd week, 2019 Vol.4

筆者が2019/8/4(日)~8/10(土)に気になったQiitaの記事のまとめのまとめを作成しました。日々のまとめ記事のWeekly版です。

皆様が興味のある言語や思いもよらぬハック方法をこの中から見つけられたら幸いです。

Java

Python

Ruby

Rails

C#

Android

Swift

Kotlin

Flutter

Fulx

JavaScript

React

Node.js

Vue.js

Vuex

Nuxt.js

Nest.js

Angular

jQuery

TypeScript

C#

PowerApps

ReactNative

Laravel

C

PHP

CakePHP

Rust

Go言語

R言語

Scala

Haskell

Unity

Spark

Twitter

Line

A-Frame

PowerApp

Line

HTML

CSS

Sass

SQL

MySQL

PostgreSQL

Oracle

MongoDB

SQL Server

Apache

ビッグデータ

Visual Studio Code

IntelliJ IDEA

AI

IoC

Git

AWS

Azure

Oracle Cloud

IBM Cloud

Active Directory

Cloud SQL

Cloud9

インフラ

ブロックチェーン

Ethereum

Hyperledger

セキュリティ

機械学習

自然言語処理

Network

RPA

CI

Docker

Heroku

VirtualBox

kubernetes

OpenStack

Swagger

AMP

OpenID

OAuth2.0

Elasticsearch

Linux

Cent OS

Windows

Mac

Redis

Google API

Google Apps Script

Google Cloud Platform

Google Colaboratory

Google Cloud Data Catalog

Google Drive

VB.Net

Firebase

Server Side

CSS

BootStrap

WordPress

Develop

Keras

PowerShell

Vim

Atom

awk

LaTex

Redmine

UML

Raspberry

RPA

IoT

Alexa

Line

SharePoint

VBA

ShellScript

PowerShell

Slack

Nim

Emacs

WPF

UI

Ansible

Arduino

Julia

Coral

ionic

QRCode

OCR

EC-CUBE

資格

転職

更新情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

盆休みだし改めてvar,let,constと思い出を振り返る

はじめに

せっかくのお盆休み(とは言っても3連休だけですが)なので、知識としては知っているもののあやふやな部分があるところに関して少し振り返ってみようかと思いました。
まずは変数宣言について振り返ります。

var,let,constについて

現在JavaScriptにおいて変数、定数を宣言する場合はlet,constが使用されます。
let,constがでる前はvarを使用していましたが、これからJavaScriptを書く場合にあえてvarを選択する理由はありません。

またletに関しても再宣言は不可能なものの再代入は可能であるため、一部の場面を除いてconstを利用する方が良いでしょう。

MDNで確認

まずはそれぞれの定義に関してMDNで確認してみます。

var

var 文は変数を宣言し、任意でそれをある値に初期化します。

let

let 文はブロックスコープの局所変数を宣言します。任意で値を代入して初期化できます。

const

定数 (const) は、 let 文を使って定義する変数と同じ、ブロックスコープです。定数の値は、再代入による変更はできず、再宣言もできません。

各宣言方法について

var > let > constの順でより制限が強くなっていきます。
for文などイテレータが必要な場合はletを使用することもありますが、書き方次第ではそれも回避できるので使用頻度は低めです。

名称 再宣言 再代入 関数スコープ ブロックスコープ
var ×
let × ×
const × × ×

再宣言、再代入

再宣言

一度宣言した変数を再度同じ変数名で宣言することです。
それぞれ実際のコード例を元にして説明していきます。

var

varの場合は、再宣言も再代入も可能です。同一名の変数に関して何度でも上書きが可能です。

// 再宣言
try {
  var cat = 'tama';
  var cat = 'saba';
  console.log(cat) // saba
} catch(err) {
  console.log(err);
}

// 再代入
try {
  let cat = 'tama';
  cat = 'mike';
  console.log(cat) // mike
} catch(err) {
  console.log(err);
}

let

letの場合は、再宣言は不可ですが、再代入は可能です。上書き自体はできてしまうので、宣言した後に書き換えられてしまう危険性は残ります。

// 再宣言
try {
  let shiba = 'maru';
  let shiba = 'jiro';
  console.log(shiba)
} catch(err) {
  console.log(err); // Uncaught SyntaxError: Identifier 'shiba' has already been declared
}

// 再代入
try {
  let shiba = 'maru';
  shiba = 'taro';
  console.log(shiba) // taro
} catch(err) {
  console.log(err);
}

const

再宣言も再代入も不可能です。一度宣言したら値への参照自体は変更することができません。
しかしobjectや配列を宣言した場合、その中身自体の変更可能です。(後述します)

// 再宣言
try {
  const Hamu = 'kuru'
  const Hamu = 'momo'
  console.log(Hamu) // Uncaught SyntaxError: Identifier 'Hamu' has already been declared
} catch(err) {
  console.log(err)
}
// 再代入
try {
  const Hamu = 'kuru';
  Hamu = 'tete';
  console.log(Hamu)
} catch(err) {
  console.log(err) // TypeError: Assignment to constant variable.
}

スコープ

グローバルスコープ

  • プログラム全体で参照可能
  • どのような場合にグローバルスコープになるか
    • 何もつけずに変数を宣言
    • 関数のトップレベルで変数を宣言
scope = 'global' // グローバルスコープの変数scope
function checkScope() {
  scope = 'local1' // 関数スコープにならずにグローバススコープ変数を上書き
  localScope = 'local2' // 関数内ではあるが、グローバルスコープとして宣言された変数localScope
  console.log('functionLocal', scope, localScope)
}
checkScope() // functionLocal, local1, local2
console.log('globalScoep', scope, localScope, window.scope, window.localScope) // globalScoep, local1, local2, local1, local2

関数スコープ

  • 変数が宣言された関数の中だけで参照可能。
  • どのような場合に関数スコープになるか
    • 関数内でvarで変数を宣言
var scope = 'global'
function checkScope() {
  var scope = 'local1' // checkScope関数の内部だけで有効な変数scope
  if (true) {
    var localScope = 'local2'  // checkScope関数の内部からアクセス可能な変数localScope
  }
  console.log('functionLocal', scope, localScope) // functionLocal, local1, local2
}
checkScope() // functionLocal, local1, local2
console.log('globalScoep', scope, window.scope, window.localScope) // globalScoep, global, global, undefined

ブロックスコープ

  • ブロック({}に囲まれた領域)内でのみ参照可能
  • 同じ関数内であっても異なるブロックであった場合アクセス不可
  • どのような場合にブロックスコープになるか
    • ブロック内でlet, constで変数,定数を宣言
function checkScope() {
  var scope = 'local1' // checkScope関数の内部だけで有効な変数scope
  if (true) {
    let localScope = 'local2'  // checkScope関数の内部からアクセス可能な変数localScope
  }
  console.log('functionLocal', scope, localScope) // localScopeはスコープが異なるため参照不可
}
checkScope() // Uncaught ReferenceError: localScope is not defined

constにおけるobject,arrayの扱い

object,arrayについては変数の参照自体の再宣言、再代入はできません。

ですがそれらの中身を変えることは可能です。自分ははじめここでかなりconstに関しては混乱をしました。
まずは一番わかりやすいただのstringとしての定数の宣言をしたいと思います、

// 再宣言
const Idol = 'shimamu-'
const Idol = 'mio' // Uncaught SyntaxError: Identifier 'Idol' has already been declared

// 再代入
const Idol = 'shimamu-'
Idol = 'mio' // Uncaught TypeError: Assignment to constant variable.

constの場合は定数(constant variable)として宣言されるので、再宣言も再代入もできません。
次にオブジェクトを宣言してみます。

// 再宣言
const Idol = {nickname: 'shimamu-'}
const Idol = {nickname: 'mio'} // Uncaught SyntaxError: Identifier 'Idol' has already been declared

// 再代入
const Idol = {nickname: 'shimamu-'}
Idol =  {nickname: 'mio'} // Uncaught TypeError: Assignment to constant variable.

当然同じように再代入も再宣言もできません。
ではオブジェクトそのものではなく、オブジェクトの中身を変更した場合はどうでしょうか

// オブジェクトの要素を変更
const Idol = {nickname: 'shimamu-'}
Idol.nickname = 'mio'
console.log(Idol) // {nickname: 'mio'}

オブジェクトはそのものの再宣言、再代入は許容されませんがオブジェクトの要素に関しては変更、追加、削除が可能です。

// オブジェクトの要素の追加
const Idol = {nickname: 'shimamu-'}
Idol.type = 'cute'
Idol.talent = 'smile'
console.log(Idol) // {nickname: "shimamu-", type: "cute", }
// オブジェクトの要素の削除
const Idol = { nickname: 'shimamu-', type: 'cute',  talent:'smile' }
delete Idol.talent
console.log(Idol) //{nickname: "shimamu-", type: "cute"}

また配列においても同様に変更、削除、追加ができます。

// 変更
const Newgene = ['shimamu-']
const idolAdd = ['mio', 'shiburin']
Newgene.push(...idolAdd)
console.log(Newgene) // ["shimamu-", "mio", "shiburin"]

// 削除
const Newgene = ['shimamu-', 'mio', 'shiburin']
Newgene.shift()
console.log(Newgene) // ["mio", "shiburin"]

// 追加
const Newgene = ['mio', 'shiburin']
Newgene.unshift('shimamu-')
console.log(Newgene) // ["shimamu-", "mio", "shiburin"]

ここでこれまで説明してきた内容を振り返りつつ、とあるアニメのとあるグループの状態遷移を再現してみたいと思います。

// objectの宣言
const Uduki = { nickname: 'shimamu-', type: 'cute', status:'doMyBest'},
      Mio = { nickname: 'chanmio', type: 'passion', status: 'fun'},
      Rin = { nickname: 'shiburin', type: 'cool', status: 'seek'}

// 配列の宣言
const UdukiMioRin = [Uduki, Mio, Rin]

//初期値の宣言
const Newgene = []

// 配列への追加、削除
const changeGroup = (groupName, method, member) => {
  if (method === 'add') {
    Array.isArray(member) ? groupName.push(...member) : groupName.push(member) // 配列への追加
  } else if (method === 'left') {
    const NickName = member.nickname
    const Order = groupName.findIndex(n => n.nickname === NickName)
    groupName.splice(Order, 1) // 配列からの削除
  }
}

// objectの変更
const changeGroupValue = (groupName, type, nickname, key, value) => {
  if (type === 'change') {
    groupName.filter(n => n.nickname === nickname)[0][key] = value
  } else if (type === 'delete') {
    delete groupName.filter(n => n.nickname === nickname)[0][key]
  }
}

// 1話
changeGroup(Newgene, 'add', UdukiMioRin)
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "chanmio", type: "passion", status: "fun"}
2: {nickname: "shiburin", type: "cool", status: "seek"}
*/

// 6話
changeGroup(Newgene, 'left', Mio)
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
 */

// 7話
changeGroup(Newgene, 'add', Mio)
changeGroupValue(Newgene, 'change', 'chanmio', 'status', 'motivated')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
2: {nickname: "chanmio", type: "passion", status: "motivated"}
*/

// 21話
changeGroupValue(Newgene, 'change', 'chanmio', 'talent', 'acting')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
 */

// 22話
changeGroupValue(Newgene, 'change', 'shiburin', 'talent', 'song')
changeGroupValue(Newgene, 'change', 'shiburin', 'status', 'fun')
changeGroupValue(Newgene, 'delete', 'shimamu-', 'talent')
changeGroupValue(Newgene, 'change', 'shimamu-', 'status', 'dark')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "dark"}
1: {nickname: "shiburin", type: "cool", status: "fun", talent: "song"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
*/

// 25話
changeGroupValue(Newgene, 'change', 'shimamu-', 'talent', 'smile')
changeGroupValue(Newgene, 'change', 'shimamu-', 'status', 'joy')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "joy", talent: "smile"}
1: {nickname: "shiburin", type: "cool", status: "fun", talent: "song"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
*/


なぜconstを使うのか

スコープを狭める

letも同じですが、スコープを狭くしておくことで変数の影響範囲がわかりやすくなり、開発がしやすくなります。

状態の把握が容易

letの場合は一度宣言しても値の再代入は可能です。そのため宣言後にも状態が変わります。
constの場合は上書きされることがないことが保証されているので現在の状態の推測が容易です。

まとめ

最後まで読んでくださってありがとうございます。

  • これからJavaScriptを書く場合varは使用しない
  • 再代入が必要な場面以外はconstを使用する

ちなみに自分の好きなものを変数にすると記事を書くのが捗りますね。皆さんも良いお盆休みを

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 正規表現(RegExp:Regular Expression)メモ

JavaScriptで利用できる主な正規表現パターン

パターン マッチする文字列
ABC 「ABC」という文字列
[ABC] A,B,Cのいずれか1文字
[^ABC] A,B,C以外のいずれか1文字
[A-Z] A〜Zの間の1文字
A|B|C A,B,Cのいずれか
X* 0文字以上のX
X? 0、または1文字のX
X+ 1文字以上のX
X{n} Xとn回一致
X{n,} Xとn回以上一致
X{m,n} Xとm〜n回一致
^ 行の先頭に一致
$ 行の末尾に一致
. 任意の1文字に一致
\w 大文字/小文字の英字、数字、アンダースコアに一致(=[A-Za-z0-9_])
\W 文字以外に一致(=[^\w])
\d 数字に一致(=[0-9])
\D 数字以外に一致(=[^0-9])
\n 改行(ラインフィード)に一致
\r 復帰(キャリッジリターン)に一致
\t タブ文字に一致
\s 空白文字に一致(=[\n\r\t\v\f])
\S 空白以外に一致(=[^\s])
\〜 「〜」で表される文字

オブジェクトの生成

var 変数名 = new RegExp('正規表現', 'オプション'); //コンストラクター

var 変数名 = /正規表現/オプション; // リテラル

正規表現のオプション

オプション 概要
g 文字列全体にマッチするか(無指定の場合、1度マッチした時点で処理を終了)
i 大文字/小文字を区別するか
m 複数行に対応するか(改行コードを行頭/行末と認識)
u Unicode対応(ES2015)

エスケープ

コンストラクター構文では「 \ 」、リテラル構文では「 / 」をエスケープする必要がある。

正規表現による検索・検証・置換・分割

String.match

String.matchメソッドはマッチした文字列を配列として取得する。
gオプションを無効にした場合、「最初にマッチした文字列全体」「サブマッチ文字列」を取得する。
サブマッチ文字列:正規表現パターンの中で丸カッコで示された部分(サブマッチパターン)に合致した部分文字列。マッチしている部分がない場合はundefined、複数マッチしている場合には最後にマッチしたものを保持する。)

str.match(pattern)
  // str:検索対象の文字列
  // pattern:正規表現

RegExp.exec

RegExp.execメソッドはgオプションの有無にかかわらず、1度の実行で1つの実行結果しか返さず、マッチした文字列全体とサブマッチ文字列を配列として返す。
execメソッド(RegExpオブジェクト)「最後にマッチした文字位置を記憶する」ため、次にexecメソッドを実行する場合、RegExpオブジェクトは「前回のマッチ位置から検索を再開」する。検索結果がない場合はnullを返す。

regexp.exec(str)
  // regexp:正規表現
  // str:検索対象の文字列

// 全てのマッチング結果を得る場合
while ((result = regexp.exec(str)) !== null){
  // 繰り返し処理
}

RegExp.test

RegExp.testメソッドを使うことで、単に正規表現にマッチしているかどうかを真偽値で返すことができる。

regexp.test(str)
  // regexp:正規表現
  // str:検索対象の文字列

String.search

String.searchメソッドは、指定した正規表現で最初にマッチした文字位置を返す。マッチした文字列が存在しない場合は戻り値として-1を返す。

str.search(pattern)
  // str:検索対象の文字列
  // pattern:正規表現

String.replace

String.replaceメソッドは、正規表現でマッチした文字列を置換することができる。
引数のrepには「\$1...$9」の特殊変数を埋め込むことができる。
特殊変数はサブマッチ文字列を保存するための変数。

str.replace(pattern, rep)
  // str:置き換え対象の文字列
  // pattern:正規表現
  // rep:置き換え後の文字列

String.split

String.splitメソッドは、正規表現で文字列を分割することができる。

str.split(sep[, limit])
  // str:分割対象の文字列
  // sep:区切り文字(正規表現)
  // limit:分割回数の上限

出典

[改訂新版]JavaScript本格入門 山田祥寛著

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【5分でデプロイまで】Gatsbyで爆速ブログを作成

概要

Gatsbyのスターターと、Netlifyを使用して誰でも5分で爆速ブログを作成する方法を紹介します。

記事内ではこちらのブログをデプロイまで行います。

Gatsbyとは?

Gatsbyに関してはこの方の記事でとても良く説明されています。

Gatsbyのインストール

Gatsbyがインストールされていない場合は以下のコマンドでインストールしましょう。

$ npm install -g gatsby-cli

ブログの作成

以下のコマンドでブログを作成します。

  • my-blogの部分でブログの名前を指定します。
  • https://github.com/gatsbyjs/gatsby-starter-blogの箇所は使用するスターターを指定します。

こちらから好きなデザインを選ぶ事が可能です。

$ gatsby new my-blog https://github.com/gatsbyjs/gatsby-starter-blog

ローカルで確認

作成したブログのディレクトリに移動し、ローカルでブログを確認しましょう。

以下のコマンドを実行すると、http://localhost:8000/でブログが起動している事が確認できます。

$ cd my-new-blog
$ gatsby develop

新しい記事の追加

content/blogに新しいマークダウンファイル(.md)を追加してみましょう。
すると、自動でブログに記事が更新されている事が確認できます。

---
title: テスト記事
---

今日はいい天気だな。

ブログをGitHubにプッシュする。

Netlifyを使用したデプロイ方法ではデプロイしたいサイトがGitHub/GitLab/Bitbucketで管理されている必要があります。
本記事ではGitHubを使用します。

git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourname/your-blog-name.git
git push -u origin master

ブログをデプロイする

Netlifyのアカウントがない場合は作成しましょう。
アカウント作成後、以下のNew site from Gitをクリックします。

image.png

今回はGitHubからのデプロイなので、GitHubを選択します。
image.png

デプロイするレポジトリで作成したブログのレポジトリを選択します。

image.png

次の画面でDeploy siteをクリックする事でデプロイが始まります。

image.png

デプロイができたら、サイトを確認してみましょう。
ここでは https://elastic-montalcini-5361d7.netlify.comがサイトのURLになります。

image.png

URLの変更

URLが気にくわない場合は Site settings をクリックし、Change Site namenetlify.com以前のURLを変更する事が出来ます。
また、カスタムドメインを使用することも出来ます。

デプロイ後のブログの更新

Neflifyではmasterブランンチにコミットやマージがあると自動的にデプロイが走り、ブログが更新されます。
ですので、記事を追加したり、デザインを変更した後に、GitHubでmasterブランチにその変更をプッシュするだけでサイトの更新が可能です。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptにおける関数

関数とは一連の処理をまとめたもの。

引数は関数に対して渡すパラメーターでそのパラメーターを元に関数内で処理を行い、戻り値を返す。この戻り値はreturnを使って戻す。returnのあとは処理が行われず、returnがないとundefined(未定義値)を返す。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】ビルドとは何か〜webpackを使ってビルドする

この辺が曖昧だったので、書き残しておきます。

  • ビルドとは何か
  • なぜビルドが必要か
  • ビルドでは何を行なっているのか
  • ビルドしたらどうなるのか

Node.jsの特徴

  • JavaScriptをサーバーサイドで動かすための実行環境。
  • モジュール機能(他ファイルの読み込み)
  • ノンブロッキングI/Oで処理を効率化

参考記事

ノンブロッキングI/O (non-blocking I/O)
ノンブロッキングI/Oと非同期I/Oの違いを理解する
Node.js を5分で大雑把に理解する
Node.jsについて調べてみた
JavaScriptが辿った変遷
npm とか bower とか一体何なんだよ!Javascript 界隈の文脈を理解しよう

ビルドツール

Node.jsスタイルのコードは、そのままの状態ではブラウザで動きません。ソースコードを元にして、実際に動くプログラム(実行可能ファイル)を作ってあげる必要があります。
その作成処理のことをビルドと言います。

ビルドを実行するためには、ビルドツールを使用します。

概要

以下、10年遅れたJavaScriptの知識をざっくり10分でアップデートしようより引用。

最近のWebアプリケーションは、開発時のコードと実際の製品に使われるコードが異なります。開発者はブラウザーにサポートされていない可能性がある最新バージョンのJavaScriptでコードを書いたり、node_modulesフォルダー内のサードパーティ製パッケージとその依存オブジェクトを多用したり、静的解析ツールや圧縮ツール(Minifier)などを使ったりして開発します。これらをすべて変換、効果的にデプロイして、大半のWebブラウザーが理解できる形にするのがビルドツールです。

何を行うのか

以下、キャッチアップJavaScriptビルドより引用。

  • モジュール管理(依存性解決)
  • テンプレート管理
  • UT/操作テスト
  • 構文チェック
  • 圧縮/難読化
  • CSSプリプロセッサー
  • ES2015コンパイル
  • altJSコンパイル

簡単に補足します。

  • CSSプリプロセッサーとは、独自の構文でCSSを生成するプログラムのこと。コンパイルにより、純粋なCSSより可読性や保守性が高くなります。
  • ES2015(ES6)はJavaScriptの新基準ですが、古いブラウザでは対応していない場合があります。そのため、コンパイルで互換性のあるES5に変換します。(トランスパイル)
  • altJSとは、コンパイルによりJavaScriptに変換されるプログラミング言語のこと(代替JavaScript)。従来のJavaScriptより可読性と保守性が高く、コーディングの負担を減らします。

テンプレート管理が何を意味しているのか、よくわからなかったです。
文字列の組み込みのことでしょうか・・・?

タスクランナーとは

ビルドツールで行われている処理(タスク)を自動化してくれるツールのこと。
grupやGruntが挙げられます。

ネットで調べた限り、ビルドツールとタスクランナーの区分は曖昧でした。
webpackの立ち位置が微妙なところです。

npmとは

Node.jsのパッケージ管理ツール。
Node.jsと同時にインストールされます。

各種ビルドツールは、npmを使ってインストールすることができます。
今回は、webpackを使ってみます。

参考記事

CSS プリプロセッサー
CSSプリプロセッサ(CSSメタ言語)のお勉強
最近のビルドツールって何なの?
タスクランナー(Gulp)の入門の入門
【保存版】gulp, webpack, parcel...増え続けるタスクランナーの特徴を理解して最適なものを選択しよう
JavaScriptをやっているとnpm/yarn/gulp/grunt/webpackなど、たくさんのツールがあって混乱したので、それぞれの役割と違いをざっくりとまとめた
プログラミングが便利に!2018年に取り入れたいオススメのタスクランナー5選
Webpackってどんなもの?

実演

初期化

テスト用ディレクトリを作成し、移動します。

$ mkdir js_test
$ cd js_test

初期化します。
いくつか質問がありますが、全てEnterで大丈夫です。

$ npm init

初期化後、ディレクトリ内にpackage.jsonが生成されます。

webpackのインストール

webpackをローカルインストールします。

$ npm install webpack --save-dev

ディレクトリ直下にpackage-lock.jsonnode_modulesディレクトリが生成されます。

webpackのバージョンを確認します。

$ npx webpack -v
4.39.1

このバージョンでは、webpack-cliというライブラリも必要になります。
webpackと同じく、ローカルインストールします。

$ npm install --save-dev webpack-cli

インストール後、package.jsonのdevDependenciesに追記されました。

package.json
{
  "name": "js_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.39.1",
    "webpack-cli": "^3.3.6"
  }
}

webpackの設定

ビルドするためには、元のソースコードの場所とビルド後に生成される実行ファイル(bundleファイル)についての情報が必要です。
元のソースコードで一番最初に読み込まれるファイルは、エントリ-ポイントと呼ばれます。

ディレクトリ直下に設定ファイルwebpack.config.jsを作成します。

$ touch webpack.config.js

次のように設定します。

webpack.config.js
const path = require('path');

module.exports = {
    // エントリーポイントの設定
    entry: './src/my-main.js',
    // ビルド後、'./dist/my-bundle.js'というbundleファイルを生成する
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'my-bundle.js'
    }
};

テストファイルの作成

テストファイルを作成します。
文字列を返すメソッドを記述し、それをモジュールとして別ファイルで読み込んで出力します。

$ touch my-index.html
$ mkdir src
$ mkdir dist
my-index.html
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="dist/my-bundle.js" charset="utf-8"></script>
</body>
</html>
$ cd src
$ touch my-main.js
$ touch my-func.js
my-main.js
var func = require('./my-func');

var res = document.createTextNode(func());
document.body.appendChild(res);
my-func.js
module.exports = function () {
    return 'my-test';
};

エイリアスコマンドの設定

webpackコマンドを簡単に実行するために、エイリアスコマンドを設定します。
package.jsonのscriptに以下を記述します。

package.json
"build": "webpack --mode development"

もしくは

package.json
"build": "webpack --mode production"

modeにより、生成されるbundleファイルの内容が異なります。(動作は同じです。)
modeを指定しない場合は、ビルド実行時に警告が発生します。

この設定によりnpm run build でビルドを実行する事が可能になりました。

ビルド実行

エイリアスコマンドを設定したので、早速実行してみます。

$ npm run build

実行後、bundleファイルであるdist/my-bundle.jsが生成されます。
ブラウザで出力した文字列を確認できれば、ビルド成功です。

ビルドせずに動作を確認したい

ビルドせずに動作を確認したい場合は、watchオプションを利用します。
package.jsonのscriptに以下を記述します。

package.json
"watch": "webpack --watch"

npm run watchを実行すると、コードの変更が検知されるようになります。そのため、(画面をリロードすれば)ビルドせずに動作を確認する事ができます。

デベロッパーツール上で元のファイルを確認したい

デベロッパーツールのSourcesタブでは、ビルド元のファイルを確認する事ができません。

それらを表示させたい場合は、webpack.config.jsに以下を記述します。

webpack.config.js
devtool: 'inline-source-map'

これで、デベロッパーツール上でmy-main.jsmy-main.jsの内容を確認する事ができます。

参考記事

package.jsonの中身を理解する
package-lock.jsonについて知りたくても聞けなかったこと
勉強メモ/npmの使い方(node.js=v0.11.16, npm=2.3.0, 2015-03時点)
npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう
webpack.config.jsがわからない
npmとwebpack4でビルド - jQueryからの次のステップ
step by stepで始めるwebpack
新人にドヤ顔で説明できるか、今風フロントエンド開発ハンズオン(Git/Node.js/ES6/webpack4/Babel7)
React.jsでビルドされる前のコードをブラウザで確認する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"Writing An Interpreter In Go " を JavaScriptで実装してみる その1 Lexer

なんとなく 個人的お盆休みの課題として
Writing An Interpreter In Goという本を読み始めた。

内容としてはMonkeyという言語のインタプリタをGo言語で実装するという内容である
ページ数としては200ページほどで、軽すぎず、重すぎず丁度いい内容。

Lexer・Parser・Evaluaterの流れですすんでいく。
ただ写経するだけではつまらないのでJavaScriptで実装し直してみている。
npmモジュールにMonkeyの日本語訳である"saru"というレポジトリで公開し始めた

[npm]
https://www.npmjs.com/package/saru

[code]
https://github.com/freddiefujiwara/saru

インストール方法はこちら

npm i -g saru

使い方はこちら

  $ saru                                                                                                                                 
Hello fumikazu! This is the Monkey programming language! 
Feel free to type in commands          
* Please type 'bye' if you want to exit 
>>>                              

サンプル

>>> let me know             
Token { type: 'LET', literal: 'let' }
Token { type: 'IDENT', literal: 'me' }
Token { type: 'IDENT', literal: 'know' }
>>> let str = "string";        
Token { type: 'LET', literal: 'let' }
Token { type: 'IDENT', literal: 'str' }
Token { type: '=', literal: '=' }
Token { type: 'STRING', literal: 'string' }
Token { type: ';', literal: ';' }
>>> let i = 10;                 
Token { type: 'LET', literal: 'let' }
Token { type: 'IDENT', literal: 'i' }
Token { type: '=', literal: '=' }
Token { type: 'INT', literal: '10' }
Token { type: ';', literal: ';' }
>>> !@                         
Token { type: '!', literal: '!' }
Token { type: 'ILLEGAL', literal: '@' }

また、始めて jestを使ってみた。
まだまだLexer部分しか作ってないが今後Parser部分やEvaluator部分も作ったら
Qiitaに投稿しようと思う

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript の 変数 まとめ ~console.log(変数名);~

目的

  • JavaScriptの変数のふるまいを知る。

押さえるポイント

  • 変数を定義してから値や文字列を格納する。
  • 同一ステップで変数定義と値や文字列の格納を行う。
  • 変数名は英数字のみ使用すること。
  • 2語以上の英単語から成る変数名のとき、2語目の頭文字を大文字にする。(user nameと変数名を命名したいときの変数名はuserNameとなる)
  • 数字で始まるものは変数名にできない

書き方の例

  • 下記に汎用的な変数の定義と格納方法を記したJavaScriptファイルの内容を記載する。
let 変数名 = 数値;
let 変数名 = "変数に格納する文字列";
  • 下記に変数に格納された文字列をコンソールに出力する処理を記したJavaScriptファイルの内容を記載する。
console.log(変数名);
# console.log("変数名");としないこと。変数に格納された値や文字列ではなく、変数名がコンソールに出力されてしまう。

より具体的な例

  • 「name」という名前の変数を定義して、文字列「miriwo」を格納する。
  • 下記に変数定義と変数への格納方法を記したJavaScriptファイルの内容を記載する。
let name = "miriwo";
  • 定義された「name」変数に格納された文字列をコンソールに出力する。
  • 下記に変数に格納された文字列をコンソールに出力する処理を記したJavaScriptファイルの内容を記載する。
console.loc(name);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Wasm By ExampleをAssemblyScriptで試してみた

参考:Wasm By Example

こちらのWebAssemblyのサンプルがとてもわかりやすかったので、実際に自分で動かしてみました。
ただ、参考サイトに書いてあることをやっていくだけなので、参考サイトの方をいていただければ私の投稿は見なくても大丈夫です。:bow:

サンプルはRustとAssemblyScriptのものがあったのですが、私はAssemblyScriptでやってみました。


私が実際にやってみたコードはこちらです

https://github.com/okumurakengo/wasm-sample

AssemblyScriptをインストール

yarn add -D AssemblyScript/assemblyscript

Hello World!

https://wasmbyexample.dev/examples/hello-world/hello-world.assemblyscript.en-us.html

hello-world.tsを作成する。※TypeScriptではなく、AssemblyScriptで書きます。

hello-world.ts
export function add(a: i32, b: i32): i32 {
  return a + b;
}

AssemblyScriptのコンパイラを使って、wasmのモジュールを作成します。

yarn asc hello-world.ts -b hello-world.wasm

これでhello-world.wasmが作成されます。


wasmモジュールをロードするための関数を追加します。参考サイトにもあるようにこちらのをそのまま使用させていただきました。

https://github.com/torch2424/wasm-by-example/blob/master/demo-util/instantiateWasm.js

instantiateWasm.js
export const wasmBrowserInstantiate = async (wasmModuleUrl, importObject) => {
    let response = undefined;

    if (!importObject) {
        importObject = {
            env: {
                abort: () => console.log("Abort!")
            }
        };
    }

    if (WebAssembly.instantiateStreaming) {
        response = await WebAssembly.instantiateStreaming(
            fetch(wasmModuleUrl),
            importObject
        );
    } else {
        const fetchAndInstantiateTask = async () => {
            const wasmArrayBuffer = await fetch(wasmModuleUrl).then(response =>
                response.arrayBuffer()
            );
            return WebAssembly.instantiate(wasmArrayBuffer, importObject);
        };
        response = await fetchAndInstantiateTask();
    }

    return response;
};

hello-world.jsを作成して、hello-world.wasmを読み込み、add関数を実行するようにします。

hello-world.js
import { wasmBrowserInstantiate } from "./instantiateWasm.js";

(async () => {
  const wasmModule = await wasmBrowserInstantiate("./hello-world.wasm");

  const addResult = wasmModule.instance.exports.add(24, 24);

  document.body.textContent = `Hello World! addResult: ${addResult}`;
})();

hello-world.htmlを作成

hello-world.html
<!DOCTYPE html>
<meta charset=utf-8>
<title>sample</title>
<script type="module" src=hello-world.js></script>

なんでもいいのでサーバーを起動し、動きを確認してみます。今回はnode-staticでサーバーを起動しました。

#Example:nodeで簡易サーバ
npx node-static -p 8000

Screen Shot 2019-08-11 at 2.49.59.png

AssemblyScriptのadd関数を実行できたのを確認できました。

Exports

https://wasmbyexample.dev/examples/exports/exports.assemblyscript.en-us.html

exportした関数と定数で使い方が若干違ったので確認してみます。

exports.ts
export function callMeFromJavascript(a: i32, b: i32): i32 {
    return addIntegerWithConstant(a, b);
}

export const GET_THIS_CONSTANT_FROM_JAVASCRIPT: i32 = 2424;

function addIntegerWithConstant(a: i32, b: i32): i32 {
    return a + b + ADD_CONSTANT;
}

const ADD_CONSTANT: i32 = 1;

見ての通り、callMeFromJavascriptGET_THIS_CONSTANT_FROM_JAVASCRIPTをexportしています。

arn asc exports.ts -b exports.wasm
exports.js
import { wasmBrowserInstantiate } from "./instantiateWasm.js";

(async () => {
    const wasmModule = await wasmBrowserInstantiate("./exports.wasm");

    // instance.exports にエクスポートされた関数などが設定されるsetteisareru
    // 参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming#Return_value
    const exports = wasmModule.instance.exports;

    // エクスポートしたcallMeFromJavascriptを実行
    console.log(exports.callMeFromJavascript(24, 24)); // 49

    // 定数の場合は `.valueOf()` を使う
    // ※場合によっては単純に exports.GET_THIS_CONSTANT_FROM_JAVASCRIPT になると注意書きあり
    console.log(exports.GET_THIS_CONSTANT_FROM_JAVASCRIPT.valueOf()); // 2424

    // exportしていない関数にはアクセスできない
    console.log(exports.addIntegerWithConstant); // undefined
})();
exports.html
<!DOCTYPE html>
<meta charset=utf-8>
<title>sample</title>
<script type="module" src=exports.js></script>

Screen Shot 2019-08-11 at 3.22.24.png

WebAssembly Linear Memory

https://wasmbyexample.dev/examples/webassembly-linear-memory/webassembly-linear-memory.assemblyscript.en-us.html

リニアメモリを使うと、wasmとjsの両方から読み書きができるバッファとして扱えるようです。
あまり詳しくないので、参考サイトのままですが動かすことはできました。

memory.ts
// Set up our memory
// By growing our Wasm Memory by 1 page (64KB)
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory#Examples
memory.grow(1);

// インデックスの0番目に24を設定
const index = 0;
const value = 24;
store<u8>(index, value);

// インデックスの1番目を取得する関数
// ※今回、jsファイルの方からインデックスの1番目を設定するコードを書きます
export function readWasmMemoryAndReturnIndexOne(): i32 {
    let valueAtIndexOne = load<u8>(1);
    return valueAtIndexOne;
}
yarn asc memory.ts -b memory.wasm
memory.js
import { wasmBrowserInstantiate } from "./instantiateWasm.js";

(async () => {
    const wasmModule = await wasmBrowserInstantiate("memory.wasm");

    const exports = wasmModule.instance.exports;

    // memoryオブジェクトを取得
    const memory = exports.memory;

    const wasmByteMemoryArray = new Uint8Array(memory.buffer);

    // wasmで設定したインデックスの0番目の値を取得できる
    console.log(wasmByteMemoryArray[0]); // 24

    // インデックスの1番目に25と設定
    wasmByteMemoryArray[1] = 25;
    // インデックスの1番目を取得する関数を実行
    console.log(exports.readWasmMemoryAndReturnIndexOne()); // 25
})();
memory.html
<!DOCTYPE html>
<meta charset=utf-8>
<title>sample</title>
<script type="module" src=memory.js></script>

Screen Shot 2019-08-11 at 3.46.16.png

Importing Javascript Functions Into WebAssembly

https://wasmbyexample.dev/examples/importing-javascript-functions-into-webassembly/importing-javascript-functions-into-webassembly.assemblyscript.en-us.html

jsで設定した関数をwasmで実行することができます。

index.ts
// 関数がないとコンパイルエラーなので declare で設定
declare function consoleLog(arg0: i32): void;

// .jsファイルで設定した consoleLog を実行
consoleLog(24);
yarn asc index.ts -b index.wasm
index.js
import { wasmBrowserInstantiate } from "./instantiateWasm.js";

(async () => {
    const wasmModule = await wasmBrowserInstantiate("index.wasm", {
        index: {
            consoleLog: value => console.log(value)
        },
        env: {
            abort: () => console.log("Abort!")
        },
    });
})();
index.html
<!DOCTYPE html>
<meta charset=utf-8>
<title>sample</title>
<script type="module" src=index.js></script>

Screen Shot 2019-08-11 at 4.14.59.png

jsで設定した関数をwasmで実行することができました。


最後まで読んでいただいてありがとうございました。m(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Electron】40万件のレコードを鬼のような速度で描画するクライアントアプリケーションの開発

背景

先日、【WPF】仮想化キャンバスを使用したDataGridの描画高速化という記事を投稿しました。
そこで仮想キャンバスによるデータ描画の高速化を行っていたのですが・・。
WPFの描画(主にスクロールパフォーマンス)めっちゃ遅い。

そもそもWPFは柔軟性が高い分、大量データの描画は不得手だったのですが、
許容できないレベルで遅延が発生していたので困り果てていました・・。
そんな時に何気なく「Clusterize.js」という大量データ描画JSを発見

このClusterize.js、描画が鬼のように速い。50万件のレコードもサクサク描画できる。
「あれ?このJSをASP.Net Codeで作成したページに埋め込んでElectronで動かしたら超高速じゃない?」
というわけでやってみました。

まずElectronの導入部分を、Electronの環境構築(for Windows) - Qiita を参考に実施、
その後Clusterize.jsの動作確認まで行った自分用忘備録です。

NodeJS - インストール

Nodejs.org - Downloads にアクセスして下記リンクからインストーラをダウンロード。
image.png

ダブルクリックで実行したら以下の画面が表示される。
しばらくボタンがDisabledだけど、少し待つと次のステップに進みます。
image.png

特に設定を変更する必要はないので、インストーラの誘導通り進めていってください。

image.png

上記画面が表示されたらNodeJSのインストール完了。
念のために完了後は以下のコマンドでバージョン確認を行いましょう。

C:\>node -v
v10.16.0

プロジェクトの作成

てきとうなフォルダを作成します。

image.png

そのフォルダ内でGit Bashを開きます。ないならインストールしてね。
image.png

開いたら npm init でプロジェクトを初期化。
基本的にエンター連打で問題なし。
Is this OK? (yes) の確認が出たら yes を打ち込んで完了。

user_name@machine_name MINGW64 ~/source/repos/magicbullet
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (magicbullet)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\Users\user_name\source\repos\magicbullet\package.json:

{
  "name": "magicbullet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) yes

最終イメージ。
ll コマンドでpackage.json ができていることを確認してね。

user_name@machine_name MINGW64 ~/source/repos/magicbullet
$ ll
total 1
-rw-r--r-- 1 owner 1049089 207  8月  9 15:01 package.json
package.json
{
  "name": "magicbullet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Electron - インストール

続いてElectronをインストールします。以下のコマンドを投入。

npm install electron -g

npm install --save-dev electron

バージョン確認を行ってインストールできていることを確認。

user_name@machine_name MINGW64 ~/source/repos/magicbullet
$ electron -v

v6.0.1

エントリーポイントの作成

package.jsonと同じパスに以下の二つのファイルを配置する。

magicbullet/index.js
"use strct";

// Electronのモジュール
const electron = require("electron");

// アプリケーションをコントロールするモジュール
const app = electron.app;

// ウィンドウを作成するモジュール
const BrowserWindow = electron.BrowserWindow;

// メインウィンドウはGCされないようにグローバル宣言
let mainWindow = null;

// 全てのウィンドウが閉じたら終了
app.on("window-all-closed", () => {
  if (process.platform != "darwin") {
    app.quit();
  }
});


// Electronの初期化完了後に実行
app.on("ready", () => {
  //ウィンドウサイズを1280*720(フレームサイズを含まない)に設定する
  mainWindow = new BrowserWindow({width: 1280, height: 720, useContentSize: true});
  //使用するhtmlファイルを指定する
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // ウィンドウが閉じられたらアプリも終了
  mainWindow.on("closed", () => {
    mainWindow = null;
  });
});
index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <p>Hello World</p>
</body>
</html>

完成イメージ。
image.png

Electron - コンパイル

以下のコマンドを投入します。

user_name@machine_name MINGW64 ~/source/repos/magicbullet
$ electron .

image.png

動いた!うれしい!!

Clusterize.js の導入

今回はClusterize.jsのデモページをElectronで試してみます。
まずはgitからデモページのソースを取得。

### 任意のディレクトリで実行
user_name@machine_name MINGW64 ~/source/repos
$ git clone https://github.com/NeXTs/Clusterize.js
Cloning into 'Clusterize.js'...
remote: Enumerating objects: 776, done.
remote: Total 776 (delta 0), reused 0 (delta 0), pack-reused 776
Receiving objects: 100% (776/776), 1.89 MiB | 2.01 MiB/s, done.
Resolving deltas: 100% (416/416), done.

user_name@machine_name MINGW64 ~/source/repos
$ cd Clusterize.js/

user_name@machine_name MINGW64 ~/source/repos
$ git checkout gh-pages
Switched to a new branch 'gh-pages'
Branch 'gh-pages' set up to track remote branch 'gh-pages' from 'origin'.

チェックアウトしたコードをElectron用ディレクトリにコピーします。

こんな感じになります。
image.png

Electronを起動。

user_name@machine_name MINGW64 ~/source/repos/magicbullet
$ electron .

image.png

40万件のデータもサクサクスクロールできる!
いまクライアントアプリケーションでこれだけのデータをストレスなく描画するにはベストな選択なのではないでしょうか?(わかんない)

以降は、実際のシステムに組み込めるか試していきたいと思います。

追記:

えっ!?Electron.Netってものがある!?

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript APIについて メモ

APIとはApplication Programming Interface(アプリケーション・プログラミング・インターフェイス)」の略です。

例えば、ある地域の天気の情報をプログラムで取得する方法を考えてみましょう。すべて自分で行おうとしたら、膨大な手間がかかることが想像できます。

その地域に気象センサーを設置
得られた情報をデータ格納用のコンピュータに蓄積
雨量や湿度から、晴れや曇りといった天気を判断
プログラムからデータ格納用のコンピュータにアクセスし、該当する地域の天気の情報を取得
しかし本来行いたいことは「ある地域の天気の情報を取得する」ことです。そこでデータの準備を切り離し、条件を指定するだけで簡単に利用できるようにしたのがAPIです。APIを使うと以下のようになります。

プログラムから天気の情報を提供するAPIに地域を指定してアクセスし、該当する地域の天気の情報を取得する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript A-Frameとは

A-Frame

VR体験を構築することができるウェブフレームワークである。HTMLで簡単に作ることができる。

<html>
  <head>
    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enebularでTwitterAPI連携してNoodlに表示する

ふわもこ部とは

突然ですが、ハッシュタグ「#ふわもこ部」をご存知でしょうか?
猫や犬、鳥、フワフワモコモコした動物たちの写真や動画に付けられる動物好きにはおなじみのハッシュタグです。
ハリネズミも腹部はフワモコなので、このハッシュタグが付けられることがあります。
癒やされるので#ふわもこ部、ぜひチェックしてみてください。

#ふわもこ部ツイートがされたらその画像を表示したい!

今日作るのはコレ!
誰かが#ふわもこ部 ツイートを投稿したら、ツイートに添付されてる画像を表示するデジタルフォトフレーム!

enebularを核としてTwitter API・Noodlをつなぎます。

【enebular】Twitter APIをつなぐ!

スクリーンショット 2019-08-10 22.28.37.png

フローはこんな感じです。バックエンドのコード書かないでTwitter APIが使えるだなんて最高!

twitterノード

検索条件: #ふわもこ部
出力: tweet

debutノードの設定「対象」は常に「オブジェクト全体」に設定すると良い!

functionノード

Twitter APIから受け取ったオブジェクトの中から、ツイートの文章と画像のURLだけを抜き出して返す関数です。
画像はついている場合とついていない場合があるので、
2行目でTwitterオブジェクトに「media」キー(画像URLのプロパティが入っている場所)が含まれているか否かを判定しています。
画像が含まれている場合のみ、メッセージを返します。

var media = "";
if ("media" in msg.tweet.entities) {
    media = msg.tweet.entities.media[0].media_url;

    msg.payload = {tweet: msg.payload,
    media: media
    };

    return msg;
}

すごく大事なことなのですが、NoodlとMQTT通信するときは、
ネスト(入れ子)になっていないJsonまたはJSオブジェクトで送りましょう!
(ネストされてても受け取れるけど、Noodl側でJavascriptノードを使ってあれやこれやする必要がある。)

  • tweet: msg.payloadはツイートの文章
  • media: media は画像のURL

mqttノード(出力)

enebular-Noodl間はMQTTでやり取りをします。
ブローカーはshiftr.ioを使っています。

トピック: firstMessage(任意のものでOKです。あとでNoodlでも使用します。)

画像ではJsonノードが入っていますが、別に必要ありませんでした。
functionノードから直接mqttノードでOKです。

【Noodl】フォトフレームを作る

enebularからデータの受信

スクリーンショット 2019-08-10 19.30.37.png
Receive Messageノードを使います。
- Topic: /firstMessage (enebularのmqttノードのトピックと同じ設定に)
- Payload: 「tweet」「media」の2つを追加。(enebularのfunctionノードで指定したキーと合わせてください。functionノードの5・6行目参照)

設定ができたら、図のようにReceive MessageのPayload tweetはtextノードに、mediaはimageノードのImage Pathにつなぎます。

うまく表示されていれば成功です!

ディゾルブ表現を作る

やっぱりフォトフレームといえばディゾルブ表現ですよね。
ジワッと変わるやつ。
dissolve.gif

2枚の写真を重ね、#ふわもこ部への新規ツイートをトリガーにそれぞれ写真の透明度を変えることで表現します。
使うノードは図の通り。
スクリーンショット 2019-08-10 23.12.24.png

  • Counterノード: enebularからツイッター情報を受け取るたびに+1カウントします。
  • Expression: count%2 == 0 カウンターが2の倍数のときにtrueを返す
  • Inverter: Expressionノードと逆のリザルトを出力する(=2の倍数じゃない時にtrueを返す)
  • States: 2枚の画像の不透明度を制御します。図のようにプロパティを入力、つないでください。
  • Model: 届いたURLを保持する
    • pic1 idをpic1、Properties「img」を追加。counterが2の倍数のときにデータを保存する。
    • pic2 idをpic2、Properties「img」を追加counterが2の倍数でないときにデータを保存する。

さらにディティールをつめてく

ここまでで、ジワッと画像を変更することはできるのですが、画像が変わる時に白っぽくなるのが気になるので細かい調整をしていきます。
2枚の写真と、その上にスタックされているRectangleノードに注目。
スクリーンショット 2019-08-10 23.29.24.png

  • Rectangle: Colorを#000000に設定
  • Image: Blend ModeをAddictiveに設定(2つとも設定してください)

これで理想のディゾルブ表現ができました!
Addictiveは加算。この辺の設定はPhotoshopっぽいですね。

本編とあまり関係ない話

enebularのスペルを頻繁に打ち間違えてしまう!!!
ちなみにミスりポイントは以下の通り!辞書に単語登録するか・・・
IMG_1811.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む