20200517のJavaScriptに関する記事は30件です。

【悪用厳禁】Pairsでツールを使わず無料で自動足跡化をする

経緯

ぼく「足跡自動化よりデートにお金使った方がよくね??」

これだけです。お疲れ様でした。

「デート以前の問題なのだが:triumph:
という方は自分磨きに使うでもいいかと思います。

運営にお金は落とすのはもちろんですが
アプリを使う1番の目的は異性と出会って素敵なゴールを迎えることではないでしょうか?

必要なもの

1.Pairsのアカウント
2.Google Chrome

使い方

1.Google ChromeでPairsにログインします
2.デベロッパーツールからConsoleを開く
3.⬇️のコードを入力
4.おわり

コード

function footLoop(max, i) {
    if (i <= max) {
        window.location.href = "https://pairs.lv/#/search/one/" + i;
        setTimeout(function () { footLoop(max, ++i) }, 2000);
    }
}
footLoop(1000, 1);

仕組み

footLoop(max, i)

  • あなたの検索条件から、i番目の相手のプロフィール画面を表示します。
  • iがmax未満であれば、2秒後にfootLoop(max, i+1)を再起呼び出しします。

ご利用は自己責任で

無料で提供しているのでそれ相応のリスクはあります。
これを使用して万が一アカウント停止等されても一切責任を負いません。

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

Arduinoの測定値をNode.jsで受けてSocket.ioとchart.jsでリアルタイムにグラフ表示

概要

Arduinoの測定値をリアルタイムにグラフ表示してみたかったので、Node.js諸々を用いてブラウザ上にグラフ表示してみました。
今回はひとまず光センサーを測定対象にしました。光センサーの抵抗値の変化を電圧としてArduinoのアナログ入力で測定しています。

普段コーディングしない人間のコードなので変な箇所が多々あるかもしれません。その辺はご了承ください。

構成

ハードウェアとソフトウェアの構成を示します。Arduino周りの回路はDEVICE PLUSの記事を参考にしてください。

ハードウェア

  • PC (Mac)
  • Arduino UNO (PCとUSB接続)
  • 光センサー回路
    • ブレッドボード
    • 光センサー
    • 抵抗(1kΩくらい)
    • ジャンパー線

ソフトウェア

  • Node.jsのフレームワークであるExpress
  • Arduinoとシリアル通信するためのserialportライブラリ
  • リアルタイム通信するためのSocket.ioライブラリ
  • グラフ表示するためのchart.jsライブラリ

ソースコード

ソースコードは以下の4つです。

  • Arduino
    • serialCom.ino
  • サーバ側
    • app.js
  • クライアント(ブラウザ)側
    • index.html
    • index.js
ディレクトリ構造
.
├── app.js
├── index.html
├── node_modules
│   ├── @serialport
│  :
│   └── yeast
├── package-lock.json
├── package.json
└── public
   └── index.js

Arduino

serialCom.ino
int analogPin=A3;
double aval=0;
double val=0;

void setup() {
    Serial.begin(9600);
}

void loop() {
    aval = analogRead(analogPin);
    val = 5 * aval / 1024;
    Serial.println(val);
    delay(100);
}

アナログ入力のA3ピンと5V出力を使用しています。(使用する端子は回路によって変わります。)
analogReadで得られる数値は10bitのA/Dコンバータの出力コードなので、電圧に変換しています。測定間隔は100msにしました。

サーバ側

app.js
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
const port = new SerialPort('/dev/cu.usbmodem141101', {
  baudRate: 9600
});
const parser = new Readline();
port.pipe(parser);

app.use(express.static('public'));

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

// ブラウザ側とのコネクション確立
io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('disconnect', () => {
    console.log('user disconnected');
  });
});

//サーバ起動
http.listen(3000, function(){
  console.log('listening on *:3000');
});

//Arduinoからデータを受信したらクライアントへ送信
parser.on('data', (data) => {
  io.emit('graph update', (data));
});

必要なモジュールをインポートしてそれぞれ設定します。
Arduinoからのデータをシリアルポートで待ち受けます。(ポート名は環境によって変わります。)
httpサーバのポート3000で待ち受けます。
socket.ioでクライアントのブラウザと接続します。
Ardionoからシリアルポート経由でデータを受信したらブラウザへデータを送信します。

クライアント側

index.html
<!doctype html>
<html>
  <head>
    <title>グラフテスト</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.min.js'></script>
    <script src='//code.jquery.com/jquery-3.2.1.min.js'></script>
    <script src='http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.2/moment.min.js'></script>
    <script src='//cdn.socket.io/socket.io-1.4.5.js'></script>
  </head>
  <body>
    <canvas id="canvas" width="500" height="500"></canvas>
    <script src='index.js'></script>
  </body>
</html>

jquery, chart.js, moment.js, socket.ioを読み込んでいます。
(moment.jsをバンドルしたchart.jsもあるみたいですが、今回はそのまま。)
canvasタグ内にchart.jsでグラフが描画されます。
script部分は別ファイルにしています。

index.js
const socket = io.connect();
var ctx = document.getElementById('canvas').getContext('2d');

// グラフの作成
var myChart = new Chart(ctx, {
  type: 'line',
  data: {
      labels: [],
      datasets: [{
          label: 'data-label1',
          data: [],
          backgroundColor: 'rgba(0,0,225,1)',
          borderColor: 'rgba(0,0,225,1)',
          borderWidth: 1,
          lineTension: 0,
          fill: false
      }]
  },
  options: {
      title: {
        display: true,
        text: 'CHART TITLE'
      },
      scales: {
          xAxes: [{
              ticks: {
                //autoSkip: true,
                maxTicksLimit: 10
              }
          }],
          yAxes: [{
              ticks: {
                  // beginAtZero:true,
                  // autoSkip: true,
                  // maxTicksLimit: 10,
                  min:0,
                  max:5,
                  stepSize:1
              }
          }]
      },
      // グラフサイズ固定
      responsive: false,
      //maintainAspectRatio: false
  }
});

$(() => {
  // サーバから値を受け取った時の処理
  socket.on('graph update', (recievedData) => {
    // 現在時刻の取得
    const time = moment();
    const outputTime = time.format('HH:mm:ss');
    // 追加するデータのラベルに時間を追加
    myChart.data.labels.push(outputTime);
    // グラフにデータを追加
    myChart.data.datasets[0].data.push(recievedData);
    // データ数が100以上なら一番古い要素を削除
    if (myChart.data.datasets[0].data.length > 100) {
      myChart.data.labels.shift();
      myChart.data.datasets[0].data.shift();
    };
    // グラフの表示を更新
    myChart.update();
  })
});

myChartがグラフの設定です。
その後に続くのがサーバからデータを受け取った時の処理です。
moment.jsでラベルに現在時刻を追加しています。
無限にデータが増え続けるので、データ数100を上限にしています。

結果

ArduinoをUSBで接続してから、ターミナルでnode app.jsと叩いてサーバを起動、ブラウザで127.0.0.1:3000へアクセスするとこんな感じのグラフが表示されます。
ダウンロード.png
光センサーの上で手で光を遮ったりしたグラフになります。光を遮ると光センサーの抵抗値が上がるので電圧も上がります。
横軸がなぜか均等にならないのですが、気が向いたら改善します。

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

【初心者向け】記念日を通知するLINE botをheroku + Node.jsで作る

概要

この記事では、HerokuとNode.jsを活用して特定のユーザ(少人数を想定)に記念日を通知するLINE Botを作るノウハウを紹介します。ここでの通知とは、時間指定でbotからユーザへの簡単なテキストメッセージを送ることを指します。

事前準備1 LINE bot のチャネルの用意

LINE Botを作成するためにはLINEのMessaging APIというサービスを使います。
まずこのサービスを利用するためのチャネルを作成します。
以下をページを参考に必要な情報を入力して進めてください。

https://developers.line.biz/ja/docs/messaging-api/getting-started/#%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90

実在するアカウントと同様にアイコンも設定できます。
ここでは、某作品より以下のようなアイコンを設定しました。 
WS000006.JPG

Messaging APIには、botからユーザへのメッセージ(Push)とユーザがbotに送ったメッセージに対する返信(Reply)
がありますが、今回使用するのは、Pushの方です。
messaging-api-thumb0.png

https://developers.line.biz/ja/services/messaging-api/
より引用

事前準備2 ホストするサーバー(Heroku)の用意

次に、botをホストするサーバを用意します。
今回は無料で多くのサービスが利用可能なHerokuを使います。
Herokuの基本的な使い方やデプロイするまでの流れは、以下の記事が参考になると思います。

https://qiita.com/arashida/items/b2f2e01259238235e187
https://qiita.com/shti_f/items/b4b5d830672d908eff4e

botの開発

今回はbotからのメッセージを送ることを想定しているので、起点はHerokuのアドオン機能であるheroku schedulerを利用することにします。(詳細は後述)
この設定は後ほど行うとして、まずは通知するためのプログウラムを記載します。
通常使うindex.jsとは別にファイルを一つ作成します。

https://qiita.com/nkjm/items/38808bbc97d6927837cd

コードを書くにあたっては、Herokuの環境変数の設定などがあり、その点は上記の記事を参考にさせていただきました。

sample.js
#!/usr/bin/env node
// モジュールのインポート
const server = require("express")();
const line = require("@line/bot-sdk"); // Messaging APIのSDKをインポート

// パラメータ設定
const line_config = {
    channelAccessToken: process.env.LINE_ACCESS_TOKEN, // 環境変数からアクセストークンセット
    channelSecret: process.env.LINE_CHANNEL_SECRET // 環境変数からChannel Secretをセット
};


// APIコールのためのクライアントインスタンスを作成
const bot = new line.Client(line_config);

      main();//メインとなる処理を適当に

//メッセージを送る処理
      function sendMessage(message){
          console.log("message:" + message);
          bot.pushMessage("XXXXXXXXXXXXXXXXXXXXXXXXXX",{  //送りたい相手のUserID
            type:"text",
            text: "今日ハ " + message + "ダゼェ!"
                      })

      }

    function main() {
      //現在日付の取得
      var today = new Date();
      var month = today.getMonth()+1;
      var date = today.getDate();
      var message = "";  //デフォルトのメッセージをなにか入れたい場合はここに入れる。


      //送るべきメッセージの判定
      //XXの誕生日
      if (month == 7 & date == 30 ) {
        message = "XXの誕生日"
        sendMessage(message);
      }
      //入籍届けを出した日
      else if (month == 8 & date == 11 ) {
        message = "入籍届けを出した日"
        sendMessage(message);
      }     
      //該当しない日は何もしない
      else {

      }
    }

今回は簡易的な作りでDBのようなものを持たせていないので、日付の判定もメッセージ送信先のIDもハードコードしています。

package.jsonにJobの追加

heroku schedulerにキックしてもらうためのジョブと実際に動かすjsのファイルをpackage.jsonで紐付けます。

package.json
{
  "name": "line_botXXXXX",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "job-push-sample": "node sample.js"
  },
  ...

スケジューラの設定

スケジューラはHerokuのアドオン機能で日時や毎時の実行であれば、この機能で十分です。
※細かい設定はできないので、そのあたりまで実装したい方は別のサービスを使ったほうがいいでしょう
実際の設定は以下の記事を参考にさせていただきました。

https://qiita.com/Taro_man/items/2eab3e3acad88c5b759e

実際の設定画面は以下の通りです。
npm run 「package.jsonで指定したjob」という記載の仕方です。
※時間は標準時での設定しかできない点に要注意。
WS000007.JPG

設定は以上です!
実際に指定したユーザのラインにメッセージが飛んでいることを確認できましたでしょうか?

まとめ

ラインは生活の中に浸透していることもあって、アイディアと工夫次第で面白いことができそうですね。
これからも可能性を探っていきたいと思います。

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

Express.js + Sequelize ORM + PostgreSQL を使って Heroku にデプロイ

組み合わせることで Node.js を使った MVC アプリを気軽に Web サーバーにデプロイすることが可能です。この記事ではコードはあまり書かずにコマンドや設定ファイルだけで進む簡単な内容になっているため、前提条件さえクリアできればジュニアソフトウェアエンジニアでも理解できると思います。運用は無視した勉強用の記事です。

アーキテクチャ

Image from Gyazo

完成版のソースコードは KtoZ/sample-expressjs-sequelize-postgresql-heroku に置いてあります。

前提条件

これらがインストールされていて使用可能な状態になっていること。

開発

アプリを開発してローカル環境で実行するまでを進めていきます。

Express.js の構築

Express generator 使って作業ディレクトリに express のテンプレートを作成します。この記事では View を書かずに進めるため View エンジンはなんでもよいです。

npm install express-generator -g
express
npm install

作成後は npm start コマンドを実行することで、ブラウザなどから localhost:3000 にアクセス可能になります。

Docker を使用した PostgreSQL の構築

PostgreSQL を構築するための docker-compose.yml を作業ディレクトリに追加します。ユーザーやパスワードなどは自由に変更して問題ありません。

./docker-compose.yml
version: "3"

services:
  postgresql:
    image: postgres:12-alpine
    container_name: postgresql
    ports:
      - 5432:5432
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: hoge123
      POSTGRES_DB: root
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8"
    hostname: postgres
    restart: always
    user: root
volumes:
  db_data: {}

docker-compose.yml を追加したら docker-compose up -d コマンドを実行することで PostgreSQL が起動します。終了したいときは docker-compose down コマンドを実行してください。

Sequelize でテーブル作成

アプリから PostgreSQL に接続するためのパッケージと、 Sequelize ORM を使用するためのパッケージをインストールします。

npm install --save pg pg-hstore sequelize sequelize-cli

Sequelize CLI を使用して Sequelize ORM を初期化します。初期化が完了すると作業ディレクトリに config.jsonmodels ディレクトリが作成されます。

npx sequelize-cli init

データベースへの接続設定が書かれている config/config.json を開いて development 設定を書き換えます。

./config/config.json
  "development": {
    "username": "root",
    "password": "hoge123",
    "database": "root",
    "host": "127.0.0.1",
    "dialect": "postgres",
    "operatorsAliases": false
  },

Sequelize CLI を使用して新しいテーブルモデルを作成します。作成が完了すると models ディレクトリに user.js ファイルが作成されます。この記事では user テーブルを作成しています。

npx sequelize-cli model:generate --name user --attributes firstName:string,lastName:string,email:string

Sequelize CLI を使用してデータベースにテーブルを作成します。マイグレーションコマンドを実行することによって、作成したモデルがテーブルとして追加されます。コマンド実行後にデータベースへ接続してテーブルが作成されていることを確認してみてください。

npx sequelize-cli db:migrate

Express.js からテーブルに接続して値を返す

routes/users.js を開いてコードをこのように置き換えます。テーブルに登録されている全てのレコードを返却しています。

./routes/users.js
var express = require("express");
var router = express.Router();
let db = require("../models/index");

/* GET users listing. */
router.get("/", function (req, res, next) {
  db.user.findAll({}).then((users) => {
    res.send(users);
  });
});

module.exports = router;

実行確認

テーブルに適当なレコードを追加したあとに npm start でアプリを実行して、ブラウザから localhost:3000/users にアクセスしてください。追加したレコードが全て画面に表示されていることが確認できます。

Image from Gyazo

デプロイ

アプリを Heroku サーバーにデプロイして本番環境で実行するまでを進めていきます。

Git の初期化

Heroku にデプロイするときは Git を使用します。そのため、現在の作業ディレクトリを Git 化にします。

git init

.gitignore ファイルを作業ディレクトリに作成します。内容は簡素化しています。きちんとしたものを使用したい場合は gitignore/Node.gitignore at master · github/gitignore を参考にしてください。

./.gitignore
node_modules/

Heroku のサーバー構築

Heroku CLI を使用してログインします。その後、サーバーとデータベースの作成を実行します。サーバー作成時に Heroku にデプロイするための Git Remote 設定がされます。

heroku login
heroku create
heroku addons:create heroku-postgresql:hobby-dev

# Git Remote 確認用のコマンド
git remote -v
# heroku  https://git.heroku.com/polar-retreat-04355.git (fetch)
# heroku  https://git.heroku.com/polar-retreat-04355.git (push)

アプリの本番環境設定

config/config.json を開いて production 設定を書き換えます。 Heroku はサーバーでアプリを実行するときに production 設定を使用します。 "use_env_variable": "DATABASE_URL" を設定することで Heroku で作ったデータベースを使用するようになります。

./config/config.json
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "postgres",
    "operatorsAliases": false,
    "use_env_variable": "DATABASE_URL"
  }

アプリと DB のデプロイ

アプリをデプロイするためのコードをコミットします。

git add .
git commit -m "Add Application"

Heroku にアプリをデプロイします。

git push heroku master

Heroku CLI を使用して Heroku データベースにテーブルを作成します。

# Heroku Terminal に接続
heroku run bash

# データベースマイグレーションを実行
sequelize db:migrate

# Heroku Terminal から切断
exit

実行確認

Heroku CLI でデプロイしたアプリをブラウザで開きます。アプリが開いたら /users にアクセスして、データベースに登録されているレコードが帰ってくることを確認します。 ※画像はレコードが何もない状態です。

heroku open
# Open https://<YOUR_APP_NAME>.herokuapp.com/

Image from Gyazo

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

RPGツクールMVのゲーム画面の大きさ・スケール(拡大)を変えるあれこれ

やりたいこと

16px/32pxのタイルセットやキャラクターセットを使いつつ48pxと同じ画面サイズ感にする。

まず

RPGツクールMVのリサイズ・スケール周りは何かとややこしい

  • F4 でフルスクリーンモード
  • F3 Streach Mode(ウィンドウサイズに応じてゲーム画面がリサイズされる)
    • スマホ・プレイヤー(nwui)の場合はデフォルトON、PCの場合はOFF
    • 参考: _updateRealScale() (rpg_core.js:1644)

初期ウィンドウサイズ(スケール)を変えたい

Graphics.scale を書き換えてウィンドウサイズを変えればよい

    var Graphics_initialize = Graphics.initialize;
    Graphics.initialize = function (width, height, type) {
        Graphics_initialize.call(Graphics, width, height, type);

        // streach mode = offの場合、scaleをもとに計算される
        Graphics.scale = 2;

        // streach mode = onの場合は、ウィンドウサイズ=スケールなのでウィンドウを変えてしまう
        window.resizeTo(SceneManager._screenWidth * Graphics.scale, SceneManager._screenHeight * Graphics.scale);
    }


整数倍でスケールさせる

http://www.yanfly.moe/wiki/Core_Engine_(YEP)
Update Real Scale をtrueにする
デフォルトでは 0.5, 1, 1.5, 2, 3倍。 1.5が邪魔なら消してしまう。
このプラグインを導入すると上記のスケールが動かなくなる
(後述のゲーム解像度変更のためウィンドウサイズを初期化時にかえるため)

YEP_CoreEngine.js
Graphics._updateRealScale = function() {
  if (this._stretchEnabled) {
    var h = window.innerWidth / this._width;
    var v = window.innerHeight / this._height;
    this._realScale = Math.min(h, v);
    if (this._realScale >= 3) this._realScale = 3;
    else if (this._realScale >= 2) this._realScale = 2;
    else if (this._realScale >= 1.5) this._realScale = 1.5;
    else if (this._realScale >= 1) this._realScale = 1;
    else this._realScale = 0.5;
  } else {
    this._realScale = this._scale;
  }
};

ゲーム画面のピクセル数(デフォルト:816x624)を変える

上記プラグインのScreen WidthScreen Heightを変えるだけ。
いわゆるビューポート自体が変わるので画面に映る範囲は狭くなる

本題

