- 投稿日:2019-08-21T23:50:35+09:00
動的なURLをブックマークする方法を紹介
こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。
動的なURLをブックマークする方法を紹介
URLに日付が含まれている場合など、動的なURLブックマークしたい場合があるかと思います。
そのような場合は、ローカルにHTMLファイルを作成してそれをブックマークしましょう。
そして、<script>
タグを作成して、window.locationプロパティを使用することで、目的のURLにリダイレクトするようにします。このようにして動的なURLをブックマークすることができます。この記事が参考になれば幸いです。
終わりに
Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/
- 投稿日:2019-08-21T23:29:55+09:00
配列をシャッフルするアルゴリズムを思いついたが既存だった話【JavaScript/Rubyサンプルコードあり】
0.前提
アルゴリズムそのものは筆者自身がゼロから考えました。もし他の方の考えたアルゴリズムと同じだったとしてもごめんなさい。
アルゴリズム界隈にそこまで詳しくないのでこれが有名なアルゴリズムだったとしても知らない
→追記:どうやら、フィッシャー–イェーツのシャッフル - Wikipediaという有名なヤツらしいです。本当に何も知らなかったんです。信じてください。ここから先、サンプルコードを書いてる途中で気づくまでウッキウキで解説してる筆者の展望をご覧ください。書き直すのも億劫なのでそのままにしておきます。1.アルゴリズム
さて、このアルゴリズムはもともと前回【JavaScript】重複のない乱数配列を作ってみた【ES6(ES2015)】でアルゴリズムを考えていたときに生まれたものです。もし気になるならそっちも見てみてください。
前回に続いて『アルゴリズムこうしん~♪』とかやろうと思いましたが今回はやめておきます。
先にアルゴリズムを説明しておくことにしましょう。サンプルコードは私が書ける言語の範囲()でこの下に書いておきます。
ひとまず、要素数5
の配列で説明します。ほぼ前回のと同じですが笑。initAry:
添字 0 1 2 3 4 要素 kurorin kotone hime hina tyanmari この配列をシャッフルするよ!
0~4(
initAry
の添字の最大値)の乱数を発生させて、initAryからその乱数の位置にある要素を一つ取り出し新しい配列randAryに入れるよ。randomNumber:
3
取り出す要素はinitAry[3]
=hime
initAry:
添字 0 1 2 3 要素 kurorin kotone hina tyanmari randAry:
添字 0 要素 hime これを、initAryが空になるまで繰り返すよ。
0~3(initAry
の添字の最大値)の乱数を発生させて、initAryからその乱数の位置にある要素を一つ取り出し配列randAryに入れるよ。randomNumber:
2
取り出す要素はinitAry[2]
=hina
initAry:
添字 0 1 2 要素 kurorin kotone tyanmari randAry:
添字 0 1 要素 hime hina ・・・(略)・・・
randomNumber:
0
取り出す要素はinitAry[0]
=kotone
initAry:
添字 要素 randAry:
添字 0 1 2 3 4 要素 hime hina kurorin tyanmari kotone さて、要素数
5
の配列がシャッフルできたよ!
これを要素数n
の配列でやればシャッフルできるよ!すっごーい!3.サンプルコード
そこまで難しくないと思うので、解説は簡略化します。
JavaScript(ES6)
//min以上max未満の乱数を発生させる関数 const getRandomInt =(min, max) => { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min)) + min; } //配列をシャッフルする関数 const getRandomAry = (ary) => { const initAry = ary.slice(); let randAry = []; while(initAry.length > 0){ randAry.push(initAry.splice(getRandomInt(0,initAry.length), 1).pop()); } return randAry; }ES6(ES2015)のアロー関数を用いているので、非対応環境では
function
とか使って書いてください。
わざわざconst initAry = ary.slice();
と二度手間を踏んでいるのは値渡しをするためです。これをしないと、参照渡しとなり、関数呼び出し時に引数として渡した配列が空になってしまうためです。アロー関数だと引数が配列の場合、参照渡し参照の値渡しになりその配列を破壊的に操作されるようですね・・・。別で記事まとめました→【JavaScript】配列を引数渡しすると破壊的に動作する話【メモ書き】Ruby
def get_random_array(initAry) randAry = Array.new while(initAry.length > 0) randAry.push(initAry.slice!(rand(initAry.length))) end return randAry endコード書いてて何故か処理が終わらないな・・・と思ってたら
slice!
をslice
と書いていて破壊的メソッドになっていないからでした。
ってか、書いてみたけどArray
クラスにshuffle
ってメソッドあるじゃん。というわけでそれを使いましょう。
C++#include<vector>
~サンプルコード作成途中の話~
・・・ん? なんかstd::shuffleってのがあるな?以下の実装では、フィッシャー - イェーツのシャッフルアルゴリズムが使用されている
shuffle - cpprefjp C++日本語リファレンス「フィッシャー - イェーツのシャッフル」・・・?
~Wikipediaを読む筆者~・・・。
・・・・・・。
一緒じゃねえか!!
誰がねェ・・・誰がどう見ても、おんなじやおんなじや思てェ!!
うわああああああああああああああああああああああああああん!!!!
・・・書きかけのサンプルコード、乗りかかった船だし
やってやろうじゃねえか!まあゆくどりさんは天才ですから?!?!
(何でも言うことを聞いてくれるアカネチャン風)
ということで動的配列vector
を使って書こうとしました。・・・・・・。
が。乱数生成の段階で詰まりました。DxLibくらいでしか触れたことのない私ではまだ無理でした。ヘッダとか色々読みましたが、最近のC++ではC言語のrand()やsrand()を使うのは非推奨なようで、よく理解してない私が「これがサンプルコードです」と提示すると界隈の方々に叩かれそうなので止めました。また理解できるようになってから覚えていたら帰ってきてここに書くことにします。
4.参考文献
JavaScriptで配列のコピー(値渡し) - Qiita
るりまサーチ(サンプルコード作成時リファレンスの検索に使用)
shuffle - cpprefjp C++日本語リファレンス5.さいごに
結局、思いついたのは既存アルゴリズムでした。まあ、このアルゴリズムを自分で思いついたのでよく理解できていますし、損はしないと思っています。また何か思いついたりしたら書きます。
ありがとうございました。
- 投稿日:2019-08-21T23:28:27+09:00
RaspberryPiで監視カメラ(カメラモジュール+USB Audioのデータをブラウザで表示+再生)
はじめに
前回、WebSocketで受信した画像をブラウザで表示する仕組みを調べたので、RaspberryPiのカメラモジュールと繋げてみました。
あと、USB Audioデバイスを買ったのでWebSocketでデータを流して、ブラウザで再生する処理も入れてみました。
簡易的な監視カメラができたので、記事としてまとめておきます。
ハードウェア
Raspberry Pi 3 Model B+
カメラモジュール
USB audio
秋月で400円くらい。lsusbで認識された情報だとIntel製。ボリュームが小さくでちょっと不満。もっと高いのにすればよかった。
クライアント
chromeで動作確認
仕様
- Imageサーバーは、ソケット接続されたらカメラの画像を送信し続ける
- Audioサーバーは、ソケット接続されたらAudio INから取得したデータを送信し続ける
- ブラウザで画像データを受信して、canvasに描画する
- ブラウザで音声データを受信して、db値をcanvasに描画する
- ボタンをクリックすると、WebAudioで音声を再生する
音声再生中はタブにスピーカーのアイコンが表示されます。
クライアント側
ブラウザで表示するためのhtmlとjsを用意します。
HTML
音声データ表示用のcanvasと再生ボタン、画像用のcanvasを用意します。
WebAudioはユーザー操作のイベントで開始する必要があるので、ボタンを用意する必要があります。
index.html<html> <head> <script src="./client.js"></script> </head> <body onload="on_load();"> <div> <canvas id="canvas_audio" width="500" height="40"></canvas> </div> <input type="button" value="play audio" onclick="on_button_play_audio();"> <div> <canvas id="canvas_image" width="640" height="480"></canvas> </div> </body> </html>javascript
画像データ受信ハンドラでcanvasに描画、音声データ受信ハンドラでcanvasにグラフを表示する&音声再生します。
- 画像データ
前回のサンプルのままだと大きい画像のbase64文字列の生成でエラーになりました。調査時はシンプルなPNGにしたので圧縮率が高かったけど、写真のjpegになったらサイズが小さくならなかったと推測。かっこ悪いですがループで文字列を結合してbase64変換しています。
- 音声データ
WebAudioを使用して音声を再生します。受信データがfloat32なのでそのまま流せば再生できます。
ユーザー操作しないと音声再生できないので、クリックのイベントを用意します。
データが受信できているかを確認できるようにdb値をcanvasに表示します。db値の計算はネットで調べてて適当に実装したから合ってるか自信ないです。
client.jsvar image_socket = null; var audio_socket = null; var audio_ctx = null; var scheduled_time = 0; var delay_sec = 1; function on_load(){ // ここではaudioの再生に関する処理は許可されないのでWebSock処理のみ行う // 画像通信用WebSocket接続 img_url = "ws://" + location.hostname + ":60002" image_socket = new WebSocket(img_url); image_socket.binaryType = 'arraybuffer'; image_socket.onmessage = on_image_message; // 音声通信用WebSocket接続 audio_url = "ws://" + location.hostname + ":60003" audio_socket = new WebSocket(audio_url); audio_socket.binaryType = 'arraybuffer'; audio_socket.onmessage = on_audio_message; } function on_button_play_audio(){ // ユーザー操作イベントでAudioの再生操作を行う if(audio_ctx == null){ audio_ctx = new (window.AudioContext||window.webkitAudioContext) } } function on_image_message(recv_data){ // 受信したデータをbase64文字列に変換 var recv_image_data = new Uint8Array(recv_data.data); var base64_data = "" for (var i=0; i < recv_image_data.length; i++) { base64_data += String.fromCharCode(recv_image_data[i]); } // 画像をcanvasに描画 var canvas_image = document.getElementById('canvas_image'); var ctx = canvas_image.getContext('2d'); var image = new Image(); image.onload = function() { ctx.drawImage(image, 0, 0); } image.src = 'data:image/jpeg;base64,' + window.btoa(base64_data); } function on_audio_message(recv_data){ audio_f32 = new Float32Array(recv_data.data); if(audio_ctx != null){ play_audio(audio_f32); } draw_audio_graph(audio_f32); } function play_audio(data){ var audio_buffer = audio_ctx.createBuffer(1, data.length, 44100); var buffer_source = audio_ctx.createBufferSource(); var current_time = audio_ctx.currentTime; audio_buffer.getChannelData(0).set(data); buffer_source.buffer = audio_buffer; buffer_source.connect(audio_ctx.destination); if (current_time < scheduled_time) { buffer_source.start(scheduled_time); scheduled_time += audio_buffer.duration; } else { buffer_source.start(current_time); scheduled_time = current_time + audio_buffer.duration + delay_sec; } } function draw_audio_graph(data){ var canvas_image = document.getElementById('canvas_audio'); var ctx_2d = canvas_image.getContext('2d'); // db値計算(自信ない) sum_data = data.reduce((a,b)=>Math.abs(a)+Math.abs(b)); mean_data = sum_data / data.length; dB = 20 * Math.log10(mean_data); // 前回描画をクリア ctx_2d.fillStyle = '#ffffff'; ctx_2d.fillRect(0,0,500,40); var rate = 2; var graph_x = Math.abs(dB) * rate; ctx_2d.fillStyle = '#000000'; ctx_2d.fillRect(0,0,graph_x,40); // 値表示 ctx_2d.fillStyle = '#ff0000'; ctx_2d.font = "10px serif"; ctx_2d.fillText("db:" + dB , 5 , 10 ); }サーバー側
画像用と音声用で2つのWebSoket通信を行うので、2つWebSoketのサーバーを用意します。
httpのサーバーも必要になるので、まとめて起動する処理も用意します。
Imageサーバー
PiCameraで取得した画像をブラウザに送信します。
imageServer.pyimport asyncio import websockets import io from picamera import PiCamera class ImageServer: def __init__(self, loop, address , port): self.loop = loop self.address = address self.port = port self.camera = PiCamera() async def _handler(self, websocket, path): with io.BytesIO() as stream: for _ in self.camera.capture_continuous(stream, format='jpeg', use_video_port=True, resize=(640,480)): stream.seek(0) try: await websocket.send(stream.read()) except: print('image send Error.') break stream.seek(0) stream.truncate() def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() if __name__ == '__main__': loop = asyncio.get_event_loop() ws_is = ImageServer(loop, '0.0.0.0', 60002) ws_is.run()Audioサーバー
pyaudioで取得した音声データをブラウザに送信します。クライアントであるWebAudioにfloat32で渡すのでpaFloat32を指定します。
同一スレッドで読み込みと送信を行ったらバッファ不足のエラーが出たので読み込みと送信は別スレッドで行うようにしています。
複数接続は考慮していない実装です。
AudioServer()クラスに、デバイス名を指定するので環境に合わせた文字列を設定してください。
AudioServer.pyimport asyncio import websockets import pyaudio import threading import queue class AudioServer: def __init__(self, loop, address, port, device_name): self.loop = loop self.address = address self.port = port self.chunk = 1024 * 4 self.device_index = self._find_device_index(device_name) self.rec_loop = False self.q = queue.Queue() def _rec_thread(self): audio = pyaudio.PyAudio() stream = audio.open( format = pyaudio.paFloat32, channels = 1, rate = 44100, input = True, frames_per_buffer = self.chunk, input_device_index=self.device_index) while self.rec_loop: audio_data = stream.read(self.chunk) self.q.put(audio_data) stream.close() audio.terminate() async def _handler(self, websocket, path): if self.rec_loop: print("_rec_thread is exists.") return # Audio INから読み出すスレッドを起動 self.rec_loop = True send_thread = threading.Thread(target=self._rec_thread) send_thread.start() # スレッドで読み込んだデータを送信する send_loop = True while send_loop: try: send_data = self.q.get(timeout=1) await websocket.send(send_data) except: print('audio send Error.') send_loop = False break self.rec_loop = False send_thread.join() def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() @staticmethod def get_devices(): '''使用可能なデバイスを列挙''' device_name_list = [] audio = pyaudio.PyAudio() for i in range(audio.get_device_count()): device = audio.get_device_info_by_index(i) device_name_list.append(device['name']) return device_name_list @staticmethod def _find_device_index(find_name): '''引数のデバイス名に対応するindexを返す''' device_index = -1 audio = pyaudio.PyAudio() for i in range(audio.get_device_count()): device = audio.get_device_info_by_index(i) if device['name'] == find_name: device_index = i break return device_index if __name__ == '__main__': device_name = None devices = AudioServer.get_devices() print("devices len = {}".format(len(devices))) if len(devices) > 0: device_name = devices[0] print("device_name = {}".format(device_name)) loop = asyncio.get_event_loop() ws_as = AudioServer(loop, '0.0.0.0', 60003, device_name) ws_as.run()httpサーバー
まとめて起動する仕組みも用意しましたが終了時のことは考慮していないので、ちゃんと実装する人は他の方法がいいと思います。
以下のスクリプトを実行して http://localhost:60001/ にアクセスすれば動作確認できます。
monitor_run.pyimport threading import http.server import socketserver import asyncio from ImageServer import ImageServer from AudioServer import AudioServer def _http_thread(): _handler = http.server.SimpleHTTPRequestHandler httpd = socketserver.TCPServer(("", 60001), _handler) httpd.serve_forever() def _image_thread(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) ws_is = ImageServer(loop, '0.0.0.0', 60002) ws_is.run() def _audio_thread(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) device_name = None devices = AudioServer.get_devices() print("devices len = {}".format(len(devices))) if len(devices) > 0: device_name = devices[0] print("device_name = {}".format(device_name)) loop = asyncio.get_event_loop() ws_as = AudioServer(loop, '0.0.0.0', 60003, device_name) ws_as.run() if __name__ == '__main__': imageThread = threading.Thread(target=_image_thread) imageThread.start() audioThread = threading.Thread(target=_audio_thread) audioThread.start() httpThread = threading.Thread(target=_http_thread) httpThread.start() imageThread.join() audioThread.join() httpThread.join()おわりに
WebAudio+pyaudioの組み合わせをやってる人がいなくて試行錯誤でした。
dB値の計算は昔やったことあるけど細かいこと忘れた。正しいdB値が欲しいわけじゃないから適当で。
激しく寝返りするようになった娘のベビーモニターとして利用するつもりですが、暗い部屋だと映らないので赤外線カメラに変更するか、USBライトの点灯制御するか悩むところ。
100均のUSBライトの点灯制御ができたら更新記事をアップしようと思います。参考
- 投稿日:2019-08-21T22:51:04+09:00
【Javascript】第一級関数と高階関数、関数を作る関数
概要
第一級関数・・・変数や配列(第一級オブジェクト)と同じように扱える関数
高階関数・・・第一級関数を引数として取る関数のことJavascriptは第一級関数な言語で使いやすいので以下Javascriptでやっていきます。
第一級関数な言語は以下に載ってます。
https://ja.wikipedia.org/wiki/第一級関数例:マップ関数
function add2(n) { return n + 2; } const arr = [1,2,3]; const result = arr.map(add2); console.log(arr); console.log(result); // > [1, 2, 3] // > [3, 4, 5]関数を作る関数
第一級関数と高階関数を使うことができれば、同じ処理を関数でまとめてしまうかのようにダブっている部分をまとめることができます。
例えば2つの配列の各要素を四則演算する関数は四則演算子以外すべて同じです。function addArray(arrA, arrB) { return arrA.map(function(_, i) { return arrA[i] + arrB[i]; }); } function mulArray(arrA, arrB) { return arrA.map(function(_, i) { return arrA[i] * arrB[i]; }); } function subArray(arrA, arrB) { return arrA.map(function(_, i) { return arrA[i] - arrB[i]; }); } function subArray(arrA, arrB) { return arrA.map(function(_, i) { return arrA[i] / arrB[i]; }); }ダブっているところを高階関数でまとめてしまいましょう。
(残念なことにJavascriptでは演算子は第一級オブジェクトでは無いので関数で改めて定義してあります。)
function add(a, b) { return a + b; } function mul(a, b) { return a * b; } function sub(a, b) { return a - b; } function div(a, b) { return a / b; } function makeFunction(fn) { return function(arrA, arrB) { return arrA.map(function(_, i) { return fn(arrA[i], arrB[i]); }); } } var addArray = makeFunction(add); var mulArray = makeFunction(mul); var subArray = makeFunction(sub); var divArray = makeFunction(div);makeFuncitonは関数(この場合は四則演算関数)を引数に取り、返り値として関数を返えす関数です。
内容は上のもののダブっていたとこはそのままに四則演算部分を関数で置き換えた関数を返しています。最後に
この手の関数の利用はLisp(commonlispやscheme)で学ばせてもらいました。あちらにはもっと自由で抽象的なマクロもあるので関数型プログラミング言語をやってみたい方はぜひLispを使ってみてください。Lispの宣伝でした。
- 投稿日:2019-08-21T22:44:43+09:00
Vue.jsでタスクキューっぽいものを実現する
はじめに
APIへのリクエストをするときに、連続でリクエストを送るようなシーンで連続送信を防止するために現在の処理が実行中の場合は処理を待つみたいなシーンがありました…
let isConnecting = false let isRetry =false api: function () { if (!isRetry) { if (!isConnecting) { isConnectiong = true axios.get('api') .then(function(res) { // 成功時の処理 isConnecting = false api() }) } else { isRetry = true } } }こうやって書くとネストが深くなって、イケてないですよね
※ 正確にはこれじゃ失敗したときにつみますがそこで、タスクキュー的なもので処理を予約するみたいなことをやりたいと思いました。
JavaScriptのタスクキュー
いいライブラリが殆どない…
ということで、自分で頑張りました
やってみた
Vue.jsで書いていたので、ここではvue.jsで書いてます。
通常のjavascriptでやるには変数のwatch
機能とかを頑張れば同じやり方でできると思います。やりたいこと
- APIへリクエストを送る
- APIへのリクエストが完了していない場合は、タスクキューにためて、前の処理が終わってから実行する
- 貯める処理は2つまでで、最終待機しているタスクは最新の状態にしたい
コード
new Vue({ el: "#app", data: function() { return { exQueue: [] // タスクを貯めておく用の配列 } }, watch: { // タスクを貯める配列の値に変化があったら呼ばれる関数 exQueue: function(queue) { /*③*/ // タスクが一つのときしか実行されないので、現在実行中の場合は無視される if (queue.length === 1) { queue[0].func(queue[0].params) } } }, methods: { main: function () { /*①*/ this.addQueue({ func: this.callAPI, params: params }) }, // タスクを追加する関数 addQueue: function (job) { /*②*/ // 3の条件を満たすために貯めれるタスクは2つまで if (this.exQueue.length < 2) { this.exQueue.push(job) } else { // 2つを超えるときは後ろを最新のものに上書きする this.exQueue.pop() this.exQueue.push(job) } }, // APIを呼び出す関数 callAPI: function(params) { /*④*/ axios.get('api') .then(function(res) { // 完了したタスク(配列の先頭を削除する) this.exQueue.shift() }) .catch((error => { console.error('APIからの取得失敗') })) } } })解説
- ① → ② → ③ → ④ の順番に実行されていく
- ④ で
exQueue
が更新されるため、 再び ③ が実行される- タスクがある場合は再度実行
- 残タスクがあるうちは繰り返し
といった形で、タスクキューを割と簡単に実装することができます。
さいごに
簡単にVue.jsでタスクキューを実装してみました。
この辺の処理効率化みたいな部分て、ゴリゴリのサーバーサイドよりなコアな言語とかでよく繰り広げられることを見るのですが、JavaScriptだと以外にないなと思い書いてて楽しかったです。※そもそも言語の思想と違う感じはしていますが
とはいえ、if文のネストをしてしまうと、視認性も悪くなったり、ストックしたいタスクを増やすってなると改修が大変になるってことを考えると配列を見てタスクキューだって分かるようになりだいぶシンプルにかけると思います。APIの通信とか非同期の処理をするときに参考になればと思います。
- 投稿日:2019-08-21T22:41:18+09:00
React Native + React Navigation で、 Android の画面遷移アニメーションを iOS と同様にする
React Navigation の画面遷移のアニメーション、 Android だけよくわからない。
デフォルトだと、モーダルが重なるようになるが、 iOS のように左から右に進んでいくようにしたい。
めっちゃ簡単にできた。
import { createAppContainer, createStackNavigator, StackViewTransitionConfigs, } from 'react-navigation' const Navigation = createStackNavigator({ screenA: ComponentA, screenB: ComponentB, }, { mode: 'card', transitionConfig: () => StackViewTransitionConfigs.SlideFromRightIOS, } export const AppNavigation = createAppContainer(Navigation)なんでこれがデフォルトじゃないんだ・・・笑
- 投稿日:2019-08-21T22:22:40+09:00
Javascriptを用いたランダムなクジ
はじめに
未来電子テクノロジーでインターンをしている<小栗>です。
「プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。Javascriptを用いたランダムなクジ
'use strict'; { const btn = document.getElementById('btn'); btn.addEventListener('click', () => { const n = Math.random(); if (n < 0.15) { btn.textContent = '$100'; } else if (n < 0.35) { btn.textContent = '$20'; } else { btn.textContent = '$1'; } }); btn.addEventListener('mousedown', () => { btn.classList.add('pressed'); }); btn.addEventListener('mouseup', () => { btn.classList.remove('pressed'); }); }if文のnの数値を変えれば、結果の割合を変えることができます。
自分は$100が15%、$20が35%、$1が残りの50%で出るようになっています。一応自分のHTMLも記しておきます。
<div class='lottery'> <div id='btn'>Lottery</div> <script src="js/main.js"></script> </div>
- 投稿日:2019-08-21T21:34:03+09:00
micro:bitでLEDと温度センサーを試してみた
マルツ電波でmicro:bitのスターターキットが安売りしていたので購入。
地元のお祭りも終わったのでちょっといじってみた。ブロックエディタ、JavaScript、Pythonで開発できるとのこと。
Windows10なので、ストアからMakeCode for micro:bitをダウンロード。エディタはブロックとJavaScriptと切り替え可能。
JavaScriptでコードを書いてブロックに切り替える、その逆も可能。
ただし、JavaScript→ブロック→JavaScriptと切り替えると、コードがブロックから再生成しているのか、整形しなおしているのか、元に戻りませんね。とりあえず、こんかいはLEDと温度センサーを使って、温度を簡易的に表示してみました。
エディタが良くできていて、パレットというのか基本や入力などを選ぶと利用できるメソッドなどが選べるのがよい。で、書いたコードは以下の通り。
大したことはしていませんが、input.temperature()で取得した温度を文字列に変えて、1文字づつ表示しています。let leds: number[][][] = [] let tmp = 0 let num = 0 basic.forever(function () { leds = [ [ [0, 0, 1, 0, 0] , [0, 1, 0, 1, 0] , [0, 1, 0, 1, 0] , [0, 1, 0, 1, 0] , [0, 0, 1, 0, 0] ], [ [0, 0, 1, 0, 0] , [0, 1, 1, 0, 0] , [0, 0, 1, 0, 0] , [0, 0, 1, 0, 0] , [0, 0, 1, 0, 0] ], [ [0, 1, 1, 1, 0] , [0, 0, 0, 1, 0] , [0, 0, 1, 1, 0] , [0, 1, 0, 0, 0] , [0, 1, 1, 1, 0] ], [ [0, 1, 1, 1, 0] , [0, 0, 0, 1, 0] , [0, 0, 1, 1, 0] , [0, 0, 0, 1, 0] , [0, 1, 1, 1, 0] ], [ [1, 0, 0, 0, 0] , [1, 0, 1, 0, 0] , [1, 0, 1, 0, 0] , [1, 1, 1, 1, 0] , [0, 0, 1, 0, 0] ], [ [0, 1, 1, 1, 0] , [0, 1, 0, 0, 0] , [0, 1, 1, 0, 0] , [0, 0, 0, 1, 0] , [0, 1, 1, 0, 0] ], [ [0, 0, 1, 1, 0] , [0, 1, 0, 0, 0] , [0, 1, 1, 0, 0] , [0, 1, 0, 1, 0] , [0, 1, 1, 0, 0] ], [ [0, 1, 1, 1, 0] , [0, 0, 0, 1, 0] , [0, 0, 1, 0, 0] , [0, 0, 1, 0, 0] , [0, 0, 1, 0, 0] ], [ [0, 0, 1, 0, 0] , [0, 1, 0, 1, 0] , [0, 1, 1, 1, 0] , [0, 1, 0, 1, 0] , [0, 0, 1, 0, 0] ], [ [0, 1, 1, 1, 0] , [0, 1, 0, 1, 0] , [0, 1, 1, 1, 0] , [0, 0, 0, 1, 0] , [0, 1, 1, 1, 0] ] ] let cnt; cnt = 0 while (true) { tmp = input.temperature() let strTmp = tmp.toString() console.log(strTmp) num = parseInt(strTmp.charAt(cnt)) for (let y = 0; y <= 5 - 1; y++) { for (let x = 0; x <= 5 - 1; x++) { if (leds[num][y][x] == 0) { led.plotBrightness(x, y, 0) } else { led.plotBrightness(x, y, 255) } } } cnt += 1 if (cnt >= strTmp.length) { cnt = 0 } basic.pause(200) } })
- 投稿日:2019-08-21T20:45:49+09:00
【JavaScript】重複のない乱数配列を作ってみた【ES6(ES2015)】
0.前提
「俺はJavaScriptで重複なし乱数配列(整数)が作りたいんや!」ってことでJavaScriptで書いてますが、アルゴリズム自体は多言語に応用できます。尚、アルゴリズムそのものは筆者自身がゼロから考えました。もし他の方の考えたアルゴリズムと同じだったとしてもごめんなさい。
1.はじめに
ES6(ES2015)に対応した環境でないと動作しません。筆者は、Repl.itのJavaScript環境(Node.js v10.16.0)にて動作を確認しています。最新のメジャーブラウザでは動作しますが、IE11以前では動作しません。
また、筆者が以前書いた記事JavaScript基礎文法を軸にコード書いているので、初心者の方は先にそちらに目を通すこと推奨。2.アルゴリズム
『アルゴリズムたいそう~♪』テッテテッテテッテテッテテー チャン♪
『こっちむいて一人で・・・ってなんでやね~ん』
はい、これ以上やると叩かれそうなのでやめておきます。
先にアルゴリズムを説明しておくことにしましょう。コードだけ欲しい人は読み飛ばしてください。目標とするのは、重複なしの乱数配列、例えば1~5の乱数配列ならこんな感じ。
添字 0 1 2 3 4 要素 3 4 5 2 1 こういう乱数が入った配列を作っていきます。いつ使うのかって?
ちょっと話が外れるので最後に書くね(この記事を読むってことはそれが必要な場面があるでしょうから・・・)。
→初めから配列をシャッフルすることを目的にしていたのですが、ここで考えたアルゴリズム自体が「配列をシャッフルする」ものだったので別で記事を書きました:配列をシャッフルするアルゴリズムを思いついたが既存だった話【JavaScript/Rubyサンプルコードあり】まずはじめに、候補となる配列を作るよ。
initAry:
添字 0 1 2 3 4 要素 1 2 3 4 5 0~4(
initAry
の添字の最大値)の乱数を発生させて、initAryからその乱数の位置にある要素を一つ取り出し新しい配列aryに入れるよ。randomNumber:
3
取り出す要素はinitAry[3]
=4
initAry:
添字 0 1 2 3 要素 1 2 3 5 ary:
添字 0 要素 4 これを、initAryが空になるまで繰り返すよ。
randomNumber:
2
取り出す要素はinitAry[2]
=3
initAry:
添字 0 1 2 要素 1 2 5 ary:
添字 0 1 要素 4 3 ・・・(略)・・・
randomNumber:
0
取り出す要素はinitAry[0]
=2
initAry:
添字 要素 ary:
添字 0 1 2 3 4 要素 4 3 5 1 2 おしまい。
3.実装
さて、実際にこれを実装していこう。今回、class宣言を利用している。先に全貌を提示する。
RandomNumber.jsclass RandomNumber{ constructor(num, min = 0){ this.num = num; this.min = min; this.init(); } /* * getRandomIntメソッド * 処理:min以上max未満の乱数を返す。 * 引数 * min:最小値(整数) * max:最大値(整数) * 返り値:生成された乱数 */ getRandomInt(min, max){ min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min)) + min; } /* * initメソッド * 処理:minを最小とするnum個の被りなしの乱数が入った配列this.aryを生成。 * 引数 * num:生成個数(整数)、初期値はthis.num * min:最小値(整数)、初期値はthis.min * 返り値:なし */ init(num = this.num, min = this.min){ this.index = 0; this.ary = []; let initAry = []; for(let i = 0; i < num; i++){ initAry.push(min + i); } while(initAry.length > 0){ this.ary.push(initAry.splice(this.getRandomInt(0,initAry.length), 1).pop()); } } /* * nextメソッド * 処理:実行するたびに、this.aryの要素を順に返す。 * 配列の最後までいったときははじめの要素に戻る。 * 引数:なし * 返り値:this.index位置のthis.aryの要素 */ next(){ if(this.index >= this.ary.length){ this.index = 0; } return this.ary[this.index++]; } }初期化も行いたいため、
init
メソッドをconstructor
と別に定義している。これによって、例えばinit()
とすると乱数配列を再生成できる。
constructor
- 処理
- numとminをそれぞれ同名のクラスメンバにする。
- その後、initメソッドを引数無しで実行する。
- 引数
- num:生成する乱数の個数
- min:生成する乱数の最小値、初期値は0
constructor(num, min = 0){ this.num = num; this.min = min; this.init(); }
constructor(num, min = 0)
のように書いてmin
の初期値を0
にすることで、第2引数を指定せずにクラス生成すると最小値0
のnum
個の整数乱数配列が生成されます。生成過程は後述のinit
メソッドで。
getRandomInt
メソッド
- 処理
- min以上max未満の乱数を返す。
- 引数
- min:最小値(整数)
- max:最大値(整数)
- 返り値
- 生成された乱数
getRandomInt(min, max){ min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min)) + min; }コードはMath.random() - JavaScript | MDNのサンプルコードを転用したもの。
※MDNのライセンスに従い掲載。
init
メソッド
- 処理
- minを最小とするnum個の重複なしの乱数が入った配列this.aryを生成。
- 引数
- num:生成個数(整数)、初期値はthis.num
- min:最小値(整数)、初期値はthis.min
- 返り値
- なし
init(num = this.num, min = this.min){ this.index = 0; this.ary = []; let initAry = []; for(let i = 0; i < num; i++){ initAry.push(min + i); } while(initAry.length > 0){ this.ary.push(initAry.splice(this.getRandomInt(0,initAry.length), 1).pop()); } }引数省略時、
this.min
を最小とするthis.num
個の整数乱数配列を生成。
this.index = 0;
:nextメソッドで使うものなので後述。
this.ary = [];
、let initAry = [];
:この2つが配列であることを宣言
for
文:候補となる配列initAry
を生成
initAry.push(min + i);
で最小値+iの値をinitAry
に入れていくことで生成している
while
文:乱数配列this.ary
を生成
initAry.splice(this.getRandomInt(0,initAry.length), 1).pop()
でinitAry
の要素からランダムで一つ取り出している。
最後に.pop()
をつけているのは、Array
クラスのsplice
メソッドは取り出す要素が一つでも要素数1
の配列を返してしまうためである。
initAry.splice(this.getRandomInt(0,initAry.length), 1)
とすると、this.ary
の中身が「要素数1
の配列」をnum
個格納した配列になってしまう。
next
メソッド
- 処理
- 実行するたびに、this.aryの要素を順に返す。配列の最後までいったときははじめの要素に戻る。
- 引数
- なし
- 返り値
- this.index位置のthis.aryの要素
next(){ if(this.index >= this.ary.length){ this.index = 0; } return this.ary[this.index++]; }なにかに使えそうと思って作成したメソッド。
forEach
などのループを使うことなく、生成された重複なしの整数乱数配列の中身を順に取り出すことができる。4.使用例
const r = new RandomNumber(5,1); console.log(r.ary); r.init(); console.log(r.ary); for(let i= 0; i < r.ary.length*2; i++){ console.log(r.next()); }実行結果
[ 4, 1, 3, 5, 2 ] [ 5, 3, 2, 4, 1 ] 5 3 2 4 1 5 3 2 4 15.参考文献
(初心者向け) JavaScript のクラス (ES6 対応) - Qiita
JavaScriptのclass - Qiita
Math.random() - JavaScript | MDN
Array.prototype.splice() - JavaScript | MDN6.さいごに
重複なしの乱数配列を作って配列をシャッフルしようとアルゴリズム考えたら最終的に配列をシャッフルするアルゴリズムになってた。なんだこれ。
ちなみに当初考えていたものは、乱数作って配列に入れて、その全要素と被らない乱数が生成されるまで繰り返すものでした。処理時間めっちゃ長い。
class
使う必要あるの?って思った人はわざわざclass
使う必要ないです。でも、どうやらモジュール化するならclass
のほうがいい?
今の所、生のJavaScriptとjQueryをHTML、CSSと併用してしか使ってないので詳しくは知りませんが。
ではまた。
- 投稿日:2019-08-21T19:51:57+09:00
JavaScriptのエキサイティングな新機能7選
以下はMostafa Gaafarによる記事、7 New Exciting JavaScript Features You Need to Knowの日本語訳です。
7 New Exciting JavaScript Features You Need to Know
JavaScript ( ECMA Script ) は進化する言語であり、たくさんのproposalやアイデアが出番を待ち受けています。
TC39 (Technical Committee 39) という委員会がJavaScript標準と新機能の定義を担当しています。
そして今年は彼らの活動が活発になっています。
以下は、現在ステージ3にある提案の一部の紹介です。
ステージ3は完成する直前の段階です。
これはつまり、この機能がブラウザやその他のJavaScriptエンジンにすぐに実装されることを表しています。
実際、以下のいくつかは既にブラウザに実装されています。1. Private fields
ChromeとNodeJS12に実装済。
はい、そうです。
JavaScriptはようやくprivateを手に入れました。
もうprivateプロパティを作るためにthis._doPrivateStuff()
したりクロージャに閉じ込めたりWeakMapを使ったりといった小細工を弄する必要はありません。構文は以下のようになります。
// private修飾子は`#` class Counter { #x = 0; #increment() { this.#x++; } onClick() { this.#increment(); } } const c = new Counter(); c.onClick(); // OK c.#increment(); // エラーProposal:https://github.com/tc39/proposal-class-fields
2. Optional Chaining ?.
オブジェクト内のネストの奥深くにあるプロパティにアクセスしようとして、悪名高い
Cannot read property 'stop' of undefined
エラーに遭遇した人は多いでしょう。
そうなったら次は、チェーン内の未定義オブジェクトに対応できるようにコードを変更します。const stop = please && please.make && please.make.it && please.make.it.stop; // あるいは'object-path'みたいなライブラリを使う const stop = objectPath.get(please, "make.it.stop");Optional Chainingがやってくると、同じことを簡単に書けるようになります。
const stop = please?.make?.it?.stop;Proposal:https://github.com/tc39/proposal-optional-chaining
3. Nullish Coalescing ??
オプション値が存在しない場合にデフォルト値を使用するという実装はとても一般的です。
const duration = input.duration || 500;この書き方の問題点は、
||
を使うと、有効な入力値である可能性のある0や''、falseといったfalseっぽい値が全て500になってしまうことです。そんなわけでundefinedおよびnullのみを識別するNull合体演算子を新設します。
const duration = input.duration ?? 500;Proposal:https://github.com/tc39/proposal-nullish-coalescing
4. BigInt 1n
ChromeとNodeJS12に実装済。
JavaScriptの数値演算がひどいものだった理由のひとつは、2^53を超える数値を扱うことができないということです。
幸いなことに、BigIntがこの問題を解決します。理屈抜きで使い方を示すとこう。
// 数値リテラルにnを付けるとBigIntになる const theBiggestInt = 9007199254740991n; // constructorでもよい const alsoHuge = BigInt(9007199254740991); // constructorには文字列も渡せる const hugeButString = BigInt('9007199254740991');BigIntには通常の数値と同じ演算子、
+-/*%
などを使うことができます。
ただし、ほとんどの操作ではBigIntと通常の数値リテラルを混ぜることはできません。
BigIntとNumberを比較することはできますが、加算はできません。1n < 2 // true 1n + 2 // Uncaught TypeError: Cannot mix BigInt and other typesProposal:https://github.com/tc39/proposal-bigint
5. static Fields
ChromeとNodeJS12に実装済。
これはとても簡単です。
多くのOOP言語同様、クラスにstaticフィールドを設定できます。
これは列挙型の代替にも使用可能で、privateとも共存可能です。class Colors { // public static static red = '#ff0000'; static green = '#00ff00'; // private static static #secretColor = '#f0f0f0'; } font.color = Colors.red; font.color = Colors.#secretColor; // ErrorProposal:https://github.com/tc39/proposal-static-class-features
6. Top Level await
Chromeに実装済。
コードのトップレベルでawaitを使用できます。
いちいち非同期関数でラップせずに済むようになるので、コンソール上でfetchのような非同期処理をデバッグするときなど非常に便利です。
Async/Await
の復習が必要なら、私の過去記事を参照ください。もうひとつの便利な使用例は、非同期で初期化が行われるESモジュールをトップレベルで使えるようになることです。
たとえば接続を確立する必要があるデータベースなどです。
このような非同期モジュールをawaitでインポートすると、そのモジュールは初期化が終わるまで待機し、その後そのモジュールに依存する次のモジュールを実行します。
これによって、返ってきたPromiseが解決されるまで待つという現在のスタンダードよりも、ずっと簡潔に非同期を処理することができます。
モジュールは、依存先モジュールが非同期なのか同期なのかを知る必要はありません。db.mjsexport const connection = await createConnection();server.mjsimport { connection } from './db.mjs'; server.start();この例では、
db.mjs
の処理が完了するまでserver.mjs
は待機します。Proposal:https://github.com/tc39/proposal-top-level-await
7. WeakRef
ChromeとNodeJS12に実装済。
弱い参照は、いつまでも存続しているとはかぎらない参照です。
const、let、varで生成した変数は、その変数がアクセス可能であるかぎり決してメモリから削除されることはありません。
これらは全て強い参照です。
弱い参照で生成されたオブジェクトは、GCによって削除される可能性があります。
WeakRefインスタンスには、参照された元オブジェクトを返すメソッドderef
があります。
元オブジェクトが削除されていたらundefined
になります。これは、メモリにずっと保存しておきたい程ではない重要性の低いオブジェクトをキャッシュしておきたいときに役立ちます。
const cache = new Map(); const setValue = (key, obj) => { cache.set(key, new WeakRef(obj)); }; const getValue = (key) => { const ref = cache.get(key); if (ref) { return ref.deref(); } }; // キャッシュにあればそれを返す、なければ再計算する const fibonacciCached = (number) => { const cached = getValue(number); if (cached) return cached; const sum = calculateFibonacci(number); setValue(number, sum); return sum; };不規則に削除される可能性があるため、リモートデータなどをキャッシュするために使うのは良い考えではありません。
それらにはLRUキャッシュなどを使った方がよいでしょう。Proposal:https://github.com/tc39/proposal-weakrefs
これらのクールな新機能を使ってみて、私と同じように興奮してください。
また、ここで取り上げなかった他のプロポーザルもTC39のGitHubに公開されています。コメント欄
「privateの
#
は頭おかしい」「完全に同意」「唖然とするわ」「privateキーワード追加しろよ」「いやまあ、色々と事情があるんだよ、きっと」
「Null合体演算子が楽しみ。Optional ChainingはKyle Simpsonから聞いただけだけど便利そう。」
「BigInt大歓迎。」
「弱参照知らなかった。うまく使えばWebアプリのパフォーマンスを大幅上昇できるかも。」
「TS使ってる身としてはそう目新しいものでもない。だが良いことだ。」
「Chromeで使えるのいいね。」感想
初見のときから思ってたんだけど、privateアクセス修飾子の
#
はあまりに気持ち悪いですね。
staticはキーワードとして導入したのに、どうしてprivateは入れなかったのでしょうか。もちろん理由はproposalに書かれてるのですが、主な理由としては『privateフィールドが存在していることすら外部に出したくない』だそうです。
いや、そこまでする必要あるのか?
PHPどころかJavaですら『privateフィールドが存在する』こと自体は外部からわかるぞ。そんなわけでprivateフィールド
#a
のあるクラスに外からa=1
とすると、privateフィールド#a
とは別のpublicフィールドa
が生えます。class A{ #a = 0; } a = new A(); a.a = 1; // エラー出ないprivateフィールドというより、外部と完全に切り離された内部変数みたいなかんじですね。
まあ、このあたりの仕様や文法を決めるにあたっては何年もの議論があったらしいので、後からちょっと覗いた程度では窺い知れぬ何か深い事情があるのでしょう。きっと。結果としてJavaScriptのprivateフィールドは、リフレクションのような手段をもってしてもアクセスできない、それどころか存在するかどうかすら検出できないという、非常に本気のステルス性を手に入れました。
でもChromeのコンソールとかでは普通に見えるんですけどね。BigIntはNumberと混ぜられないのが面倒ですね。
GMPを見習ってどうぞ。PHP$n = gmp_init('9007199254740991'); var_dump($n+1); // GMP(9007199254740992)PHPの場合、GMPオブジェクトに数値演算すると結果もGMPオブジェクトとなり、透過的に計算できるので便利です。
どうしてこうしなかったのでしょうか?とまあ少々疑問に感じる点もないでもないものの、いずれの機能も、導入されれば役に立つであろうことは間違いないはずです。
また、proposalでは、他にも多くの新機能や構文が自分の登場する順番を待っています。
色々見てみると楽しいかもしれません。個人的には追加よりむしろ末尾
;
補完を削除してほしいのですが、これはどう考えても無理だろうな。
- 投稿日:2019-08-21T18:17:04+09:00
ES6 class 構文のパフォーマンスについて
概要
この記事は「class 構文」と「クロージャによるカプセル化」のパフォーマンス比較記事です。
検証ブラウザーは Chrome のみになります。何か1つの処理を作るのに態々 class 化しなくても、クロージャ(関数)を作成しカプセル化するなどの方法もある。しかしパフォーマンスの観点で考えると、どちらで実装していくのが良いのだろうか...という疑問があった。本記事では、その検証結果をまとめる。
検証前に「class 構文」と「クロージャによるカプセル化」に対する考えを述べる。class 構文に対する考え(検証前)
class 構文は実質 prototype の糖衣構文なので、同じようなオブジェクトを幾つも生成する場合は class の方が良さそうではある。しかし、class から instance(オブジェクト)を生成する new はそこそこのコストを孕んでいそうなので、オブジェクトの生成数が1〜2つ程度なら態々 class 構文で書かなくても良さそう。
また、プライベートなプロパティーを作成できない点が気に掛かる。class構文の例class Sample { constructor(val) { this.value = val; // value には外部からもアクセスできる } getValue() { return this.value.toUpperCase(); } } const test = new Sample('hello world'); test.getValue(); // HELLO WORLD test.value // hello world(アクセスできちゃう)クロージャによるカプセル化に対する考え(検証前)
class で実現できないプロパティーのプライベート化(外部からアクセス不可)を実現することができる。class と異なり、実行の都度毎回新しいオブジェクト(prototypeではない)を返すので、オブジェクトの生成数が多ければ多いほどメモリの消費が気に掛かる。
クロージャによるカプセル化の例const sample = (val) => { const value = val; // value には外部から直接アクセスできない return { getValue() { return value.toUpperCase(); } }; }; const test = sample('hello world'); test.getValue(); // HELLO WORLDパフォーマンス検証
「class 構文」と「クロージャによるカプセル化」の2通りについて同一の処理を用意し、「メモリの使用量」と「オブジェクト生成速度」を比較する。なお、下記コードが比較対象のコードである。
比較コード(class構文)class Shape { constructor(option) { this.shape = document.createElement('div'); this._text = document.createTextNode(option.text); this._width = option.width; this._height = option.height; this._backgroundColor = option.backgroundColor; this.parent = option.parent; } add() { this.width().height().backgroundColor(); this.shape.appendChild(this._text); this.parent.appendChild(this.shape); } text(t) { this._text.nodeValue = t; return this; } width(w = this._width) { if (w !== this._width) this._width = w; this.shape.style.width = `${this._width}px`; return this; } ...略(コードが長いため) }比較コード(クロージャによるカプセル化)const shape = (opiton) => { const el = document.createElement('div'); const _text = document.createTextNode(option.text); const _width = option.width; const _height = option.height; const _backgroundColor = option.backgroundColor; const parent = option.parent; return { add() { this.width().height().backgroundColor(); el.appendChild(_text); parent.appendChild(el); }, text(t) { _text.nodeValue = t; return this; }, width(w = _width) { if (w !== _width) _width = w; el.style.width = `${_width}px`; return this; }, ...略(コードが長いため) } };メモリの使用量とオブジェクト生成速度の計測
class
Shape
と関数shape
で生成したオブジェクトをsavingVariable
に for 文で任意数追加し終わる速度と、その時のメモリ使用量を計測する。
メモリの使用量に関しては Chrome の「タスクマネージャ」を利用して「JavaScript メモリ」の値を計測する(タスクマネージャによるメモリ使用量のリアルタイム監視)。
- 計測は片方ずつ行う。片方計測時はもう片方はコメントアウトしておく。
- 10 回ループと 100000 回ループで数値を取る
検証コード// 作成したオブジェクトを保存する(メモリ使用量の計測用) window.savingVariable = []; console.time("no new"); for (let i = 0; i < 100000; i++) { savingVariable.push(shape(option)); } console.timeEnd("no new"); console.time("new"); for (let i = 0; i < 100000; i++) { savingVariable.push(new Shape(option)); } console.timeEnd("new");オブジェクト生成速度のみの計測
「メモリの使用量とオブジェクト生成速度の計測」では、メモリの圧迫による速度低下が考えられるので、こちらでは毎回作成したオブジェクトを永続的に参照可能な変数には保存しないようする(GC(メモリ解放)させるようにする)。
※ なお、メモリの圧迫による速度低下は 10回ループ程度場合では考えられないため、こちらでは 100000 回ループのみ計測。
- 計測は片方ずつ行う。片方計測時はもう片方はコメントアウトしておく。
- 100000 回ループで数値を取る。
検証コードconsole.time("no new"); for (let i = 0; i < 100000; i++) { const box = shape(option); } console.timeEnd("no new"); console.time("new"); for (let i = 0; i < 100000; i++) { const box = new Shape(option); } console.timeEnd("new");検証結果
検証の結果は下記のようになった。
メモリの使用量とオブジェクト生成速度の計測(結果)
コード ループ回数 メモリ使用量 速度(3回計測) 関数 shape
10 4.594MB 0.198ms, 0.171ms, 0.175ms class Shape
10 4.590MB 0.148ms, 0.172ms, 0.161ms 関数 shape
100000 62.518MB 288.227ms, 232.893ms, 244.277ms class Shape
100000 20.369MB 107.515ms, 93.580ms, 131.524ms class
Shape
は関数shape
に比べて圧倒的にメモリ使用量が少ない。大体予測は付いていたが、実際数値で見ると prototype の使用は圧倒的に効率がいいなと再認識させられた。
また、速度についてもメモリの圧迫に引っ張られてなのか classShape
の方が圧倒的に早い。
10 回ループについては、大した差は出なかった。オブジェクト生成速度のみの計測(結果)
コード ループ回数 速度(3回計測) 関数 shape
100000 86.072ms, 83.144ms, 92.013ms class Shape
100000 79.072ms, 77.062ms, 85.856ms やはり、メモリ使用量の計測と同時に行った結果については速度に差が出たが、純粋にオブジェクト生成速度の計測のみを行うコチラでは大きく速度の差は開かなかった。
その結果、new による instance の生成はコストというほどパフォーマンスに影響を与えるものではない事が分かった。追加・補足検証
一番最初の例(概要の章)で出した class
Sample
と関数sample
についても計測してみた。
理由としては、classShape
や関数shape
のように多くの機能を持ったオブジェクトの生成比較とは別に、小さなオブジェクト生成の比較結果を得るため。追加・補足検証結果
コード ループ回数 メモリ使用量 速度(3回計測) 関数 sample
10 4.713MB 0.198ms, 0.171ms, 0.175ms class Sample
10 4.713MB 0.148ms, 0.172ms, 0.161ms 関数 sample
100000 22.388MB 7.521ms, 7.259ms, 6.287ms class Sample
100000 11.488MB 10.986ms, 8.089ms, 14.462ms class
Sample
/ 関数sample
自体、メンバ変数 / 関数 の数が少ないので、100000 回 for 文で処理してもあまり差は無かった。メモリ使用量を見るとやはり classSample
の方が少なくはあるが、メモリの圧迫も然程無い為かそれによる速度差は特に見られなかった(誤差の範囲)。その為、classShape
/ 関数shape
で行なった速度のみの計測は行わなかった。結果としてやはりオブジェクトが大きいものであるほど、それが多数生成された際に class 構文による恩恵が大きく見られることが分かった。
パフォーマンス検証を終えて
第一に class 構文優秀じゃん!と思いました。個人的に new ってコストのかかるイメージを固定概念的で持っていたので、それが払拭されたのが1つと、検証結果の数値を見るに prototype (class) を使用することの効率の良さ(無駄にメモリを喰わない)を改めて再認識できた点が大きいです。
プロパティーのプライベート化については現状 class 構文で使用できない(Chrome74以降のみ可)ですが、将来的に実装もされそうですし、この検証結果の元もっと class 構文を積極的に使っていこう!という考えを自分の中で促進することができました。
- 投稿日:2019-08-21T12:47:12+09:00
IntelliJ・WebStrom・PhpStorm等のJetBrains製IDEで、文字列の直前に「language=JSON」と書くと、その文字列にJSONのシンタックスハイライトが効いて便利だった。
IntelliJやWebStrom、PhpStormなどのJetBrains製IDEで、文字列の前に
// language=JSON
というコメントをつけると、IDEが文字列をJSONとして認識してくれるため、
- JSONとしてのシンタックスハイライト
- JSON構文エラーの警告
- JSONのコード補完
- コード整形
といった、地の文でJSONを書いたときにIDEがやってくれるような恩恵を享受できるようになる。
この機能はLanguage Injectionと呼ばれるもの。コメントが書ける大抵の言語なら、JavaでPHPでもJavaScriptでもScalaでも使えるようだ。
この機能はJSONに限ったものでなく、
language=SQL
やlanguage=HTML
などのlanguage_ID
を指定することで他の言語にも対応可能。// language=<language_ID>
PHPは
@lang
が使えるPHPではPhpDocの
@lang
でもLanguage Injectionすることができる。Javaなどでは
@Language
アノテーションが使えるJavaやGroovy、Kotlinでは
org.jetbrains:annotations
をMavenなどの依存に追加することで、@Language
アノテーションを使うことができる。Scalaには対応していない様子。参考文献
所感
IntelliJやWebStrom、PhpStormなどのJetBrains製IDEで、
— suin❄️TypeScriptが好き (@suin) August 20, 2019
文字列の前に
// language=JSON
って書くと、
・JSONがシンタックスハイライトされる
・構文エラーが表示される
・補完がされる
・コード整形もできる
って知ってました?
僕は知りませんでした? pic.twitter.com/2UphpOThoM
- 投稿日:2019-08-21T11:39:35+09:00
2.2 Node JSのモジュールシステムとパターン
2.2.1 公開モジュールパターン
JavaScriptは「ネームスペース」が存在しないので、グローバルな変更が容易です。これを回避するために使うパターンが「Living Module Pattern」です。
test.jsconst module = (() => { const privateFoo = () => {...} const privateBar = () => [] const exported = { publicFoo: () => {...}, publicBar: () => {...} } })() console.log(module)JavaScriptがプライベートなスコープを形成するという性質を利用して、必要なものだけ公開します。
2.2.2 Node JSモジュールシステムの詳細
2.2.2.1 自作のモジュールローダ
test.jsfunction loadModule(filename, module, require) { const wrappedSrc = `(function(module, exports, require) { ${fs.readFileSync(filename, 'utf8')} })(module, module.exports, require)` eval(wrappedSrc) } const requireMine = (moduleName) => { console.log(`RequireMine invoked for module: ${moduleName}`) const id = requireMine.resolve(moduleName) if (requireMine.cache[id]) { return requireMine.cache[id].exports } const module = { exports: {}, id: id } requireMine.cache[id] = module loadModule(id, module, requireMine) return module.exports } requireMine.cache = {} requireMine.resolve = (moduleName) => { // モジュール名を完全な識別子に変換する。 }※evalとは、引数に指定した文字列をJavaScriptプログラムコードとして評価・実行する機能をもつ関数です。
security holeを作ってしまうので、気をつけてください。2.2.2.2 モジュールの定義
test.jsconst dependency = require('./anotherModule') function log() { console.log('Well done ${dependency.username}') } module.exports.run = () => { log() }module.exportに代入されない限りモジュールないの全ての変数が非公開になります。
2.2.2.3 グローバル変数の定義
globalオブジェクトに定義されたプロパティは、自動的にグローバル変数/関数として参照可能となります。しかし、モジュールシステムのカプセル化の利点を損なうので、一般的には使わないです。
2.2.2.4 module.exportsとexportsの使い分け
単純に新しいプロパティ追加
test.jsexports.hello = () => { console.log('Hello') }誤ったコード
test.jsexports = () => { console.log('Hello') }exportsに何か入れたい時には
test.jsmodule.exports = () => { console.log('Hello') }2.2.2.5 requireは同期関数
require()関数は同期関数なので、module.exportsオブジェクトへの操作も同期的に行われる必要があります。
→requireで呼ばれるモジュールの定義は同期処理として実装するしかありません。2.2.2.6 依存解決
myApp/ foo.js node_modules depA index.js depB bar.js node_modules depA index.js depC foobar.js node_modules depA index.js・/myApp/foo.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depA/index.jsをロードする
・/myApp/node_modules/depB/bar.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depB/node_modules/depA/index.jsをロードする
・/myApp/node_modules/depC/foobar.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depC/node_modules/depA/index.jsをロードするこのような呼び方で依存地獄を解決します。
2.2.2.7 モジュールのキャッシュ
パフォーマンス上不可欠ですが、次の天に注意してください。
・キャッシュによりモジュールの循環参照が可能になる。
・あるパッケージ内で同じモジュールが複数回requireされた場合、それらは同じインスタンスを参照する。
・require.cacheのキーをdeleteして、キャッシュを削除することも可能だが、しないほうがいい。2.2.2.8 モジュールの循環参照
a.jsexports.loaded = false; const b = require('./b') module.exports = { bWasLoaded: b.loaded, loaded: true }b.jsexports.loaded = false; const a = require('./a') module.exports = { aWasLoaded: a.loaded, loaded: true }main.jsconst a = require('./a') const b = require('./b') console.log(a) console.log(b)結果
{ bWasLoaded: true, loaded: true } { aWasLoaded: true, loaded: true }→キャッシュの影響
2.2.3 モジュール定義におけるパターン
参考文献
Node.jsデザインパターン 第2版 - Mario Casciaro (著), Luciano Mammino (著), 武舎 広幸 (翻訳), 阿部 和也 (翻訳)
- 投稿日:2019-08-21T11:26:58+09:00
mouseleave などで消えてしまう要素を setTimeout + debugger で無理やり捕まえる
- Google Chrome Dev Tool のコンソールで setTimeout + debugger を仕込む
- 時間が切れないうちに表示したい要素を表示させる
- setTimeout -> debugger が発火して要素選択できるようになる (mouseleave しても停止中なので消えない)
setTimeout(function() { debugger; }, 3000);CSS Trick にも同じ内容のものがあった
Set a Timed Debugger To Web Inspect Hard-To-Grab Elements
https://css-tricks.com/set-timed-debugger-web-inspect-hard-grab-elements/
- 投稿日:2019-08-21T09:44:48+09:00
Javascriptでnew 演算子でも普通の関数としても呼び出せるコンストラクタの作り方
- 投稿日:2019-08-21T09:44:44+09:00
React Hooks でページネーションを実装する
やりたいこと
Hooks がリリースされてから、コンポーネントのシンプルな状態管理に関しては
useState
などで済ませられるようになったが、やや複雑なコンポーネントの状態管理+アルファを Hooks でやってみたかった。つくったもの
今回は
useReducer
を使って市町村名をリストで表示するページネーションを実装してみた。
サンプルコード:https://codesandbox.io/s/paginator-demo-mnxvc
UIフレームワークは Material UI でサクッと用意。useReducer を使った例としては公式のリファレンスでもカウンターなどがありますが、もう少し実用的で、複雑な状態管理と副作用をうまく利用することが要求されるコンポーネントを作って試してみたいと思っていたので今回それをやりました。
ページネーションを題材とした理由は、主に
- コンポーネントの操作で非同期にデータをGETする必要性がある
- ページの始点・終点・中間点等でコンポーネント(「進む」「戻る」などのボタン、ページ位置の表示)の制御を切り替える必要がある
- 実際のWebアプリケーションでも使用される機会が多い
という点。その他に1ページあたりの表示件数とか、データの表示スタイルの切り替えといったアレンジも加えやすいので、思いつく限り今回は機能を盛り込んでみました。
※今回はサンプルコードを全て公開している都合上、公開APIを叩いて非同期でデータをGETするかわりに静的なデータ(市町村名とIDの一覧)を用意し擬似的に何件かずつarray.slice
で切り出して表示するという実装に変えてあります。ソースコードとコンポーネントについて
詳しくは上記の CodeSandbox の中身をみていただくとして、今回はページネーションのコンポーネントとReducerの動きについて軽く説明します。
<Paginator />
コンポーネント初期Stateは以下のように設定しています。
index.jsfunction App() { return ( <div className="App"> <Container fixed> <Paginator sum={cities.length} //データの総件数 per={10} //1ページあたりの表示件数 initialData={cities.slice(0, 10)} //読み込み時に表示するデータ component={ListComponent} //データを渡すPresentaionコンポーネント /> </Container> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
<Button>
前へ・次へページの操作を行います。
「前へ」ボタンがクリックされたら前n件のデータとともにviewPreview
という関数を、「次へ」ボタンがクリックされたらviewNext
という関数を発行します。Paginator.jsconst reducer = (state, action) => { switch (action.type) { //[前へ]ボタンをクリック時に発行。 //ページ数のデクリメント case "viewPreview": return { ...state, currentPage: state.currentPage - 1, resourceData: action.data }; //[次へ]ボタンをクリック時に発行。 //ページ数のデクリメント case "viewNext": return { ...state, currentPage: state.currentPage + 1, resourceData: action.data }; . . . } };reducer は発行される直前の
state
とdispatch()
の中身(≒変更するstate)をaction
として引数にとります。変更を加えないstateは...state
とスプレッド演算子を使って return しないと上手くコンポーネントが機能しないため忘れずに。
<Select>
1ページあたりの表示件数の切り替えSelectボックスを変更した場合、
setPerAmount
という関数を発行します。Paginator.js//reducer //表示件数の切り替え時に発行。 //現在のページを1ページ目にリセット。 case "setPerAmount": return { ...state, currentPage: 1, per: action.per, pageAmount: Math.ceil(state.sum / action.per), resourceData: action.data };表示件数を切り替えた場合は一番先頭のデータからまた表示し直すことが良い?気がするので
action
に入っているデータは先頭からn件の内容です。
<Switch>
表示形式(コンポーネントのスタイル)を切り替え初期状態では市町村名とIDが2段組になったリスト形式で表示していますが、スイッチを切り替えることでシンプルな1行のコンポーネントで現在のページの内容を表示し直します。
スイッチの切り替え時にデータを1行ずつ表示するための子コンポーネントとswitchComponent
という関数を発行します。Paginator.js//reducer //表示形式の切り替え時に発行。 case "switchComponent": return { ...state, component: action.component };最後に
公式リファレンスを読むと使い方がなんとなくわかった気になりますが、実務ではカウンターやTodoリスト以上に複雑な機能を扱うことの方が多いためどういう場面で Hooks の力を最大限利用できるのかはまだ手探り中です。今回は
useContext
抜きで実装しましたが、扱うコンポーネントの数が増えてきたら避けては通れなくなるかも。
- 投稿日:2019-08-21T02:24:38+09:00
mathjsで桁あふれする規模の二項分布の計算をしてみた
mathjsで桁あふれする規模の二項分布の計算をしてみた
やってみたら、小規模なら簡単でも大規模だと工夫が必要であった。
別に言語にはこだわりは無いのだけど、今回は JavaScript を使用した。実装的な結論から言うと BigNumber を使うだけの話です。
数式からプログラムを実装することに慣れていない人は参考になるかもしれない。環境
Ubuntu 18.04
node: v10.16.2Step 1. 数式のおさらい
$n$枚のコインを投げて、$x$枚が表になるような確率は、次のように表される。
Xは確率変数である。具体的には X={0枚のコインが表(すべて裏)の事象, 1枚のコインが表になる事象, ...} である。P_{X}(x) = {}_{n}C_{k} p^{x}(1-p)^{n-x}答え合わせしやすいように、次のサイトと同じパラメーターで数式、およびプログラムの実装例を示す。
具体的に、コインを10回投げて、表が3回となる確率を考える。
根元事象は 3枚のコインが表になる事象 で、確率変数$x$の値は 3 である。
なお、理想的なコインを考えて、$p=0.5$とする。\begin{aligned} P_{X}(3) &= {}_{10}C_{3} 0.5^{3}(1-0.5)^{10-3} \\ &= \dfrac{10 \cdot 9 \cdot 8}{3 \cdot 2 \cdot 1} \cdot 0.125 \cdot 0.0078125 \\ &= 0.1171875 \end{aligned}mathjs (Node.js)を用いた実装例
数式そのままです。特に解説はいりませんね。const math = require('mathjs') const n = 10 const k = 3 const p = 0.5 p_n_k = math.combinations(n, k) * math.pow(p, k) * math.pow(1 - p, n - k) console.log('P(n=%d,x=%d)=%f', n, k, p_n_k)実行例
結果も手計算で求めたものに一致します。$ node throw_coin_10_times_3_head.js P(n=10,x=3)=0.1171875 $Step 2. コインを10回投げて、表が3回以上となる確率
コインを10回投げて、表が3回 以上 となる確率を考える。
根元事象の集合は 3枚のコインが表になる事象 ∪ 4枚のコインが表になる事象 ∪…∪ 10枚のコインが表になる事象 で、確率変数$x$={3, 4, ..., 10} である。離散型の確率分布であることに注意(3.5とかは存在していない)。この確率、どのくらいになると思いますか?数式はこんな感じです。
\begin{aligned} P_{X}(3 \cup 4 \cup \cdots \cup 10) &= P_{X}(3) + P_{X}(4) + \cdots + P_{X}(10) \\ &= \sum_{x=3}^{10} {}_{10}C_{x} 0.5^{x}(1-0.5)^{10-x} \\ &= {}_{10}C_{3} 0.5^{3}(1-0.5)^{10-3} + \cdots + {}_{10}C_{10} 0.5^{10}(1-0.5)^{10-10} \\ &\simeq 0.945 \end{aligned}mathjs (Node.js)を用いた実装例
for文でぐるぐる回しているだけです。まだ、簡単なはず。特に解説はいりませんね。const math = require('mathjs') const k = 3 const p = 0.5 for (let n = 10; n <= 10; n++) { let sum = 0.0; for (let i = k; i <= n; i++) { p_n_k = math.combinations(n, i) * math.pow(p, i) * math.pow(1 - p, n - i) console.log('P(n=%d,x=%d)=%f', n, i, p_n_k) sum += p_n_k } console.log('P(n=%d,3<=x)=%f', n, sum) }実行例
結果も手計算で合計したものに大体一致します。ちなみに、約95%ですね。$ node throw_coin_10_times_3to10_head.js P(n=10,x=3)=0.1171875 P(n=10,x=4)=0.205078125 P(n=10,x=5)=0.24609375 P(n=10,x=6)=0.205078125 P(n=10,x=7)=0.1171875 P(n=10,x=8)=0.0439453125 P(n=10,x=9)=0.009765625 P(n=10,x=10)=0.0009765625 P(n=10,3<=x)=0.9453125 $Step 3. : 5%のあたりくじを400本引いて、あたりの本数が20回以上となる確率
Step 2.の数値を置き換えができれば、数式は簡単です。
$p= 0.05, n=400, k=\{20, 21, \cdots, 400\}$数式はこんな感じです。
\begin{aligned} P_{X}(20 \cup 21 \cup \cdots \cup 400) &= P_{X}(20) + P_{X}(21) + \cdots + P_{X}(21) \\ &= \sum_{x=20}^{400} {}_{400}C_{x} \cdot 0.05^{x}\cdot(1-0.05)^{400-x} \\ &= \text{???} \end{aligned}ただ、 400Cx をこれまでのやり方で数値計算してみようとすると、桁あふれします。
mathjs (Node.js)を用いた実装例(順列の計算だけ)
順列の計算だけを抜き出したコードです。const { combinations } = require('mathjs') let n = 400; for (let k = 20; k <= 400; k++) { console.log('(n=%d, k=%d) -> %d', n, k, combinations(n, k)) }実行結果
Number型で桁あふれしてしまっています。Σの合計どころではありません。$ node mathjs_node_combinations_sample.js (n=400, k=20) -> 2.788360983670897e+33 (n=400, k=21) -> 5.045605589499718e+34 (n=400, k=22) -> 8.692202356456333e+35 (n=400, k=23) -> 1.4285445611915188e+37 ...snip (n=400, k=120) -> 5.7072682234758346e+104 (n=400, k=121) -> 1.3206901674158961e+105 (n=400, k=122) -> Infinity (n=400, k=123) -> Infinity (n=400, k=124) -> Infinity (n=400, k=125) -> Infinity (n=400, k=126) -> Infinity ...snipFinal Step. :プログラムの実装例
実は、桁あふれ以外にも、加算時の情報落ちなど、もとのコードは色々問題があります。
mathjs は BigNumber を使えるので、全面的にこれを使います。これで、BigNumberで扱える範囲の問題なら数値計算ができます。
mathjs (Node.js)を用いた実装例(BigNumer使用)
const math = require('mathjs') const n = 400 const k = 20 const p = 0.05 let sum = math.bignumber(0.0); for (let i = k; i <= n; i++) { let c_n_k = math.bignumber(math.combinations(math.bignumber(n), math.bignumber(i))) let pow_p = math.pow(math.bignumber(p), math.bignumber(i)) let pow_np = math.pow(math.bignumber(1 - p), math.bignumber(n - i)) p_n_k = math.bignumber(c_n_k * pow_p * pow_np) console.log('P(n=%d,k=%d)=%s * %s * %s -> %s', n, i, c_n_k.toPrecision(8), pow_p.toPrecision(4), pow_np.toPrecision(4), p_n_k.toPrecision(4)) sum = math.add(sum, p_n_k) } console.log('P(n=%d, %d<=x) = %s', n, k, sum.toFixed(4))実行結果
表示桁が長い部分(4桁以上)は、削っています。Infinity扱いとなっていた計算に成功しています。$ node lottery_400_hit_over_20.js P(n=400,k=20)=2.7883610e+33 * 9.537e-27 * 3.427e-9 -> 0.09114 P(n=400,k=21)=5.0456056e+34 * 4.768e-28 * 3.608e-9 -> 0.08680 P(n=400,k=22)=8.6922024e+35 * 2.384e-29 * 3.798e-9 -> 0.07870 P(n=400,k=23)=1.4285446e+37 * 1.192e-30 * 3.998e-9 -> 0.06808 ...snip P(n=400,k=120)=5.7072682e+104 * 7.523e-157 * 5.789e-7 -> 2.486e-58 P(n=400,k=121)=1.3206902e+105 * 3.762e-158 * 6.094e-7 -> 3.027e-59 P(n=400,k=122)=3.0202669e+105 * 1.881e-159 * 6.414e-7 -> 3.644e-60 P(n=400,k=123)=6.8262942e+105 * 9.404e-161 * 6.752e-7 -> 4.334e-61 P(n=400,k=124)=1.5249060e+106 * 4.702e-162 * 7.107e-7 -> 5.096e-62 P(n=400,k=125)=3.3669925e+106 * 2.351e-163 * 7.482e-7 -> 5.922e-63 P(n=400,k=126)=7.3485948e+106 * 1.175e-164 * 7.875e-7 -> 6.803e-64 ...snip P(n=400,k=247)=1.5240999e+114 * 4.422e-322 * 0.0003906 -> 2.618e-211 P(n=400,k=248)=9.4027133e+113 * 2.211e-323 * 0.0004111 -> 7.640e-213 P(n=400,k=249)=5.7398089e+113 * 1.105e-324 * 0.0004328 -> 0.000 P(n=400,k=250)=3.4668446e+113 * 5.527e-326 * 0.0004556 -> 0.000 ...snip P(n=400,k=397)=10586800 * 3.098e-517 * 0.8574 -> 0.000 P(n=400,k=398)=79800.000 * 1.549e-518 * 0.9025 -> 0.000 P(n=400,k=399)=400.00000 * 7.745e-520 * 0.9500 -> 0.000 P(n=400,k=400)=1.0000000 * 3.873e-521 * 1.000 -> 0.000 P(n=400, 20<=x) = 0.5320 $所感
数値計算は奥深いですね。
実際、BigNumer の範囲では計算はできるのですが、逆に言えばできることはその範囲までです。
BigNumberを使用すると整数型や浮動小数点の計算に比べてパフォーマンス上の問題もあって、ループ回数を増やしたりすると時間がかかり、時間的にできることも縮小します。
(最近流行のDeep Learningが流行っているのは、数値計算ライブラリのアルゴリズム、計算時間、ハードウェアなどを活用して、計算が可能になったことが一つのピースとなったと言えます。)こういったAPIなんて使用せず、自前で実装するなら、計算順序などを工夫して情報落ちなど避けることはできます。
今回で言うと、 $0.05^{122}$ とかですね。 1.880e-159 とかほぼ0です。今回は (デカイ数) × (小さい数) × (小さい数) という構造をしていて、400Cx が math.combinations(n,k) と math.pow(p, k) などと、数式が単位にAPIがあるのは、とても分かりやすい。ただ、分かりやすくAPIが意味的(数式的に)に分離されていることとは、必ずしも数値計算に有利だとは限らない。こういうのは、素朴に数式をハードウェアにあわせて最適化するアプローチもあるが一方で、シミュレーテッド・アニーリングのアルゴリズムのように意味的な関連付けは困難だが、入力とそれに対する解の性質は分かっていて使い物になる……そういうアプローチもある。今回のは、実装は綺麗に見えて、計算機科学的にダサいそういう感じのものであろう。
参考
数学・計算機科学関連
- 13-1. 二項分布 | 統計学の時間 | 統計WEB
- 順列・組合せ - 高精度計算サイト
異なる n個のものから r個を選ぶ組み合わせの総数 nCr を求めます。- 情報落ち、桁落ち、丸め誤差、打切り誤差の違い
プログラミング関連
- mathjs
- 数値オブジェクト|Numberオブジェクト|JavaScript/DOM|PHP & JavaScript Room
- 階乗、順列、組み合わせの計算をPython、Java、JavaScriptで行う | Monotalk
その他
- 投稿日:2019-08-21T00:46:58+09:00
Nuxt.js 2.9 がリリースされたぞ!
Logo from Nuxt TypeScript https://typescript.nuxtjs.org/Vue を活用したユニバーサルアプリフレームワークの Nuxt.js の最新バージョン v2.9 がリリースされました。
以下リリースノートを翻訳していきます。あまりなれてないので誤訳があったらごめんなさい ?
https://github.com/nuxt/nuxt.js/releases/tag/v2.9.0⚠️ 重要事項
- Node.js のサポートバージョンが v8.9.0 以降となりました
- TypeScript のサポートが外部化されました
- ※ おそらく nuxt と @nuxt/typescript の分離が進んだということだと思います
- 公式ガイド と マイグレーションガイド を参照ください
vue-meta
が 2.0.0 に更新されました。対応するリリースノートを参照してください。scrollBehavior
オプションが非推奨となりました PR#6055
app/router.scrollBehavior.js
を代わりに使用してください。- devModules オプションが非推奨になりました。 PR#6203
buildModules
を代わりに使用してください。? バグフィックス
- General
modulepreload
警告の修正- QuietMode の際のビルドエラーで例外を投げる
- ModernMode のための Babel CoreJS のサポート
- Babel Pollyfill が無効化できない件
- Renderer
- Safari 10 における
nomodule
スクリプトの完全な読み込み- CLI
nuxt
とnuxt-edge
が両方インストールされているときの対策- vue-app
- Hash Navigation での
triggerScroll
の発火- vue-renderer
createRenderer
の実行前にrender:resourcesLoaded
を呼び出す機能の追加- キャッシュの変更を防ぐための SPAメタ情報の複製
- Webpack
- CSSの展開が HMR と Source-map を破壊する問題の対策
? 新機能
- CLI
- Option が Export されるようになった
- server
renderAndGetWindow
にloadingTimeout
と他のパラメータも渡せるようになった- Webpack
build.transpile
に関数をエントリを追加できる様になった- vue-app
$nuxt.refresh
が追加- ビルドインジケータの表示に WebSocket の代わりに EventSource を使用するようになった
scrollBehavior
からapp/router.scrollBehavior.js
への移行<no-ssr>
のエイリアスとして<client-only>
の追加- ローディングインジケータのカスタマイズができるようになった
- asyncData と fetch で
$nuxt.refresh()
が利用できるようになった- Option が Export されるようになった
watchQuery
をサポート- vue-renderer
- V1系の互換性のためCSPオプションを追加
- SSR時点でのbodyタグの前後へのタグの差し込みのサポート
- typescript
- TypeScript サポートのための仕組みの外部パッケージ化
- 後述します
? リファクタリング
- config
devModules
はbuildModules
にリネームされました- babel-preset-app
babel-plugin-dynamic-import-node
の削除- Webpack
- トランスパイルの正規化がシンプルになった
- General
@nuxt/eslint-config
v1 への依存のリファクタ?その他
- 省略
TypeScript サポートの外部パッケージ化について
TypeScript 大好きクラブ会員として気になったので少し紹介します。
今回 Nuxt.js 2.5 からサポートされた TypeScript のサポートのパッケージ分割がすすみ、
@nuxt/typescript
で提供されていた機能が Nuxtのビルド時に効果を発揮する@nuxt/typescript-build
、 型定義を提供する@nuxt/types
、そして、実行時に TypeScript を評価できるようにする@nuxt/typescript-runtime
に分割されました。これは Nuxt.js 2.9 のリファクタリングの一環によるもので、主にモジュールが Nuxt.Builder を拡張できる機能や、WebpackのWarningFix プラグインのフィルタをカスタマイズすること、あるいは Nuxt が内包する BabelLoader の設定を拡張したり、ts-loader で tsx をハンドリングしたりするシーンに置いてより柔軟に便利にする目的があるようです。(https://github.com/nuxt/nuxt.js/pull/5854))
おそらく、モジュールが分割されたことで、ゴリゴリカスタムしたいシーンなどでもより便利になったということでしょう。また以降は Nuxt.js の TypeScript サポートは Nuxt TypeScript という nuxtjs.org 配下の公式コンテンツとして取り扱われ、TypeScript の コード補完やコードの品質、型安全 を意識したより手堅い Nuxt.js による開発手法をサポートしていくようです。
現在確認できる情報として、分割されたパッケージについての説明や、詳しい設定方法の他、サンプルとして Object API、 Class API、 Function API それぞれでの実装方法が例示されています。(なんて呼んであげたらいいかわからなかったのでオブジェクト記法とかクラス記法とか個人的には呼んでたのですが、正式な呼び方ができたようで一安心。)移行ガイド
今回のリファクタリングによりパッケージの分割と廃止が行われたため、package.json に記録されている依存のパッケージの入れ替え、および設定の変更追従が必要となります。下記はマイグレーションガイドを簡単に翻訳したものです。
1. 依存パッケージの切り替え
@nuxt/typescript
非推奨となったので代替として@nuxt/typescript-build
@nuxt/types
をインストールします。npm uninstall @nuxt/typescript npm install -D @nuxt/typescript-build @nuxt/types2. 型定義参照元の切り替え
@nuxt/vue-app
と@nuxt/config
から参照していた型定義は@nuxt/types
から参照するように変更します。"compilerOptions": { "types": [ "@nuxt/types" ] }3. @nuxt/typescript-build を nuxt.config.js のモジュール定義に追加
export default { modules: ['@nuxt/typescript-build'] }4. @nuxt/typescript の設定は build.typescript に移動
export default { typescript: { typeCheck: true, ignoreNotFoundWarnings: true } }下記のように書くこともできます
export default { modules: [ ['@nuxt/typescript-build', { typeCheck: true, ignoreNotFoundWarnings: true }] ] }5. 直接TypeScript実行したい場合 (オプション)
すべてをTypeScriptで書きたい皆様向けの設定として、
@nuxt/typescript-runtime
が用意されています。これをインストールするとnuxt-ts
もしくはnuxts
がコマンドとして使用できるようになり、設定ファイルはnuxt.config.ts
とできる他、モジュールや serverMiddleware も TypeScript で配置し実行できるようになります。npm install @nuxt/typescript-runtime
"scripts": { "dev": "nuxt-ts", "build": "nuxt-ts build", "start": "nuxt-ts start" "generate": "nuxt-ts generate" }まとめ
リリースノートを見る限り、
scrollBehavior
やdevModules
などの廃止と移行がある以外はあまり大きな変更はなさそうでした。TypeScriptでNuxt.jsを実装している場合は、移行ガイドに従ってパッケージの入れ替えが必要になりますのでサクッと追従してあげると良さそうです。最新と手元のバージョンの差が大きくならないように頑張っていきましょう! ?from: Scrapbox
- 投稿日:2019-08-21T00:35:53+09:00
Reactでメモアプリを作る(React基礎講座9)
はじめに
今回は、Reactを使って、メモを追加・削除ができる簡単なメモアプリを作成していきます。
挙動は以下のような感じです。
シリーズ
本記事はReact基礎講座のための連載になっています。気になる方のために、前の章は以下です。
React開発で見かける配列処理系のメソッド map , filter について(React基礎講座8) - Qiita
最初の記事は、以下です。
Reactを使ってJSXの内容をレンダリングする(create-react-app)(React基礎講座1) - Qiita
メモアプリとは
仕様は以下のようなシンプルなReactアプリケーションです。
- テキストエリアにテキストを入力して、入力ボタンを押したらその内容が一覧で表示される
- メモの一覧にはそれぞれ、削除ボタンがあり、それを押したらそのアプリが消える
- メモはDBには格納されない
ファイル構成
root/ ├ public/ └ src/ └ components/ | └ Form.js | └ List.js └ memoApp.js └ index.js
- index.js
React.Component
であるmemoApp
をimportする- MemoApp.js
- stateとして以下の情報を初期設定します
memos
: 具体的なメモの情報nextId
: 次に追加するメモのidの情報
- stateを持たせるので、クラスコンポーネントで実装します
- メモを保存する機能
addMemo
と、削除する機能deleteMemo
を作る
- これら2つの機能が
state
を変更させます- コンポーネント
Form.js
とList.js
をimportする
- これらも今回はクラスコンポーネントで実装しましょう
- /components/Form.js
- メモのフォーム部分の見た目を作る
- /components/List.js
- メモ一覧の見た目を作る
コンポーネントの配置
まずは、ファイル構成どおりにコンポーネントを配置していきましょう。
まずは、
index.js
index.jsimport React from "react"; import { render } from "react-dom"; import MemoApp from "./MemoApp"; render(<MemoApp />, document.getElementById("root"));次に、
MemoApp.js
このコンポーネントから、
<Form />
と<List />
をimportして呼び出していることが分かります。MemoApp.jsimport React from "react"; import { render } from "react-dom"; import Form from "./components/Form"; import List from "./components/List"; class MemoApp extends React.Component { constructor(props) { super(props); } render() { return ( <div> <h2>MemoApp</h2> <Form /> <List /> </div> ); } } export default MemoApp;続いて、
Form
とList
コンポーネントを作成していきます。Form.jsimport React from "react"; import { render } from "react-dom"; class Form extends React.Component { constructor(props) { super(props); } render() { return <h3>Form</h3>; } } export default Form;List.jsimport React from "react"; import { render } from "react-dom"; class List extends React.Component { constructor(props) { super(props); } render() { return <h3>List</h3>; } } export default List;すると、こんな感じで作成できましたかね?これで、下準備というか、コンポーネントの配置は完了です。
MemoAppにStateを定義してListで表示
MemoAppにStateを定義していきます。
前出しましたが、定義するStateは2種類です。
memos
: 具体的なメモの情報
- 配列の中に連想配列を持たせましょう(keyは、
id
とcontent
にしましょう)- これ、最終的にはテキストボックスからcontentを生成しますが、今回はコンポーネントの見た目確認のため、Stateに定義します
nextId
: 次に追加するメモのidの情報
MemoApp
では、<List />
コンポーネント部分にstateのmemos
を渡して、List.js
で表示させるようにしましょう。
List.js
では、props
で、MemoAppにStateとして定義したmemos
を要素全て表示させましょう。
その時に、使用するメソッドは....mapでしたね。それでは、実装していきましょう。まずは、
MemoApp.js
から。MemoApp.jsimport React from "react"; import { render } from "react-dom"; import Form from "./components/Form"; import List from "./components/List"; class MemoApp extends React.Component { constructor(props) { super(props); this.state = { memos: [ { id: 1, content: "one" }, { id: 2, content: "two" }, { id: 3, content: "three" }, { id: 4, content: "four" }, { id: 5, content: "five" } ], nextId: 0 }; } render() { return ( <div> <h2>MemoApp</h2> <Form /> <List memos={this.state.memos} /> </div> ); } } export default MemoApp;こちらは、
State
を定義して、それをState
として<List />
コンポーネントにmemos
として渡します。
List.js
では、MemoApp.js
からState
に乗って渡ってきたmemos
をmap
で要素ごとに処理した結果を変数list
コンポーネントに入れて、その下のrenderメソッドで{list}
として受け取ります。List.jsimport React from "react"; import { render } from "react-dom"; class List extends React.Component { constructor(props) { super(props); } render() { const list = this.props.memos.map(memo => { return ( <li> #{memo.id} - {memo.content} </li> ); }); return ( <div> <h2>List</h2> {list} </div> ); } } export default List;こんな感じですね。見た目は、こんな感じになります。
Form.jsを作成する
今度は、メモを作成する
Form
のコンポーネントを作成します。Form.jsimport React from "react"; import { render } from "react-dom"; class Form extends React.Component { constructor(props) { super(props); this.state = { content: "content" }; } render() { return ( <div> <h2>Form</h2> <input value={this.state.content} /> <input type="submit" value="Add Memo" /> </div> ); } } export default Form;ただ、このままだと、テキストエリアの値がいつまでも変わらないので、テキストエリア内の値が更新された場合、その変更後の値をテキストエリアに表示させます。テキストエリア内の値のstateが変更されたことを検知するイベントハンドラといえば、
onChange
ですね。これは、フォーム内の要素の内容が変更された時に起こるイベントハンドラです。これをハンドラにして、メソッド
handleChange
を呼び出すような処理を書きましょう。handleChange
関数の引数にevent
をようして、関数内でevent.target.value
と記述すると、イベントの結果更新された値を取得することができます。それを変数content
として格納する。その変数(
content
)を使って、setState
してフォーム内の値(state)を更新します。つまり、handleChange
関数を使って自分自身の値を更新させていることがわかります。それでは、実装していきましょう。
Form.jsimport React from "react"; import { render } from "react-dom"; class Form extends React.Component { constructor(props) { super(props); this.state = { content: "content" }; } render() { return ( <div> <h2>Form</h2> <input value={this.state.content} onChange={this.handleChange} /> <input type="submit" value="Add Memo" /> </div> ); } handleChange = event => { const content = event.target.value; this.setState({ content: content }); }; } export default Form;次に、
submit
した際の挙動を書いていきましょう。input
タグをform
タグで囲って、<input type="submit" value="Add Memo" />
をクリックした際の処理(関数を)form
の開始タグに記述します。その際のハンドラは、onSubmit
ですね。onSubmit
の詳細は、以下の記事を見て見てください。参考
https://www.sejuku.net/blog/28720
onSubmit
で検知した際に、処理させる関数名はhandleSubmit
とでもしましょう。今回も引数event
を受け取るようにしましょう。処理内容は、一旦以下のようにします。
- まずは、submitをクリックした際のデフォルトの挙動をしないようにする
- 次に、アラートにて、現状の
this.state.content
の値を表示させるようにする- アラートで、現状のcontentを表示させたら、現状のテキストエリアの値は何も無いことにします
こんな感じです。では、実装していきましょう。
Form.jsimport React from "react"; import { render } from "react-dom"; class Form extends React.Component { constructor(props) { super(props); this.state = { content: "content" }; } render() { return ( <div> <h2>Form</h2> <form onSubmit={this.hamdleSubmit}> <input value={this.state.content} onChange={this.handleChange} /> <input type="submit" value="Add Memo" /> </form> </div> ); } handleChange = event => { const content = event.target.value; this.setState({ content: content }); }; hamdleSubmit = event => { event.preventDefault(); alert(this.state.content); this.setState({ content: "" }); }; } export default Form;こんな感じですね。挙動は、こんな感じになります。
ここまでできれば一旦OKです。
なので、
Form.js
のコンストラク内のstateのcontentで定義している文字列は空白でもいいかもですね。次は、いよいよ、アラートを出すのではなく、実際に、stateを変更して、
List
コンポーネントを変更するような機能を作成していきます。stateを変更する機能を作成する
今度は、テキストフォームにメモの内容を記述したら、
List
コンポーネントにその値が反映されるようにしましょう。手順は以下のような感じです。
- (色々なやり方がありますが今回は)
MemoApp.js
でテキストを入力してボタンを押したら
- 関数(
addMemo
)が走るようにしましょう
- 関数(
addMemo
)自体は、
setState
が走るようにして- その中でstate(memosの配列に
Spread operator
で既存のものを残しつつ、新たな要素を追加します)- contentの内容は
Form.js
内のhamdleSubmit
関数の中で引数として渡します- 宣言元の
addMemo
は引数としてhamdleSubmit
関数から渡ってきたメモ内容(this.state.content
)を引数(content
)として受け取って、Spread operator
(3点)の後にカンマして、あらたなオブジェクトに渡す- そして、idも
this.state.nextId
もセットして、その後のsetState
内で 1たす項目を設けます- 既存のstate(
memos
)にsubmit
した際、要素が加われば、コンストラクタで定義した既存のstate(memos
)は消しましょうJSでは、
Spread operator
(スプレッド構文)は頻出シンタックスなのでぜひ覚えてください。https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax
また、今回実装した
state
を変更するメソッド(addMemo
)を親(MemoApp.js
)に持たせて、それを子(Form.js
)に渡して実行する方法はよくあるので難しいですが手に馴染ませてください。子に渡っているのは
hamdleSubmit
関数の中のthis.props.addMemo(this.state.content);
← この部分ですね。それでは、実装していきましょう。
MemoApp.jsimport React from "react"; import { render } from "react-dom"; import Form from "./components/Form"; import List from "./components/List"; class MemoApp extends React.Component { constructor(props) { super(props); this.state = { memos: [], nextId: 0 }; } addMemo = content => { this.setState({ memos: [...this.state.memos, { id: this.state.nextId, content: content }], nextId: this.state.nextId + 1 }); }; render() { return ( <div> <h2>MemoApp</h2> <Form addMemo={this.addMemo} /> <List memos={this.state.memos} /> </div> ); } } export default MemoApp;Form.jsimport React from "react"; import { render } from "react-dom"; class Form extends React.Component { constructor(props) { super(props); this.state = { content: "" }; } render() { return ( <div> <h2>Form</h2> <form onSubmit={this.hamdleSubmit}> <input value={this.state.content} onChange={this.handleChange} /> <input type="submit" value="Add Memo" /> </form> </div> ); } handleChange = event => { const content = event.target.value; this.setState({ content: content }); }; hamdleSubmit = event => { event.preventDefault(); this.props.addMemo(this.state.content); this.setState({ content: "" }); }; } export default Form;こんな感じですね。挙動は、こんな感じになります。
stateを削除する機能を作成する
次は、個々のメモを削除するような機能を作成していきます。
手順は以下のような感じです。
List.js
のコンポーネントにタグを設置- 機能自体は
MemoApp.js
のaddMemo
の下にdeleteMemo
メソッドを作成していきましょう
deleteMemo
メソッドは引数にmemosのid
を取れるようにしてください- では、上記の呼び出しを
List.js
に定義したbuttonタグにハンドラはonClick
として、アロー関数をdeleteMemo
メソッドのコールバックとして実行してください- コール先の関数の中の処理ですが
- 配列(
memos
)をfilter
で処理して、引数として受け取ったid以外をTRUE
として返すような条件でにして、結果を変数filteredArray
に格納し、それをmemosのsetState
として渡すそれでは、実装していきましょう。
MemoApp.jsimport React from "react"; import { render } from "react-dom"; import Form from "./components/Form"; import List from "./components/List"; class MemoApp extends React.Component { constructor(props) { super(props); this.state = { memos: [], nextId: 0 }; } addMemo = content => { this.setState({ memos: [...this.state.memos, { id: this.state.nextId, content: content }], nextId: this.state.nextId + 1 }); }; deleteMemo = id => { const filteredArray = this.state.memos.filter(memo => { return memo.id !== id; }); this.setState({ memos: filteredArray }); }; render() { return ( <div> <h2>MemoApp</h2> <Form addMemo={this.addMemo} /> <List memos={this.state.memos} deleteMemo={this.deleteMemo} /> </div> ); } } export default MemoApp;react.List.jsimport React from "react"; import { render } from "react-dom"; class List extends React.Component { render() { const list = this.props.memos.map(memo => { return ( <li> #{memo.id} - {memo.content}{" "} <button onClick={() => this.props.deleteMemo(memo.id)}>delete</button> </li> ); }); return ( <div> <h2>List</h2> <ul>{list}</ul> </div> ); } } export default List;こんな感じですね。挙動は、こんな感じになります。
冗長な部分をリファクタ
冗長な書き方になってしまっている部分を
const { ... } = this.state;
(props
でも同様)と定義してリファクタしてあげましょう。また、この記述
import { render } from "react-dom";
が不要なファイルはあるので、不要であれば消します。そして、
Warning: Each child in a list should have a unique "key" prop.というエラー対応をします。具体的には、
タグにList
コンポーネント’にあるid
を追加します。<li key={memo.id}>こんな感じですね。
それでは、実装していきましょう。
MemoApp.jsimport React from "react"; import Form from "./components/Form"; import List from "./components/List"; class MemoApp extends React.Component { constructor(props) { super(props); this.state = { memos: [], nextId: 0 }; } addMemo = content => { const { memos, nextId } = this.state; this.setState({ memos: [...memos, { id: nextId, content: content }], nextId: this.state.nextId + 1 }); }; deleteMemo = id => { const { memos } = this.state; const filteredArray = memos.filter(memo => { return memo.id !== id; }); this.setState({ memos: filteredArray }); }; render() { const { memos } = this.state; return ( <div> <h2>MemoApp</h2> <Form addMemo={this.addMemo} /> <List memos={memos} deleteMemo={this.deleteMemo} /> </div> ); } } export default MemoApp;List.jsimport React from "react"; class List extends React.Component { render() { const { memos, deleteMemo } = this.props; const list = memos.map(memo => { return ( <li key={memo.id}> #{memo.id} - {memo.content}{" "} <button onClick={() => deleteMemo(memo.id)}>delete</button> </li> ); }); return ( <div> <h2>List</h2> <ul>{list}</ul> </div> ); } } export default List;長くなりましたが、Reactを使ったメモアプリ作成は以上です。参考にしてみてください。
参考
- 改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで | 山田 祥寛
- 投稿日:2019-08-21T00:15:01+09:00
jestでパラメータ化テストをする
はじめに
境界値分析など、引数を変えて試験をしたいことはよくあるとおもいます。
Javaではspockなどが有名ですが、jestでもパラメータ化テストは可能です!試験対象
以下の現在時刻が昼間かどうかを判定するモジュールを試験することにします。
isDaytime.jsmodule.exports = () => { const hours = new Date().getHours(); return ((6 < hours) && (hours <= 18)); }書き方
書き方には2種類あります。
テストの性質や好みで書き分けられます。配列を使った書き方
const isDaytime = require('./isDaytime'); beforeEach(() => { jest.clearAllMocks(); }); const OriginalDate = Date; // 退避 test.each([ [0, false], [5, false], [6, false], [7, true], [17, true], [18, true], [19, false], ])('%i時のとき、%pを返す', (hours, expected) => { jest.spyOn(global, 'Date').mockImplementation(() => { return new OriginalDate(2019, 8, 20, hours) }); expect(isDaytime()).toBe(expected); });https://jestjs.io/docs/ja/api#1-testeachtable-name-fn-timeout
test.each([[パラメータ1], [パラメータ2]])(テストケース名, 関数, タイムアウト)
の形式で記載します。
テストケースのタイトルにはprintf書式を使うことができ、パラメータで指定した順に参照されます。
以下のように、書き方を工夫するとタイトルをより読みやすくすることも可能です
const isDaytime = require('./isDaytime'); beforeEach(() => { jest.clearAllMocks(); }); const OriginalDate = Date; // 退避 test.each([ [0, '夜中', false], [5, '夜中', false], [6, '夜中', false], [7, '昼間', true], [17, '昼間', true], [18, '昼間', true], [19, '夜中', false], ])('%i時のときは、%s', (hours, _, expected) => { jest.spyOn(global, 'Date').mockImplementation(() => { return new OriginalDate(2019, 8, 20, hours) }); expect(isDaytime()).toBe(expected); });テンプレートリテラルを使った書き方
const isDaytime = require('./isDaytime'); beforeEach(() => { jest.clearAllMocks(); }); const OriginalDate = Date; // 退避 test.each` hours | expected ${0} | ${false} ${5} | ${false} ${6} | ${false} ${7} | ${true} ${17} | ${true} ${18} | ${true} ${19} | ${false} `('$hours時のとき、$expectedを返す', ({hours, expected}) => { jest.spyOn(global, 'Date').mockImplementation(() => { return new OriginalDate(2019, 8, 20, hours) }); expect(isDaytime()).toBe(expected); });https://jestjs.io/docs/ja/api#2-testeach-table-name-fn-timeout
テンプレートリテラルを使用してパラメータを書けます。
変数が多い時などにこの書き方にすると、見やすくなると思います。
文字列をパラメータに使いたい場合、${"文字列"}
の形式で指定できます。便利な設定
書き方をよく忘れてしまうので、VSCodeのスニペットなどに、パラメータテストを呼び出せるような設定をしておくと便利です
javascript.json{ // Place your snippets for javascript here. Each snippet is defined under a snippet name and has a prefix, body and // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the // same ids are connected. // Example: // "Print to console": { // "prefix": "log", // "body": [ // "console.log('$1');", // "$2" // ], // "description": "Log output to console" // } "jestEach":{ "prefix": "jest.each", "body": [ "test.each([", " [0, false],", "])('%iのとき、%pを返す', (hoge, expected) => {", "})" ], "description": "パラメータテスト" } }
- 投稿日:2019-08-21T00:11:42+09:00
Mermaid.js Live Editorのテーマを変更する
はじめに
最近フローチャートを描くために使っている、Mermaid.jsのLive Editor。
何もインストールせずにつかえて、書き終わったらリンクを送るだけでいいので仕事で重宝しています。https://mermaidjs.github.io/mermaid-live-editor
テーマ変更方法
Mermaid configurationを変更します。
{ "theme": "neutral" }テーマの種類
themeはいまのところ4種類あります。
theme 色 dark default forest neutral 参照