RPGツクール2000みたいな化石の資産を使おうとしてもMVではタイルセットが48px固定になっている。
(逆にキャラクターセットは比率が合っていればどのサイズでもOK。なぜ? https://tkool.jp/mv/course/04.html)

そのためプラグインでタイルセットの基準サイズを変えるプラグインを入れる
https://forums.rpgmakerweb.com/index.php?threads/change-tile-size.46748/

  • これ単体で16pxタイルセットを使うと広い画面に対して小さなタイルセットが並んでしまうので
  • 上記解像度を1/3(272x208)にするとぴったり納まる
  • ウィンドウが小さいのでスケールで2~3倍にする
  • たぶんいい感じになる

問題点

  • いろいろ描画が壊れる。テキストボックスとか。

結論

下手にプログラムをいじらずに素材を48pxにスケールしてそのまま使ったほうが楽。

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

JS初級者がYouTubeMusic→Youtubeに移動するためののChrome拡張機能を作ってみた。

悲しきかな

最近のニュースに、こんなのがありました。

Google play musicの年内サービス終了

私は結構な頻度でGooglePlayMusicを使ってたわけですが、
こういうニュースもあったのでYoutubeMusicに移行することにしました。
と言いましても、まだ移行手続きの準備はできてないとかなんとか。
そこで試しにYoutubeMusicの下見に行きました。

コレナンデ概要欄ないん

私が使ってはじめに抱いたのはこの疑問でした。
確かに無いならないでいいのですが、
ちょっと気になります。
しかも、YoutubeMusicなのにYoutube上の同じ動画に、飛ぶ直リンクが無いではありませんか。

しかもちょうどよく、URLをちょっといじれば飛べるじゃないですか。

これはChrome拡張機能を作るしかねぇ。(作ったことはありません。)

こんな感じ

image.png
moveというのがボタンになっている。
(違和感しかない)

用意するもの

メモ帳、、、は無理なのでVSCODE
Chrome
パソコン
絵を書くやつ(とりあえずクリップスタジオ?みたいなやつ)
Githubにファイル一式あります。
https://github.com/haraday0403/YoutubeMusic_to_Youtube_links

1manifest.jsonを作ろう

私:これ何
知り合い:定義書みたいなもの?


{
  "name": "YoutubeMusic_linker",
  "version": "1",
  "description": "YoutubeMusicで再生中の動画をYoutubeで見るためのボタンみたいなのを出してくれます。\nとってもいびつですね。",
  "permissions": ["activeTab","declarativeContent"],
  "content_scripts": [{
    "js": ["background.js"],
    "css":["bootstrap.min.css"],
    "matches": ["https://music.youtube.com/*"],
    "run_at": "document_end",
    "all_frames": true
  }],
  "icons": {
    "48": "images/get_started48.png"
  },

  "manifest_version": 2
}

name:拡張機能名
version:拡張機能のバージョン
description:説明文
permissions:権限?知り合いに任せた。
content_scripts:ページ内で実行するものの読み込むコードなど書く場所
matches:動作させるページの指定
run_at:実行タイミング(確か最後に読み込む?)
all_frames:よくわからんけど書けって言われた
icon:アイコンの設定前の数字が何かはわからん
manifest_version:このファイルのバージョン(あってるかは不明)

チェックポイント
今回はBootstrapを使いたかったので、ダウンロードして、作業フォルダにコピーしました。
(UIデザインが全くできないため)
アイコンは先に用意しましょう。
get_started48.png
ダサ(IconにもBootstrapをください)

2 jsを書こう

知り合い:適当にエレメント触ればいいぞ。
私:appendChildさせてくらないんですが。
知り合い:(こいつマジで何も知らねえんじゃ)

今回一番の闇ポイント(同時に本題でもある)

const target=document.getElementsByClassName("title style-scope ytmusic-player-bar")
const observer = new MutationObserver(records => {
    loader()
  })
  observer.observe(target[0] ,{
    childList: true
  })
function loader(){
if (!document.getElementById("urlteleporter")){
main();
}
else{
var url=location.href;
document.getElementById("urlteleporter").href=url.replace("music.youtube","youtube");
}
}
function main(){
    var elem=document.getElementsByClassName("player-minimize-button style-scope ytmusic-player");
    var alter=document.createElement("a");
    var url=location.href;
    alter.id="urlteleporter"
    alter.innerText="Move";
    alter.href=url.replace("music.youtube","youtube");
    alter.classList.add("btn","btn-dark","text-center");
    elem[0].parentNode.appendChild(alter);
    }


ざっくり動きの説明
1:タイトルバーが変化(もしくは出現)すると、オブザーバーが検知してLoaderを実行
2:Loaderはこっちで用意したIDを持つものがあるか検知し、なければ生成、あればURL書き換えを行う。
3:ない場合は、Mainを実行し、新しくボタンを作成する。
BootstrapはCSSで読み込んでるので無問題。

チェックポイント
1:最初オブザーバーなしで行った
=>ページ移動がないせいでURLが変化しない
2:オブザーバーをおいた
=>一回目の実行のときに何故か2こ出てきたので、初期化処理しないようにした。
3:Classlistはスペースで区切れない=>,で分けてやる

読み込み

chrome://extensions/
からデバッグモード=>パッケージ化されてない拡張機能を読み込むで作業フォルダを読み込めば完成。

対応しないページは灰色アイコンになる。

言いたいこと

デザインって大事だわ。
(皆さんはaタグ内にimgタグとボタン用画像を用意するのをおすすめします。)

この拡張機能をGoogleに出してこよう。

多分通らない気がしますが出してきます。

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

ググる!勉強会:JavaScript編【第2回】

ググる!勉強会:JavaScript編【第2回】

本日は、ググる!勉強会にお越しくださいましてありがとうございます。

ググる!勉強会:HTML、CSS編【第1回】の続きになります。

本勉強会の目的

「実際にプログラミングしながら、ソフトウェア開発に必要なググり力(Google検索力)を身につけることです。」

第2回では、こういうの作ります。

https://js-page-7umggk7w6a-uc.a.run.app/

作ったWebページは、第3回のWebページ公開編でURLからWebページを見れるようにします。

Webページでできる事を知る

ググるためには、やりたいこと。知りたいこと。
が必要です。

Webページで出来る事がわかれば、後は、調べるだけです。

MUUUUU.ORG

SANKOU! Webデザインギャラリー・参考サイト集

技術の概要

「フロントエンド」とは

検索すると分かるように、
フロントエンド」と「バックエンド
の2種類の用語があることがわかります。

2つの対になる言葉(対義語)や似た用語が出た場合、

違い

と検索するとわかりやすくなります。

これは、前者の場合、1つの単語調べるため、専門的な説明が出てきますが、
後者の「違い」を検索すると
両者の特徴的な違いが検索結果に出ます。

下記の単語も検索してみると良いでしょう。

CUI GUI 違い

中級編

AI 機械学習 違い

上級編

モノリシック マイクロサービス 違い

フロントエンド」は、ユーザーが見る、触れる、Webサイトの部分です。

言語:HTML、CSS、JavaScriptなど

バックエンド」は、
目につかない裏側の処理を言います。
例えば、ログイン機能や決済、インフラ(サーバなど)があります。

企業によっては、「サーバーサイド」とも言います。

言語:Java、PHP、Python、SQL、Node.jsなど

フロントエンドで使われる言語について

HTML

HTMLは、Webページの文書の各部分が、
どのような役割を持っているのかを示す
(マークアップする)ための言語です。

CSS

CSSとは、HTMLに色や大きさや背景などを加えて見栄えを良くする(装飾する)ための言語です。

JavaScript(JS)

JavaScript(JS)は、Webページの動的な処理を記述するためのプログラミング言語です。
例えば、
Googleフォームでユーザーが入力していないと「この質問は必須です」と出現させる。

JSの強みは、Webサイトとサーバでいちいち情報を送受信しなくとも、
Webページ上(フロントエンド)だけで処理を実行させることが出来ることです。

もしJS(Webページ上で動くプログラミング言語)がなく、表示を変えたい場合、、、

ちょっとした処理ならサーバを介さず、JSがやってくれる。

但し、複雑な処理やデータをきちんと保存する場合、サーバに情報を送る必要がある。

近年は、SPA(シングルページアプリケーション)という単一ページのWebアプリもあります。

JSでHTML(正確には、DOM:後述)を操作し、更新したい部分だけをサーバに伝えることで高速で快適なWebページを作る技術です。

グリグリ回すと地図を読み込んでいますが、
Webページ全体をいちいち読み込んではありません。
Google Map

JSの文法

変数とは

プログラミング言語では、文字列や数値などのデータに名前を付けることが出来ます。
これを「変数」と言います。

数式で変数を表してみる

$$
y = 2x
$$

プログラミング言語では、xに好きな名前を付けられます。
hensuと命名してみます。

$$
hensu = 3
$$

$$
y = 2hensu
$$

$$
y = 6
$$

JavaScriptでの変数

JavaScriptでも、変数を宣言し、定義できます。

JavaScriptでは、「これは変数です」という宣言をするキーワードとして、 varletconstの3つがあります。

使う場面によって意見が別れますが、今回は、let

JavaScriptで変数を使う方法【初心者向け】

今回は、letの後に変数名(変数の名前)を付けて変数を定義しています。

JavaScript実行環境で実行してみる

// 変数宣言
let suuti = 120;
let mozi = "秒後に別のサイトに飛びます";
// alertでポップアップを表示
alert(suuti + mozi);

関数とは

関数は変数や処理を一つにまとめてくれます。

数式で関数を表してみる

$$
f(2x)
$$
数式では、x3を代入すると、2 x 3 = 6と計算できます。

プログラミング言語では、代入する値に名前を付けられます(変数)。

$$
hensu = 3
$$

数式のfも好きな名前を付けられます。

$$
kansumei(2*hensu) = 6
$$

このように関数は処理を一つにまとめられます。

大きな処理も関数にまとめておけば、値を入れるだけで結果を得られます。

JavaScriptで表現する

functionで関数を使うことを宣言します。

function kansumei() {
    // この中に好きな処理を書いていく
}
let x = 10;
let y = 20;

function kakezan(x, y) {
    // returnでkakezan()内に値を入れる
    return x + y ;
}

alert('求めた数値は、' + kakezan(x, y));

今回は関数に数値を入れるため、
関数の外で変数を定義し、kakezan()に値を代入しています。

switchで条件分岐

JavaScriptでは、数値や文字列を単純に比較したい場合、switch文を使います。

let 変数 = '入力値';

switch (変数){
    case '入力値':
        処理;
        break;  // breakで処理はここまで!と言う。
    case '入力値':
        処理;
        break; // breakしないと次も処理実行されます。
    default: // それ以外の場合、
        それ以外の処理;
        break;
}

実行してみましょう

let drink = 'オレンジジュース';

switch (drink){
    case 'コーヒー':
        console.log('100円です。');
        break;  // breakで処理はここまで!と言う。
    case 'オレンジジュース':
        console.log('120円です。');
        break; // breakしないと次も処理実行されます。
    default: // それ以外の場合、
        console.log('すみません。品切れです。');
        break;
}

if...elseで条件分岐

もし、この条件の時、こういう処理を行いたい、、、場合、if...else文を使います


if...elseの説明(今回は使わないので割愛)

ifelseの意味は、プログラミングでも同じです。

ifもし〜なら
elseがそれ以外

を表します。

JavaScriptでは、if..else文を以下のように書きます。

if (条件式) {
処理;
} else if (条件式) {
処理;
}
else  {
処理;
}

「条件式」というのが、「〜〜の条件の時」という意味を表す式になります。

比較演算子 意味
A == B AとBは、同じ
A != B AとBは、異なる
A > B Aの方が大きい
A < B Bのほうが大きい
A >= B AはBと同じもしくは大きい(AはB以上)
A <= B BはAと同じもしくは大きい(BはA以上)

例えば、60点が合格のテストがあり、
「不合格」、「合格」を比較演算子を使い、判定を自動化してみる。

let tensu = 40;

let goukaku = 60;

// tensuは、goukaku以上?
let kekka = tensu >= goukaku;

console.log("合格点越えてる?:" + kekka);

>=tensugoukaku以上?
となるので、
tensu40の場合、
結果:falseになります。

「以上」なので、60点は、Trueになります。

JavaScriptでif..else

条件が複数あり、比較、区別したい場合にif...else文を使います。

JavaScriptでは、if...else文は以下の書き方をします。

if (条件) {
処理;
} else if (条件) {
処理;
}
else  {
処理;
}

テストの点数を自動振り分けしたい。

let score = 40;

// もしsocreが60点以上ならば合格を出力
// console.logで文字を書き出します。
if (score >= 60) {
console.log("合格です!おめでとう!");
} else if (score >= 50) {
console.log("不合格です!おしい!");
}
else  {
console.log("不合格です!がんばれ!");
}

JSでWebページを操作する

検索キーワード

js webページ 操作

と検索するとDOMという単語が出てきますが、
https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model
このサイトを辞書のように使うと良いでしょう。

DOMとは

DOMとは、ブラウザがHTMLを構造化し、JSなど外部からアクセスできるようにするための規格です。

ブラウザがHTMLをDOMでキレイに決まった形で構造化しているため、
JavaScriptなどのプログラミング言語でHTMLを操作できます。

<body>
    <div>
        <h1>タイトル</h1>
    </div>
    <div>
        <p>適当な文章</p>
        <p>適当な文章</p>
    </div>
</body>

ブラウザ(Chromeなど)がDOMに則り、階層構造にしてくれることでJSなどのプログラミング言語でHTMLの要素を指定できるようになります。

JSでdiv要素を指定したい場合、

document.getElementsByTagName('div');

documentはブラウザに読み込まれたDOM全体を表します。

JSでWebページを書き換える

Netflixという動画配信サービスがあります。

セレクトボックスを選ぶと動画が紹介されます。
画像は、入れ替わりますが、いちいちページを読み込んでいません。

このような機能を実装してみましょう。

Netflix

liveweaveで実行する

前回のコード

HTML

<!DOCTYPE html>
<html>
<head>
    <title>ググる!勉強会!</title>
</head>
<body>
    <div class="main">
        <h1 class="title-class">ググる!勉強会!</h1>
        <p>Webページには、様々な技術が使われています。</p>
        <p>この勉強会では、HTML、CSS、JSなどの言語を使い、</p>
        <p>1からWebサイト公開までの工程を体験できます。</p>
        <img src="https://i.imgur.com/L11kB5t.jpg">
    </div>

</body>
</html>

CSS

.main {
    background-color: #303F9F;
    color: white;
    text-align: center;
    height: 500px;
  }

.title-class {
    line-height: 100px;
    text-align: center;
    color: #303F9F;
    font-size: 50px;
    background-color: white;
  }

p {
    line-height: 50px;
}

/* この下にコード追加 */



body {
    background-color: white;
}

/* リセットCSS。各ブラウザには、元から余白を作ってくれるので消去する */

* {
    margin: 0;
    padding: 0;
}

HTMLでセレクトボックスの領域を作る

HTML

<!--
    セレクトボックスで要素を変える
-->

    <div class="select-area">
        <label class="label-text">あなたが学びたい事は、</label>
        <select class="select-box" id="select-box" onchange="select_box()">
            <option value="1">HTML、CSS</option>
            <option value="2">JavaScript</option>
            <option value="3">デプロイ</option>
        </select>

        <div class="display-area">
            <img id="select-img">
            <div id="message_area"></div>
        </div>
    </div>

divで複数のタグをまとめられる。ー>領域を決める。
labelで簡単に横付けできる。
selectでセレクトボックスを作れる。

classidで名前を付けられる。
idは1つしか名前を付けられないことに注意。

CSSで体裁を整える

.select-area {
    text-align: center; /* HTMLを中央寄せ。rightと書くと右寄せ */
    height: 500px; /* 領域の高さを設定 */
    background-color: coral; /* 背景色 whiteと書くと白くなる */
}

.label-text {
    font-size: 30px; /* 文字の大きさ */
    font-weight: bold; /* 文字の太さ */
}

.select-box {
    margin-top: 50px; /* 上の領域と隙間を空ける */
    width: 20%; /* 幅 */
    height: 40px;
    font-weight: bold;
    font-size: 20px;
}

.display-area {
    margin-top: 20px;
    font-size: 20px;
}

JSでDOMを操作

関数 select_box

    変数 img_area は、img-area
    変数 select_number は、select-boxの値

    変数 message は、空(なんでも投げ込める)を定義

    切り替える、select_number
        入力値 '1'、
            img_area は、sample_1.png
            message は、'HTMLは、Webページの構造を作ります。CSSは、HTMLを装飾します。'

        入力値 '2'、
            img_area は、sample_2.png
            message は、'JavaScriptは、Webページに動的な処理を与えます。'

        入力値 '3'の時、
            img_area は、sample_3.png
            message は、'Webにおけるデプロイとは、Webページを実際に見れるようにする事です。'

    message_area に message を挿入

select_boxという関数を宣言します。

function select_box() {
    // この中に処理を書いていく
}

getElementByIdプロパティでHTMLのidを指定できます。

MDN Web docs:Document
には、Webページを操作できるプロパティ(設定)に関する情報が載っています。

    let img_area = document.getElementById('img-area')
    let select_number = document.getElementById('select-box').value;

今回は紹介しませんが、
addEventListener()でイベント処理が可能です。

Xボタンをクリックすると、メニューが閉じるのは、
addEventListener()で行われています。

【JavaScript入門】addEventListener()によるイベント処理の使い方!

JS

function select_box() {

    let img_area = document.getElementById('select-img')
    let select_number = document.getElementById('select-box').value;

    let message = '';

    switch(select_number) {
      case '1':
        img_area.src = 'https://i.imgur.com/NyuLkeS.jpg';
        message = 'HTMLは、Webページの構造を作ります。CSSは、HTMLを装飾します。';
        break;

      case '2':
        img_area.src = 'https://i.imgur.com/XdxpTtV.jpg';
        message = 'JavaScriptは、Webページに動的な処理を与えます。';
        break;

      case '3':
        img_area.src = 'https://i.imgur.com/9jeeKuc.jpg';
        message = 'Webにおけるデプロイとは、Webページを実際に見れるようにする事です。';
        break;

    }

    document.getElementById('message_area').innerHTML = message;
}

全コード

HTML

<!DOCTYPE html>
<html>
<head>
    <title>ググる!勉強会!</title>
</head>
<body>
    <div class="main">
        <h1 class="title-class">ググる!勉強会!</h1>
        <p>Webページには、様々な技術が使われています。</p>
        <p>この勉強会では、HTML、CSS、JSなどの言語を使い、</p>
        <p>1からWebサイト公開までの工程を体験できます。</p>
        <img src="https://i.imgur.com/L11kB5t.jpg">
    </div>
<!--
    セレクトボックスで要素を変える
-->

    <div class="select-area">
        <label class="label-text">あなたが学びたい事は、</label>
        <select class="select-box" id="select-box" onchange="select_box()">
            <option value="1">HTML、CSS</option>
            <option value="2">JavaScript</option>
            <option value="3">デプロイ</option>
        </select>

        <div class="display-area">
            <img id="select-img">
            <div id="message_area"></div>
        </div>
    </div>


</body>
</html>

CSS

.main {
    background-color: #303F9F;
    color: white;
    text-align: center;
    height: 500px;
  }

.title-class {
    line-height: 100px;
    text-align: center;
    color: #303F9F;
    font-size: 50px;
    background-color: coral;
  }

p {
    line-height: 50px;
}

/* この下に追加 */

.select-area {
    text-align: center; /* HTMLを中央寄せ。rightと書くと右寄せ */
    height: 500px; /* 領域の高さを設定 */
    background-color: white; /* 背景色 whiteと書くと白くなる */
}

.label-text {
    font-size: 30px; /* 文字の大きさ */
    font-weight: bold; /* 文字の太さ */
}

.select-box {
    margin-top: 50px; /* 上の領域と隙間を空ける */
    width: 20%; /* 幅 */
    height: 40px;
    font-weight: bold;
    font-size: 20px;
}

.display-area {
    margin-top: 20px;
    font-size: 20px;
}

body {
    background-color: white;
}

* {
    margin: 0;
    padding: 0;
}

JavaScript

function select_box() {

    let img_area = document.getElementById('select-img')
    let select_number = document.getElementById('select-box').value;

    let message = '';

    switch(select_number) {
      case '1':
        img_area.src = 'https://i.imgur.com/NyuLkeS.jpg';
        message = 'HTMLは、Webページの構造を作ります。CSSは、HTMLを装飾します。';
        break;

      case '2':
        img_area.src = 'https://i.imgur.com/XdxpTtV.jpg';
        message = 'JavaScriptは、Webページに動的な処理を与えます。';
        break;

      case '3':
        img_area.src = 'https://i.imgur.com/9jeeKuc.jpg';
        message = 'Webにおけるデプロイとは、Webページを実際に見れるようにする事です。';
        break;

    }

    document.getElementById('message_area').innerHTML = message;
}

Webページに使われるライブラリ

「ライブラリ」とは

皆さんは、カレーを作ったことがありますか?

じゃがいもやニンジンを煮込んで、カレールーを入れるだけでカレーが出来上がりますね。

何種類ものスパイスを調合する必要はありません。

ライブラリも同じです。
ライブラリを読み込み、自分の欲しい処理を決められた書き方で書いてあげるだけで、
本来は複雑かつ巨大なコードが必要な処理を簡単に実装できます。

今回は時間の都合上、やりませんがライブラリの一つを紹介しています。

次回、第3回では、作ったWebページをURLから見れるようにします。

アプリケーションやシステムを使えるようにすることを「デプロイ」と言います。

昨今のデプロイでは、クラウドサービスを使うのがトレンドです。
お楽しみに!

最後にアンケートにお答えください

ググる!勉強会:第2回アンケート

第2回は、これにて終わります。
ありがとうございました。

画像スライダーを作る

現代のWebページに画像スライダーは必須です。


画像スライダーを作る

「Swiper」を使ってみる

Swiperは、画像スライダーを作るためのJSライブラリです。
首相官邸からディズニーの公式ホームページまで使われており、幅広い実績があります。

多くの人が使っているため、ググりやすく、比較的軽量な優秀なライブラリです。

詳しい使い方は、下記のサイトを参考

Swiper公式サイト

【実例12パターン】画像スライダーはSwiper使っておけば間違いない!実用的な使い方を紹介

HTML

head内にSwiperのCSSライブラリを読み込む

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.3.8/css/swiper.min.css">

最後のbodyの上にjsライブラリを置く

<!--
    ライブラリ(Swiper)のJSを読み込み(bodyの最後)
-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.3.8/js/swiper.min.js"></script>

セレクトボックスの上に設置します。

HTML

<!--
    画像スライダー「Swiper」
    セレクトボックスの上に追加
-->

    <div class="swiper-container">
        <div class="swiper-wrapper">
            <div class="swiper-slide"><img src="https://i.imgur.com/qZ7vRaT.png"></div>
            <div class="swiper-slide"><img src="https://i.imgur.com/2pjjBz0.png"></div>
            <div class="swiper-slide"><img src="https://i.imgur.com/qZ7vRaT.png"></div>
        </div>
        <div class="swiper-pagination"></div>
        <div class="swiper-button-prev"></div>
        <div class="swiper-button-next"></div>
    </div>

CSS

.swiper-container {
    width: 100%;
    height: 400px;
    background-color: orange;
}

.swiper-slide {
  top: 20px;
    text-align: center;
    background-color: orange;
}

JS

var mySwiper = new Swiper ('.swiper-container', {
    loop: true
})

オプション付けてみる

var mySwiper = new Swiper ('.swiper-container', {
    loop: true,
    autoplay: {
    delay: 3000,
    },
    pagination: {
        el: '.swiper-pagination',
        type: 'bullets',
        clickable: true
    },
    navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev'
    },
})

jQueryで処理を書く

jQueryは、JSを短く書ける事を目指したライブラリです。

数あるJSのライブラリの中でも、最もWebページで使われているという調査もあります。

デメリットとしては、
jQueryは、巨大なライブラリのため、Webページが遅くなります。
しかし、jQueryの書き方に似て、軽量なライブラリも存在するため、覚えておくと得します。

Umbrella.js
nanoJS

短くかけます。(動きません)

$(function(){
  var box = $('select-box').value;

  $('#message_area').innerHTML = message;
});

全コード

HTML

<!DOCTYPE html>
<html>
<head>
    <title>ググる!勉強会!</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.3.8/css/swiper.min.css">
</head>
<body>
    <div class="main">
        <h1 class="title-class">ググる!勉強会!</h1>
        <p>Webページには、様々な技術が使われています。</p>
        <p>この勉強会では、HTML、CSS、JSなどの言語を使い、</p>
        <p>1からWebサイト公開までの工程を体験できます。</p>
        <img src="https://i.imgur.com/L11kB5t.jpg">
    </div>

<!--
    画像スライダー「Swiper」
-->

    <div class="swiper-container">
        <div class="swiper-wrapper">
            <div class="swiper-slide"><img src="https://i.imgur.com/qZ7vRaT.png"></div>
            <div class="swiper-slide"><img src="https://i.imgur.com/2pjjBz0.png"></div>
            <div class="swiper-slide"><img src="https://i.imgur.com/qZ7vRaT.png"></div>
        </div>
        <div class="swiper-pagination"></div>
        <div class="swiper-button-prev"></div>
        <div class="swiper-button-next"></div>
    </div>

<!--
    セレクトボックスで要素を変える
-->

    <div class="select">
        <label class="label-text">あなたが学びたい事は、</label>
        <select class="select-box" id="select-box" onchange="select_box()">
            <option value="1">HTML、CSS</option>
            <option value="2">JavaScript</option>
            <option value="3">デプロイ</option>
        </select>

        <div class="display-area">
            <img src="https://i.imgur.com/qZ7vRaT.png" id="select-img">
            <div id="message_area"></div>
        </div>
    </div>

<!--
    ライブラリ(Swiper)のJSを読み込み
-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.3.8/js/swiper.min.js"></script>
</body>
</html>

CSS

.main {
    background-color: #303F9F;
    color: white;
    text-align: center;
    height: 500px;
  }

.title-class {
    line-height: 100px;
    text-align: center;
    color: #303F9F;
    font-size: 50px;
    background-color: white;
  }

p {
    line-height: 50px;
}

.swiper-container {
    width: 100%;
    height: 380px;
    background-color: orange;
}

.swiper-slide {
  top: 20px;
    text-align: center;
    background-color: orange;
}

.select {
    text-align: center;
    height: 500px;
    background-color: coral;
}

.label-text {
    font-size: 30px;
    font-weight: bold;
}

.select-box {
    margin-top: 50px;
    width: 20%;
    height: 40px;
    font-weight: bold;
    font-size: 20px;
}

.display-area {
    margin-top: 20px;
    font-size: 20px;
}

body {
    background-color: white;
}

* {
    margin: 0;
    padding: 0;
}

JS

var mySwiper = new Swiper ('.swiper-container', {
    loop: true,
    autoplay: {
    delay: 3000,
    },
    pagination: {
        el: '.swiper-pagination',
        type: 'bullets',
        clickable: true
    },
    navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev'
    },
})

function select_box() {

    var elem = document.getElementById('select-img')
    var box = document.getElementById('select-box').value;

    var message = '';

    if(box === '1') {
        elem.src = 'https://i.imgur.com/2pjjBz0.png'
        message = 'HTMLは、Webページの構造を作ります。CSSは、HTMLを装飾します。';
    }
    else if(box === '2') {
        elem.src = 'https://i.imgur.com/qZ7vRaT.png'
        message = 'JavaScriptは、Webページに動的な処理を与えます。'
    }
    else if(box ==='3') {
        elem.src = 'https://i.imgur.com/L11kB5t.jpg'
        message = 'Webにおけるデプロイとは、WebページをURLから見れるようにする事です。'
    }
    document.getElementById('message_area').innerHTML = message;
}

本勉強会で得られた知見

本勉強会は、外出自粛の影響でオンライン勉強会になりました。

Google Meetで繋がり、HackMDで教材を配布しました。

アンケートの実施

今回、本勉強会には、6名が集まりました。
そのうち、5名がアンケートに答えて頂きました。

年齢層

1年生:60%
3年生:40%
となりました。

アンケートでは、理解度が最も低い評価でした。

これは、DOMを操作する例が少なく、体系化ぽくまとめられていなかったため
ではないかと考えました。

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

オブジェクト指向とクラス、インスタンスについての初学者向け記事

初学者向けの記事です

はじめまして、プログラミング学習歴1ヶ月の新参者です。
Progateとかドットインストールで基本的なところを学習して、
クラス宣言などのコードは何となく書けるようになったものの、
いざオブジェクト指向って何?オブジェクトとどうちがうの?
クラスとインスタンスを説明してとなると言語化が難しい
っていうレベルの人向けに調べたことを書いていきます。

<目次>

  • オブジェクト指向(OOP)とは

    • OOPはなぜ重要なのか
    • OOPのメリットとは
    • 小ネタ スティーブ・ジョブズとOOP
    • OOPっていったい何?
  • 2種類のオブジェクト指向とは

    • クラス型オブジェクト指向の特徴
    • プロトタイプ型オブジェクト指向の特徴
    • ふたつの違い
    • 小ネタ JavaScriptは変わり種
  • コードを書きながら各用語について理解する

    • 車の設計図からプリウスとレクサスを作る
    • オブジェクト、プロパティ、メソッドとは
    • クラス、コンストラクター、インスタンスとは
    • 3つの言葉の抽象度
  • まとめ

  • 参考資料

<OOPはなぜ重要なのか>

オブジェクト指向(OOP)について調べていく上で、ある言葉に出会いました。

JavaScriptを理解するとは、オブジェクト指向を理解することだ

つまり、OOPを理解できれば JavaScriptを使いこなせるという意味ですが、この言葉の背景には何があるんでしょうか。

オブジェクト指向の生みの親

1972年〜1980年にsmalltalkというプログラミング言語を公開したアラン・ケイが、その言語の説明のためにオブジェクト指向という言葉を生み出しました。そしてその後オブジェクト指向を元に多数の言語が作られました。C++やPython、JavaやRubyそしてJavaScriptなどです。これらはOOPという共通の考え方を持っています。だからこそJavaScriptを理解するとはオブジェクト指向を理解することだと言われているわけです。OOPは多数の言語が共通して持つ考え方ですから、いわばこれは前提知識です。理解しておけば他の言語の学習にも役立ちます。

OOPのメリット

なぜ多くの言語でOOPが使われているのか、それはひとえに変更に対して柔軟に対応できるからです。スマホのアプリやOSなどもよくアップデートされていますが、一度組んだプログラムはバグの修正や更新をする必要があります。メンテナンスのために変更を加えたい部分だけを抜き出すことができるという性質が変更への対応のしやすさに繋がっています。そして変更したくない部分は触らずにメンテナンスできるということは、保守性にも優れてるということです。メンテナンスと保守性の両方のバランスが優れているということがオブジェクト指向型プログラミング言語の特徴です。

小ネタ:スティーブ・ジョブズとOOP

ちなみに、アラン・ケイが指揮をとって開発したSmalltalkの環境で動くAltoというコンピュータがあるんですが、それに影響を受けたのが当時20代のスティーブ・ジョブズです。このAltoというコンピュータを見学したことがのちのmacの開発に繋がったというエピソードがあります。

OOPっていったい何?

オブジェクトとは「モノ」という意味です。オブジェクト指向というのはものづくりの考え方で、モノが共通して持つ特徴を捉えて、それをプログラミングで表現して問題を解決していこうという考え方のことを言います。例えるなら設計図です。設計図にはデータや機能が書かれています。それらをコンストラクターやプロパティ、メソッドと言ったものを使い表現するのですが、これはそれぞれの言葉の意味を説明するよりもみた方がイメージを掴みやすいので、後ほど説明していきます。

2種類のオブジェクト指向

オブジェクト指向には大きく分けて2種類あります。クラス型オブジェクト指向とプロトタイプ型オブジェクト指向です。それぞれの特徴と二つの違いをなんとなく頭に入れておいてください。

クラス型オブジェクト指向

クラス型のオブジェクト指向の特徴は、設計図にクラスを使用します。そして親クラスである設計図の内容をすべて引き継いだ設計図である子クラスを作ることができます。これを継承といいます。さらに、子クラスに親クラスにはない新しいデータを追加したり、親クラスの持っていたメソッドを上書き(オーバーライド)することができます。

プロタイプ型オブジェクト指向

プロトタイプ型オブジェクト指向の特徴は、設計図にプロトタイプを使用します。そしてそのプロトタイプを複製して新しいオブジェクトを生成します

ちがいは何?

二つの大きな違いはクラス型のオブジェクト指向は後から設計図を変更することができませんが、プロトタイプ型は後からでも設計図を変更できるという点にあります。
クラス型のオブジェクト指向でも、後から設計図を変更することは現実では可能ではあるものの、適した使い方ではありません。子クラスが親クラスの内容を引き継いでいて、二つの間に関係性が生まれていますので、後から親クラスの内容を変更してしまうと子クラスにまで影響が及んでしまうため、親クラスの変更は極力避けたほうがいいでしょう。基本的には親クラスから子クラスへは単純→複雑 抽象→具体といった作り方が適しています。

小ネタ:JavaScriptは変わり種

ところで、JavaScriptはクラス型かプロトタイプ型のどちらだと思いますか?
Progateなどをある程度やっている方はクラス型だと思うかもしれませんが、JavaScriptはプロトタイプ型です。人気のある他言語のほとんどがクラス型のオブジェクト指向であり、プロトタイプ型の馴染みがないので、2015年のES6からJSにもclass構文が追加されました。そのためクラス型のオブジェクト指向言語と変わらなくなりました。しかし、それ以前からもともとプロトタイプ型の言語の中でも異端の存在で、他のプロトタイプ型の言語が持っていないnew演算子やprototypeオブジェクトというものを持っていました。詳しい説明は省きますが、JavaScriptは元々はプロトタイプ型のオブジェクト指向言語ではあったものの、実際にはクラス型のオブジェクト指向言語に近い書き方をしなければならなかったのです。

コードを書きながら各用語について理解する

それでは実際に設計図を作りコードを書いていきましょう。まずは車をオブジェクト捉えるならば、以下のような設計図を作りました。

オブジェクト名:車
プロパティ:車の名前、最高速度
メソッド:前に進む

車の設計図からプリウスとレクサスを作る

この設計図からプリウスとレクサスを作っていきます。
後ほど解説しますが、これをインスタンスを生成するといいます。

プロパティ:車の名前:プリウス,最高速度:180km
メソッド:前に進む

プロパティ:車の名前:レクサス,最高速度:250km
メソッド:前に進む

オブジェクト、プロパティ、メソッドとは

車というオブジェクトの中身にプロパティとして車の名前と最高速度、メソッドとして前に進むというものがありますが、それについて説明します。
オブジェクトとはプロパティの集まりです。そしてプロパティはプロパティ名と値で成り立っています。先ほどの例では出しませんでしたが、タイヤはハンドル、車体の色や重さなどもプロパティとして設定することができます。また、例えば車が「走行している」と言った状態もプロパティに含まれます。
次にメソッドですが、メソッドとは値に関数をもつプロパティのことです。前に進む、後ろに進む、左右に曲がると言った車の機能に加えて、電気で走る。自動運転と言った最近の流行りの機能もメソッドになります。

コードで書いてみよう

 では、実際にこれをプログラミングで表現していきます。今回はクラス構文を使って書いていきます。
まず、力技で書くとこうなります。

qiita.rb
// 車名プリウス 最高速度180km 機能:前に進む
// 車名レクサス 最高速度250km 機能:前に進む

console.log('プリウスの最高時速は180km');
console.log('プリウスの機能は前に進む');

console.log('レクサスの最高時速は180km');
console.log('レクサスの機能は前に進む');

//実行結果
//プリウスの最高時速は180km
//プリウスの機能は前に進む
//レクサスの最高時速は180km
//レクサスの機能は前に進む

これは2台ならいいが1000台10000台となると大変だし、変更もしずらくなっています。
なので、class宣言を使って書いていきます。

qiita.rb
// 車名プリウス 最高速度180km 機能:前に進む
// 車名レクサス 最高速度250km 機能:前に進む

//クラス名Carの先頭の文字は慣例的に大文字にします。
class Car{
  constructor(){
    this.name = '';
    this.maxspeed = 0;
  }

  goForward(){
    console.log('前に進む');
  }
}

//インスタンスを生成する
let prius = new Car();
prius.name = 'プリウス';
prius.maxspeed = 180;
console.log(prius.name + 'の最高速度は' + prius.maxspeed + 'kmです');
prius.goForward();


let lexus = new Car();
lexus.name = 'レクサス';
lexus.maxspeed = 250;
console.log(lexus.name + 'の最高速度は' + lexus.maxspeed + 'kmです');
lexus.goForward();

//実行結果
//プリウスの最高時速は180kmです
//プリウスの機能は前に進む
//レクサスの最高時速は180kmです
//レクサスの機能は前に進む

クラス、コンストラクター、インスタンスとは

ここで出てきたクラス、コンストラクター、インスタンスの説明をしておきます。
クラスはオブジェクトを生み出すための設計図です。データ(プロパティ)とデータに関連した振る舞い(メソッド)をもちます。
コンストラクターというのは関数です。new演算子でインスタンスが生成された瞬間に実行されています。クラス内で1度しか使うことができません。
インスタンスというのは実体のことで、クラスから生成されたオブジェクトのことをさします。設計図を元に作った具体的なモノということです。先ほどの例でいうとプリウスとかレクサスを作ることをインスタンスを生成すると言います。

コンストラクターに引数を持たせるとさらに簡潔に書ける

コンストラクターは関数ですので、引数を持たせることができます。インスタンスを生成する際にclassに引数を渡すことでより簡潔なコードが書けます。

qiita.rb
// 車名プリウス 最高速度180km 機能:前に進む
// 車名レクサス 最高速度250km 機能:前に進む

class Car{
  constructor(name,maxspeed){
    this.name = name
    this.maxspeed = maxspeed
  }

  tellCar(){
    console.log(this.name + 'の最高速度は' + this.maxspeed + 'kmです')
  }

  goForward(){
    console.log('前に進む');
  }
}

let prius = new Car('プリウス',180);
prius.tellCar();
prius.goForward();

let lexus = new Car('レクサス',250);
lexus.tellCar();
lexus.goForward();

//実行結果
//プリウスの最高時速は180kmです。
//プリウスの機能は前に進む
//レクサスの最高時速は250kmです。
//レクサスの機能は前に進む

オブジェクトとクラスとインスタンスという3つの言葉の抽象度

この3つの中ではオブジェクトが最も抽象的です。クラスは設計図ですと説明しましたが、設計図もモノなのでオブジェクトであると言えます。インスタンスはクラスから生み出された実体ですが、これも当然オブジェクトです。
車というオブジェクト→車の設計図(クラス)→プリウス、レクサス(インスタンス)という順に抽象度が下がり具体的になっています。

まとめ

  • オブジェクト指向を理解しておくと、多数の言語の理解に役立つ
  • メンテナンスと保守性のバランスに優れている
  • オブジェクト指向とはものづくりの考え方
  • クラス型とプロトタイプ型の二種類がある。

Everything is an object.(全てはオブジェクトである)
冒頭で紹介したアラン・ケイはオブジェクト指向のコンセプトを6つに要約して説明しました。その1文目がEverything is an objectという言葉です。つまり、オブジェクトひいてはオブジェクト指向の汎用性の高さを表す1文になっています。

最後までお付き合いくださりありがとうございました。

参考資料

オブジェクト指向
オブジェクト指向と10年戦ってわかったこと
何となくjavascriptを書いていた人が一歩先に進むための本
これから学ぶjavascript

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

レースゲーム(Assetto Corsa)の走行データをPlotlyで可視化してみた

はじめに

私はレースゲームが好きでグランツーリスモ5/6をやり込んでいたのですが、Assetto Corsaに移ってから全く上手く走れていないことに気付きました。具体的には、AI車両(Strength=100%)に1周4秒近く離される状況です(カタルニアサーキット/TOYOTA GT86、ゲームパッド使用時)。
GT6ではABS=1/TCS=1以外のアシストOFFでオールゴールド取れているので、そんなに下手ではないと思っていたのですが…

なぜこれほど差が付くのかを分析するために、可視化ツールのお勉強も兼ねて、自分のプレイとAIのプレイの走行データをPythonで取得して、Plotlyで可視化してみました。

ちなみにですが、データ取得&可視化するだけならMotec i2 Proというツールがあります。今回は可視化ツールの調査も兼ねているので、Motecは使いませんが。

データ取得

Assetto Corsaには 「in-game app」(ゲーム内アプリケーション)と呼ばれる仕組みがあり、走行データをゲーム画面上に表示するアプリケーションをユーザーがPython言語を用いて独自に開発することができます。走行データを取得するためのAPIや画面上に表示を行うためのAPIなどが準備されています。

これらの参考情報をもとに、以下の情報を取得するプログラムを作りました。

  • 周回数
  • 現在の周回の経過時間(秒)
  • スタート地点からの距離(スタート地点=0~ゴール地点=1.0)
  • 車速(km/h)
  • アクセル開度(0.0~1.0)
  • ブレーキ開度(0.0~1.0)
  • ギア
  • エンジン回転数(rpm:revolutions per minute)
  • ステア(ハンドル)の切り角(degree)
  • 車両の現在位置 (3D座標)

ACTelemetry.py

ACTelemetry.py
class ACTelemetry:

()

    def logging(self):
        if self.outputFile == None:
            return

        lapCount = ac.getCarState(self.carId, acsys.CS.LapCount) + 1
        lapTime = ac.getCarState( self.carId, acsys.CS.LapTime)
        speed = ac.getCarState(self.carId, acsys.CS.SpeedKMH)
        throttle = ac.getCarState(self.carId, acsys.CS.Gas)
        brake = ac.getCarState(self.carId, acsys.CS.Brake)
        gear = ac.getCarState(self.carId, acsys.CS.Gear)
        rpm = ac.getCarState(self.carId, acsys.CS.RPM)
        distance = ac.getCarState(self.carId, acsys.CS.NormalizedSplinePosition)
        steer = ac.getCarState(self.carId, acsys.CS.Steer)
        (x, y, z) = ac.getCarState(self.carId, acsys.CS.WorldPosition)

        self.outputFile.write('{}\t{:.3f}\t{:.4f}\t{:.2f}\t{:.3f}\t{:.3f}\t{}\t{:.0f}\t{:.1f}\t{:.2f}\t{:.2f}\t{:.2f}\n'.format(\
        lapCount, lapTime/1000, distance, speed, throttle, brake, 
        gear, rpm, steer, x, y, z))

()

def acUpdate(deltaT):
    global telemetryInstance
    telemetryInstance.logging()

コードの細かい説明は割愛しますが、in-game appの仕組みではacUpdate(deltaT)がグラフィック更新の度(私の環境では1秒間に60回)実行されます。acUpdate(deltaT)から呼び出されるACTelemetry.logging()においてデータ取得&ファイル出力をしています。

このin-game appを有効にするためには、以下の手順を行います。

  1. 「(Steamインストール先フォルダ)\steamapps\common\assettocorsa\apps\python\ACTelemetry」以下にACTelemetry.pyを配置
  2. ゲーム内で「Options」⇒「General」⇒「UI Models」で「ACTelemetry」にチェック
  3. ゲームプレイ時(リプレイ時でもOK)にマウスを画面右端に移動し、表示されたアプリの中から「ACTelemetry」を選択

以下のようなUIが表示されるので、「Next」ボタンでデータ取得対象の車両を選択し、「Start」ボタンでログ取得を開始します。
ui.png

結果、以下のようなデータが取得できます。このデータを自分のプレイとAIのプレイの両方取得して比較したいと思います。

logger_20190817_1257.log
Course : ks_barcelona
Layout : layout_gp
Car Id : 0
Driver : abe.masanori
Driver : ks_toyota_gt86

lapCount    lapTime distance    speed   throttle    brake   gear    RPM steer   x   y   z
1   151.829 0.9399  115.7   1.00    0.00    4   6425    33  490.4   -14.6   -436.3
1   151.846 0.9400  115.8   1.00    0.00    4   6425    33  490.5   -14.6   -435.7
1   151.862 0.9401  115.8   1.00    0.00    4   6421    33  490.5   -14.7   -435.2
1   151.879 0.9402  116.0   1.00    0.00    4   6425    33  490.6   -14.7   -434.7

可視化する前のデータ整形

今回はPlotlyのJavascriptライブラリを利用してデータを可視化したいと思います。上述のヘッダ付きタブ区切りファイルのままでも扱えるのですが、多少面倒なので、事前に以下の加工・整形を加えます。

  • ヘッダ(先頭5行の情報や項目の行)の削除
  • 該当周回以外のデータ行削除
  • 該当周回のデータにおいても先頭5行と最後5行の削除(スタート/ゴール前後に発生するおかしなデータを除去するため)
  • タブ区切りからJavascript配列にフォーマット変換

以下のようなファイルになります。

my_data_before.js
my_data = [
    [2, 0.125, 0.0017, 155.96, 1.000, 0.000, 5, 6672, 0.0, 365.52, -18.43, -187.32],
    [2, 0.142, 0.0019, 155.96, 1.000, 0.000, 5, 6672, 0.0, 365.13, -18.43, -186.72],
    [2, 0.158, 0.0020, 156.11, 1.000, 0.000, 5, 6674, 0.0, 364.73, -18.43, -186.11],
    [2, 0.175, 0.0022, 156.11, 1.000, 0.000, 5, 6676, 0.0, 364.34, -18.44, -185.51],
    (以下略)

取得したデータの可視化

Plotlyを使って、以下のようなVizを作ってみたいと思います。(こちらから実際に動かせます。ちょっと重いですがアニメーションGIFはこちら

  • 速度、アクセル/ブレーキ開度、ギア、エンジン回転数、ステアリング角度について、自分とAI(CPU)のデータをスタートからの距離を横軸にして線グラフで左側に表示する。
  • 自分のコース上の走行位置を右側に表示する。
  • 速度のグラフで横軸範囲指定(Zoom)すると、他のグラフもそれに追従する。

この手のデータは、通常は横軸=時刻、縦軸=メトリクスを表示させるものなのですが、今回それをやると自分とAI(CPU)のデータが比較しづらいので、横軸にはスタートからの距離を表す数値(0.0:スタート ~ 1.0:ゴール)を採用します。

goal.png

HTMLファイル作成

まず、ベースとなるHTMLファイルを作成します。

  • Plotlyのライブラリを読み込みます。
  • div要素にPlotlyでグラフを差し込むことになるので、グラフ表示のdividを付与しておきます。

viz_before.html

viz_before.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>自分(改善前)とAIのデータ比較</title>
        <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
        <style>
            html,
            body {
                margin: 0;
                padding: 0;
                height: 100%;
                display: flex;
            }
            </style>
    </head>
    <body>
        <div>
            <div id="div-speed"></div>
            <div id="div-throttle"></div>
            <div id="div-brake"></div>
            <div id="div-gear"></div>
            <div id="div-rpm"></div>
            <div id="div-steer"></div>

        </div>
        <div id="div-position"></div>
    </body>
    <script src="data/my_data_before.js"></script>
    <script src="data/cpu_data.js"></script>
    <script src="my_viz.js"></script>
</html>

速度グラフなどの作成

速度、アクセル/ブレーキ開度、ギア、エンジン回転数、ステアリング角度についてはほぼ同じグラフになるため、自分のデータ、AI(CPU)のデータ、グラフ作成位置、縦軸タイトルを引数として渡して線グラフを作成する関数を作成します。

my_viz.js

my_viz.js
function plot_speed(my_x, my_y, cpu_x, cpu_y, divId, title_y){
    var data_me = {
        x: my_x,
        y: my_y,
        mode: 'lines',
        name: 'me'
    };

    var data_cpu = {
        x: cpu_x,
        y: cpu_y,
        mode: 'lines',
        name: 'cpu'
    };

    var layout = {
        autosize: false,
        yaxis: {title: title_y},
        width: 600,
        height: 250,
        margin: {l: 70, r: 70, b: 25, t: 25}
    };

    Plotly.newPlot(divId, [data_me, data_cpu], layout);
}

my_data_distance = Array.from(my_data, x => x[2]);
cpu_data_distance = Array.from(cpu_data, x => x[2]);

my_data_speed = Array.from(my_data, x => x[3]);
cpu_data_speed = Array.from(cpu_data, x => x[3]);
plot_speed(
    my_data_distance, my_data_speed, cpu_data_distance, cpu_data_speed, 
    'div-speed', '速度 (km/h)'
);
(以下略)

2次元配列をそのまま渡せないのはちょっと面倒に感じます。

位置データのグラフ作成

こちらもグラフ作成の関数を作成します。

  • 後で距離で絞り込みを行うため、表示するデータの距離下限と上限を引数で渡し、その情報に基づきデータを絞り込みます。
  • こちらのグラフはデータを入れ替えて再作成が発生するので、Plotly.newPlot()関数ではなくPlotly.react()関数を利用します(PlotlyのリファレンスによるとnewPlot()よりreact()の方が「far more efficiently」らしいのですが、ならreact()だけ使えば良いのではと思わなくもないのですが…)
  • 位置データに関して、南北(z)の値はプラス側が南、マイナス側が北となるため、range: [600, -600]の指定を指定して上下を逆転させています。autorange: 'reversed'でも上下逆転させられるのですが、データ絞り込みの際に軸の範囲が変わり縦横比が変わってしまうので、固定値を指定しています。
  • データを絞り込んだ際に、コースの度の部分に該当するか分かりやすくするために、コース図をグラフの背景画像として設定します。
my_viz.js
function plot_position(min_distance, max_distance) {
    my_x = Array.from(my_data.filter(v => (min_distance < v[2]) && (v[2] < max_distance)), x => x[9]);
    my_z = Array.from(my_data.filter(v => (min_distance < v[2]) && (v[2] < max_distance)), x => x[11]);

    my_pos = {
        x: my_x,
        y: my_z,
        mode: 'scatter',
        mode: 'line',
    };

    var layout = {
        xaxis: {autorange: false, range: [-600, 600]},
        yaxis: {autorange: false, range: [600, -600]},
        autosize: false,
        width: 300,
        height: 300,
        margin: {l: 50, r: 50, b: 50, t: 50, pad: 10},
        showlegend: false,
        images: [{
            source: 'pos_base.png',
            xref: 'x',
            yref: 'y',
            x: 500,
            y: 600,
            xanchor: 'right',
            yanchor: 'bottom',
            sizex: 1000,
            sizey: 1200,
            sizing: 'stretch',
            opacity: 0.4,
            layer: 'below'
        }]
    };

    Plotly.react('div-position', [my_pos], layout);
}

グラフ間の連携

速度グラフで横軸の範囲選択(Zoom)をした場合、そのイベントをトリガーに他のグラフにもその結果を反映させます。

  • Plotlyではマウスのドラッグ&ドロップで範囲選択した場合と、グラフ上でダブルクリックするなどしてZoom解除した場合で発生するZoomイベントの内容が異なりるので、if..else..で処理を分けます。
  • アクセル/ブレーキ開度、ギア、エンジン回転数、ステアリング角度は横軸の範囲を変えるだけなので、Plotly.relayout()でグラフのレイアウトを変更します。
  • 位置グラフについては、表示データを絞り込む必要があるので、グラフを再作成します(上で作成したplot_position()関数の実行)。
my_viz.js
document.querySelector('#div-speed').on(
    'plotly_relayout',
    function(eventdata) {
        if(eventdata['xaxis.autorange']) {
            x_start = 0.0;
            x_end = 1.0;
            option = {'xaxis.autorange': true};
        } else {
            x_start = eventdata['xaxis.range[0]'];
            x_end = eventdata['xaxis.range[1]'];
            option = {'xaxis.range': [x_start, x_end]}
        }

        Plotly.relayout('div-throttle', option);
        Plotly.relayout('div-brake', option);
        Plotly.relayout('div-gear', option);
        Plotly.relayout('div-rpm', option);
        Plotly.relayout('div-steer', option);

        plot_position(x_start, x_end);
    }
);

可視化結果の確認

これで、以下の流れで分析することができるようになりました。

  1. 速度グラフを見て、自分がAI(CPU)より遅い部分を確認する。
  2. 遅いと特定した部分にZoomする。
  3. 他のメトリクスを確認して、遅い原因を特定する。

(速度グラフをダブルクリックすれば、Zoomは解除されます)

まぁ、私が遅い原因を確認するにはそこまでする必要はなく、単純にステアリングの角度が大き過ぎたというだけなのですが。
原因が分かってしまえば単純ですが、GT5/6ではステアリングの角度に上限が設けられており(動的に変わるらしい)、ここまでひどい状況にはならなかったようです。

newplot (1).png

ステアリングの切り過ぎが原因だということを意識してプレイすることで、AI(CPU)との差も4秒から1秒以下までに縮めることができました。ただ、再度データを確認すると、まだステアリングが切り過ぎ&切り方が急なようなので、まだまだ改善しなければいけませんが。

自分(改善後)とAIのデータ比較

Plotly感想

今回、データを可視化するにあたり、Plotly以外にも以下のツールを試したので、少し感想を。

  • Tableau、Qlik Sense
    • 商用製品だけあり、機能も豊富で、作りたいグラフやダッシュボードがサクッと作成できる。
  • Grafana
    • 横軸が時間でないグラフをどう作成するのか、よく分からなかった。(できるとは思うけど)
  • Metabase、Superset、Redash
    • DB上のデータから1つグラフを作るのはとてもとても簡単。
    • ダッシュボード上で絞り込みをさせる、複数グラフを連携させるとなると、できなくはないけど、結構難しい or 使いづらいUIになりがち。
  • picasso.js
    • Qlik Senseの背後で使われているJavascript可視化ライブラリということで使ってみたけど、コード量が多くなりがち & マニュアルを読んでも良く分からない。
  • Plotly
    • DBからのデータ取得などはできないので、別途コーディングが必要。
    • 少ないコード量でグラフが作成できるし、Zoomや画像エクスポート、ホバーの機能がデフォルトで有効になっている。
    • ダッシュボード上での複数グラフに渡ったデータ絞り込みや連携はコーディングが必要だが、仕組みが分かれば比較的簡単。

まとめると、Plotlyとても良い。

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

HTMLエスケープ文字列のデコード用関数にしっくりくるものが無かったので、自分で作ってみた

HTMLデコード

function decodeHTML(str) {
  return str.replace(/&(?:([a-z]+?)|#(\d+?));/g, function(m, c, d) {
    return c ? ({
      "amp": "&",
      "lt": "<",
      "gt": ">",
      "quot": '"',
      "nbsp": " "
    }[c] || m) : d ? String.fromCharCode(d) : m;
  });
}

decodeHTML("&lt;a onclick=&quot;hoge(&#039;fuga&#039;)&quot;&gt;")
// <a onclick="hoge('fuga')">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

P5.js 日本語リファレンス(noiseSeed)

このページでは「P5.js 日本語リファレンス」 の noiseSeed関数を説明します。

noiseSeed()

説明文

noise() のシード値を設定します。デフォルトでは、プログラムが実行されるたびに noise() は異なる結果を生成します。実行するたびに同じ結果を生成するには seed に定数を設定します。

構文

noiseSeed(seed)

パラメタ

  • seed
    Number:シード値

// 実行する度に同じ動きになります。
let xoff = 0.0;

function setup() {
  createCanvas(200, 200);
  noiseSeed(15);
}

function draw() {
  background(204);
  xoff = xoff + 0.01;
  let n = noise(xoff) * width;
  line(n, 0, n, height);
}

実行結果

https://editor.p5js.org/bit0101/sketches/wPkhWNGut

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(noiseDetail)

このページでは「P5.js 日本語リファレンス」 の noiseDetail関数を説明します。

noiseDetail()

説明文

Perlin ノイズ関数によって生成される特性と詳細レベルを調整します。物理学の高調波と同様に、ノイズは数オクターブにわたって計算されます。オクターブが低いほど出力信号に寄与し、ノイズの全体的な強度が定義されます。一方、オクターブが高いほどノイズシーケンスの細部が細かくなります。

デフォルトでは、ノイズは4オクターブにわたって計算されます。各オクターブは前のオクターブの50%の強度から始まり、前のオクターブの半分に相当します。この減衰量は、fallout パラメタで変更できます。例えば、減衰量0.75は, 各オクターブが以前の低いオクターブの75%(25%減少)の影響を持つことを意味します。 0.0から1.0までの任意の値が有効ですが、0.5より大きい値は, noise() から1.0より大きい値が返される場合があることに注意してください。

これらのパラメータを変更することにより、noise() によって作成された信号を特定のニーズや特性に合わせて調整できます。

構文

noiseDetail(lod, falloff)

パラメタ

  • lod

    Number:ノイズが使用するオクターブの数

  • falloff

    Number:各オクターブの減衰係数

function setup() {
  createCanvas(200, 200);
}

function draw() {
  let noiseScale = 0.03;

  background(0);
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      noiseDetail(4, 0.4);
      let noiseVal = noise(x * noiseScale, y * noiseScale);
      stroke(noiseVal * 255);
      point(x, y);
    }
  }  
}

実行結果

https://editor.p5js.org/bit0101/sketches/Q53FHEpd3

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(noise)

このページでは「P5.js 日本語リファレンス」 の noise関数を説明します。

noise()

説明文

指定された座標での Perlin ノイズ値を返します。Perlin ノイズは、標準の random() 関数と比較してより自然な順序で調和のとれた一連の数値を生成するランダムシーケンスジェネレータです。 1980年代に Ken Perlin によって発明され, 手続き型のテクスチャ、自然な動き、形状、地形などを生成するためにグラフィカルアプリケーションで使用されました。

random() 関数との主な違いは、Perlin ノイズが無限のn次元空間で定義されることです。この場合、座標の各ペアは、固定されたセミランダム値に対応します(プログラムの存続期間に対してのみ固定されます。noiseSeed() を参照してください) p5.j​​s は、指定された座標の数に応じて、1D、2D、および3Dノイズを計算できます。結果の値は常に0.0 から 1.0 の間になります。上記の例で示したように、ノイズ値はノイズ空間を移動することでアニメーション化できます。 2番目と3番目の次元も時間として解釈できます。

関数による周波数の使用に関しては、実際のノイズはオーディオ信号と同様に構成されています。物理学における高調波の概念と同様に、Perlin ノイズは最終結果のために加算されるいくつかのオクターブにわたって計算されます。

結果のシーケンスの特性を調整する別の方法は入力座標のスケールです。関数は無限空間内で機能するため、座標の値自体は問題ではなく、連続する座標間の距離のみが関係します(たとえばループ内で noise() を使用する場合) 一般的なルールとして、座標間の差が小さいほど結果のノイズシーケンスは滑らかになります。 0.005〜0.03 のステップがほとんどのアプリケーションに最適ですが、これは使用状況によって異なります。

構文

noise(x, [y], [z])

パラメタ

  • x

    Number:ノイズ空間のx座標

  • y

    Number:ノイズ空間のy座標(オプション)

  • z

    Number:ノイズ空間のz座標(オプション)

戻り値

Number:指定された座標でのPerlinノイズ値(0と1の間)

例1

function setup() {
  createCanvas(200, 200);
}

function draw() {
  let noiseScale = 0.03;

  background(0);
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      let noiseVal = noise(x * noiseScale, y * noiseScale);
      stroke(noiseVal * 255);
      point(x, y);
    }
  }  
}

実行結果

https://editor.p5js.org/bit0101/sketches/Q8ufiqtMG

例2

var ns = 0.02; // ノイズスケール(係数)
var sz = 5; // 1ドットの縦横サイズ
var t = 0; // 時間軸

function setup() {
  createCanvas(200, 200);
  colorMode(HSB);
  noStroke();
}

function draw() {
  for (var x = 0; x < width; x += sz) {
    for (var y = 0; y < height; y += sz) {
      var n = noise(x * ns, y * ns, t);
      fill(n * 360, 100, 100); // HSBでカラー指定
      rect(x, y, sz, sz);
    }
  }
  t += 0.005; // 時間の更新
}

実行結果

https://editor.p5js.org/bit0101/sketches/obJ5qGjSQ

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

【JavaScript】関数の定義4種類

main.js
function getTryangle1(base, height) {
    return base * height / 2;
}
console.log(`三角形の面積: ${getTryangle1(5, 2)}`);

var getTryangle2 = new Function('base', 'height', 'return base * height / 2;');
console.log(`三角形の面積: ${getTryangle2(5, 2)}`);

var getTryangle3 = function(base, height) {
    return base * height / 2;
};
console.log(`三角形の面積: ${getTryangle3(5, 2)}`);

let getTryangle4 = (base, height) => {
    return base * height / 2;
};
console.log(`三角形の面積: ${getTryangle4(5, 2)}`);

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

4歳娘「パパ、Promiseやasync/awaitって何?」〜Promise編〜

とある休日

娘(4歳)「ねえパパ」

ワイ「なんや、娘ちゃん?」

娘「非同期って何?」

ワイ「ひ、非道鬼!?

娘「そうそう、非同期処理とかいうやつ」

ワイ「非道鬼処理やて・・・!?」

非道鬼「ヴォ〜〜〜!!!」

娘「!?」
娘「・・・現れたわね、非道鬼」
娘「処理してあげる」

ワイ「娘ちゃん、まだ4歳なのに、もう厨二病か・・・?」

よめ太郎「おい」
よめ太郎「お前まさか、非同期も知らんのか・・・?」

ワイ「いやいや、まさかまさか」
ワイ「流石に知っとるわ」
ワイ「それはそれは・・・極悪非道な・・・鬼のことや・・・」

よめ太郎「お前が非道鬼に喰われてしまえ

非同期処理とは

よめ太郎「ええか、娘ちゃん」
よめ太郎「まず、同期って言葉は」
よめ太郎「タイミングが合うって意味や」

娘「じゃあ、非同期っていうのはタイミングが合わないってこと?」

よめ太郎「せや」

娘「なんかピンとこないね・・・」

よめ太郎「ほな、例を挙げて説明するわ」

例えば、カウントダウンする処理

よめ太郎「ほな、例として」
よめ太郎「5、4、3、2、1、0って感じで」
よめ太郎「カウントダウンする処理を書いてみるで」

娘「うん」

よめ太郎「まず↓こんな感じのコードを書いてみたで」

JavaScript
setTimeout(() => console.log(5), 1000);
setTimeout(() => console.log(4), 1000);
setTimeout(() => console.log(3), 1000);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(1), 1000);
setTimeout(() => console.log(0), 1000);

よめ太郎「↑このコードを実行するのに、合計で何秒かかると思う?」

娘「ええと、setTimeout()は確か」
娘「タイマーをセットするメソッドだから」
娘「1,000ミリ秒後・・・つまり1秒後にconsole.log(5)が実行されるよね」

よめ太郎「せやな」

娘「その後、次の行が処理されるから」
娘「また1秒経過して、今度はconsole.log(4)が実行される」
娘「5、4、3、2、1、0・・・だから」
娘「合計6秒かかる!」

よめ太郎「・・・って思うやろ?」
よめ太郎「実は1秒しか掛からへんねん」

娘「そうなんだ・・・!」

よめ太郎「1行目のsetTimeout()に渡した関数は」
よめ太郎「1秒後に実行される訳やけど」
よめ太郎「その1秒間を待たずに、次の行が実行されてしまうねん」

娘「へえ〜」

よめ太郎「それが非同期処理やな」

娘「つまり・・・」
娘「1行目の処理が終わったら、2行目」
娘「2行目の処理が終わったら、3行目」
娘「そんな風にちゃんとタイミングを合わせて処理してくれるのが」
娘「同期的な処理で」

よめ太郎「そうそう」

娘「前の処理の完了を待たずに、次の処理が走るのが」
娘「非同期処理なんだね」

よめ太郎「その通りや」
よめ太郎「そして、さっきのsetTimeout()は」
よめ太郎「非同期的に処理されるメソッド、ってことやな」

娘「なるほどね〜」

よめ太郎「ほかにも、外部APIからデータを取得する処理なんかも非同期やな」
よめ太郎「例えばQiitaのAPIからデータを取得しようとして・・・」

  1. QiitaのAPIから、やめ太郎のフォロワーさん達の一覧を取得して、変数に格納する。
  2. フォロワーさん達を、リストとして画面に表示する。

よめ太郎「↑こんな処理をしようとした場合に」
よめ太郎「非同期処理のことを考えずにコードを書いてしまうとマズいんや」

JSくん「よっしゃ、QiitaのAPIに通信開始や!」
JSくん「そして、すぐさまフォロワーさんの一覧を表示や!」
JSくん「このfollowersいう変数に入っとるはずやな!」
JSくん「あれ?何も入っとらんで!」
JSくん「あかん、エラーや!!!」

よめ太郎「そらせやろ」
よめ太郎「まだ通信中や!」

よめ太郎「↑こんな感じになってしまうんや」

娘「なるほどー」
娘「APIとの通信とかって、何ミリ秒かかるのか予測できないから」
娘「完了を待たずに次の処理をしてくれようとしちゃうんだね」

よめ太郎「せやな」
よめ太郎「setTimeout()もそんな感じで」
よめ太郎「1秒待ってる間にも、次のコードを実行しようとしてしまうんや」

娘「同期が取れてないんだねぇ」

じゃあ、どうやってカウントダウンするの?

娘「でもさあ」
娘「それなら、どうやってカウントダウン機能を実装するの?」

ワイ「そこはワイに任しとき!」
ワイ「要はタイミングが合うように書いてやればええんや」

JavaScript
setTimeout(() => {
    console.log(5);
    // ここに次の処理を書く。
}, 1000);

ワイ「↑こんな感じや」

娘「どういうこと?」

ワイ「ええとな」
ワイ「setTimeout()の第一引数には、1秒後に実行したい関数を渡すやろ?」

娘「うん」

ワイ「引数として渡される関数・・・つまりコールバック関数やな」
ワイ「そのコールバック関数の中に、次にやりたい処理も書いてやるんや」
ワイ「上のコードで言うと、console.log(5);が実行された直後の部分に」
ワイ「次の処理も追加で書いてやればええんや」

娘「なるほどね」
娘「1秒経って、console.log(5);が実行された後で」
娘「次の処理が走るように、ってことかぁ」
娘「じゃあ、そこに次のsetTimeout()を書けばいいんだね!」

ワイ「そうそう」
ワイ「せやから次は・・・」

JavaScript
setTimeout(() => {
    console.log(5);
    setTimeout(() => {
        console.log(4);
        // ここに、更に次の処理を書く。
    }, 1000);
}, 1000);

ワイ「↑こうやな」
ワイ「こんな感じで、どんどん入れ子にしてやればええねん」

娘「なるほどー」
娘「パパ、すご〜い!」

ワイ「最終的には↓こうや!」

JavaScript
setTimeout(() => {
    console.log(5);
    setTimeout(() => {
        console.log(4);
        setTimeout(() => {
            console.log(3);
            setTimeout(() => {
                console.log(2);
                setTimeout(() => {
                    console.log(1);
                    setTimeout(() => {
                        console.log(0);
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);

娘「あれ?なんか・・・」
娘「地獄みたいに読みづらいコードになっちゃったけど・・・」

よめ太郎「コールバック地獄いうやつやな」

娘「このコールバック地獄に非道鬼が住んでるの?」

ワイ「せや」

よめ太郎「ムチャクチャ言うなや」
よめ太郎「せっかく娘ちゃんが理解しかけてたのに」
よめ太郎「非道鬼の話に戻ってしまったやないか」

ワイ「いやいや、綺麗なコードやと思うで」
ワイ「見てみい」

スクリーンショット 2020-05-16 12.26.37.png

ワイ「天使が持ってるハープみたいやで」
ワイ「地獄どころか、天国や」
ワイ「ワイは好きやけどな〜」

よめ太郎「そんなに好きなら貴様を天国に送ったるわ

ワイ「ひ、非道鬼ィ!

よめ太郎「誰が非道鬼や」

娘「(なんだこの家・・・)」

それじゃあ、どうやって書けばいいの?

娘「で、結局どうすればいいの?」
娘「コールバック地獄にならないように、うまく非同期処理を繋げられる方法があるの?」

よめ太郎「あるで」
よめ太郎「Promiseとかasync/awaitやな」

娘「ふーん」
娘「パパ、Promiseやasync/awaitって何?

ワイ「(いや何でワイに聞くねん)」

Promise、async/awaitとは

ワイ「プロミス・・・つまりやな」
ワイ「消費者金融にお金を借りに行く前に」
ワイ「a think
ワイ「いったん考えるんや」
ワイ「気軽に何十万も借りると、後から大変なことになってまうからな」

娘「そっか」

ワイ「そして、await
ワイ「審査の間は大人しく待つんや」

よめ太郎「結局申し込んどるやないかい」

よめ太郎「代われ、わしが説明する」
よめ太郎「まずはPromiseからや」

Promiseとは

よめ太郎「あのな?娘ちゃん」
よめ太郎「new Promise()ってすることで」
よめ太郎「promiseオブジェクトを生成することが出来んねん」

娘「promiseオブジェクト・・・」

よめ太郎「せや」

娘「よく分かんないけど、setTimeout()はどこに書くの?」

よめ太郎「new Promise()するときに、引数としてコールバック関数を渡すんやけど」
よめ太郎「そのコールバック関数の中にsetTimeout()を書くんや」

娘「またコールバック・・・?」
娘「連続してコールバックすると地獄になっちゃわない・・・?」

よめ太郎「大丈夫や」
よめ太郎「やってみるで」
よめ太郎「new Promise()するときにコールバック関数を渡すから・・・」

JavaScript
const promiseObj = new Promise(() => {});

よめ太郎「↑こんなイメージやな」
よめ太郎「こんな感じでpromiseオブジェクトを生成して、変数に格納するイメージや」

娘「うん」

よめ太郎「ほんで、そのコールバック関数の中に」
よめ太郎「setTimeout()とかconsole.log(5)を書くわけやから・・・」

JavaScript
const promiseObj = new Promise(() => {
    setTimeout(() => {
        console.log(5);
    }, 1000);
});

よめ太郎「↑こんな感じや」
よめ太郎「この時点でsetTimeout()は実行されて」
よめ太郎「1秒タイマーが開始されるんや」

娘「じゃあ、1秒後にconsole.log(5)が実行されるんだね」
娘「じゃあ、次のsetTimeout()とかconsole.log(4)はどこに書くの?」

よめ太郎「console.log(5)次に処理したい内容は」
よめ太郎「promiseオブジェクトが持ってるthen()メソッドに渡して登録するんや」
よめ太郎「コールバック関数としてな」

JavaScript
promiseObj.then(() => {
    setTimeout(() => {
        console.log(4);
    }, 1000);
});

よめ太郎「↑こんな感じや」

娘「ふーん」

よめ太郎「でも、then()メソッドに渡すだけでは」
よめ太郎「次にやりたい処理は実行されへんねん」
よめ太郎「まだ登録されただけや」

嫁「じゃあどうやって実行するの?」

よめ太郎「最初にnew Promise()に渡してあったコールバック関数あるやろ?」

娘「うん」
娘「最初に渡したやつね」
娘「setTimeout()console.log(5)が書いてあるやつ」

よめ太郎「その最初のコールバックが実行されるとき」
よめ太郎「ある引数が渡されて実行されるんや」

娘「ある引数・・・?」

よめ太郎「せや」
よめ太郎「その引数にresolveっていう名前をつけて受け取ってやるで」

JavaScript
const promiseObj = new Promise(resolve => {
    setTimeout(() => {
        console.log(5);
    }, 1000);
});

娘「resolveっていう引数には、何が入って来るの?」

よめ太郎「resolve関数や」
よめ太郎「そのresolve関数を実行すると」
よめ太郎「then()メソッドに渡したコールバック関数なんかが実行されるんや」
よめ太郎「setTimeout()とかconsole.log(4)が実行される訳や」

娘「えええ・・・?」
娘「先にnew Promise()して、後からthen()する訳でしょ?」

よめ太郎「せや」

娘「後からthen()に渡した関数が」
娘「先にPromise()に渡してあった関数の、引数の中に入って来るっていうこと?」

よめ太郎「いや、入って来るっていうか」
よめ太郎「上の方のresolveが実行されるまでは」
よめ太郎「下の方でthen()に渡したコールバック関数は実行されへん、っていうだけや」

娘「へー」
娘「下のthen()で登録した関数が」
娘「上のresolve()きっかけで発動する」

よめ太郎「そうそう」
よめ太郎「resolveが実行されると」
よめ太郎「then()で登録してあった次にやりたい処理が走る」
よめ太郎「だからresloveは、タイマー処理後に実行されるように書かんといかん」
よめ太郎「そうすることでタイミングが合うんや」

JavaScript
const promiseObj = new Promise(resolve => {
    setTimeout(() => {
        console.log(5);
        // タイマー発動後にresolve()する。
        resolve();
    }, 1000);
});

よめ太郎「↑こうや」
よめ太郎「ちなみに」
よめ太郎「同じpromiseオブジェクトで複数回then()することもできるで?」
よめ太郎「その場合、コード全体としては・・・」

JavaScript
const promiseObj = new Promise(resolve => {
    setTimeout(() => {
        console.log(5);
        resolve();
    }, 1000);
});

// then()メソッドに渡したコールバック関数は
// 上のresolveを実行したタイミングで実行される。
promiseObj.then(() => {
    setTimeout(() => {
        console.log(4);
    }, 1000);
});

// 同じpromiseオブジェクトで複数回then()することもできる。
// ↓この処理も、一番上のresolveを実行したタイミングで実行される。
promiseObj.then(() => {
    setTimeout(() => {
        console.log('もう1個!');
    }, 1000);
});

よめ太郎「↑こうやな」
よめ太郎「これで、1秒後に5がコンソールに表示されて」
よめ太郎「それから更に1秒後に4もう1個!がコンソールに表示されるんや」

娘「へえ〜」
娘「何となくだけど分かってきたかも」
娘「1秒後に実行する処理を、setTimeout()の中に書くんじゃなく」
娘「1つ外に出てthen()に渡す関数のところで書けるから」
娘「連続した非同期処理をしてもネスト地獄にならなさそうだね!」

よめ太郎「そう!」
よめ太郎「その通りや!」

これでスッキリ書けるの?

娘「でも、コードがあんまりスッキリしてなくない?」
娘「Promiseを使って、連続した非同期処理をスッキリ書くこともできるの?」

よめ太郎「できるで」
よめ太郎「まず、new Promise()する部分は関数にしておくんや」
よめ太郎「何回も使うからな」

JavaScript
const promiseMaker = num => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(num);
            resolve();
        }, 1000);
    });
};

よめ太郎「↑こんな感じや」
よめ太郎「console.log()に渡す数値は毎回変わるから」
よめ太郎「このpromiseMaker()の引数として渡せるようにしておいたで」
よめ太郎「引数名はnumや」

娘「このpromiseMaker()を実行すると」
娘「さっき言ってたpromiseオブジェクトが返ってくるんだね」

よめ太郎「せや」
よめ太郎「戻り値はpromiseオブジェクトやからthen()を持ってる」
よめ太郎「そしてthen()の戻り値もpromiseオブジェクトやから、またthen()を持ってる」
よめ太郎「せやからthen()の後にまたthen()を繋げて実行できるんや」
よめ太郎「使い方としては・・・」

JavaScript
promiseMaker(5)
    .then(() => promiseMaker(4))
    .then(() => promiseMaker(3))
    .then(() => promiseMaker(2))
    .then(() => promiseMaker(1))
    .then(() => promiseMaker(0));

よめ太郎「↑こうや」

娘「だいぶ非道鬼っぽさが減ったね」

よめ太郎「せや。スッキリやろ?」
よめ太郎「ちなみにこれは、さっきのとは違くて
よめ太郎「同じpromiseオブジェクトで複数回then()してる訳ではないで」

娘「そうなんだ」
娘「さっきと違って」
娘「毎回新しいpromiseオブジェクトが作られてるってこと?」

よめ太郎「せや」
よめ太郎「全体の流れとしては・・・」

  1. promiseMaker(5)を実行する。
    • promiseオブジェクトが生成される。
    • その時点でsetTimeout()が実行される。
    • 「未来にresolve()を実行するとき、then()で登録した処理も実行するからね」
      という約束がなされる。
  2. setTimeout()で予約された処理が、1秒後に発動。
    • console.log(5)が実行される。
    • resolve()が実行される。
  3. 約束通り、then()に登録してあったpromiseMaker(4)が実行される。

    • またpromiseオブジェクトが生成される。
    • ここで2つ目のsetTimeout()が実行される。
    • 「未来にresolve()を実行するとき、then()で登録した処理も実行するからね」
      という約束が成される。
  4. 二つ目のタイマー処理が発動する。

    • console.log(4)が実行される。
    • resolve()が実行される。
  5. resolve()きっかけで、今度はpromiseMaker(3)が実行され・・・

よめ太郎「↑この繰り返しやな」
よめ太郎「これをPromiseチェーンて呼ぶんや」

娘「へぇぇ」

よめ太郎「処理の流れを追いたい人のために」
よめ太郎「promiseMaker()関数を作らずに、愚直に書いた場合のコードも置いておくわ」


関数化しなかった場合のコード
JavaScript
new Promise(resolve => {
    setTimeout(() => {
        console.log(5);
        resolve();
    }, 1000);
})
    // ↓このthen()に渡した関数は、1つ上のresolve時に実行される。
    .then(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(4);
                resolve();
            }, 1000);
        });
    })
    // ↑ここの戻り値はpromiseオブジェクト。
    // そのため続けてthen()メソッドを呼べる。
    // ↓このthen()に渡した関数は、1つ上のresolve時に実行される。
    .then(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(3);
                resolve();
            }, 1000);
        });
    })
    // ↑ここの戻り値はpromiseオブジェクト。
    // そのため続けてthen()メソッドを呼べる。
    // ↓このthen()に渡した関数は、1つ上のresolve時に実行される。
   .then(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(2);
                resolve();
            }, 1000);
        });
    })
    // ↑ここの戻り値はpromiseオブジェクト。
    // そのため続けてthen()メソッドを呼べる。
    // ↓このthen()に渡した関数は、1つ上のresolve時に実行される。
    .then(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(1);
                resolve();
            }, 1000);
        });
    })
    // ↑ここの戻り値はpromiseオブジェクト。
    // そのため続けてthen()メソッドを呼べる。
    // ↓このthen()に渡した関数は、1つ上のresolve時に実行される。
    .then(() => {
        setTimeout(() => {
            console.log(0);
        }, 1000);
    });

外部APIからデータを取得する例

よめ太郎「APIからデータを取得するときなんかも」
よめ太郎「非同期的に処理されるから」
よめ太郎「連続してやろうとするとコールバック地獄になる可能性があるんやけど」
よめ太郎「でも、Promiseを使えば大丈夫や」
よめ太郎「外部APIから何らかのユーザー情報を取得する例で説明するで?」

JavaScript
const promise = new Promise(resolve => {
    getApiData('http://api.example.com/user/1', response => resolve(response.userData));
    // APIからデータを取得できたら、resolveを実行する。
})
    .then(userData => nextFunc(userData));
    // resolve実行時に渡した引数がuserDataに入って来る。

よめ太郎「↑こんな感じで1回外に出て
よめ太郎「then()に渡す関数のところで次の処理を書けるから」
よめ太郎「ネスト地獄にはならへんのや」
よめ太郎「例えAPIを連続で叩いてもな」

娘「へえ〜」
娘「っていうか、resolve()実行時に引数を渡せるんだね」

よめ太郎「そうやで」
よめ太郎「むしろresolve()に引数を渡すことの方が多いで」
よめ太郎「外部APIからデータを取得して、そのデータをresolve()に渡す」
よめ太郎「そのデータを受け取るためには」
よめ太郎「then()に渡す関数も、引数としてそのデータを受け取るように書いてやらなあかん」

JavaScript
    .then(userData => nextFunc(userData));

よめ太郎「↑こんな感じや」

娘「そっか」
娘「resolve()実行時に渡した引数が」
娘「このuserDataっていう引数に入って来るんだね」
娘「無理やり擬人化すると・・・」

※このセクションはイメージです。

プロミスくん「ああ〜、早く生まれたいな〜」
プロミスくん「ねぇ、そこの4歳娘ちゃん!」
プロミスくん「ワイを生成してや!」
プロミスくん「お役に立てるかもしれんで!」

娘「いいけど、何の役に立ってくれるの?」

プロミスくん「連続した非同期処理の扱いが得意や!」
プロミスくん「連続してAPIを叩く系の処理とか、上手く扱えるで!」

娘「え?助かる!」
娘「ちょうど連続してAPIを叩きたかったの!」
娘「プロミスくん、生成したい!

プロミスくん「おお、もうすぐワイも誕生できるんやな!」
プロミスくん「ちなみに、どんな非同期処理を扱ってほしいの?」

娘「えっとね」
娘「↓この案件を扱ってほしいの!」

  • APIからデータを取得してくる。
  • 無事データを取得してこれたら、そのデータを元に2つ目のAPIを叩きたい。

プロミスくん「APIからデータを取ってこれたら」
プロミスくん「次はそのデータを元に別のAPIを叩きたい感じか!」
プロミスくん「よっしゃ!この案件、ワイが扱うで!」

娘「ありがと!」

プロミスくん「ほな、まず1つ目のAPIを叩く処理を関数として書いて」
プロミスくん「それをコールバック関数として渡しながらワイを生成してくれ!」

娘「分かった!」
娘「はい!コールバック関数を書いたよ!」

プロミスくん「あ、そのコールバック関数を実行するときに」
プロミスくん「resolveってものを渡すから」
プロミスくん「resolveを受け取って実行するように書いといてくれ!」

娘「resolveを受け取って、実行する・・・」
娘「実行する、ってことはresolveは関数なんだね!」

プロミスくん「せや!」

娘「分かった!ちょっと書き換えるね!」
娘「でもresolveはどのタイミングで実行するように書けばいいの?」

プロミスくん「APIデータ取得完了後に」
プロミスくん「resolve(apiData)って感じで実行するように書いてくれ!」

娘「データ取得完了後にresolve(apiData)ね!」
娘「そこは自分で書かなきゃいけないんだね!」
娘「はい!そういう関数に書き換えたよ!」

プロミスくん「ほな、その関数を渡して、ワイを生成してくれ!」

娘「出でよ!プロミスくん!」

プロミスくん「やっと誕生できたわ!」
プロミスくん「これで、無事データが取って来れた暁には」
プロミスくん「そのデータがresolve()経由で次の処理に渡るで!」

娘「約束だからね!」

プロミスくん「おう!無事データが取れたらな!」

娘「了解!」
娘「あれ、でもまだ2つ目のAPIを叩く処理の内容を伝えてないよ!」

プロミスくん「せや!」
プロミスくん「ほな、続きの処理の内容を関数として書いて
プロミスくん「ワイのthen()メソッドに渡してや!」

娘「分かった!続きの処理then()メソッドに渡すね!」
娘「1つ目の処理のその後に実行したい処理だから」
娘「then()メソッド、っていう名前なんだね!」

プロミスくん「せや!」

娘「・・・はい!続きの処理の関数も書けたよ!」
娘「この関数は、最初のAPIデータを引数として受け取って」
娘「それを元に2つ目のAPIを叩く・・・」
娘「そんなテイで書いたよ!」

プロミスくん「それでオッケーや!」
プロミスくん「これで、最初のAPIからデータが取れた場合」
プロミスくん「そのデータを元に、2つ目のAPIが叩かれるはずや!」

娘「ありがとう!」

プロミスくん「おっ、最初のAPIからデータが無事に取得できたようやで!」

娘「あ、ちゃんと2つ目の処理も実行されてる!」

プロミスくん「よかったな!」

娘「うん!でも・・・」
娘「なんで関数を2個も書かないといけないの?」
娘「プロミスくんを生成する時に渡す関数と」
娘「then()に渡す関数」

プロミスくん「いや、むしろ2つに分けるから」
プロミスくん「コールバック地獄にならへんねん」

娘「あ、そっか」
娘「1つの関数の中にネストして書くと地獄になっちゃうけど」
娘「プロミスくんがresolve()経由で値をうまいこと渡してくれるから」
娘「then()に渡す関数のところで続きの処理を書けるんだね」

プロミスくん「そういうことや!」

妄想終了

娘「・・・こんな感じだね」

よめ太郎「せやな」
よめ太郎「実際の場面としては・・・」

「やめ太郎のフォロワーさん一覧をAPIから取得や!」
「次は、そのフォロワーさん達が他に誰をフォローしているかAPIから取得や!」
「その情報を元に、また別のAPIを叩く!」

よめ太郎「・・・なんて処理をする時は、Promiseを使うと書きやすくなるかもな」

処理が失敗したときのことも書ける

よめ太郎「非同期処理は失敗することもあるから・・・」

プロミスくん「なんかエラーが出て失敗した場合にはどうすればいい?」
プロミスくん「エラーが起きた場合にやって欲しい処理があったら」
プロミスくん「その内容を関数にして、ワイのcatch()メソッドに渡してや!」

よめ太郎「なんてこともしてくれるんやで」

娘「なんかthen()に似てるね」

よめ太郎「そうそう」
よめ太郎「失敗した場合のthen()みたいなもんや」

娘「そっか、APIからのデータ取得処理なんかは」
娘「必ず成功するわけじゃないもんね」

よめ太郎「せやせや」
よめ太郎「あの有名なaxios1get()メソッドなんかも」
よめ太郎「戻り値はpromiseオブジェクトやで」
よめ太郎「だから・・・」

JavaScript
axios.get('http://api.example.com/user/1')
  .then(response => nextFunc(response))
  .catch(error => console.log(error));

よめ太郎「↑こんな風に書けるんや」

娘「なるほどね〜」

よめ太郎「まだまだ奥が深いから」
よめ太郎「よかったら下の方に書いてある参考文献も読んでみてな!」

娘「うん!」

そういえばasync/awaitは?

娘「ママ、そういえば」
娘「async/awaitはどんな感じなの?」

よめ太郎「長くなり過ぎたから、次回説明するわ」

娘「その記事はいつ公開?」

よめ太郎「公開タイミングは分からん」
よめ太郎「非同期だけにな」

〜つづく〜

まとめ

  • 入れ子になりそうなコールバック処理も、
    Promiseを使ったら割と平坦に書けた。

参考文献

  1. Promiseを使う - JavaScript | MDN
  2. Promise - MDN - Mozilla
  3. Promise.prototype.then() - MDN - Mozilla
  4. Promise() コンストラクター - JavaScript | MDN

  1. 外部APIからデータを取得する為のライブラリ。 

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

先日いよいよAnimate.cssがv4になったのでwow.jsの使い方にコツがいる

Animate.cssとWOW.js、駆け出しwebエンジニアなら誰しもが一度は通る道ですよね。
とくにWOW.jsのはっちゃけたオフィシャルサイトは印象強いかも。

WOW.js
本家: https://github.com/graingert/wow
Fork: https://wowjs.uk/
Forkの方がStar多い...。内容はほぼ同じです。

そんな彼らですが、先日のAnimate.css v4.0.0リリースで、使い方にコツがいるようになりました。

変更点

  • クラス名にprefixがつくようになった
    • Animate.cssから提供される全てのクラス名にanimate__というprefixがつきます。
  • utilityクラス名の命名が変更になった?(DurationとかDelayとか)

① Animate.cssを導入

npmの場合

npm install animate.css --saveとかyarn add animate.cssとかする

node_modules下のcssファイルは ~ を使って読み込むのですよ。こんな感じ▼

@import "~animate.css/animate.min.css";

CDNの場合

公式サイトに沿ってCDN経由で読み込む。

② WOW.jsを導入

GitHubからローカルに導入するか、npm install wowjsでいけます。探すとCDNもあります。(公式からの案内はない)。

本家 ... wowjs
Fork ... wow.js

③スクリプトを配置

Typescriptの例ですがJavascriptでも同じです。
TSの場合はdefinition fileは提供されていないので require 関数で読み込みます。
ESLintを導入しているひとは例外を認めるコメントを書く必要があるかもです。下を参考にどうぞ。

app.ts
// eslint-disable-next-line @typescript-eslint/no-var-requires
const WOW = require('wow.js');

new WOW({
  animateClass: 'animate__animated'
}).init();

これだけで従来通り使えます。

ただしアニメーションのクラス名も
fadeIn -> animate__fadeIn
と変わっているのでお気をつけくださいまし。

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

【JavaScript】分割代入

オブジェクトのプロパティを変数に代入することができる。

main.js
let menu = {
    title: 'スパゲッティポリタン',
    cook: 'メインシェフ',
    price: 1500,
    resources: {
        pasta: '生パスタ',
        main: 'ベーコン'
    }
};
console.log(menu.title);
console.log(menu.cook);
console.log(menu.resources.pasta);
console.log(menu.resources.main);

let { title, cook, resources: { pasta, main, topping } } = menu;

console.log(title);
console.log(cook);
console.log(pasta);
console.log(main);
console.log(topping);
スパゲッティポリタン
メインシェフ
生パスタ
ベーコン

スパゲッティポリタン
メインシェフ
生パスタ
ベーコン
undefined

別の変数名に割り当てることも可能

main.js
let { title: name, cook: partner, resources: { pasta: kind, main, topping } } = menu;


console.log(name);
console.log(partner);
console.log(kind);
console.log(main);
console.log(topping);

スパゲッティポリタン
メインシェフ
生パスタ
ベーコン
undefined
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaとJavaScriptでwebブラウザとのソケット通信

背景

ネット上で多人数同時通信のものを作成するため、ソケット通信について実践したことを記述。
チャットやオンラインゲームのような、複数でのリアルタイム通信を希望。

「webブラウザ」で実行できるようなプログラムを「Java」で組みたかったのだが、ググってもあまり出てこないので記述しておく。
(知識不足でネットワークプログラミングという言葉も知らなかったため、ググり方に問題があるかも…)

データの読み書き等の超基本的なところからスタート。

ソケット通信

通信方法の1種。
サーバーとクライアントに対して、HTTPのgetアクセスやpostアクセスのように単一方向への通信ではなく、リアルタイムでの双方向通信が可能。
⇒ サーバープログラムとクライアントプログラムの2つが必要。

詳しくはググってほしい。

実行環境

  • windowds
  • eclipse (Tomcat8.0)
  • Google Chrome

言語

サーバープログラム:Java
クライアントプログラム:JavaScript

成果物

  1. webブラウザで動くチャットアプリ

開発手順

  1. クライアント側のプログラムを組む
  2. サーバー側のプログラムを組む
  3. クライアントプログラムからサーバーへアクセス
  4. サーバーからの応答があればOK

実践項目

  1. クライアントエコーによるソケット通信
  2. 単一クライアントとサーバーのチャットアプリ
  3. 複数クライアントとサーバーのチャットアプリ
  4. 複数クライアントでのじゃんけんシステム

1. クライアントエコーによるソケット通信

クライアント側のプログラムはJavaScriptで記述する。
ここはほぼパクり。
サーバー側のプログラムは必要なし。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>WebSocket通信</title>
        <script type="text/javascript">

            var wSck= new WebSocket("echo.websocket.org");// WebSocketオブジェクト生成

            wSck.onopen = function() {//ソケット接続時のアクション
                document.getElementById('show').innerHTML += "接続しました。" + "<br/>";
            };

            wSck.onmessage = function(e) {//メッセージを受け取ったときのアクション
                document.getElementById('show').innerHTML += e.data + "<br/>";
            };

            var sendMsg = function(val) {//メッセージを送信するときのアクション
                var line = document.getElementById('msg');//入力内容を取得
                wSck.send(line.value);//ソケットに送信
                line.value = "";//内容をクリア
            };
        </script>
    </head>

    <body>
        <div
            style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;"
            id="show"></div>
        <input type="text" size="80" id="msg" name="msg" />
        <input type="button" value="送信" onclick="sendMsg();" />
    </body>
</html>

コードの解説

1.Webソケットオブジェクトを生成し、引数にアドレスを代入。
var 変数 = new WebSocket("ws://IPアドレス:ポート番号");

今回はアドレスに「echo.websocket.org」を使用。
⇒クライアントから送信したものをそのまま返してくれる。
 サーバープログラムを作成しなくてもソケット通信が可能なため便利。

2.ソケット接続時のアクションを記述
ソケット.onopen = function() {・・・};
ソケット接続に成功したら、まずこの関数が実行される。

3.メッセージを受け取ったときのアクションを記述
ソケット.onmessage = function() {・・・};
サーバーからデータが送られて来たらこの関数が実行される。

4.メッセージを送る内容を記述
ソケット.send(・・・);

実行結果

キャプチャ.JPG
キャプチャ2.JPG
キャプチャ3.JPG

送信した内容がそのまま架空サーバーから送り返されて表示される。

2. 単一クライアントとサーバーのチャットアプリ

架空サーバーじゃなく、実際にサーバーを作成してクライアントからの送信内容をそのまま返信する。
サーバー側のプログラムをJavaで記述する。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Encoder;

class SocketFree {

    private static ServerSocket sSck;// サーバー用のソケット
    private static Socket sck;// 受付用のソケット
    private static InputStream is;// 入力ストリーム
    private static InputStreamReader isr;// 入力ストリームを読み込む
    private static BufferedReader in;// バッファリングによるテキスト読み込み
    private static OutputStream os;// 出力ストリーム

    public static void main(String[] args) {
        try {
            sSck=new ServerSocket(60000);//サーバーソケットのインスタンスを作成(ポートは60000)
            System.out.println("サーバーに接続したよ!");
            sck=sSck.accept();//接続待ち。来たらソケットに代入。
            System.out.println( "参加者が接続しました!");

            // 必要な入出力ストリームを作成する
            is=sck.getInputStream();//ソケットからの入力をバイト列として読み取る
            isr=new InputStreamReader(is);//読み取ったバイト列を変換して文字列を読み込む
            in=new BufferedReader(isr);//文字列をバッファリングして(まとめて)読み込む
            os=sck.getOutputStream();//ソケットにバイト列を書き込む

            //クライアントへ接続許可を返す(ハンドシェイク)
            handShake(in , os);

            //ソケットへの入力をそのまま返す ※125文字まで(126文字以上はヘッダーのフレームが変化する)
            echo(is , os);

        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //クライアントへ接続許可を返すメソッド(ハンドシェイク)
    public static void handShake(BufferedReader in , OutputStream os){
        String header = "";//ヘッダーの変数宣言
        String key = "";//ウェブソケットキーの変数宣言
        try {
            while (!(header = in.readLine()).equals("")) {//入力ストリームから得たヘッダーを文字列に代入し、全行ループ。
                System.out.println(header);//1行ごとにコンソールにヘッダーの内容を表示
                String[] spLine = header.split(":");//1行を「:」で分割して配列に入れ込む
                if (spLine[0].equals("Sec-WebSocket-Key")) {//Sec-WebSocket-Keyの行
                    key = spLine[1].trim();//空白をトリムし、ウェブソケットキーを入手
                }
            }
            key +="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";//キーに謎の文字列を追加する
            byte[] keyUtf8=key.getBytes("UTF-8");//キーを「UTF-8」のバイト配列に変換する
            MessageDigest md = MessageDigest.getInstance("SHA-1");//指定されたダイジェスト・アルゴリズムを実装するオブジェクトを返す
            byte[] keySha1=md.digest(keyUtf8);//キー(UTF-8)を使用してダイジェスト計算を行う
            Encoder encoder = Base64.getEncoder();//Base64のエンコーダーを用意
            byte[] keyBase64 = encoder.encode(keySha1);//キー(SHA-1)をBase64でエンコード
            String keyNext = new String(keyBase64);//キー(Base64)をStringへ変換
            byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
                    + "Connection: Upgrade\r\n"
                    + "Upgrade: websocket\r\n"
                    + "Sec-WebSocket-Accept: "
                    + keyNext
                    + "\r\n\r\n")
                    .getBytes("UTF-8");//HTTP レスポンスを作成
            os.write(response);//HTTP レスポンスを送信
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //ソケットへの入力を無限ループで監視するメソッド
    public static void echo(InputStream is , OutputStream os) {
        try{
            while(true) {
                byte[] buff = new byte[1024];//クライアントから送られたバイナリデータを入れる配列
                int lineData =is.read(buff);//データを読み込む
                for (int i = 0; i < lineData - 6; i++) {
                    buff[i + 6] = (byte) (buff[i % 4 + 2] ^ buff[i + 6]);//7バイト目以降を3-6バイト目のキーを用いてデコード
                }
                String line = new String(buff, 6, lineData - 6, "UTF-8");//デコードしたデータを文字列に変換
                byte[] sendHead = new byte[2];//送り返すヘッダーを用意
                sendHead[0] = buff[0];//1バイト目は同じもの
                sendHead[1] = (byte) line.getBytes("UTF-8").length;//2バイト目は文字列の長さ
                os.write(sendHead);//ヘッダー出力
                os.write(line.getBytes("UTF-8"));//3バイト目以降に文字列をバイナリデータに変換して出力

                if (line.equals("bye")) break;//「bye」が送られたなら受信終了
            }
        } catch (Exception e)  {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

コードの解説

1.ソケットやストリーム等の必要な変数を宣言。

2.サーバーソケットのインスタンスを作成。
ServerSocket 変数 = new ServerSocket(ポート番号)

3.acceptメソッドでクライアントからの接続を待機。

4.接続されたクライアントの入出力ストリームを作成する。

5.クライアントからの接続要求に対してレスポンスを返す。(handShakeメソッド)

リクエストのヘッダーに必要な情報が記述されているため、編集して送り返す。
編集内容の詳細はコードのコメント及び参照サイトを参考。
要は、リクエストのヘッダーにある『WebSocketキー』を送り返す必要がある。
『ハンドシェイク』

6.ソケット接続後、無限ループでクライアントからの入力をそのまま返す。(echoメソッド)

クライアントから送信されたデータはバイナリデータとしてエンコードされている。
サーバー側でデコードし、再度エンコードしてクライアントへ送信する。
デコード内容の詳細はコードのコメント及び参照サイトを参考。
※送信された文字列の長さによってヘッダーのフレームが変化するので、ここでは簡単のため125文字以下に限定する。

7.クライアントから「bye」が送信されるとループを抜け受信終了

実行結果

  1. サーバープログラムを起動
  2. ポート番号を指定し、localhostへ接続

キャプチャ3.JPG

※クライアントプログラムのアドレスを変更しておく(今回は60000番)
var 変数 = new WebSocket("ws://127.0.0.1:60000");

架空サーバー(echo.websocket.org)を使用したときと同様の結果が得られた。
今回はエコーするだけだが、サーバー側でデータをいろいろ弄って返信することも可能。

複数クライアントとサーバーのチャットアプリ

サーバープログラムを編集し、複数のクライアントからの接続を許可する。
また、各クライアント間でリアルタイム通信となるようにする。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Encoder;

class SocketFree {

    private static ServerSocket sSck;// サーバー用のソケット
    private static Socket[] sck;// 受付用のソケット
    private static InputStream[] is;// 入力ストリーム
    private static InputStreamReader[] isr;// 入力ストリームを読み込む
    private static BufferedReader[] in;// バッファリングによるテキスト読み込み
    private static OutputStream[] os;// 出力ストリーム
    private static ClientThread user[];//各クライアントのインスタンス
    private static int member;// 接続しているメンバーの数

    public static void main(String[] args) {
        int n=0;
        int maxUser=100;
        //各フィールドの配列を用意
        sck=new Socket[maxUser];
        is=new InputStream[maxUser];
        isr=new InputStreamReader[maxUser];
        in=new BufferedReader[maxUser];
        os=new OutputStream[maxUser];
        user=new ClientThread[maxUser];

        try {
                sSck=new ServerSocket(60000);//サーバーソケットのインスタンスを作成(ポートは60000)
                System.out.println("サーバーに接続したよ!");
                while(true) {
                    sck[n]=sSck.accept();//接続待ち。来たらソケットに代入。
                    System.out.println( (n+1)+"番目の参加者が接続しました!");

                    // 必要な入出力ストリームを作成する
                    is[n]=sck[n].getInputStream();//ソケットからの入力をバイト列として読み取る
                    isr[n]=new InputStreamReader(is[n]);//読み取ったバイト列を変換して文字列を読み込む
                    in[n]=new BufferedReader(isr[n]);//文字列をバッファリングして(まとめて)読み込む
                    os[n]=sck[n].getOutputStream();//ソケットにバイト列を書き込む

                    //クライアントへ接続許可を返す(ハンドシェイク)
                    handShake(in[n] , os[n]);

                    //各クライアントのスレッドを作成
                    user[n] = new ClientThread(n , sck[n] , is[n] , isr[n] , in[n] , os[n]);
                    user[n].start();

                    member=n+1;//接続数の更新
                    n++;//次の接続者へ
                }
            } catch (Exception e) {
                System.err.println("エラーが発生しました: " + e);
        }
    }

    //クライアントへ接続許可を返すメソッド(ハンドシェイク)
    public static void handShake(BufferedReader in , OutputStream os){
        String header = "";//ヘッダーの変数宣言
        String key = "";//ウェブソケットキーの変数宣言
        try {
            while (!(header = in.readLine()).equals("")) {//入力ストリームから得たヘッダーを文字列に代入し、全行ループ。
                System.out.println(header);//1行ごとにコンソールにヘッダーの内容を表示
                String[] spLine = header.split(":");//1行を「:」で分割して配列に入れ込む
                if (spLine[0].equals("Sec-WebSocket-Key")) {//Sec-WebSocket-Keyの行
                    key = spLine[1].trim();//空白をトリムし、ウェブソケットキーを入手
                }
            }
            key +="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";//キーに謎の文字列を追加する
            byte[] keyUtf8=key.getBytes("UTF-8");//キーを「UTF-8」のバイト配列に変換する
            MessageDigest md = MessageDigest.getInstance("SHA-1");//指定されたダイジェスト・アルゴリズムを実装するオブジェクトを返す
            byte[] keySha1=md.digest(keyUtf8);//キー(UTF-8)を使用してダイジェスト計算を行う
            Encoder encoder = Base64.getEncoder();//Base64のエンコーダーを用意
            byte[] keyBase64 = encoder.encode(keySha1);//キー(SHA-1)をBase64でエンコード
            String keyNext = new String(keyBase64);//キー(Base64)をStringへ変換
            byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
                    + "Connection: Upgrade\r\n"
                    + "Upgrade: websocket\r\n"
                    + "Sec-WebSocket-Accept: "
                    + keyNext
                    + "\r\n\r\n")
                    .getBytes("UTF-8");//HTTP レスポンスを作成
            os.write(response);//HTTP レスポンスを送信
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //各クライアントからのデータを全クライアントに送信するメソッド
    public static void sendAll(int number , byte[] sendHead , String line){
        try {
            for (int i = 0; i <member ; i++) {
                os[i].write(sendHead);//ヘッダー出力
                os[i].write(line.getBytes("UTF-8"));//3バイト目以降に文字列をバイナリデータに変換して出力
                System.out.println((i+1)+"番目に"+(number+1)+"番目のメッセージを送りました!" );
            }
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

class ClientThread extends Thread{
    //各クライアントのフィールド
    private int myNumber;
    private Socket mySck;
    private InputStream myIs;
    private InputStreamReader myIsr;
    private BufferedReader myIn;
    private OutputStream myOs;

    //コンストラクタでインスタンスのフィールドに各値を代入
    public ClientThread(int n , Socket sck , InputStream is , InputStreamReader isr , BufferedReader in , OutputStream os) {
        myNumber=n;
        mySck=sck;
        myIs=is;
        myIsr=isr;
        myIn=in;
        myOs=os;
    }

    //Threadクラスのメイン
    public void run() {
        try {
            echo(myIs , myOs , myNumber);
        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //ソケットへの入力を無限ループで監視する ※125文字まで(126文字以上はヘッダーのフレームが変化する)
    public void echo(InputStream is , OutputStream os , int myNumber) {
        try{
            while(true) {
                byte[] buff = new byte[1024];//クライアントから送られたバイナリデータを入れる配列
                int lineData =is.read(buff);//データを読み込む
                for (int i = 0; i < lineData - 6; i++) {
                    buff[i + 6] = (byte) (buff[i % 4 + 2] ^ buff[i + 6]);//7バイト目以降を3-6バイト目のキーを用いてデコード
                }
                String line = new String(buff, 6, lineData - 6, "UTF-8");//デコードしたデータを文字列に変換
                byte[] sendHead = new byte[2];//送り返すヘッダーを用意
                sendHead[0] = buff[0];//1バイト目は同じもの
                sendHead[1] = (byte) line.getBytes("UTF-8").length;//2バイト目は文字列の長さ

                SocketFree.sendAll(myNumber , sendHead , line);//各クライアントへの送信は元クラスのsedAllメソッドで実行

                if (line.equals("bye")) break;//「bye」が送られたなら受信終了
            }
        } catch (Exception e)  {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

コードの解説

1.クライアントのナンバリングや接続数を把握するためのフィールド変数を追加。

2.複数のクライアントに対応するため、各フィールドを配列形式にする。
main関数で配列の初期化を行い、これまでの変数を編集。

3.複数クライアントを同時並行で処理するため、Threadクラスを継承したClientThreadクラスを作成する。
ハンドシェイクまではmain関数内で処理。
それ以降は各クライアント毎にClientThreadインスタンスを作成して並行処理。

Threadクラス:main関数から分岐して同時に処理することができる。
.start();でrun()が実行される謎仕様。

4.ClientThreadクラスにecho関数を移動。
データ受信 ⇒ デコード ⇒ データ送信
この内データ送信はmain関数が所属するクラスで実行するため、新しくsendAllメソッドを作成。
※データ送信は全クライアントに対して実行。
 全クライアントを扱っているのはmain関数のクラス。

5.その他、メソッドの型や変数名等の細かい修正。

実行結果

  1. サーバープログラムを起動
  2. 複数のブラウザからlocalhostへ接続

キャプチャ5.JPG

1つのクライアントから送信すると、全てのブラウザで同じテキストが表示される。
どのクライアントからテキストを送信しても表示結果は同じ。

その他

改善点

・ブラウザを閉じたときのエラー処理
「bye」を送信すると監視のループを抜けてエラーなく終了できるが、監視中にブラウザを閉じるとエラーが発生しサーバーが停止してしまう。

・closeメソッドの追記
本来はソケット通信を終了する際はcloseメソッドを使用して切断するが、今回は使用していない。

・デコード処理の強化
デコードの処理を甘えた為、125文字以下の通信しかできない。
126文字以上になるとwebソケットのフレームが変化するため、場合分けが必要になってくる。

・セキュリティ関連の強化
今回は何も対策しておらずセキュリティがガバガバなので、送受信の方法を考える必要がある。

備考

・ポートの解放
プログラム内で指定したポートが既に使用されている場合、サーバー起動時にエラーが発生する。
1. コマンドプロンプトで>netstat -anoを実行
2. 指定したローカルアドレスからPIDを確認
3. タスクマネージャーのプロセスからPDIのアプリケーションを確認
4. 必要ない物ならプロセスを終了してポートを開放
キャプチャ6.JPG

・ハンドシェイク
ソケット通信を行う前に、クライアントとサーバー間で合意を取る必要がある。
クライアントからサーバーへ接続要求があるなら、それに対してレスポンスを返す必要がある。
レスポンスの内容は参考ページに詳しく記述されている。

・受信データのデコード
webソケットのデータにはフレームフォーマットが存在し、そのフレームに沿ってバイナリデータが送信されている。
データをデコードするには、フレーム構造を理解して1バイト毎に変換していく必要がある。
フレームフォーマットの詳細やデコード方法は参考ページに詳しく記述されている。

感想

思った以上にややこしい。

ググってもなかなか出てこないし、マイナーな方法?
よく見かけるのは、サーバープログラムを「Node.js」で記述したもの。
ドットインストールの動画ではNode.jsで作成しており、レスポンスやデコード等の複雑な処理はしていなかった。
多分、Node.jsの勉強をした方が早い。

Javaでも、アノテーションを利用するとデコード処理が無くても通信できるような記事も見かけたが、今回はパスした。
パスしない方が良かったかも。

プログラミングの勉強をしていたはずが、いつの間にかプロトコルの勉強になっていた・・・
初学者が首を突っ込むところじゃない気がしたが、勉強になったので良し。

間違えているところがあったらスマン。
気が付けば修正する。
改善すべき点もかなり多いので、可能なら更新していく。

参考ページ

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

JavaとJavaScriptでwebブラウザのソケット通信

背景

ネット上で多人数同時通信のものを作成するため、ソケット通信について実践したことを記述。
チャットやオンラインゲームのような、複数でのリアルタイム通信を希望。

「webブラウザ」で実行できるようなプログラムを「Java」で組みたかったのだが、ググってもあまり出てこないので記述しておく。
(知識不足でネットワークプログラミングという言葉も知らなかったため、ググり方に問題があるかも…)

データの読み書き等の超基本的なところからスタート。

ソケット通信

通信方法の1種。
サーバーとクライアントに対して、HTTPのgetアクセスやpostアクセスのように単一方向への通信ではなく、リアルタイムでの双方向通信が可能。
⇒ サーバープログラムとクライアントプログラムの2つが必要。

詳しくはググってほしい。

実行環境

  • windowds
  • eclipse (Tomcat8.0)
  • Google Chrome

言語

サーバープログラム:Java
クライアントプログラム:JavaScript

成果物

  1. webブラウザで動くチャットアプリ

開発手順

  1. クライアント側のプログラムを組む
  2. サーバー側のプログラムを組む
  3. クライアントプログラムからサーバーへアクセス
  4. サーバーからの応答があればOK

実践項目

  1. クライアントエコーによるソケット通信
  2. 単一クライアントとサーバーのチャットアプリ
  3. 複数クライアントとサーバーのチャットアプリ
  4. 複数クライアントでのじゃんけんシステム

1. クライアントエコーによるソケット通信

クライアント側のプログラムはJavaScriptで記述する。
ここはほぼパクり。
サーバー側のプログラムは必要なし。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>WebSocket通信</title>
        <script type="text/javascript">

            var wSck= new WebSocket("echo.websocket.org");// WebSocketオブジェクト生成

            wSck.onopen = function() {//ソケット接続時のアクション
                document.getElementById('show').innerHTML += "接続しました。" + "<br/>";
            };

            wSck.onmessage = function(e) {//メッセージを受け取ったときのアクション
                document.getElementById('show').innerHTML += e.data + "<br/>";
            };

            var sendMsg = function(val) {//メッセージを送信するときのアクション
                var line = document.getElementById('msg');//入力内容を取得
                wSck.send(line.value);//ソケットに送信
                line.value = "";//内容をクリア
            };
        </script>
    </head>

    <body>
        <div
            style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;"
            id="show"></div>
        <input type="text" size="80" id="msg" name="msg" />
        <input type="button" value="送信" onclick="sendMsg();" />
    </body>
</html>

コードの解説

1.Webソケットオブジェクトを生成し、引数にアドレスを代入。
var 変数 = new WebSocket("ws://IPアドレス:ポート番号");

今回はアドレスに「echo.websocket.org」を使用。
⇒クライアントから送信したものをそのまま返してくれる。
 サーバープログラムを作成しなくてもソケット通信が可能なため便利。

2.ソケット接続時のアクションを記述
ソケット.onopen = function() {・・・};
ソケット接続に成功したら、まずこの関数が実行される。

3.メッセージを受け取ったときのアクションを記述
ソケット.onmessage = function() {・・・};
サーバーからデータが送られて来たらこの関数が実行される。

4.メッセージを送る内容を記述
ソケット.send(・・・);

実行結果

キャプチャ.JPG
キャプチャ2.JPG
キャプチャ3.JPG

送信した内容がそのまま架空サーバーから送り返されて表示される。

2. 単一クライアントとサーバーのチャットアプリ

架空サーバーじゃなく、実際にサーバーを作成してクライアントからの送信内容をそのまま返信する。
サーバー側のプログラムをJavaで記述する。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Encoder;

class SocketFree {

    private static ServerSocket sSck;// サーバー用のソケット
    private static Socket sck;// 受付用のソケット
    private static InputStream is;// 入力ストリーム
    private static InputStreamReader isr;// 入力ストリームを読み込む
    private static BufferedReader in;// バッファリングによるテキスト読み込み
    private static OutputStream os;// 出力ストリーム

    public static void main(String[] args) {
        try {
            sSck=new ServerSocket(60000);//サーバーソケットのインスタンスを作成(ポートは60000)
            System.out.println("サーバーに接続したよ!");
            sck=sSck.accept();//接続待ち。来たらソケットに代入。
            System.out.println( "参加者が接続しました!");

            // 必要な入出力ストリームを作成する
            is=sck.getInputStream();//ソケットからの入力をバイト列として読み取る
            isr=new InputStreamReader(is);//読み取ったバイト列を変換して文字列を読み込む
            in=new BufferedReader(isr);//文字列をバッファリングして(まとめて)読み込む
            os=sck.getOutputStream();//ソケットにバイト列を書き込む

            //クライアントへ接続許可を返す(ハンドシェイク)
            handShake(in , os);

            //ソケットへの入力をそのまま返す ※125文字まで(126文字以上はヘッダーのフレームが変化する)
            echo(is , os);

        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //クライアントへ接続許可を返すメソッド(ハンドシェイク)
    public static void handShake(BufferedReader in , OutputStream os){
        String header = "";//ヘッダーの変数宣言
        String key = "";//ウェブソケットキーの変数宣言
        try {
            while (!(header = in.readLine()).equals("")) {//入力ストリームから得たヘッダーを文字列に代入し、全行ループ。
                System.out.println(header);//1行ごとにコンソールにヘッダーの内容を表示
                String[] spLine = header.split(":");//1行を「:」で分割して配列に入れ込む
                if (spLine[0].equals("Sec-WebSocket-Key")) {//Sec-WebSocket-Keyの行
                    key = spLine[1].trim();//空白をトリムし、ウェブソケットキーを入手
                }
            }
            key +="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";//キーに謎の文字列を追加する
            byte[] keyUtf8=key.getBytes("UTF-8");//キーを「UTF-8」のバイト配列に変換する
            MessageDigest md = MessageDigest.getInstance("SHA-1");//指定されたダイジェスト・アルゴリズムを実装するオブジェクトを返す
            byte[] keySha1=md.digest(keyUtf8);//キー(UTF-8)を使用してダイジェスト計算を行う
            Encoder encoder = Base64.getEncoder();//Base64のエンコーダーを用意
            byte[] keyBase64 = encoder.encode(keySha1);//キー(SHA-1)をBase64でエンコード
            String keyNext = new String(keyBase64);//キー(Base64)をStringへ変換
            byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
                    + "Connection: Upgrade\r\n"
                    + "Upgrade: websocket\r\n"
                    + "Sec-WebSocket-Accept: "
                    + keyNext
                    + "\r\n\r\n")
                    .getBytes("UTF-8");//HTTP レスポンスを作成
            os.write(response);//HTTP レスポンスを送信
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //ソケットへの入力を無限ループで監視するメソッド
    public static void echo(InputStream is , OutputStream os) {
        try{
            while(true) {
                byte[] buff = new byte[1024];//クライアントから送られたバイナリデータを入れる配列
                int lineData =is.read(buff);//データを読み込む
                for (int i = 0; i < lineData - 6; i++) {
                    buff[i + 6] = (byte) (buff[i % 4 + 2] ^ buff[i + 6]);//7バイト目以降を3-6バイト目のキーを用いてデコード
                }
                String line = new String(buff, 6, lineData - 6, "UTF-8");//デコードしたデータを文字列に変換
                byte[] sendHead = new byte[2];//送り返すヘッダーを用意
                sendHead[0] = buff[0];//1バイト目は同じもの
                sendHead[1] = (byte) line.getBytes("UTF-8").length;//2バイト目は文字列の長さ
                os.write(sendHead);//ヘッダー出力
                os.write(line.getBytes("UTF-8"));//3バイト目以降に文字列をバイナリデータに変換して出力

                if (line.equals("bye")) break;//「bye」が送られたなら受信終了
            }
        } catch (Exception e)  {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

コードの解説

1.ソケットやストリーム等の必要な変数を宣言。

2.サーバーソケットのインスタンスを作成。
ServerSocket 変数 = new ServerSocket(ポート番号)

3.acceptメソッドでクライアントからの接続を待機。

4.接続されたクライアントの入出力ストリームを作成する。

5.クライアントからの接続要求に対してレスポンスを返す。(handShakeメソッド)

リクエストのヘッダーに必要な情報が記述されているため、編集して送り返す。
編集内容の詳細はコードのコメント及び参照サイトを参考。
要は、リクエストのヘッダーにある『WebSocketキー』を送り返す必要がある。
『ハンドシェイク』

6.ソケット接続後、無限ループでクライアントからの入力をそのまま返す。(echoメソッド)

クライアントから送信されたデータはバイナリデータとしてエンコードされている。
サーバー側でデコードし、再度エンコードしてクライアントへ送信する。
デコード内容の詳細はコードのコメント及び参照サイトを参考。
※送信された文字列の長さによってヘッダーのフレームが変化するので、ここでは簡単のため125文字以下に限定する。

7.クライアントから「bye」が送信されるとループを抜け受信終了

実行結果

  1. サーバープログラムを起動
  2. ポート番号を指定し、localhostへ接続

キャプチャ3.JPG

※クライアントプログラムのアドレスを変更しておく(今回は60000番)
var 変数 = new WebSocket("ws://127.0.0.1:60000");

架空サーバー(echo.websocket.org)を使用したときと同様の結果が得られた。
今回はエコーするだけだが、サーバー側でデータをいろいろ弄って返信することも可能。

複数クライアントとサーバーのチャットアプリ

サーバープログラムを編集し、複数のクライアントからの接続を許可する。
また、各クライアント間でリアルタイム通信となるようにする。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Encoder;

class SocketFree {

    private static ServerSocket sSck;// サーバー用のソケット
    private static Socket[] sck;// 受付用のソケット
    private static InputStream[] is;// 入力ストリーム
    private static InputStreamReader[] isr;// 入力ストリームを読み込む
    private static BufferedReader[] in;// バッファリングによるテキスト読み込み
    private static OutputStream[] os;// 出力ストリーム
    private static ClientThread user[];//各クライアントのインスタンス
    private static int member;// 接続しているメンバーの数

    public static void main(String[] args) {
        int n=0;
        int maxUser=100;
        //各フィールドの配列を用意
        sck=new Socket[maxUser];
        is=new InputStream[maxUser];
        isr=new InputStreamReader[maxUser];
        in=new BufferedReader[maxUser];
        os=new OutputStream[maxUser];
        user=new ClientThread[maxUser];

        try {
                sSck=new ServerSocket(60000);//サーバーソケットのインスタンスを作成(ポートは60000)
                System.out.println("サーバーに接続したよ!");
                while(true) {
                    sck[n]=sSck.accept();//接続待ち。来たらソケットに代入。
                    System.out.println( (n+1)+"番目の参加者が接続しました!");

                    // 必要な入出力ストリームを作成する
                    is[n]=sck[n].getInputStream();//ソケットからの入力をバイト列として読み取る
                    isr[n]=new InputStreamReader(is[n]);//読み取ったバイト列を変換して文字列を読み込む
                    in[n]=new BufferedReader(isr[n]);//文字列をバッファリングして(まとめて)読み込む
                    os[n]=sck[n].getOutputStream();//ソケットにバイト列を書き込む

                    //クライアントへ接続許可を返す(ハンドシェイク)
                    handShake(in[n] , os[n]);

                    //各クライアントのスレッドを作成
                    user[n] = new ClientThread(n , sck[n] , is[n] , isr[n] , in[n] , os[n]);
                    user[n].start();

                    member=n+1;//接続数の更新
                    n++;//次の接続者へ
                }
            } catch (Exception e) {
                System.err.println("エラーが発生しました: " + e);
        }
    }

    //クライアントへ接続許可を返すメソッド(ハンドシェイク)
    public static void handShake(BufferedReader in , OutputStream os){
        String header = "";//ヘッダーの変数宣言
        String key = "";//ウェブソケットキーの変数宣言
        try {
            while (!(header = in.readLine()).equals("")) {//入力ストリームから得たヘッダーを文字列に代入し、全行ループ。
                System.out.println(header);//1行ごとにコンソールにヘッダーの内容を表示
                String[] spLine = header.split(":");//1行を「:」で分割して配列に入れ込む
                if (spLine[0].equals("Sec-WebSocket-Key")) {//Sec-WebSocket-Keyの行
                    key = spLine[1].trim();//空白をトリムし、ウェブソケットキーを入手
                }
            }
            key +="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";//キーに謎の文字列を追加する
            byte[] keyUtf8=key.getBytes("UTF-8");//キーを「UTF-8」のバイト配列に変換する
            MessageDigest md = MessageDigest.getInstance("SHA-1");//指定されたダイジェスト・アルゴリズムを実装するオブジェクトを返す
            byte[] keySha1=md.digest(keyUtf8);//キー(UTF-8)を使用してダイジェスト計算を行う
            Encoder encoder = Base64.getEncoder();//Base64のエンコーダーを用意
            byte[] keyBase64 = encoder.encode(keySha1);//キー(SHA-1)をBase64でエンコード
            String keyNext = new String(keyBase64);//キー(Base64)をStringへ変換
            byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
                    + "Connection: Upgrade\r\n"
                    + "Upgrade: websocket\r\n"
                    + "Sec-WebSocket-Accept: "
                    + keyNext
                    + "\r\n\r\n")
                    .getBytes("UTF-8");//HTTP レスポンスを作成
            os.write(response);//HTTP レスポンスを送信
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //各クライアントからのデータを全クライアントに送信するメソッド
    public static void sendAll(int number , byte[] sendHead , String line){
        try {
            for (int i = 0; i <member ; i++) {
                os[i].write(sendHead);//ヘッダー出力
                os[i].write(line.getBytes("UTF-8"));//3バイト目以降に文字列をバイナリデータに変換して出力
                System.out.println((i+1)+"番目に"+(number+1)+"番目のメッセージを送りました!" );
            }
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

class ClientThread extends Thread{
    //各クライアントのフィールド
    private int myNumber;
    private Socket mySck;
    private InputStream myIs;
    private InputStreamReader myIsr;
    private BufferedReader myIn;
    private OutputStream myOs;

    //コンストラクタでインスタンスのフィールドに各値を代入
    public ClientThread(int n , Socket sck , InputStream is , InputStreamReader isr , BufferedReader in , OutputStream os) {
        myNumber=n;
        mySck=sck;
        myIs=is;
        myIsr=isr;
        myIn=in;
        myOs=os;
    }

    //Threadクラスのメイン
    public void run() {
        try {
            echo(myIs , myOs , myNumber);
        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }

    //ソケットへの入力を無限ループで監視する ※125文字まで(126文字以上はヘッダーのフレームが変化する)
    public void echo(InputStream is , OutputStream os , int myNumber) {
        try{
            while(true) {
                byte[] buff = new byte[1024];//クライアントから送られたバイナリデータを入れる配列
                int lineData =is.read(buff);//データを読み込む
                for (int i = 0; i < lineData - 6; i++) {
                    buff[i + 6] = (byte) (buff[i % 4 + 2] ^ buff[i + 6]);//7バイト目以降を3-6バイト目のキーを用いてデコード
                }
                String line = new String(buff, 6, lineData - 6, "UTF-8");//デコードしたデータを文字列に変換
                byte[] sendHead = new byte[2];//送り返すヘッダーを用意
                sendHead[0] = buff[0];//1バイト目は同じもの
                sendHead[1] = (byte) line.getBytes("UTF-8").length;//2バイト目は文字列の長さ

                SocketFree.sendAll(myNumber , sendHead , line);//各クライアントへの送信は元クラスのsedAllメソッドで実行

                if (line.equals("bye")) break;//「bye」が送られたなら受信終了
            }
        } catch (Exception e)  {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

コードの解説

1.クライアントのナンバリングや接続数を把握するためのフィールド変数を追加。

2.複数のクライアントに対応するため、各フィールドを配列形式にする。
main関数で配列の初期化を行い、これまでの変数を編集。

3.複数クライアントを同時並行で処理するため、Threadクラスを継承したClientThreadクラスを作成する。
ハンドシェイクまではmain関数内で処理。
それ以降は各クライアント毎にClientThreadインスタンスを作成して並行処理。

Threadクラス:main関数から分岐して同時に処理することができる。
.start();でrun()が実行される謎仕様。

4.ClientThreadクラスにecho関数を移動。
データ受信 ⇒ デコード ⇒ データ送信
この内データ送信はmain関数が所属するクラスで実行するため、新しくsendAllメソッドを作成。
※データ送信は全クライアントに対して実行。
 全クライアントを扱っているのはmain関数のクラス。

5.その他、メソッドの型や変数名等の細かい修正。

実行結果

  1. サーバープログラムを起動
  2. 複数のブラウザからlocalhostへ接続

キャプチャ5.JPG

1つのクライアントから送信すると、全てのブラウザで同じテキストが表示される。
どのクライアントからテキストを送信しても表示結果は同じ。

その他

改善点

・ブラウザを閉じたときのエラー処理
「bye」を送信すると監視のループを抜けてエラーなく終了できるが、監視中にブラウザを閉じるとエラーが発生しサーバーが停止してしまう。

・closeメソッドの追記
本来はソケット通信を終了する際はcloseメソッドを使用して切断するが、今回は使用していない。

・デコード処理の強化
デコードの処理を甘えた為、125文字以下の通信しかできない。
126文字以上になるとwebソケットのフレームが変化するため、場合分けが必要になってくる。

備考

・ポートの解放
プログラム内で指定したポートが既に使用されている場合、サーバー起動時にエラーが発生する。
1. コマンドプロンプトで>netstat -anoを実行
2. 指定したローカルアドレスからPIDを確認
3. タスクマネージャーのプロセスからPDIのアプリケーションを確認
4. 必要ない物ならプロセスを終了してポートを開放
キャプチャ6.JPG

・ハンドシェイク
ソケット通信を行う前に、クライアントとサーバー間で合意を取る必要がある。
クライアントからサーバーへ接続要求があるなら、それに対してレスポンスを返す必要がある。
レスポンスの内容は参考ページに詳しく記述されている。

・受信データのデコード
webソケットのデータにはフレームフォーマットが存在し、そのフレームに沿ってバイナリデータが送信されている。
データをデコードするには、フレーム構造を理解して1バイト毎に変換していく必要がある。
フレームフォーマットの詳細やデコード方法は参考ページに詳しく記述されている。

感想

思った以上にややこしい。

ググってもなかなか出てこないし、マイナーな方法?
よく見かけるのは、サーバープログラムを「Node.js」で記述したもの。
ドットインストールの動画ではNode.jsで作成しており、レスポンスやデコード等の複雑な処理はしていなかった。
多分、Node.jsの勉強をした方が早い。

Javaでも、アノテーションを利用するとデコード処理が無くても通信できるような記事も見かけたが、今回はパスした。
パスしない方が良かったかも。

プログラミングの勉強をしていたはずが、いつの間にかプロトコルの勉強になっていた・・・
初学者が首を突っ込むところじゃない気がしたが、勉強になったので良し。

間違えているところがあったらスマン。
気が付けば修正する。
改善すべき点もかなり多いので、可能なら更新していく。

参考ページ

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

最低限知っておきたいBootstrapの使い方

はじめに

ここでは、Bootstrapの基本的な利用方法について解説します。

スターターテンプレート

Bootstrapには、色々なデザインが用意されていますが、その大元となる「スターターテンプレート」があります。

よく使うデザイン

Jumbotron

ウェブページのヘッダーのテンプレートとして使いやすいデザインです。
https://getbootstrap.jp/docs/4.4/components/jumbotron/

Alerts

処理完了やエラーメッセージの表示に使えます。
alert-のclass属性は、色の指定にも使えるので便利です。

https://getbootstrap.jp/docs/4.4/components/alerts/

Buttons

他のページへのリンクやフォームの送信ボタンなどに使えます。
こちらもbtn-successなどのクラス属性は、色を変える時に使えます。

https://getbootstrap.jp/docs/4.4/components/buttons/

Cards

ブログの記事一覧で個々の記事を表示したりするのに使えます。

https://getbootstrap.jp/docs/4.4/components/card/

Forms

新規登録や記事投稿などのフォームに使えます。

https://getbootstrap.jp/docs/4.4/components/forms/

Navbar

ヘッダー部分のメニューバーなどとして使えます。

https://getbootstrap.jp/docs/4.4/components/navbar/

Pagination

記事一覧ページなどのページネーションに使えます。

https://getbootstrap.jp/docs/4.4/components/pagination/

チートシート

Bootstrapのチートシートのページには、様々なデザインと、対応するコードがまとめられています。

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

p5.toioのα版リリースについて

サマリ

p5.jsの環境でtoio™のプログラミングを簡単に楽しめるライブラリp5.toioα版(v0.5.0)をリリースいたしました。https://github.com/tetunori/p5.toio

ブラウザからアクセスするだけで、下記の様なサンプルをすぐに確認・制作できます。
toioのCubeと、p5.js及びp5.toioの多様なライブラリ群を利用することで、今まで画面内に閉じていたプログラミングの表現を簡単に現実世界へと拡張することが出来ます。
芸術作品だけでなく、プロトタイプ作成や、デーリーコーディング、ゲーム制作、小学生からのテキストプログラミングなど色々と使ってみてください。(α版なので、いろいろ制約事項有ります。)

P5 Editorでもすぐに始められますよ!(OpenProcessingは非対応:sob:
P5 Editor: p5.toio basic sample

成果物

minifyした成果物はこちらですが、TypeScriptで書いた元のソースコード群もどうぞ。
※詳細は後述ですがAPI Reference(TypeDoc)もあるよ。
いずれもMIT Licenseです。

開発モチベーション

課題

toio™Core Cubeを使って趣味で色々なものを作ってきて、下記の課題を感じています。

  • 手軽に開発できない
    • Node.jsはまだ導入が簡単な部類だが、とはいえ『yarn/npm install実行したのに、なんか動かない』問題でどれだけの人類の心を折ってきただろうか。ほんとの初心者にはつらいと思う。
  • toio™Visual Programmingは手軽だけどスケールしない
    • コピペできない、成果物をシェアしにくい、凝ったロジックを作ると膨大なブロック数になり、メンテは・・・
    • 別途アプリ立ち上げが必要だったり、始めるまでにステップ数が最小ではない
  • 個人的にはWebBluetoothを良く使うが、手続きが多くてしんどい
    • 俺はただ、ランプをつけたいだけなのに、なぜこんなにコード行数必要なのだ?何回Promiseさせるんだい・・・と。
    • ↑WebBluetoothは悪くなく、クラス化していない自分が悪い。
  • toioの制御ライブラリがプリミティブなものしか存在しない。
    • もうMath.atan2の計算したくない。
  • 成果物をシェアするのに、毎回GitHubリポジトリ作るのは面倒だな
  • JSの開発環境に関する知識が周回遅れになってしまっていて、話題についていけないので、モダンな開発手法に再チャレンジしたい

きっかけ

全然別の話題ですが、最近Daily Coding勢のツイートがよくタイムラインに表示されており、P5 EditorOpenProcessingなどのオンラインツールを使って、気軽に楽しそうにプログラミングをし成果物をシェアされていました。

早速僕もp5.jsの勉強がてらOpenProcessingで色々作っては公開する日々を続けてみましたが、これは良い。

本当に気軽だし、p5.jsのライブラリも多いのでエンドレスなやりこみ要素もある。

この手軽さでtoioもプログラミングできないか?と思い、P5 EditorでWebBluetoothを仮実装してみたところうまく動作したので、課題を解決するソリューションを作ってみようと奮起した次第であります。

課題解決の方針

下記を満たす、JSライブラリp5.toioを作成する

  • URLにアクセスするだけで、手軽に始められる、使える、創れる。 ⇒ toio™初心者歓迎
  • P5 Editorで動作する ⇒ URLで作品シェアできる。
  • WebBluetoothでスマホ・PC問わず、広く使える
  • 面倒な処理はぜーーーんぶラップしてAPI提供 ⇒ 『追いかけっこ』すら1行とか。
  • JSのClass, ES6記法, TypeScript, ESLint, Prettier, TypeDoc, Jest, Docusaurus など自分にとって新しい技術領域にチャレンジする。

p5.toioのライブラリ構成

p5.toioは2つのクラスP5tCubeP5tIdから構成されています。

P5tCubeクラスは、キューブを簡単にコントロールするためのたくさんのAPIやユーティリティを提供します。詳細はP5tCube Interfaceでご確認ください。

また、P5tIdクラスは、toio™のマットやカード、ステッカーなどに印刷されているtoio™IDについてのAPI・プロパティ群となっています。こちらも、P5tId Interfaceをご参照ください。

なお、今回説明した2クラスはp5.jsに特化する形でライブラリ化していますが、この2つのクラス以外は通常のWebBluetoothでもほぼ同等の機能が使用可能なので、そちらもぜひご利用ください。

使い方

動作環境

toio™Core Cube

少なくとも1つは必要です。最近単売が始まったので、お手軽に導入できますね。Chargerも忘れずに!
また、本ライブラリを使う際は、スマホアプリから最新のFWへアップデートを御願いします

PC環境

本ライブラリはWebBluetoothへ依存しているため、下記の環境での動作となります。

  • OS: Windows, MacOS, Android。iOS/iPadOSは非サポート。
  • Browser: Google Chromeを強く推奨。

また、P5 Editorでは動作確認できていますが、OpenProcessingは残念ながら、動作出来ませんでした。(iFrameでSketchが動く都合、WebBluetoothに必要なユーザーアクションが不正と判断されるため)

ライブラリのインポート

<head>の中で、2つのp5.jsスクリプトp5.jsp5.sound.min.jsの後に、1行読み込んでください。

<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/addons/p5.sound.min.js"></script>

<!-- INSERT HERE -->
<script src="https://tetunori.github.io/p5.toio/dist/0.5.0/p5.toio.min.js"></script>

Minifyする前のライブラリも一応あるので、そちらでもOKです。

<script src="https://tetunori.github.io/p5.toio/dist/0.5.0/p5.toio.js"></script>

スケッチで使う

1. Cubeと接続しよう

API P5tCube.connectNewP5tCube()をコールして、Promiseresolveされた、P5tCubeインスタンスを受け取ってください。これだけで、すべてのAPIにアクセス可能です!簡単!
なお、このAPIはWebBluetoothに依存しておりますので、接続の処理は必ず、ユーザーアクションの関数mouseClicked()keyPressed()などで実施してください。さもなくばエラーが出てしまいます。

const gCubes = [];

function mouseClicked() {
  P5tCube.connectNewP5tCube().then( cube => {
    // 'cube' is an instance of connected toio™Core Cube.
    // Now you can call any API!
    gCubes.push( cube );
  } );
}

P5 Editor Sample 1: Turn Light On

2. APIを発行しよう

使用例 1: ランプを点灯する

P5tCubeインスタンスがあれば、API発行は簡単です。
得られたインスタンスがcubeだとして、ランプを白につけてみましょう。

// Turn the light on with white
cube?.turnLightOn( 'white' );

Done. これだけ。
P5 Editor Sample 1: Turn Light On

こんな感じで、全ての機能にアクセスできますので、詳しくは、下記のTypeDoc API Referenceを参照してください。後日、ちゃんとした説明のドキュメント作成します。
- P5tCube
- P5tId

使用例 2: MIDI メロディーを再生する

// Play sequence C-D-E
cube?.playMelody( [ 
  { note: 0x3C, duration: 0x1E }, 
  { note: 0x3E, duration: 0x1E }, 
  { note: 0x40, duration: 0x1E } 
] );

P5 Editor Sample 2: Play MIDI melody

使用例 3: マウスのX座標の方を向く

複数のキューブをtoioCollectionの土俵マットの右上においてください。複数のキューブがマウスのX座標の方を追いかけて回転します。

// Keep on gazing at mouse point
for( const cube of connectedCubeArray ){
  const x = Math.floor(mouseX * 300 / windowWidth + 200);
  const y = 144;
  const speed = 115; 
  cube?.turnToXY( x, y, speed );
}

P5 Editor Sample 3: Keep on gazing at mouse point

使用例 4: 2つのキューブのインタラクション

2つのキューブをtoioCollectionの土俵マットの上においてください。

// Keep on gazing at the othre Cube
const speed = 115;
cubeP?.turnToCube( cubeQ, speed );

P5 Editor Sample 4-1: Keep on gazing at the other Cube

// Keep on chasing the othre Cube
const moveType = P5tCube.moveTypeId.withoutBack;
const speed = 80;
cubeP?.moveToCube( cubeQ, speed, moveType );

P5 Editor Sample 4-2: Keep on chasing the other Cube

使用例 5: カラータイルのマット

キューブをtoioCollectionのカラータイルマットの上においてください。

  // Set background color with touched colored tile on mat
  const color = P5tId.ColorTileMat.getTileColor(cube?.x, cube?.y);
  background( color );

P5 Editor Sample 5: Change background color with touched mat color

3. Eventを受信しよう

本ライブラリでは、各種通知の取り扱い方法として、addEventListenerと、p5.jsっぽいコールバック関数の定義(mouseClicked()keyPressed()等と一緒)の両方をサポートしております。

使用例 6: addEventListner

// Button press event
const type = 'buttonpress';
cube?.addEventListener(type, ()=>{
  console.log(type);
});
// Posture change event
const type = 'sensorposturechange';
cube?.addEventListener(type, (posture)=>{
  console.log(type, posture);
});

P5 Editor Sample 6: addEventListner

使用例 7: コールバック関数定義

もしこれらの関数が定義されていたら、通知が発生次第コールバックされます。

const cubePositionIdChanged = (info) => {
  console.log('cubePositionIdChanged!', info);
}

const cubeStandardIdChanged = (info) => {
  console.log('cubeStandardIdChanged!', info);
}

P5 Editor Sample 7: Callback definition

以下に全てのコールバック関数定義をリストアップします。

const onButtonPressed()
const onButtonReleased()
const onBatteryLevelChanged(batteryLevel: number)
const onFlatChanged(flat: boolean)
const onCollisionOccurred()
const onDoubleTapped()
const onPostureChanged(posture: string)
const onPositionIdChanged(info: positionIdInfo)
const onStandardIdChanged(info: standardIdInfo)

Tips

パフォーマンスを上げるには

CubeのAPIコールにはasyncを使おう

もちろんPC環境にもよりますが、Cubeへのコマンドとp5.js上の描画処理等をシーケンシャルに動かすと両方のパフォーマンスが落ちてしまうことが多いです。

そのため、それほど高いフレームレートを要求しないCubeのAPI発行はは下記の様に非同期関数としてもらえると、全体のパフォーマンスが向上します。

function draw() {
  // Cube control command with async
  asyncCubeControl();

  // Then code your sketch...
  ellipse( mouseX, mouseY, 20, 20 );
}

async function asyncCubeControl() {
  // Cube control.
}

フレームレートあげる

このライブラリでは、僕のプアなPC環境でも満足に動くように、WebBluetooth経由のAPI発行レートを15fpsに抑えています。(どんなに早く発行しても、15fpsを超えるものは破棄される)
そのため、位置情報の追従などは、若干精度に問題があるように感じるかたもいらっしゃると思います。そんなときはCubeのフレームレートを変更(上限30fps)してあげると、パフォーマンス向上する場合がありますので、お試しください。新しいMacBookなどではめっちゃ改善しますよ。

cube?.setFrameRate(30);

わかっている問題点

特定のWindows環境でのみですが、moveToMulti APIが、3位置以上の指定をするとエラーが出てしまっています。Mac等では問題ない。

制約事項

p5.toioはまだα版(0.5.0)のため、テストコードや詳細なAPIの説明資料、豊富なサンプル等が内包されていません。また、API仕様も改変する可能性もありますので、ご了承くださいませ。
次回のβ版(0.8.0)ではそのあたりも改善したものにする予定ですので、ご期待してお待ちください。

Enjoy!!!

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

vueで改行に合わせて伸縮するテキストフォーム

実装

textareaを利用することを想定。rowsの値を変更してformを伸び縮みさせている。

function fitToForm(target: HTMLFormElement, text: string, defaultRows = 1): void {
    const lines = text.match(/\n/g)?.length || 1;
    if (defaultRows <= lines) {
      target.rows = lines + 1;
    }
    if (defaultRows > lines) {
      target.rows = defaultRows;
    }
}

利用イメージ

v-model渡しているstateをfitToFormの第二引数に渡しています。
第三引数にtextareaのデフォルトのrowsを設定すると、それ以上小さくなりません。

<textarea
    v-model="comment"
    placeholder="コメントを入力"
    rows="4"
    type="text"
    @input="fitToForm($event.target, state.comment, 4)"
/>

完成gif

May-17-2020 14-58-11.gif

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

deno で遊んでみよう

Image

deno とは

deno は Node.js の製作者 Ryan Dahl 氏により開発された TypeScript を標準でサポートするランタイムです。
Node.js のときに得た教訓をもとに設計がされていますが、Node.js と互換性のある実装というわけではありません。
JavaScript のエンジン自体は Node.js と同じく V8 を利用しています。
公式サイトはこちら

2020-05-13 にめでたく v1.0 がリリースされたので今回ちょっとだけ遊んでみることにします。
https://deno.land/v1

Node.js との違い

たくさんありますが大きな違いだけ。

  • TypeScript が標準でサポートされる
    • 簡単なものであれば tsconfig.json の用意はしなくて良い
  • パッケージの取得はソースコードの import 文をもとに deno が実行時に取得する
    • スクリプトはすべて ES Module 方式になっている
    • npm や yarn のようなパッケージマネージャは今の所不要
    • バージョニングは URL をベースに行う
  • ファイルアクセスやネットワークなどのセキュリティフラグがいくつかある
    • 実行時に指定したもの以外は基本的にブロックされる

細かなところは公式サイトをご覧ください。

セットアップ

deno はシングルバイナリで動作するランタイムなので Github の Releases から取得した zip を展開して得られる実行ファイルをパスの通ったところに配置すれば直ぐに利用ができます。
公式ではこの作業を簡略化するための deno_install というプロジェクトが立ち上がっておりワンライナーでインストールが可能です。

  • Linux / WSL / MacOS
    • curl -fsSL https://deno.land/x/install/install.sh | sh
  • MacOS
    • brew install deno (確認時点ではLinuxBrewには非対応)
  • Cargo
    • cargo install deno

インストール後はパスを通したりするよう指示が出たりしますが適切に対処しましょう。
正しくインストールされていればバージョン情報を確認できるようになっています。
TypeScript のバージョンまで出てるのが斬新ですね。

version
❯ deno --version
deno 1.0.0
v8 8.4.300
typescript 3.9.2

初めての deno

公式にリモートからコードを取得して直接実行するサンプルが用意されていますので試しに実行してみましょう。

deno run https://deno.land/std/examples/welcome.ts

実行すると下記のような出力を得ます。見たらわかりますが、リモートのURLを直接実行することができ、スクリプトがコンパイルされ実行されていますね。

❯ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno ?

今回実行したスクリプトは https://deno.land/std/examples/welcome.ts ですが直接ブラウザで開くとこのように中身を確認できます。
Image

あっけなく終わってしまいましたが deno のスクリプトを実行する事ができました。
ここまででは Node.js をインストールして サンプルスクリプトを実行したのとあまり大差ないのでもう少し遊んでみましょう。

deno をより体感する

続いても公式サイトにあるサンプルスクリプトを利用します。(自分好みにスタイルを変更しています)
下記のファイルをダウンロードするか写経して server.ts として保存しましょう。

server
import { serve } from 'https://deno.land/std@0.50.0/http/server.ts'

const s = serve({ port: 8000 })
console.log('http://localhost:8000/')
for await (const req of s) {
  req.respond({ body: 'Hello World ?\n' })
}

早速実行してみます。

server.tsの実行結果
❯ deno run ./server.ts
Compile file:///path/to/workspace/server.ts
Download https://deno.land/std@0.50.0/http/server.ts
...
Download https://deno.land/std@0.50.0/bytes/mod.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at Object.listen ($deno$/ops/net.ts:51:10)
    at listen ($deno$/net.ts:152:22)
    at serve (https://deno.land/std@0.50.0/http/server.ts:261:20)
    at file:///path/to/workspace/server.ts:2:11

ぞろぞろとパッケージが取得されていき、実行されるかと思いきやエラーになりました。
これは最初に説明した deno のセキュリティ機構によるもので、deno run したときに許可した権限以上の処理ができないようになっています。
今回の場合、サーバーアプリケーションを起動するために必要なネットワークに対する権限が実行時に付与されていないので、権限が足りないよということを指摘されています。

ということで、--allow-net をフラグに指定して権限を追加してリトライしてみると今度はうまく起動してくれました。
試しに curl でアクセスしてみると Hello World の出力を得ることができます。

server.tsの実行結果
❯ deno run --allow-net ./server.ts &
http://localhost:8000/

❯ curl localhost:8000
Hello World ?

このようにして無事Webサーバーアプリケーションを deno で作って動作させることができました。

ライブラリやエコシステムについて

deno には npm や yarn といったパッケージマネージャが現時点では存在していません。
ここまでやってきたように、deno の標準パッケージも含め、必要な外部ライブラリの類はすべて実行時にリモートから取得される仕組みになっています。

加えて、deno では中央集権的なパッケージリポジトリは存在しておらず、代わりにスクリプトが HTTP でアクセス可能な場所に配置されていればそれを利用することが可能なようになっています。そのため、ライブラリの公開や利用については GitHub Pagespika.dev or jspm.io などの CDN を利用して手軽に誰でも公開することができます。
これは Node.js において npm が実質的に中央集権的なパッケージリポジトリとなってしまったことに基づく設計だそうで、「Node.jsに関する10の反省点」でも言及されています。

この方式においては npm を探して必要なパッケージを見つけるというスタイルが失われてしまいますが、それを防ぐ取り組みとして deno.land/x でパッケージを探せる仕組みが提供されています。これはパッケージそのものをホスティングしているのではなく、あくまで電話帳のような仕組みなので deno.land/x を利用してやりたいことに対応するパッケージを探したりここから利用することができるようになっています。

まとめ

この度めでたく v1.0 がリリースされた deno でちょっとだけ遊んでみました。軽く触っただけでも Node.js との違いがちょっとだけわかった気がします。今後、3rd Party ライブラリがより充実したりなど、エコシステムが拡充されてくるとより輝いてくるのではないかと期待しています。
何より標準で TypeScript が実行できる状態にあるというのが TypeScript 大好きクラブ 会員としては最高だなーという印象です。

Node.js との棲み分けや移行についてが気になるところですが、少なくとも Node.js のライブラリが今すぐそのまま deno でも使えるというわけでもないので、徐々に両対応するライブラリが出てきたり、deno 専用の革新的な何かが出てきたりすることを期待しています。(deno版のElectronに相当するなにかとか)

これから普段遣いしていくぞというところも v1.0 のお墨付きがあるので、単純に書き捨てのスクリプトを TypeScript で書くという用途においてはもう deno でやったほうが手軽さがあるのではないかな思ったりしました。

from Scrapbox ?

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

Chakraエンジン対応のWSHスクリプトをダブルクリックで実行できるようにする

概要

WSHで使われているJScriptは大変古い仕様のJavaScriptですが、コマンドラインから//E:でCLSID等を指定すると任意のスクリプトエンジンを設定でき、
Windows10ではEdge内蔵のChakraエンジンを指定することで新しい仕様のJavascriptがWSHで使えるようになっています。

WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )

しかし、WSHのキモであるダブルクリックでの実行では自動的に古いJScriptエンジンで実行してしまうので、
Chakraエンジンでの実行をバッチファイル埋め込みではなく.jsファイルをダブルクリックするだけ実行できるようにしてみました。

スクリプト(Ver.1)

古いJScriptエンジンで起動されたらChakraエンジンを指定して起動しなおすという単純な仕組みになっています。

chakraV1.js
/*@cc_on
@if (1)
//この部分は古いJScriptエンジンで実行されます。
var sh = WScript.CreateObject("WScript.Shell");

//Chakraエンジンを指定して同じスクリプトを実行
var command = sh.ExpandEnvironmentStrings("%windir%") + "\\System32\\wscript.exe //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} " + WScript.ScriptFullName;
sh.Exec(command);

//一応終了
WScript.Quit();

@end
@*/
//@if (0)
//↑のコメントは消さないこと!

//Chakraエンジンで動かすJavaScriptをここに記述します。以下は適当なサンプル。
class Test {
  constructor(name) {
    this._name = name;
  }

  output() {
    return 'Hello ' + this._name;
  }
}

class ExTest extends Test {
  output() {
    return  super.output() + '!!';
  }
}

var test = new Test('World');
WScript.Echo(test.output()); //Hello World

var extest = new ExTest('Worlds');
WScript.Echo(extest.output()); //Hello Worlds!!

//↓のコメントは消さないこと!
//@end

スクリプト(Ver.2)

WSHはUTF-8に対応していないので、Ver.1でハードコーディングした日本語文字列を表示しようとすると文字化けします。
直接メモ帳等のエディタやIDEでコーディングするならShift-JIS(ANSI)で保存すればVer.1でも対応できますが、
WSHはJScriptの範囲ではどうあがいてもUTF-8の文字列は正しく扱わないし、TypeScriptはどうもUTF-8固定でトランスパイルする模様(えぇ…)なので、
Chakraエンジンで再起動する前にUTF-8からShift-JISに変換した一時ファイルを出力することで対応してみました。

chakraV2.js
/*@cc_on
@if (1)
//この部分は古いJScriptエンジンで実行されます。
var sh = WScript.CreateObject("WScript.Shell");
var fso = WScript.CreateObject("Scripting.FileSystemObject");
var ads = WScript.CreateObject("ADODB.Stream");

//このスクリプトをUTF-8指定で読み込む
ads.charset =  "UTF-8";
ads.Open();
ads.LoadFromFile(WScript.ScriptFullName);
var text = ads.ReadText(-1);
ads.Close();

//一時ファイルとして_付きのファイル名を設定
var outf = fso.GetFile(WScript.ScriptFullName).ParentFolder.Path + "\\_" + WScript.ScriptName;

//Shift-JIS指定で一時ファイルに書き込み
ads.charset = "Shift-JIS";
ads.Open();
ads.WriteText(text, 1);
ads.SaveToFile(outf, 2);
ads.Close();

//Chakraエンジンを指定して一時ファイルとして書き込んだスクリプトを実行、終了まで待つ。
var command = sh.ExpandEnvironmentStrings("%windir%") + "\\System32\\wscript.exe //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} " + outf;
sh.run(command, 1, true);

//一時ファイルを削除
fso.DeleteFile(outf);

//一応終了
WScript.Quit();

@end
@*/
//@if (0)
//↑のコメントは消さないこと!

//Chakraエンジンで動かすJavaScriptをここに記述します。以下は適当なサンプル。
class Test {
    constructor(name) {
        this.name = name;
    }
    output() {
        return 'Hello ' + this.name;
    }
}
class ExTest extends Test {
    output() {
        return super.output() + '!!';
    }
}
var test = new Test('わーるど');
WScript.Echo(test.output()); //Hello わーるど

var extest = new ExTest('わーるず');
WScript.Echo(extest.output()); //Hello わーるず!!

//↓のコメントは消さないこと!
//@end

メリット

JavaScriptとして直接記述するならバッチファイルに埋め込みでもいいと思いますが、
TypeScript等のaltjsでトランスパイルした場合にファイル名の変更などの処理しなくても出力されたjavascriptファイルでそのままダブルクリック起動ができるようになるのがメリットだと思います。
TypeScriptでWSH(Chakraエンジン使用)スクリプトを作成する方法も記事にしたいと思っています。
→記事書きました。TypeScriptでWSH(Chakra)を書く(2020年版)

やっぱりバッチファイルに埋め込むほうがよさそう

こちら「Windowsでshebangもどき、またはバッチにスクリプトを埋め込む方法」をどうぞ。

仕組み

Chakraエンジンは従来のJScriptに存在していた条件付きコンパイルに対応していないこと、
JScriptの条件付きコンパイルではステートメントとコードをコメント内に埋め込める機能があることを利用しています。
これで条件付きコンパイルに対応していないChakraエンジンはコメント内の条件付きコンパイルをただのコメントとして扱い、
古いJScriptはコメント内の条件付きコンパイルを解釈して適用するコードを判断するので、
これによって起動したスクリプトエンジンの判別が可能になっています。
コンパイルエラーにならないようコメント付きの//@if (0)~//@endで囲むことでChakraが実行すべきコメント外のコードを古いJScriptエンジンでは実行対象外のコードになるようにしています。

参考

WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )
WSH JScriptを使いこなそう
JavaScriptの条件付きコンパイル

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

rails server起動時に「Could not find a JavaScript runtime.」とエラーが出る場合の対処法について

windowsでは出来ていたサーバーの立ち上げが買ったばかりのMacでは出来ず・・・。
色々調べた結果なんとか出来たのでまとめました。

エラー内容 

Progateの「Ruby on Railsの環境構築をしてみよう!(macOS)」の「5. ローカルでRailsサーバを立てる」を進め、rails sコマンドを実行したところ以下のエラーが発生。

% rails s
  (略)
Could not find a JavaScript runtime. 
See https://github.com/rails/execjs 
for a list of available runtimes. 
(ExecJS::RuntimeUnavailable)
 

ブラウザでlocalhost:3000と入力してもエラーで表示されず。
どうやらJavaScript runtimeとやらが存在しないためエラーが出ている模様。runtimeが分からず検索すると以下の記事を発見。

https://wa3.i-3-i.info/word13464.html

記事によると、

ランタイム(英:runtime)とは

プログラムとかを動かすとき(実行時)のこと。

あるいは

プログラムとかを動かすときに必要な部品のこと

とのこと。つまり、
Ruby自体にはJavaScriptを実行する機能が存在せず、実行するためにはruntimeというものが必要
ということらしい。

解決まで

とりあえずネットでCould not find a JavaScript runtime.と検索したところRuby on Railsチュートリアル の記事を発見。

https://railstutorial.jp/chapters/beginning?version=4.0#sec-rails_server

記事によると、

(JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムを確認してください。Node.jsが特にお勧めです)。

とのこと。早速エラー文中にあるGitHubのサイトへアクセス。

https://github.com/rails/execjs

Node.jsをクリックし、公式サイトからダウンロード後インストール。
その後もう一度コマンドでrails sを実行するとサーバーが立った。

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

初心者の初心者による初心者のためのJavaScriptのPromise

JavaScriptの醍醐味である非同期処理におけるPromiseの使い方等についてまとめてみました!



「 The Complete JavaScriipt Course 2020:Build Real Project! 」というUdemyのJavaScript動画講座を中心に学習し、不明点を公式リファレンス等で補う形のスタイルで学習を進めて得たものをアウトプットしたいと思います!

Promise

Promiseとは、サーバーとの通信のように失敗する可能性がある処理をする時に、それがうまくいったかどうかを判定して、その結果に応じてその後の処理を変えることが出来るオブジェクト。

Promiseを使用する際は、


  • Promiseに関数をひっつける

  • new演算子をPromiseの前に記載

  • Promiseの下にthenとcatchを記載

のような形が基本形だと思います。
    new Promise((resolve,reject) => { 
        // ここにPromiseによって成功or失敗を判定したい処理を記載
        resolve(/*処理が成功した時にthenに受け渡したい値を記載*/)

    })
    .then(result => {

        //処理が成功した時に実行する処置(コード)を記載

    })
    .catch(error => {

        //処理が失敗した時に実行する処置(コード)を記載

    });


Promiseに関数をひっつける

と書いていますが、MDNではその関数のことをexecutorと呼んでいます。
(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)

このexecutorが処理の結果(成功か失敗か)に応じて、その後の処理を決めていると思われます。

new演算子を記載

new演算子を使いインスタンスを作成することでPromiseがPromiseとして機能することになります。

then

then以降にはPromiseで記載した処理が成功した場合の処理を関数の形で書く。

引数を1つ与えることで、その引数に最初のPromise内部で取得したデータを引き渡すことができる。

catch

catch以降にはPromiseで記載した処理が失敗した場合の処理を関数の形で書く。
引数を1つ与えることで、その引数にエラーが返る。


上のコードの空白の部分を書き進めると以下のようになります。

    new Promise((resolve,reject) => {

        setTimeout(pub => {
            const data = [1,2,3]
            resolve(data[0])            

        },1500)
    })
    .then(result => {
        console.log(result); //data配列の1が表示されます
    })
    .catch(error => {
        console.log(error);
    });

引数resolveに続く形で、then以降に受け渡したいデータを記載する必要があります。引数の名前は何でもよいですが、処理が成功した場合に取得されるデータは1個目の引数に入れられthenに引き渡されます。

ここでの処理はsetTimeout関数で実施しており処理に失敗しようがないので、失敗時の検証ができません。そのため、次にfetchを使用して実際に外部のサーバーからデータを取得する処理で説明を行います。

サーバーからデータ取得して成功、失敗を判定

webAPIからデータを取得するにはfetchを使用します。

    new Promise((resolve,reject) => {

        const data = fetch(`https://jsonplaceholder.typicode.com/todos/1`)
        resolve(data)
    })
    .then(result => {
        console.log(result); //データ取得に成功するとResponseが表示される
    })
    .catch(error => {
        console.log(error); //データ取得に失敗するとTypeErrorが表示される
    });

上記のコードを実行して、コンソールを開くと、Responseと表示され、データが取得されているのが分かると思います。

試しにPCのインターネット接続を切断して、上記コードを実行してみると、TypeErrorが返ってきます。ここまで実行するとPromiseが正常に働いているのを実感できるのではないかと思います。

より進化した書き方

上記の書き方でも実行できるのですが、変数に代入する形でPromiseを使用することもできます。
実際のコードがこちらです。

    const getData  =  new Promise((resolve,reject) => {

        const data = fetch(`https://jsonplaceholder.typicode.com/todos/1`)
        resolve(data)
    })

    getData
    .then(result => {
        console.log(result); //データ取得に成功するとResponseが表示される
    })
    .catch(error => {
        console.log(error); //データ取得に失敗するとTypeErrorが表示される
    });

この書き方でも、先ほどと同様の結果になります。

MDNではPromiseのコンストラクタとしての説明に、

コンストラクタは主に、まだ Promise をサポートしていない関数をラップするのに使用されます。(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)

と記載されていますが、ここで行ったことがまさにこのことだと解釈しました。
100%正確かどうかは保証できませんが、上記の引用文は「コンストラクタは主に、変数をPromiseとして扱えるようにするために使用されます。」に言い換えられるのでは無いかと考えています。

fetchについて

先ほどfetchを使用しましたが、実はその場合コードをもう少し単純にすることができます。

    fetch(`https://jsonplaceholder.typicode.com/todos/1`)
    .then(result => {
        console.log(result); //データ取得に成功するとResponseが表示される
    })
    .catch(error => {
        console.log(error);  //データ取得に失敗するとTypeErrorが表示される
    });

これでも同様の結果が得られます。

これがなぜ正常に動作するかについての説明としては、MDNのfecthのページに根拠になりうる記載を見つけました。(https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch)

上記ページの冒頭部分によるとfetchはPromiseを返すとのことです。
うまいこと説明できてないかもしれませんが、fetchそのものにPromiseの機能が備わっていると思っても良いかもしれません。

終わりに

JavaScriptを勉強して日が浅いこともあり、すべてを説明できていませんし、正式な解釈、表現ではない部分もあったかもしれませんが、私と同じ初心者の方にとってはPromiseについて勉強するきっかけにはなろうかと思います。もし、先輩エンジニアの方が記事を読んでいただき、異論、訂正、ダメ出し、お叱り等々ある場合メッセージ頂けると非常にありがたく思います!

最後まで読んでいただきありがとうございました!

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

JavaScriptの非同期処理について(Promise、async/await)

また自分の理解したことのメモです。

JavaScriptの非同期処理に関して、主にPromiseasync/awaitについて理解したことをまとめたいと思います。

非同期処理

同期処理とは、簡単に説明すると処理が上から順番に行われていくことです。

script
console.log(1);
console.log(2);
console.log(3);
出力
1
2
3

これに対し、非同期処理とは1つの処理が終わるのを待たずに次の処理が行われることです。例として非同期関数であるsetTimeout()を使用してみます。

script
console.log(1);
setTimeout(function(){
  console.log(2)
},100);
console.log(3);
出力
1
3
2

このようにsetTimeout()の処理を待たずにあとの処理が先に行われました。これはサーバーとの通信などで時間のかかる処理によって、プログラム全体が止まってしまわないようにするために使われます。

Promise

Promiseとは非同期処理の状態を表すオブジェクトです。Promiseには以下の3つの状態があります。
pending : 処理が実行中
fulfilled : 処理が成功
rejected : 処理が失敗

Promiseの処理が終了した後に、結果を利用するにはPromiseのメソッドであるthen()catch()を用います。
then() : Promiseの状態がfulfilledのとき実行される処理
catch() : Promiseの状態がrejectedのとき実行される処理

以下のような例で具体的に説明します。

script
//Promiseオブジェクトを返す関数を定義
function time(sec){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
      //100より小さいと成功(fulfilledの状態を返す)
      if(sec <= 100){
        resolve();
      }
      //それ以上では失敗(rejectedの状態を返す)
      else{
        reject();
      }
    },sec)
  })
};

console.log(1);
time(100)
  .then(function(){console.log('success')})
  .catch(function(){console.log('error')})
time(1000)
  .then(function(){console.log('success')})
  .catch(function(){console.log('error')})
console.log(2);
出力
1
2
success
error

Promiseオブジェクトを返す関数として戻り値がnew Promiseとなる関数を定義します。resolve(),reject()はそれぞれ処理が完了した時、失敗したときに呼び出されます。time(100)のときはPromiseの状態がfulfilledで返されるため、then()の中の処理が行われ、一方、time(1000)のときはPromiseの状態がrejectedで返されるため、catch()の中の処理が行われます。

また、then()メソッドは戻り値としてPromiseオブジェクトを返すため、連続して書くことができます。この時途中でエラーが起きると処理が飛ばされcatch()が呼び出されます。

script
time(100)
  .then(function(){console.log(1)})
  .then(function(){console.log(2)})
  .then(function(){console.log(3)})
  .catch(function(){console.log('error')})
console.log(4)
出力
4
1
2
3

async/await

async/awaitを用いた非同期処理の構文について説明します。asyncとは関数の前に宣言することで、非同期関数を定義することができます。

script
//非同期関数の宣言
async function sample() {}

非同期関数は戻り値として、Promiseオブジェクトを返します。関数の処理が実行され、値を返すと戻り値をresolveが呼び出され、何らかのエラーなどが起こるとrejectが呼び出されます。

asyncで定義した非同期関数の中ではawaitという演算子が使用できます。awaitを関数の前に指定すると、その関数のPromiseオブジェクトが結果を返すまでその後の処理が待機します。

script
//awaitを用いない時
async function sample1(){
  time(100)
    .then(function(){console.log(1)})
  console.log(2);
}
//awaitを用いた時
async function sample2(){
  await time(100)
    .then(function(){console.log(1)})
  console.log(2);
}


sample1();
sample2();
出力
2
1 
1
2

awaitを用いない場合、関数の中の非同期処理より先にあとの処理が実行されますが、awaitを用いると指定した関数の処理が終了するまであとの処理が行われません。

非同期処理を同期的に処理したい場合、Promisethen()メソッドを用いるよりも、async/awaitを用いるほうが簡潔な記述でかけることが多いです。

まとめ

JavaScriptの非同期処理に関することとして、Promiseasync/awaitについてまとめてみました。async/awaitを用いると記述が簡潔になりますが、新しい規格のため対応していないこともあるので注意が必要です。

参考記事

【JavaScript】Promise で非同期処理を記述するー株式会社ライトコードー
JavaScriptと非同期処理の話
async/await 入門(JavaScript)

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

JavaScript(ブラウザ環境)でOAuth2 PKCE用パラメータを生成する

JavaScript(ブラウザ環境)でProof Key for Code Exchange (RFC 7636) のcode_verifier、code_challenge(code_challenge_methodがS256の場合)を生成するコードを作成した。

https://jsfiddle.net/unhurried/8vgk6fnr/

実装の要点

Cryptoが利用できないブラウザ(IE10以前など)でも動作するように実装している。(ただし、この場合は乱数生成にMath.random()を使うため乱数の暗号強度はブラウザ実装によっては下がる可能性がある。)

ハッシュ生成にはcrypto-jsを使っている。

なお、乱数生成にもcrypto-jsを利用できるが、crypto-jsの乱数生成処理は3.3.0ではMath.random()、4.0.0ではCryptoを固定で利用するようになっているため、ブラウザのCryptoサポートによって切り替えるように独自実装している。

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

[JavaScript] Symbolによるprivateを現実的にする

はじめに

接頭詞に#を付ける方法は、対応ブラウザが少ないため、#を使用せずにprivateを実現する方法として、こちらの記事を参考にさせて頂きました。
JavaScriptでprivate「Symbol」編

問題点

  • クラスを定義する前にprivateなプロパティ名を列挙しなければならない
  • publicとprivateで書き方がだいぶ違う
  • クラス作るたびにアロー関数作ってシンボル作ってとやることが多い
  • 完全なprivateじゃない

上記の記事では、これらの問題点が挙げられていました。

解決策

これらの問題点は「完全なprivateじゃない」を除き

  • Symbolの保持
  • 保持したSymbolの参照

これらの書き方が異なることに起因しています。
つまり、これらの書き方を1つに統一することが出来れば、問題は解決すると言えます。

「完全なprivateじゃない」については

delete Object.getOwnPropertySymbols

このように記述しておくことで、Hard privateを実現出来なくは無いので特に問題はありません。

シンプルなSymbol処理

const PrivateModifier = function() {
  return new Proxy({}, {
    get(obj, name) {
      if (obj[name] === undefined) {
        obj[name] = Symbol(name)
      }
      return obj[name]
    },
    set() { }
  })
}

解説

通常、objectは存在しないプロパティにアクセスした場合、undefinedを返します。
Proxyを利用することで、デフォルトの挙動を変更出来ます。

PrivateModifierは、存在しないプロパティにアクセスした際、Symbolを動的に設定するobjectを作成します。

例えば

const _ = {}
_.hoge = Symbol("hoge")
console.log(_.hoge) // Symbol(hoge)
_.huga = Symbol("huga")
console.log(_.huga) // Symbol(huga)
const _ = new PrivateModifier
console.log(_.hoge) // Symbol(hoge)
console.log(_.huga) // Symbol(huga)

これらは同じ意味になります。

値はこのようになります。

const _ = new PrivateModifier
console.log(_.hoge === _.hoge) // true

使用方法

パターン1 (prototype)

const ClassName = (function() {
  const _ = new PrivateModifier

  // constructor
  function ClassName(privateValue, publicValue) {
    // private instance
    this[_.privatePropertyName] = privateValue
    // public instance
    this.publicPropertyName = publicValue
  }

  // alias
  const proto = ClassName.prototype

  // private prototype
  proto[_.privateMethodName] = function() { }
  // public prototype
  proto.publicMethodName = function() { }

  return ClassName
})()

パターン2 (class)

const ClassName = (() => {
  const _ = new PrivateModifier

  return class ClassName {
    constructor(privateValue, publicValue) {
      // private instance
      this[_.privatePropertyName] = privateValue
      // public instance
      this.publicPropertyName = publicValue
    }

    // private instance
    ;[_.privateMethodName] = function() { }
    // public instance
    publicMethodName = function() { }
  }
})()

パターン2は、インスタンスごとに定義される点に注意してください

対応状況

  • IE以外のブラウザで使用できます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】ブラウザでJavaScriptが効かない時の対処法

プログラミングを勉強中です。

簡単なJavaScriptのプログラムを書いていたタイミングで、
ブラウザ(Googlechrome)でJavaScriptのコードが全く反省されない事態が発生し焦りました。

以下に私の対処法をまとめさせていただきます。

状況

JavaScriptを用いて簡単な単語帳アプリ を作成中。

【front】の部分をクリックすると裏側の単語が表示される、という機能を
addEventListenerで設定しようとしていましたが、
画面の変化が全く無い状況。

スクリーンショット 2020-05-17 12.29.15.png

試したこと① JavaScriptが有効かどうかの確認

そもそも使用しているGooglechromeでJavaScriptは有効になっているのかどうか?を疑いました。
下記のサイトでJavaScriptが有効かどうかを簡単にチェックできます。

あなたのブラウザでJavaScriptを有効にする方法

私のブラウザでもしっかり有効になっていました。

スクリーンショット 2020-05-17 12.34.01.png

試したこと② consoleでのエラー文の確認

JavaScriptでも、デベロッパーツール上にエラーメッセージが表示される為、
comnsoleを確認しました。

Macの方であれば、右上のGooglechromeのボタンクリック→その他のツール→デベロッパーツールで
立ち上げることができます。
私はショートカットキーで、⌘(コマンドキー)+Shiftキー+「c」を使用しています。

スクリーンショット 2020-05-17 12.40.07.png

エラーがいくつか出ていました。
一番上の、「ReferenceError: "x" is not defined」ですが、
これはXという変数が定義されていません。という意味になります

スクリーンショット 2020-05-17 12.42.22.png

今回の場合、「carnFrontという変数はないですよ」と教えてもらっているので
エラーが出ている行を確認、「cardFront」という変数を定義しているにもかかわらず、
呼び出すタイミングで、「carnFront」とtypoがあったことがわかりました。。。

今後気をつけること

JavaScriptが効かない場合、consoleに答えがあることがわかったので、
確認する癖をつけること、
また、script内に'use strict';と記載することで文法チェックを行ってくれるので
便利なツールはどんどん使用していこうと思いました。

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