20210425のJavaScriptに関する記事は19件です。

【Vue.js】transition ラッパーは単一要素/コンポーネントで使うと言う話とか

Vue.js のtransitionについて。 単一要素はtransitionで複数要素はtransition-groupと言う単純な覚え方ではアカンと思ったので、覚書。 前置き transitionは Vue.js で、 遷移とか移り変わりの表現をいい感じにしてくれるラッパーコンポーネントである。 要素のフェードイン / フェードアウトを簡単に実装する事が出来るので、そう言う例が多く見られるが、 要するにvueが用意してくれているトランジションクラス(v-enterとかv-leaveとか)を用いて、要素がDOMに追加、更新、削除される意図したタイミングでアニメーションを指定する事が出来る。 本題 transitionでラップ出来るのは次のようなケース ・条件付きの描画 (v-if を使用) ・条件付きの表示 (v-show を利用) ・動的コンポーネント ・コンポーネントルートノード (Component root nodes) で、これらは単一要素/コンポーネントである事が条件としてあると言う事。 <!-- これはダメ --> <transition> <div v-if="show">message1</div> <div v-if="show">message2</div> </transition> 対象が単一要素であれば良いので、これなら問題ない。 (まあ普通はこっちになると思うけど) <!-- これは良い --> <transition> <div v-if="show"> <div>message1</div> <div>message2</div> </div> </transition> またパターンをifや動的プロパティで表現する時に複数要素を指定するのは問題ない。 <!-- これは良い --> <transition> <button v-if="show === 'message1'" key="message1"> message1 </button> <button v-if="show === 'message2'" key="message2"> message2 </button> <button v-if="show === 'message3'" key="message3"> message3 </button> </transition> つまり、条件の単一要素と言うのは「独立したノード」又は「一度に1つのみ描画される複数ノード」であれと言う事。 またtransition-groupでも使う時に注意が必要な点がある。 ドキュメントにも重要であると書かれており、使う上で直ぐに気づくと思うが、v-forで回す要素をspanでラップする形でDOMが形成される。 これはtag属性で指定する事でspan以外のタグを設定する事が出来るが、場合によってはコーディングに影響が出るので注意が必要。 <transition-group name="list" tag="p"> <span v-for="item in items" v-bind:key="item" class="list-item"> {{ item }} </span> </transition-group>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jest の watch モードで Visual Studio Code のデバッガーを一瞬で再起動させる方法

デバッガーを起動するのに時間がかかる問題 Jest を速くする watch モードとデバッガーに接続するしくみ 起動が早い Jest + TypeScript のテストコードのプロジェクトを作ってみる Node.js をインストールします Visual Studio Code をインストールします プロジェクトを新規作成します Visual Studio Code で新しいフォルダー try_jest を開きます Project/package.json ファイルを作ります 必要な Node モジュールをインストールします Project/tsconfig.json ファイルを作ります Project/src/lib.ts ファイルを作ります Project/src/lib.test.ts ファイルを作ります Jest のウォッチモードを起動して Visual Studio Code と接続します デバッガーに接続して失敗したテストをデバッグします テストを再起動します プログラムがブレーク中のとき プログラムが実行中で終わらないとき 1つのテストのみ実行します テストする関数を限定します テストするファイルを限定します 感想 デバッガーを起動するのに時間がかかる問題 Jest を使った JavaScript や TypeScript の テスト コード をデバッグするとき、 Visual Studio Code の Jest の拡張機能(Extension)を使うと、 テストコードに Run | Debug ボタンが表示されて便利なのですが、 デバッガーの起動に約6秒かかってしまいます。 たかが6秒なのですが、 デバッグするときは何度も再起動を行うため徐々にイライラが蓄積されていきます。 多いときは約10秒ごとに再起動することもあるのでほとんどが待ち時間になります。 早く直したいのに待たされるのは古いPCで作業しているようなものです。 ところで、Jest には watch モードがあります。 テストコードを編集して保存すると自動的にテストコードを実行してくれて 問題の早期発見につながります。 この watch モードからテストコードを呼び出していることは分かるのですが、 Debug ボタンを押すとなぜか別の Jest を起動しようとして約6秒もかかってしまいます。 すでに起動している Jest の watch モードとデバッガーを接続できたらいいのに というのは誰しも思いつくことでしょうが、 具体的にどのようにすればできるのかがネットにあまり書いていないので ここで紹介したいと思います。 その構成の場合、テストコードの起動が 0.1秒ぐらいなります。まさに一瞬です。 Jest を速くする watch モードとデバッガーに接続するしくみ Visual Studio Code の端末(Terminal)には、JavaScript Debug Terminal という特殊な端末があることが分かりました。 この JavaScript Debug Terminal を開いて Jest の watch を起動すると、 自動的に Visual Studio Code のデバッガーと接続できます。 また、TypeScript の場合、ts-node や ts-jest の Node モジュールを使うことで TypeScript のファイルを直接 Node.js や Jest からで実行できるようになります。 また、詳しくは分かっていませんが、Jest の maxWorkers という設定項目によって ワーカーを適度に動かしておく必要があるようです。 起動が早い Jest + TypeScript のテストコードのプロジェクトを作ってみる では、具体的にどのように設定すればいいかについて説明したいと思います。 ただ、設定ファイルのこことここの設定が必要という説明では成功しないことがよくあります。 成功したシンプルなプロジェクトを作り、それをリファレンスとして 徐々に使いたいプロジェクトとの違いを少なくしていくのがより成功しやすい手順になりますので、 ここではシンプルなプロジェクトを作っていこうと思います。 また例によってリセット状態の Windows からの手順の説明になります。 この記事を書いたときに使った各種モジュールの最新バージョンは以下のとおりです。 モジュール バージョン Windows 10 Pro 20H2 Node.js 14.16.1 Visual Studio Code 1.55.2 typescript 4.2.4 ts-jest 9.1.1 @types/node 14.14.41 jest 26.6.3 ts-jest 26.5.5 @types/jest 26.0.22 Node.js をインストールします https://nodejs.org/ja/download/ >> Windows Installer (.msi) >> 64-bit ダウンロードしたファイル(例:node-v14.16.1-x64.msi)を開きます インストール オプションはデフォルトを使用 プロキシがある LAN に Windows がある場合: Windows スタート >> PowerShell (などのシェルを開きます) npm config -g set proxy "http://_._._._:__" npm config -g set https-proxy "http://_._._._:__" Visual Studio Code をインストールします https://code.visualstudio.com/ ダウンロードしたファイル(例:VSCodeUserSetup-x64-1.55.2.exe)を開きます インストール オプションはデフォルトを使用 拡張機能(Extensions)は不要です (推奨)VSCode (Visual Studio Code をタスクバーにピン止めします (推奨)Ctrl + S キーを押したときに全てのファイルを保存するように設定します VSCode >> File >> Preferences >> Keyboard Shortcuts >> save all (と入力) >> File: Save All (をダブルクリック) >> Ctrl + S キー >> Enter キー プロジェクトを新規作成します Visual Studio Code で新しいフォルダー try_jest を開きます。 このフォルダー名はコード上でのプロジェクト名に相当します。 try_jest から変えることもできますが、変えたときは以後の try_jest を読み替えてください。 Visual Studio Code で新しいフォルダー try_jest を開きます VSCode >> File >> Open Folder >> デスクトップ >> 新しいフォルダー >> try_jest >> フォルダーの選択 Project/package.json ファイルを作ります package.json ファイルは、npm コマンドや Node モジュールの設定ファイルです。 Project は try_jest フォルダーを表します。 内容は下記をコピー&ペーストしてください。 すぐに保存も行ってください。 VSCode の EXPLORER(の中を右クリック)>> New File >> package.json Project/package.json { "name": "try_jest", "version": "0.0.1", "description": "", "scripts": { "setup": "npm ci", "test": "jest --watchAll", "clean": "powershell rm -r -fo node_modules" }, "author": "", "license": "ISC", "private": false, "dependencies": {}, "devDependencies": {}, "jest": { "roots": [ "<rootDir>/src" ], "testMatch": [ "**/__tests__/**/*.+(ts|tsx|js)", "**/?(*.)+(spec|test).+(ts|tsx|js)" ], "transform": { "^.+\\.(ts|tsx)$": "ts-jest" }, "maxWorkers": 1 } } 上記の内容は変更しなくてもそのまま使えます。 内容を少し説明します。 シンボル 説明 name プロジェクト名 scrips/test npm test を実行したときのコマンド。 Jest の watch モードを起動します jest/roots Jest が対象とするフォルダー jest/testMatch Jest が対象とするファイル jest/transform Jest が対象とする TypeScript のファイル jest/maxWorkers Jest のワーカーを動かす割合 必要な Node モジュールをインストールします VSCode >> Terminal >> New Terminal Terminal npm install typescript ts-node @types/node jest ts-jest @types/jest --save-dev Project/tsconfig.json ファイルを作ります tsconfig.json ファイルは TypeScript の設定ファイルです。 Project/tsconfig.json { "compilerOptions": { "declaration": true, "strict": true, "lib": [ "es2015", "dom" ], "inlineSourceMap": true, "inlineSources": true, "outDir": "build" } } 上記の内容は変更しなくてもそのまま使えます。 内容を少し説明します。 シンボル 説明 outDir TypeScript ファイルを JavaScript にトランスパイルしたファイルを格納する場所 Project/src/lib.ts ファイルを作ります __.ts ファイルは TypeScript のファイルです。 テスト対象のサンプルとして src フォルダーを作ってから lib.ts ファイルを作ってください。 Project/src/lib.ts export function add(a: number, b: number): number { return a + b; } Project/src/lib.test.ts ファイルを作ります __.test.ts ファイルは TypeScript で書かれたテストコードです。 Project/src/lib.test.ts import { add } from './lib'; test('add', () => { const a = 2; const b = 3; const answer = 5; const result = add( a, b ); expect(result).toBe(answer); }); 内容を少し説明します。 シンボル 説明 test Jest にテストケースであることを設定します。it というシンボルでも構いません expect テストケースを実行した結果の期待値をチェックします Jest のウォッチモードを起動して Visual Studio Code と接続します VSCode >> Terminal >> 1: powershell(Terminal の上)>> Create JavaScript Debug Terminal Terminal npm test 実行結果と Jest の watch のメニュー 以上の操作により、Visual Studio Code のデバッガーと Jest の watch を接続し、 テストを実行し、Jest の watch のメニューを表示します。 テストコードを実行した後でも、Jest の watch は デバッガーと接続しながら動き続けています。 npm test コマンドによって npm の scripts 環境の中で package.json#scripts/test に書かれた jest --watchAll コマンドが実行されます。 Jest の watch が動いている状態では、 ファイルを編集して保存すると自動的にテストを再実行します。 また、テストの結果が表示されている Terminal(端末)をクリックして f キーなどを押すことで、Jest の watch のメニュー項目を選択することもできます。 メニュー項目の内容については、以下で説明していきます。 デバッガーに接続して失敗したテストをデバッグします デバッグを試すために、テストが失敗するコードに変更してみます。 add 関数の内容をたとえば以下のように変更します。 Project/src/lib.ts 変更前: a + b 変更後: a + a Jest の watch のメニューが選択されるのを待っている状態でファイルを変更して保存すると、 自動的にテストコードが呼ばれます。 テストが失敗するように変更したのでテストが失敗したと表示されます。 ちなみに、Expected: に期待値、Received: に実行結果、失敗したチェック内容も表示されるので、 デバッグするときの参考になるでしょう。 テストコードを再実行したときは、Jest の watch のメニューの項目が表示されませんが、 w キーを押すと表示されます(と英語で表示されます)。 ブレークポイントを張ります。 たとえば Project/src/lib.test.ts の add 関数を呼び出すコードにブレークポイントを張るために、 add 関数を呼び出しているコードの行番号の左をクリックして赤い丸を表示させます。 そして、失敗したテストコードを実行させる Jest の watch の f メニューを選択すると、 ブレークポイントで一時停止します。 ブレークポイントを張ります(行番号の左をクリックします) Terminal をクリックしてアクティブにします f キーを押します 一瞬でブレークポイントに到達しました。すごいですね。 もちろん、F10 キーで ステップ オーバー、F11 キーで ステップ イン しますし、 ウォッチして変数の値やオブジェクトの内容をツリー表示することもできます。 テストを再起動します テストの実行中やブレーク中には、Jest の watch メニューが表示されていません。 その状態でテストを再実行する方法を説明します。 プログラムがブレーク中のとき プログラムがブレーク中のときは、F5 キーまたは Continue ボタン(下記の左端)を押します。 最後まで実行したら、Jest の watch メニューが表示されます。 そして、失敗したテストコードを実行させる Jest の watch のメニューを選択すると、 テストコードが再実行され、ブレークポイントで一時停止します。 Terminal をクリックしてアクティブにします f キーを押します 最後まで実行するまでに何度もブレークポイントで一時停止してしまうときは、 ブレークポイントを一時的にすべて無効にするとよいでしょう。 Run and Debug ボタン(左上) BREAKPOINTS(左下)の Toggle Active Breakpoints ボタン Jest の watch モードでは、 Visual Studio Code の Restart ボタン(右から2番目)を押しても テストを再実行することはできません。 また、Disconnect ボタン(右端)を押すと、テストの最後まで実行しますが デバッガーとの接続が切れてしまいます。 ですので、この2つのボタンは使えません。 プログラムが実行中で終わらないとき テストコードやプログラムの実行が終わるまで Jest の watch のメニューは表示されないのですが、 無限ループやタイムアウト待ちで実行が終わらないときにテストコードを再実行するときは、 Jest の watch を強制終了して再起動します。 試しに、しばらく実行が終わらないように、テストコードを下記のように変えてみます。 テストコードの関数の最初の左に async を追加し、 テスト関数の最後のほうで 5秒待つコードを追加してみます。 import { add } from './lib'; test('add', async () => { // async を追加 const a = 2; const b = 3; const answer = 5; const result = add( a, b ); await new Promise(resolve => setTimeout(resolve, 5000)); // 追加 expect(result).toBe(answer); }); このテストコードを実行すると、5秒間実行してから Jest の watch に戻ることを 確認してみてください。 プログラムがブレークしない状況にするため、ブレークポイントは削除してください。 テストコードが実行中に再起動するには、まず、JavaScript Debug Terminal を強制終了します。 Terminal タブ(下)>> ゴミ箱アイコン(右) テストコードを再実行して、実行中に強制終了させると、すぐに終了します。 そして、Jest の watch を再起動させます。 Terminal >> 1: powershell(Terminal の上)>> Create JavaScript Debug Terminal しかし、すぐに Terminal の入力待ちにはなりません。 約5秒後に右下に応答しないというエラーが表示されるので、 Restart pty host ボタンを押してください。 それでも Terminal の入力待ちにはなりませんが、 もう一度 JavaScript Debug Terminal を強制終了して再起動すると入力待ちになるので、 npm test コマンドを実行してください。 テストコードの実行が再開されます。 Terminal npm test 1つのテストのみ実行します 1つのコードの変更で多くテストが失敗したときは、 1つのテストのみに集中してデバッグするとよいでしょう。 テストする関数を限定します test 関数を呼び出すコードを test.only 関数を呼び出すコードに変更すると、 その関数のテストコードだけ実行するように限定することができます。 変更前: test(__ 変更後: test.only(__ テストするファイルを限定します Jest の watch メニューの p コマンドで テストするテストコードがあるファイルを限定することができます。 Terminal をクリックして Jest の watch メニューの端末をアクティブにします p キーを押します ファイル名(例: lib.test.ts)またはその一部にマッチする正規表現のパターンを入力します 感想 Jest の拡張機能をインストールして Debug ボタンを押すことに比べると あまりスマートではありませんが、Debug ボタンがあるファイルを開かなくても、 f キーを押すだけで一瞬で再実行できるのでデバッグがかなり快適になりました。 そのうち、一瞬で再実行できる Jest の拡張機能が登場するかもしれないので、 それも期待しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語ごとの週番号の扱いについて[Python,JavaScript,MySQL]

はじめに みなさん、プログラム内で週番号を扱ったことがあるでしょうか。 例えば、2021年の23週目、という形でデータを持つことを考えます。 その場合に注意するべきなのは、週の初めを1周として扱うのか、0週として扱うのか、ということです。 しかも、プログラミング言語ごとに週番号の扱い方が異なるので注意が必要です。 しっかりと理解しておかないと、かなり危険なのでまとめたいと思います。 今回の記事では週の始まりを月曜日として考えます。 Pythonにおける週番号の扱い Pythonのリファレンスでは以下となっています。 strftime() と strptime() の書式コード %W 0埋めした10進数で表記した年中の週番号 (週の始まりは月曜日とする)。新年の最初の月曜日に先立つ日は 0週に属するとします。 つまり、1月1日が月曜日かどうかでその年が0週から始まるか、1週から始めるのかが変化する、ということになります。 Pythonで日付から週番号を取得する場合 では、日付から週番号を取得してみましょう。 from datetime import datetime # 2021年の1月1日は金曜日 date1 = '2021-01-01' #2024年の1月1日は月曜日 date2 = '2024-01-01' # result: date1 week number: 0 print(f'date1 week number: {int(datetime.strptime(date1,"%Y-%m-%d").strftime("%W"))}') # result: date1 week number: 1 print(f'date2 week number: {int(datetime.strptime(date2,"%Y-%m-%d").strftime("%W"))}') 上記のコードの結果を解説すると、2021年1月1日の週番号は0週として取得され、2024年1月1日の週番号は1週として取得される、ということになります。 Pythonで週番号から日付を取得する場合 次は週番号から週のはじめの日付を取得してみましょう。 先ほどの結果でPythonにおいて2021年は0週から始まり、2024年は1週から始まる、ということが分かりました。 では、2021年と2024年の第2週目の週の始まりの日付を求めてみます。 from datetime import datetime # 2021年の2週目 date1 = '2021-W2' # 2024年の2週目 date2 = '2024-W2' # result: 2021-01-11 00:00:00 print(datetime.strptime(f'{date1}-1', '%Y-W%W-%w')) # result: 2024-01-08 00:00:00 print(datetime.strptime(f'{date2}-1', '%Y-W%W-%w')) コードでは分かりにくいので、図で説明します。 以下の図では、赤枠が0週目、緑枠が1週目、黄色枠が2週目、となります。 1月1日が含まれる週を1週目とする、という考えではなく年の初めの月曜日が含まれる週を1週目とする、という考えなので、単純に週番号を考えていると週の数え方が年によってずれる、ということが分かるかと思います。 JavaScriptにおける週番号の扱いについて JavaScript(以下JSとする)においてライブラリを使わずに日付を扱うことはないかと思うので、今回はDay.jsの場合に絞って解説します。 Day.jsで日付から週番号を取得する場合 const dayjs = require("dayjs"); const weekOfYear = require("dayjs/plugin/weekOfYear"); const updateLocale = require("dayjs/plugin/updateLocale"); dayjs.extend(weekOfYear); dayjs.extend(weekOfYear); dayjs.extend(updateLocale); dayjs.updateLocale("en", { weekStart: 1, // OPTIONAL, set the start of a week. If the value is 1, Monday will be the start of week instead of Sunday。 yearStart: 1, // OPTIONAL, the week that contains Jan 4th is the first week of the year. }); dayjs.locale("en"); const day1 = "2021-01-01"; const day2 = "2024-01-01"; // result: 1 console.log(dayjs(day1, "YYYY-MM-DD").week()); // result: 1 console.log(dayjs(day2, "YYYY-MM-DD").week()); 上記結果を見ると、2021年と2024年両方とも1月1日は1週目として取得されています。 つまり、1月1日が含まれる週が第1週目として扱うことができている、ということになります。 Day.jsで週番号から日付を取得する場合 次は週番号から週の開始の日付を取得する処理を見てみましょう。 以下のコードでは2021年と2024年の第2週目の週の開始の日付を取得します。 const dayjs = require("dayjs"); const weekOfYear = require("dayjs/plugin/weekOfYear"); const updateLocale = require("dayjs/plugin/updateLocale"); dayjs.extend(weekOfYear); dayjs.extend(weekOfYear); dayjs.extend(updateLocale); dayjs.updateLocale("en", { weekStart: 1, // OPTIONAL, set the start of a week. If the value is 1, Monday will be the start of week instead of Sunday。 yearStart: 1, // OPTIONAL, the week that contains Jan 4th is the first week of the year. }); dayjs.locale("en"); const day1 = "2021"; const week1 = 2; const day2 = "2024"; const week2 = 2; // result: 2021-01-04 console.log(dayjs(day1, "YYYY").week(week1).startOf('week').format("YYYY-MM-DD")); // result: 2024-01-08 console.log(dayjs(day2, "YYYY").week(week2).startOf('week').format("YYYY-MM-DD")); コードだと分かりにくいので、図で説明します。 Day.jsの場合は1月1日が含まれる週が必ず第1週目として扱うことができていることが分かります。 MySQLにおける週番号の扱いについて 次はMySQLにおける週番号の扱いを見ていきます。 日付および時間関数 WEEK(date[,mode]) この関数は、date に対応する週番号を返します。 2 つの引数を取る形式の WEEK() を使用すると、週が日曜日と月曜日のどちらから始まるのか、および戻り値が 0 から 53までと 1 から 53 までのどちらの範囲内であるのかを指定できます 週番号の0~53か1~53かについては、その年の最初の週を0週とするか、前年の53週(52週の場合もある)とするかどうか、という意味になります。 今回はPythonと合わせるため0~53週とします。 MySQLで日付から週番号を取得する場合 SELECT WEEK('2021-01-01',5); # result: 0 SELECT WEEK('2024-01-01',5); # result: 1 上記の結果を見ると分かりますが、Pythonと同じく1月1日が月曜日かどうかで結果が変わります。 2021年は0週から始まり、2024年は1週から始まります。 MySQLで週番号から日付を取得する場合 実際のコードを見てみましょう。 #2021年の第1週目 SELECT STR_TO_DATE('202101 Monday', '%x%v %W'); # result: 2021-01-04 #2024年の第1週目 SELECT STR_TO_DATE('202401 Monday', '%x%v %W'); # result: 2024-01-01 ここで注意すべきは、週番号から日付を取得する場合、月曜日始まりで0周開始の条件で変換することができない、ということです。 理由はDATE_FORMAT関数のmodeが0~3までしか対応していないことが原因です。 DATE_FORMAT(date,format) なので、MySQLのWEEK関数の出力を使ってそのままDATE_FORMAT関数を利用しても正しく処理できない可能性がある、ということに注意してください。 ※MySQLにあまり精通していないので、他の方法があればコメントでご指摘頂きたいです。 まとめ 言語ごとに週番号の扱い方が異なります。 ISOで決められている週番号の管理方法もありますが、第1週が前年度の週として扱われたりと人間がロジックを考える場合は少し考えにくい部分が難点かと思っています。 いずれにしても、しっかりと言語ごとの週番号の管理方法を理解しておかないと、思わぬ落とし穴にはまりますのでご注意ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptリファクタリング[文字列操作]

レガシーかつ酷いJavaScriptコードを少しづつリファクタリングしてみます。 仕様 「atiiq no tsop tsrif」の文字列を反転等で操作して「First post on Qiita」と表示する。 STEP 0 何をしているか分り辛いコード。こんなコードに出会ったことのない貴方は幸せ者。 function reverse(string) { const reversed = string.trim() .split('') .reverse() .join(''); return reversed[0].toUpperCase() + reversed.substring(1, 14) + reversed[14].toUpperCase() + reversed.substring(15) + (reversed[reversed.length -1] = "."); }; reverse('atiiq no tsop tsrif'); STEP 1 reverse関数をアロー関数に変更 文字列を返す関数を用意 セミコロンを削除 const reverse = string => string.trim() .split('') .reverse() .join('') const target = string => string.indexOf('qiita') ... STEP 2 投稿するサービスを定数で定義 ピリオドを文末に追加する関数を作成 完成した文字列を返す関数を作成 const serviceName = 'qiita' ... const target = string => string.indexOf(serviceName) ... function reverseSentence(string) { const reversed = reverse(string) const addPeriod = string => string + (string[string.length -1] = "." return reversed[0].toUpperCase() + reversed.substring(1, target(string)) + reversed[target(string)].toUpperCase() + reversed.substring(target(string) +1) } ... STEP 3 reverseSentenceをアロー関数に 完成した文字列を作成するsentence関数を作成 タイポを防ぐためピリオドが文末に既に存在する場合は表示しない ... const sentence = string => string[0].toUpperCase() + string.substring(1, target(string)) + string[target(string)].toUpperCase() + string.substring(target(string) +1) ... const addPeriod = string => string + (string[string.length -1] === "." ? "" : ".") const reverseSentence = string => sentence(addPeriod(reverse(string))) ... Done const serviceName = 'qiita' const reverse = string => string.trim() .split('') .reverse() .join('') const target = string => string.indexOf(serviceName) const sentence = string => string[0].toUpperCase() + string.substring(1, target(string)) + string[target(string)].toUpperCase() + string.substring(target(string) +1) const addPeriod = string => string + (string[string.length -1] === "." ? "" : ".") const reverseSentence = string => sentence(addPeriod(reverse(string))) reverseSentence('atiiq no tsop tsrif') >"First post on Qiita" 関数名で処理を伝えるようにしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OBSでハッシュタグのツイートを表示するツールの話

最近こんなツールを公開しました OBSのブラウザソースでTwitterのツイートを表示するツイートです よくテレビとかで見るシステムですね 1000RTも超えてホンマありがたいっすわ 同じようなツールがすでに存在していたのですがTwitterAPIのOAuth認証部分をすべてユーザーに丸投げでしたのでもっと良いものを作ろうと思い開発しますた でもまだ技術面についてどこにも書いていなかったのでココに書いておきます Githubリポジトリ https://github.com/CubeZeero/OBS-Twitter-Stream システムの大まかな説明 このツールは Python + Javascript で動かしています。 Javascript単体でも開発することは可能ですが、すべてローカルで動かす必要があったのでどうしようと考えた末、PythonとJSでwebsocket通信させる方式を取りました (TwitterAPIのキーとかもあるしね) PythonがTwitterAPIの担当でJSがOBS上でのツイート表示担当です Websocket通信 WebsocketについてはPythonがサーバーとなりJSがクライアントとして動作します フローチャートは以下のとおりです JSをPythonに接続しPythonが接続されたことを確認する Pythonが "接続完了" という文字列をJSへ送信し表示させる "接続完了" という文字列を表示し終わったJSが "RT" という文字列を送信 "RT" という文字列を受信したPythonがツイートを取得しJSへ送信 ツイートを表示し終えたJSが "RT" という文字列を送信 以下4,5のループ という感じです "RT" をコールサインとして使用しています Javascriptについて Javascriptのコードです (このツール作るためだけにJavascriptを学び始めたので、ありえんくらい汚い描き方とかだったらごめん) main.js $(function(){ let css_selector = document.getElementById('twitter-text-main'); let css_fontsize = window.getComputedStyle(css_selector, null).getPropertyValue('font-size'); let css_ls = window.getComputedStyle(css_selector, null).getPropertyValue('letter-spacing'); var ws = new WebSocket("ws://127.0.0.1:10356/"); ws.onmessage = function(message){ var span = document.createElement('span'); span.style.position = 'absolute'; span.style.top = '-1000px'; span.style.left = '-1000px'; span.style.whiteSpace = 'nowrap'; span.innerHTML = message.data; span.style.fontSize = css_fontsize; span.style.letterSpacing = css_ls; document.body.appendChild(span); var text_width = span.clientWidth; var text_width_int = parseInt(text_width); var text_scroll = 1920 - text_width_int span.parentElement.removeChild(span); if (text_width_int >= 1920) { $("#twitter-text-main").text(message.data); $('#twitter-text-main').animate({opacity: 1},{queue: false,duration: 2000,easing: 'swing'}); $('#twitter-text-main').animate({marginLeft: 0},{queue: false,duration: 2000,easing: 'swing'}).delay(3000); $('#twitter-text-main').animate({marginLeft: text_scroll-10},{duration: text_width_int*2,easing: 'linear'}).delay(3000); $('#twitter-text-main').animate({opacity: 0},{duration: 2000,complete: function(){ws.send('RT'),$('#twitter-text-main').css('margin-left','250px');}}); }else{ $("#twitter-text-main").text(message.data); $('#twitter-text-main').animate({opacity: 1},{queue: false,duration: 2000,easing: 'swing'}); $('#twitter-text-main').animate({marginLeft: 0},{queue: false,duration: 2000,easing: 'swing'}).delay(5000); $('#twitter-text-main').animate({opacity: 0},{duration: 2000,complete: function(){ws.send('RT'),$('#twitter-text-main').css('margin-left','250px');}}); } } }) 文字のアニメーションはjqueryのanimateでcssの値をアニメーションさせ、また文字列が長くはみ出てしまう場合は文字列を表示した際の長さをpx単位で取得してその分スクロールさせています アニメーションは全部animateでゴリ押しです ここでjqueryが最高なことを知りました Pythonについて Pythonについては下のフローチャートそのまんまなので割愛 リポジトリ覗いてみてね ツイートの取得方法について ツイートはなるべく最新のものを優先して取得するようにしています 以下雑なフローチャート なるべく最新のツイートを表示させたいので5回ツイートを表示させたら最新の物があるか確認しています しかし最新のツイートが全く更新されない場合古いツイートを延々と垂れ流し始めるのであまりよろしくないですね 取得したツイートはリスト型に保存して乱数で引っこ抜いて表示させています 以上 こんな感じです 最初JSの書き方何も知らなかったのでうまくいくんかなと思っていましたが意外と簡単に実装できました いろんなストリーマーの方に使っていただけたら嬉しいですね(Vtuberさん使ってください) 剣持刀也さんが使ってくれてましたうれしい(もう使わないとか言ってたけどw) ではでは
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

うん、JavascriptのImportとVueのコンポーネントを使えるようになるぞ!

これまで、Javascriptのimportを特に使わず、グローバル宣言で満足していましたし、Vueも宣言的レンダリング(双方向バインディング)で満足していたのですが、世間様では、Importやコンポーネントをを使うことが当たり前になってきたので、使いこなせるように、いつも使っているテンプレート環境をアップデートします。 ついでに、いつもテンプレートの使い方を忘れるので、ここでまとめておきます。 テンプレートは以下のGitHubに上げてあります。 poruruba/express_template https://github.com/poruruba/express_template ※たびたび機能追加しているので、ここに記載していない機能も入っている可能性大です。 テンプレートでできること node.jsさえインストールしてあれば、すぐにWebページのホスティングサーバ、RESTサーバ、GraphQLサーバを立ち上げられるようにしています。また、HTMLページのひな型も置いており、よく使う機能はライブラリとしてまとめておいてあります。 今回の投稿では、HTMLのひな型の使い方のまとめです。 立ち上げ方 以下から、Code一式をダウンロードします。 poruruba/express_template https://github.com/poruruba/express_template > unzip express_template-master.zip > cd express_template-master > npm install > mkdir cert > npm run start 上記だけで、HTTPサーバが立ち上がります。 もし、HTTPSでも立ち上げたい場合には、certフォルダにSSL証明書ファイルを配置します。 app.js の以下の場所のファイル名を、配置したファイル名に合わせます。 app.js var options = { key: fs.readFileSync('./cert/server.key'), // 秘密鍵ファイル cert: fs.readFileSync('./cert/server.crt'), // SSL証明書ファイル ca: fs.readFileSync('./cert/JPRS_DVCA_G2_PEM.cer') //SSL中間証明書ファイル }; 特に何も指定しなければ、ポート番号は、HTTPは10080、HTTPSは10443で立ち上がります。 ポート番号を変えるには、.envに以下を記載します。 .env PORT=【HTTPのポート番号】 SPORT=【HTTPSのポート番号】 静的ページの作成 public/template に、静的ページのひな型がありますので、適当な名前でコピーします。この名前がURLでのパスになります。 public/test とした場合、URLは、http://localhost:10080/test となります。 Vue2とBootstrap5とその他もろもろを使っています。 あとは、Vueの宣言的レンダリング(双方向バインディング)の機能を使えばたいていのことは事足ります。 たとえば、以下のようにindex.htmlに追記して、 public/template/index.html <h1>Template</h1> {{message}} <button class="btn btn-primary" v-on:click="btn_clicked">Button Click</button> start.js の該当箇所に追記すると、 public/template/js/start.js data: { message: "Hello" }, computed: { }, methods: { btn_clicked: function(){ this.message = "World"; } ブラウザから見るとHelloと表示されて、ボタンを押すとWorldに変わります。 {{ }} やv-on:clickはVue、buttonやbtn btn-primaryはBootstrapの機能です。 プログレスダイアログの表示 時間がかかる処理をするときには、処理中であることがわかるようにプログレスバーのあるダイアログを表示してあげると便利です。 5秒間プログレスダイアログを表示したのちに消してみます。 プログレスダイアログの表示  this.progress_open(表示タイトル, backdrop = 'static'); プログレスダイアログの非表示  this.progress_close(); pubic/template/index.html <button class="btn btn-primary" v-on:click="progress_clicked">Progress Click</button> public/template/js/start.js methods: { progress_clicked: function(){ this.progress_open(“プログレス表示です。”); setTimeout(() =>{ this.progress_close(); }, 5000); } backdropには、’static’またはtrueまたはfalseが指定できます。 クリップボードへのコピー&ペースト Javascript標準のClipboard APIを使っています。 クリップボードへのコピー  this.clip_copy(文字列) クリップボードからペースト  this.clip_paste() モーダルダイアログ Bootstrapを使ったモーダルダイアログです。あらかじめ使いやすいようにコンポーネントとして登録しているため、以下のように使います。 index.html にモーダルダイアログの表示内容を定義します。 public/template/index.html <modal-dialog size="lg" id="dialog_test"> <div slot="content"> <div class="modal-header"> タイトル </div> <div class="modal-body"> 本文 </div> <div class="modal-footer"> <button class="btn btn-primary" v-on:click="dialog_close('#dialog_test')">閉じる</button> </div> </div> </modal-dialog> 後は、ボタン押下などの契機に、以下のように呼び出します。 public/template/js/start.js modal_clicked: function(){ this.dialog_open('#dialog_test'); } モーダルダイアログの表示  this.dialog_open(‘#【ダイアログのID】’); モーダルダイアログの非表示  this.dialog_close(‘#【ダイアログのID】’); アコーディオン Bootstrapのアコーディオン表示です。 扱いやすいようにコンポーネント化していますので、以下のように定義すればよいです。 public/template/index.html <collapse-panel id="accordion_test" title="タイトル" collapse="true"> <span slot="content"> <div class="card-body"> 本文 </div> </span> </collapse-panel> collapseをtrueにすれば、初期状態で畳んだ状態となり、falseにすれば開いた状態になります。以下のJavascriptでも動的に制御できます。 アコーディオンのオープン  this.panal_open(‘#【アコーディオンのID】’) アコーディオンのクローズ  this.panal_close(‘#【アコーディオンのID】’) トーストの表示 右上に、トーストを表示させます。複数同時に表示することができ、時間がたつと消えてくれます。 this.toast_show(message, level = "success") 内部で以下を使わせてもらっています。 ooyun0/siiimple-toast https://github.com/ooyun0/siiimple-toast Cookieを設定・取得する Cookieの設定  Cookies.set(‘【名前】', JSON.stringify(【設定したい値】), { expires: 365 }); Cookieの取得  var value = Cookies.get('【名前】'); 単にこちらを使わせていただいているだけです。 js-cookie/js-cookie https://github.com/js-cookie/js-cookie/tree/latest URLのQueryStringとフラグメント識別子 URLに指定されたQueryStringやフラグメント識別子は、mountedで呼び出している関数proc_load()で処理され、変数searchsとhashsに格納されています。 ・QueryString(?の後に続くパラメータ):searchs http://localhost:10080/test/index.html?param1=abcdの場合 searches = { param1: 'abcd' } ・フラグメント識別子(#の後に続くパラメータ):hashs http://localhost:10080/test/index.html#param1=abcdの場合 hashs = { param1: 'abcd' } Vueコンポーネント さあ、本題である、Vueコンポーネントを追加します。 例えば、こんなコンポーネントを作ります。 public/template/js/comp/comp_test.js export default { mixins: [mixins_bootstrap], template: ` <div> <h2>テストコンポーネント</h2> {{message}} </div>`, data: function () { return { message: 'Hello World', } }, methods: { } }; start.js に以下を追加します。 public/template/js/start.js /* add additional components */ import comp_test from './comp/comp_test.js'; vue_add_global_component('comp_test', comp_test); index.htmlに以下を追加します。 public/template/index.html <comp_test></comp_test> コンポーネントに以下のMixinsを入れているので、作成するコンポーネントの中で、前の方で説明したプログレスダイアログやモーダルダイアログ、アコーディオンも使えます。 mixins: [mixins_bootstrap], WebAPI呼び出し WebAPI呼び出しをよく使うので、関数を作っておきました。内部でfetchを使っており、Promiseが返ります。 以下、いくつかの種類がありますが、いずれもURLに加えて、パラメータをオブジェクトで渡します。 ・do_post (url, body)  JSON/POST(application/json)呼び出しです。 ・do_post_urlencoded (url, params)  application/x-www-form-urlencoded 呼び出しです。 ・do_post_formdata (url, params)  フォームデータを呼び出しているのと同じです。 ・do_get (url, qs)  GET呼び出しです。 戻り値は、JSON(application/json)を前提としているため、 public/js/vue_utils.js return response.json(); としています。異なる場合はそれに合わせて以下のように変更してください。 public/js/vue_utils.js // return response.json(); // return response.text(); // return response.blob(); // return response.arrayBuffer(); GraphQL呼び出し この呼び出しがすぐ忘れそうだったので、今回の記事をQiitaに投稿した理由です。 いろんな呼び出し方を用意したので、お好みでどうぞ。 また、X-API-Key(apikey)は、AWS Appsyncなど、必要に応じて設定してください。 public/template/js/start.js var base_url=”【GraphQLのエンドポイント】”; var p1 = “Hello”; var p2 = 1234; var templ = `query($param1: String, $param2: Int){ hello( param1: $param1, param2: $param2){ id } }`; var ret = await gql_query(base_url, templ, { param1: p1, param2: p2 }, apikey); var templ = `{ hello(param1: \"Hello\", param2: 1234){ id } }`; var ret = await gql_query(base_url, templ, null, apikey); var templ = gql_templ`{ hello(param1: ${0}, param2: ${1}){ id } }`; var ret = await gql_query(base_url, templ, [p1, p2], apikey); var templ = gql_templ`{ hello(param1: ${0}, param2: ${1}){ id } }`; var ret = await gql_query(base_url, templ(p1, p2), null, apikey); graqhql_requestやapolloなどいろいろあったのですが、WebPackなど使わず、Javascriptだけで手軽に使いたかったため自作し、js/gql_utils.js にまとめておきました。 仮想コンソールの表示 スマホでの表示など、Chrome DevToolsでconsole.logなどのコンソールの出力が見えないときに使います。(Remote devicesを使う方法もありますが、それよりも手軽です) start.js で以下の部分のコメントを外します。それだけです。 public/template/js/start.js const vConsole = new VConsole(); とはいっても、ただ単に、以下を使わせていただいているだけです。 Tencent/vConsole https://github.com/Tencent/vConsole dat.GUIを使う dat.GUIを表示させます。 監視対象に、Vueのデータを含められるようにしました。 dataarts/dat.gui https://github.com/dataarts/dat.gui 登録は以下の関数です。  this.datgui_add(property, p1, p2, p3) まずは、start.js の以下の部分のコメントを外します。 public/template/js/start.js window.datgui = new dat.GUI(); そうすると、以下のように右上に、dat.GUIが現れます。 例えば、このようなVueに定義を含めた場合に、 public/template/js/start.js data: { message: "Hello" }, 以下のように、mountedなどで、フィールド名を文字列で指定すると、dat.GUIに内容が表示されます。 public/template/js/start.js this.datgui_add("message"); 値が変われば自動的に右上のdat.GUIの表示も変わりますし、dat.GUIのところで値を変えれば、実際の値も変わります。 dat.GUIの仕様を参考に、p1, p2, p3も指定してみてください。 終わりに 次回は、WebAPIの定義方法を紹介しようと思います。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavascriptのImportとVueのコンポーネントを使えるようになってみる

これまで、Javascriptのimportを特に使わず、グローバル宣言で満足していましたし、Vueも宣言的レンダリング(双方向バインディング)で満足していたのですが、世間様では、Importやコンポーネントをを使うことが当たり前になってきたので、使いこなせるように、いつも使っているテンプレート環境をアップデートします。 ついでに、いつもテンプレートの使い方を忘れるので、ここでまとめておきます。 テンプレートは以下のGitHubに上げてあります。 poruruba/express_template https://github.com/poruruba/express_template ※たびたび機能追加しているので、ここに記載していない機能も入っている可能性大です。 テンプレートでできること node.jsさえインストールしてあれば、すぐにWebページのホスティングサーバ、RESTサーバ、GraphQLサーバを立ち上げられるようにしています。また、HTMLページのひな型も置いており、よく使う機能はライブラリとしてまとめておいてあります。 今回の投稿では、HTMLのひな型の使い方のまとめです。 立ち上げ方 以下から、Code一式をダウンロードします。 poruruba/express_template https://github.com/poruruba/express_template > unzip express_template-master.zip > cd express_template-master > npm install > mkdir cert > npm run start 上記だけで、HTTPサーバが立ち上がります。 もし、HTTPSでも立ち上げたい場合には、certフォルダにSSL証明書ファイルを配置します。 app.js の以下の場所のファイル名を、配置したファイル名に合わせます。 app.js var options = { key: fs.readFileSync('./cert/server.key'), // 秘密鍵ファイル cert: fs.readFileSync('./cert/server.crt'), // SSL証明書ファイル ca: fs.readFileSync('./cert/JPRS_DVCA_G2_PEM.cer') //SSL中間証明書ファイル }; 特に何も指定しなければ、ポート番号は、HTTPは10080、HTTPSは10443で立ち上がります。 ポート番号を変えるには、.envに以下を記載します。 .env PORT=【HTTPのポート番号】 SPORT=【HTTPSのポート番号】 静的ページの作成 public/template に、静的ページのひな型がありますので、適当な名前でコピーします。この名前がURLでのパスになります。 public/test とした場合、URLは、http://localhost:10080/test となります。 Vue2とBootstrap5とその他もろもろを使っています。 あとは、Vueの宣言的レンダリング(双方向バインディング)の機能を使えばたいていのことは事足ります。 たとえば、以下のようにindex.htmlに追記して、 public/template/index.html <h1>Template</h1> {{message}} <button class="btn btn-primary" v-on:click="btn_clicked">Button Click</button> start.js の該当箇所に追記すると、 public/template/js/start.js data: { message: "Hello" }, computed: { }, methods: { btn_clicked: function(){ this.message = "World"; } ブラウザから見るとHelloと表示されて、ボタンを押すとWorldに変わります。 {{ }} やv-on:clickはVue、buttonやbtn btn-primaryはBootstrapの機能です。 プログレスダイアログの表示 時間がかかる処理をするときには、処理中であることがわかるようにプログレスバーのあるダイアログを表示してあげると便利です。 5秒間プログレスダイアログを表示したのちに消してみます。 プログレスダイアログの表示  this.progress_open(表示タイトル, backdrop = 'static'); プログレスダイアログの非表示  this.progress_close(); pubic/template/index.html <button class="btn btn-primary" v-on:click="progress_clicked">Progress Click</button> public/template/js/start.js methods: { progress_clicked: function(){ this.progress_open("プログレス表示です。"); setTimeout(() =>{ this.progress_close(); }, 5000); } backdropには、’static’またはtrueまたはfalseが指定できます。 クリップボードへのコピー&ペースト Javascript標準のClipboard APIを使っています。 クリップボードへのコピー  this.clip_copy(文字列) クリップボードからペースト  this.clip_paste() モーダルダイアログ Bootstrapを使ったモーダルダイアログです。あらかじめ使いやすいようにコンポーネントとして登録しているため、以下のように使います。 index.html にモーダルダイアログの表示内容を定義します。 public/template/index.html <modal-dialog size="lg" id="dialog_test"> <div slot="content"> <div class="modal-header"> タイトル </div> <div class="modal-body"> 本文 </div> <div class="modal-footer"> <button class="btn btn-primary" v-on:click="dialog_close('#dialog_test')">閉じる</button> </div> </div> </modal-dialog> 後は、ボタン押下などの契機に、以下のように呼び出します。 public/template/js/start.js modal_clicked: function(){ this.dialog_open('#dialog_test'); } モーダルダイアログの表示  this.dialog_open(‘#【ダイアログのID】’); モーダルダイアログの非表示  this.dialog_close(‘#【ダイアログのID】’); アコーディオン Bootstrapのアコーディオン表示です。 扱いやすいようにコンポーネント化していますので、以下のように定義すればよいです。 public/template/index.html <collapse-panel id="accordion_test" title="タイトル" collapse="true"> <span slot="content"> <div class="card-body"> 本文 </div> </span> </collapse-panel> collapseをtrueにすれば、初期状態で畳んだ状態となり、falseにすれば開いた状態になります。以下のJavascriptでも動的に制御できます。 アコーディオンのオープン  this.panal_open(‘#【アコーディオンのID】’) アコーディオンのクローズ  this.panal_close(‘#【アコーディオンのID】’) トーストの表示 右上に、トーストを表示させます。複数同時に表示することができ、時間がたつと消えてくれます。 this.toast_show(message, level = "success") 内部で以下を使わせてもらっています。 ooyun0/siiimple-toast https://github.com/ooyun0/siiimple-toast Cookieを設定・取得する Cookieの設定  Cookies.set(‘【名前】', JSON.stringify(【設定したい値】), { expires: 365 }); Cookieの取得  var value = Cookies.get('【名前】'); 単にこちらを使わせていただいているだけです。 js-cookie/js-cookie https://github.com/js-cookie/js-cookie/tree/latest URLのQueryStringとフラグメント識別子 URLに指定されたQueryStringやフラグメント識別子は、mountedで呼び出している関数proc_load()で処理され、変数searchsとhashsに格納されています。 ・QueryString(?の後に続くパラメータ):searchs http://localhost:10080/test/index.html?param1=abcd の場合 searches = { param1: 'abcd' } ・フラグメント識別子(#の後に続くパラメータ):hashs http://localhost:10080/test/index.html#param1=abcd の場合 hashs = { param1: 'abcd' } Vueコンポーネント さあ、本題である、Vueコンポーネントを追加します。 例えば、こんなコンポーネントを作ります。 public/template/js/comp/comp_test.js export default { mixins: [mixins_bootstrap], template: ` <div> <h2>テストコンポーネント</h2> {{message}} </div>`, data: function () { return { message: 'Hello World', } }, methods: { } }; start.js に以下を追加します。 public/template/js/start.js /* add additional components */ import comp_test from './comp/comp_test.js'; vue_add_global_component('comp_test', comp_test); index.htmlに以下を追加します。 public/template/index.html <comp_test></comp_test> コンポーネントに以下のMixinsを入れているので、作成するコンポーネントの中で、前の方で説明したプログレスダイアログやモーダルダイアログ、アコーディオンも使えます。 mixins: [mixins_bootstrap], 以下も参考になるかも  Vueのカスタムコンポーネントで双方向データバインディングを入れてみた WebAPI呼び出し WebAPI呼び出しをよく使うので、関数を作っておきました。内部でfetchを使っており、Promiseが返ります。 以下、いくつかの種類がありますが、いずれもURLに加えて、パラメータをオブジェクトで渡します。 ・do_post (url, body)  JSON/POST(application/json)呼び出しです。 ・do_post_urlencoded (url, params)  application/x-www-form-urlencoded 呼び出しです。 ・do_post_formdata (url, params)  フォームデータを呼び出しているのと同じです。 ・do_get (url, qs)  GET呼び出しです。 戻り値は、JSON(application/json)を前提としているため、 public/template/js/vue_utils.js return response.json(); としています。異なる場合はそれに合わせて以下のように変更してください。 public/template/js/vue_utils.js // return response.json(); // return response.text(); // return response.blob(); // return response.arrayBuffer(); 以下も参考になるかも  fetchの呼び出し @Javascript & Node.js 実験室 GraphQL呼び出し この呼び出しがすぐ忘れそうだったので、今回の記事をQiitaに投稿した理由です。 いろんな呼び出し方を用意したので、お好みでどうぞ。 また、X-API-Key(apikey)は、AWS Appsyncなど、必要に応じて設定してください。 public/template/js/start.js var base_url="【GraphQLのエンドポイント】"; var p1 = "Hello"; var p2 = 1234; var templ = `query($param1: String, $param2: Int){ hello( param1: $param1, param2: $param2){ id } }`; var ret = await gql_query(base_url, templ, { param1: p1, param2: p2 }, apikey); var templ = `{ hello(param1: \"Hello\", param2: 1234){ id } }`; var ret = await gql_query(base_url, templ, null, apikey); var templ = gql_templ`{ hello(param1: ${0}, param2: ${1}){ id } }`; var ret = await gql_query(base_url, templ, [p1, p2], apikey); var templ = gql_templ`{ hello(param1: ${0}, param2: ${1}){ id } }`; var ret = await gql_query(base_url, templ(p1, p2), null, apikey); graqhql_requestやapolloなどいろいろあったのですが、WebPackなど使わず、Javascriptだけで手軽に使いたかったため自作し、js/gql_utils.js にまとめておきました。 仮想コンソールの表示 スマホでの表示など、Chrome DevToolsでconsole.logなどのコンソールの出力が見えないときに使います。(Remote devicesを使う方法もありますが、それよりも手軽です) start.js で以下の部分のコメントを外します。それだけです。 public/template/js/start.js const vConsole = new VConsole(); とはいっても、ただ単に、以下を使わせていただいているだけです。 Tencent/vConsole https://github.com/Tencent/vConsole dat.GUIを使う dat.GUIを表示させます。 監視対象に、Vueのデータを含められるようにしました。 dataarts/dat.gui https://github.com/dataarts/dat.gui 登録は以下の関数です。  this.datgui_add(property, p1, p2, p3) まずは、start.js の以下の部分のコメントを外します。 public/template/js/start.js window.datgui = new dat.GUI(); そうすると、以下のように右上に、dat.GUIが現れます。 例えば、このようなVueに定義を含めた場合に、 public/template/js/start.js data: { message: "Hello" }, 以下のように、mountedなどで、フィールド名を文字列で指定すると、dat.GUIに内容が表示されます。 public/template/js/start.js this.datgui_add("message"); 値が変われば自動的に右上のdat.GUIの表示も変わりますし、dat.GUIのところで値を変えれば、実際の値も変わります。 dat.GUIの仕様を参考に、p1, p2, p3も指定してみてください。 終わりに 次回は、WebAPIの定義方法を紹介しようと思います。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

...mapState でストア呼び出し

mapstateとは 一言でいうと、ストアのデータ呼び出しの記述を短くするためのもの、です。 this.$store.state.プロパティ名 でストアのデータを呼び出せますが、長くなってしまうので computed にまとめて呼び出すことが可能です。 mapStore自体がオブジェクトを返すのでこのようにします。 <script> import { mapState } from "vuex"; // vuexからmapStateをimport export default { computed: mapState(["lists"]), }; </script> 本来はコンポーネント内で this.$store.state.lists と記述しますが、 mapState を使うことで、 state に this.$store.state が代入され、簡潔に呼び出しができます computed と mapstate を共存させるには 最初に紹介した方法では、computedが定義できませんが、こんな記述方法もあります。 computed: { ...mapState(["lists"]), otherFunction() { // 略 }, }, ... ←の意味 これ( ... ) はスプレッド構文という名前で、オブジェクトを個々の値に展開することができるものです。 スプレッド構文を使わず下記の様にしてしまうと・・ computed: { mapState(['lists']), } mapstateがオブジェクトを返すので、このように {} が重なった状態になってしまいます。 computed: { { lists () { return this.$store.state.lists }, } } 以上、Vuexで最初よくわからなかった ...mapstate の意味についての備忘録でした。 参考文献 Vue公式 Vuex state
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マインドフルネスの記事は新月にアップされるのが多いのか?を検証してみた!

はじめに 4日前にJavaScriptを始めたばかりの初心者です。 これがQiita初めての投稿です。 まだまだ未熟者ではありますが、これからも興味のあるものをどんどんアウトプットしていければと思っています。 ご指摘、アドバイスなどいただけると嬉しいです! 目的 ・Qiita上で「マインドフルネス」というキーワードを見つけたので、マインドフルネスに関連している記事がどれぐらい投稿されていて、その投稿している日が新月と関係があるかを可視化します。 ※私たちは少なからずとも月の影響を受けて生活をしていますが、新月の日は新月のパワーが背中を押してくれるため、何かにチャレンジしようとか、願いごとをするなどの影響を受ける人が多くなるため、記事を書くパワーと関係性があるのではないか?という単純な疑問からです。 サンプルコード キーワードは「マインドフルネス」の他に「mindfulness」や「瞑想」でも記事を取得しました。 const axios = require("axios"); async function main() { let response = await axios.get( "https://qiita.com/api/v2/items?per_page=100&query="+encodeURIComponent("瞑想"||"マインドフルネス"||"mindfulness") ); for(let i = 0; i < response.data.length; i++){  console.log(response.data[i].created_at); 抽出結果と考察 今回は意外にも対象件数が多かったので2020年1月~のデータのみを対象にしました。 グラフがちょっと見づらくなってしまいました。 ・新月の当日に投稿されてはいないものの、その近い日に投稿されているのがよくわかりますね。前述でも記載したように新月にはマインドが突き動かされるのだと考察します。 ・また、新月との関わりの他に第3回目の緊急事態宣言前にかなりの記事が投稿されているのもわかります。これは、「不安への対処法としてマインドフルネスが推奨される」というのも関係しているのではないかと推察します。 おわりに やりたいことがいっぱいなのに技術が伴わないため焦るばかりで、結局は授業で教わったロジックしか組めませんでした。(涙) 今後もいろいろな検証をしていきたいので、もっとスキルを磨いてチャレンジします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】Array.lengthの不可解な挙動

問題 突然ですが以下のlengthの値ってどうなると思いますか? const array = []; array['book'] = 'santai'; console.log(array); // [ age: 'santai'] console.log(array.length); // …? ぜひ、ちょっと考えてみてください。 答えは0です。 筆者はundefinedだと思ったのですがJavaScriptの仕様では0になるようです。他にも1になるのではと考えた人もいるんじゃないでしょうか? 最初から処理を追って行ってみましょう。 まずJavaScriptでは配列にプロパティを追加することができてしまうみたいです。 array['book'] = 'santai'; console.log(array); // [ age: 'santai'] この時点で配列は連想配列に変化すると思っていたのですが、どうやら配列のままのようです。 console.log(Array.isArray(array)); // true もしも配列が連想配列に変化していたのなら、lengthプロパティを呼び出すとundefinedが返ってくるはずです。 const obj = {}; console.log(obj.length); // undefined 配列のままなので正常にlengthプロパティを呼び出すことができ、答えは1になる……と思いきや帰ってきたのは0。 console.log(array.length); // 0 どうやらlengthは配列内に数値以外のキーがあった場合、それを検知できないみたいです。 const array = []; array['book'] = 'santai'; array.push(1) console.log(array); // [ 1, book: 'santai' ] console.log(array.length); // 1 MDNを漁ってみたのですがこのような仕様について記述してある項目は見つからず……。どなたか詳しく書いてある文献を知っていたら教えてください<(_ _)> 解決策 解決策としてはObject.keys()を使えば期待するような結果が得られます。 const array = []; array['book'] = 'santai'; console.log(array); // [ age: 'santai'] console.log(Object.keys(array).length); // 1 const array = []; array['book'] = 'santai'; array.push(1) console.log(array); // [ 1, book: 'santai' ] console.log(Object.keys(array)); // [ '0', 'book' ] console.log(array.length); // 2 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

仮想通貨の時価総額とQiitaの関連記事数の相関を調べてみた!

はじめに 初投稿になります! ”あったらいいな!”や”気になる!”を形にしていこうと思います。 プログラミング経験のない超初心者ですが、アウトプット重視で発信をしていこうと思いますので、 どうぞ、よろしくお願いします! 概要 コロナ禍となり、外出もあまりしなくなった今日この頃皆さまはいかがお過ごしでしょうか。 私はというと、何か新しい趣味をと思い、数年前にやっていた仮想通貨を再開しました。 仮想通貨の持つボラティリティの高さに心を奪われ、 暇あらば仮想通貨のチャートに張り付いてしまう日々をすごしております。 最近、Bitcoinの時価総額がGoldを上回ったり、有名人の呟いたDogeが爆騰したり、よくわからない草コインが数万%上昇したりとバブルな様相を呈していますよね。 そんな中、ふとコインの持つ技術的注目度と時価総額に相関があるのか気になりはじめました。 本記事では、技術的注目度を「各仮想通貨名がタグつけされQiitaに投稿された記事数」、 時価総額をCoinMarketCap時価総額上位100を参考に相関を見ていきたいと思います。 CoinMarketCap時価総額上位100 環境 Node.js 15.13.0 axios 0.21.1 各仮想通貨名のタグが付いている記事の取得 QiitaAPIを使用し、各仮想通貨名のタグが付いている、記事数を取得するコード const axios = require('axios'); async function main() { let response = await axios.get('https://qiita.com/api/v2/tags/”コイン名”'); console.log(response.data.items_count); } main(); QiitaAPIで取得した記事数と時価総額の散布図 上位50位までの時価総額、Qiita記事数で検証(2021/04/25 16:00時点) 相関係数 0.60 まとめ 今回は、上記のようなとても微妙な結果となりました。 それもそのはず、ヒットした記事数も0のものがが多く、各仮想通貨の日本での認知度もかなり影響している模様です。 時価総額上位でも、タグにすら登場しない通貨もちらほら・・・。 このリサーチはあまり投資判断の参考にはならないので、もっと別対象(TwitterやGitHubなど)も含め分析していきたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのクラスとインスタンス

はじめに JavaScriptのクラスとインスタンスの基本について学んだので備忘録としてまとめたいと思います。クラスとインスタンスの知識はReactを扱う際にも必要な知識なので改めてES6から実装された新機能は学ぶべきだと思いました。 オブジェクトとは クラスとインスタンスに入る前にオブジェクトについておさらいです。 オブジェクトは複数の値をプロパティという名前をつけて管理できる格納庫みたいなものです。 const user = {name: 'アナゴ', sex: 'male'}; # 定数名.プロパティ名で値を取得できます。 console.log(user.name); またオブジェクトの値には関数を用いることができます。 また、その関数を呼び出すには定数名.プロパティ名()と記述します。 以下のコードでは関数の部分をアロー関数で記述しています。 const user = { name: 'アナゴ', greet: () => { console.log('おはよう'); } } # 関数の呼び出し user.greet(); オブジェクトを量産したい Webサービスでは上記のようなオブジェクトをいくつも扱っています。 例えば以下のようにオブジェクトをいくつも作成する場合は毎回ゼロから作成していたらとても手間になります。 そこで効率よくオブジェクトを作成するために設計図を用意し、ぞの設計図を元にデータを作成していきます。 この設計図のことをクラス、一方一つ一つのデータをインスタンスと呼びます。 クラスの定義 まずは設計図であるクラスを定義します。 定義の仕方はclass クラス名です。 ちなみにクラスの命名規則としてはキャメルケース(単語の先頭を大文字にする)が慣例です。 class IsonoFamily { } インスタンスの生成 設計図は用意できたので個別のデータを作成します。クラスからオブジェクトを生成にはnew クラス名()と記述します。このクラスから生成したオブジェクトをインスタンスといいます。 class IsonoFamily { } // IsonoFamilyクラスのインスタンスを定数namiheiに代入 const namihei = new IsonoFamily(); namiheiというインスタンスはこれで作成できました。しかしクラスの中身に何も記述していないのでインスタンスもカラの状態です。次にクラスに中身を追加していきます。 コンストラクタ クラスにはコンストラクタという機能が用意されています。 コンストラクタはインスタンスを生成するときに実行したい処理や設定を追加するための機能です。 クラスの中にconstructor() { }と記述し、さらにこのコンストラクタの中に処理を記述します。 class IsonoFamily { // コンストラクタを追加 constructor() { console.log('インスタンスを生成しました'); } } const namihei = new IsonoFamily(); プロパティと値を追加 コンストラクタの中で生成したインスタンスに情報を追加します。 this.プロパティ名 = 値と記述することで情報を追加できます。 class IsonoFamily { constructor() { // ageプロパティを追加 this.age = 54; } } const namihei = new IsonoFamily(); // 年齢を出力 console.log(namihei.age); インスタンスごとに値を変える 現段階のままでは新しくインスタンスを生成してもどれも同じ情報が付与されてしまいます。 これを解決するにはconstructor(age, voiceActor)のようにコンストラクタに引数を渡してあげます。 またインスタンスを生成する際にnew クラス名()の()に値を追加します。 class IsonoFamily { // 引数に「name」と「age」を追加してください constructor(voiceActor, age) { // voiceActorを引数として受け取る this.voiceActor = voiceActor; // ageを引数として受け取る this.age = age; } } // 引数に「"モカ"」と「8」を渡してください const namihei = new IsonoFamily("茶風林", 54); console.log(`声優: ${namihei.voiceActor}`); console.log(`年齢: ${namihei.age}`); メソッドとは メソッドとはインスタンスの動作みたいなものです。 メソッドはクラスの中でメソッド名(){ }と記述し定義します。関数とにたようなものなので{ }内に処理を記述していきます。またメソッドを呼び出す際はインスタンス.メソッド名()と記述します。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } // greetメソッドを追加 greet() { console.log('ばかもん!'); } } const namihei = new IsonoFamily("茶風林", 54); console.log(`声優: ${namihei.voiceActor}`); console.log(`年齢: ${namihei.age}`); // namiheiに対してgreetメソッドを呼びだす namihei.greet(); メソッド内で値を使う メソッド内でインスタンスの値を使うにはthis.プロパティ名と記述します。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } greet() { console.log('ばかもん!'); } // infoメソッドを追加 info() { console.log(`声優は${this.voiceActor}です`); console.log(`${this.age}歳です`); } } const namihei = new IsonoFamily("茶風林", 54); // namiheiに対してgreetメソッドを呼びだす namihei.greet(); // namiheiに対してinfoメソッドを呼びだす namihei.info(); メソッド内でメソッドを使う メソッド内で他のメソッドを呼び出すこともできます。 メソッド内でthis.メソッド名()と記述することで同じクラスの他のメソッドを使うことができます。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } greet() { console.log('ばかもん!'); } info() { // greetメソッドを呼びだす this.greet(); console.log(`声優は${this.voiceActor}です`); console.log(`${this.age}歳です`); } } const namihei = new IsonoFamily("茶風林", 54); namihei.info(); 継承 継承とはすでにあるクラスをもとに新しくクラスを作成する方法です。 例えばIsonoFamilyクラスからFugutaFamilyクラスを継承するとIsonoFamilyクラスの全ての機能を引き継いでクラスを生成します。 継承はclass FugutaFamily extends IsonoFamilyと記述します。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } greet() { console.log('ばかもん!'); } info() { // greetメソッドを呼びだす this.greet(); console.log(`声優は${this.voiceActor}です`); console.log(`${this.age}歳です`); } // IsonoFamilyクラスを継承してFugutaFamilyクラスを定義 class FugutaFamily extends IsonoFamily { } } const namihei = new IsonoFamily("茶風林", 54); namihei.info(); 継承したクラスの特性 FugutaFamilyクラスはIsonoFamilyクラスの全ての機能を引き継いでいます。 なのでFugutaFamilyクラスの中身は空でもIsonoFamilyクラスで定義されているメソッドなどはそのまま使うことができます。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } greet() { console.log('ばかもん!'); } info() { // greetメソッドを呼びだす this.greet(); console.log(`声優は${this.voiceActor}です`); console.log(`${this.age}歳です`); } // IsonoFamilyクラスを継承してFugutaFamilyクラスを定義 class FugutaFamily extends IsonoFamily { } } // 定数taraにFugutaFamilyクラスのインスタンスを代入 const tara = new FugutaFamily("さすがたかこ", 3); tara.info(); 子クラスにプロパティを追加したい 親クラスで設定したプロパティの他に、子クラスに個別にプロパティを付与させることができます。 その場合は子クラスのコンストラクタの中にsuper()と記述し、その中に親クラスが受け取る引数を記述します。 class IsonoFamily { constructor(voiceActor, age) { this.voiceActor = voiceActor; this.age = age; } greet() { console.log("ばかもん!"); } info() { this.greet(); console.log(`声優は${this.voiceActor}です`); console.log(`${this.age}歳です`); } } class FugutaFamily extends IsonoFamily { // constructorを追加 constructor(voiceActor, age, hairStyle) { super(voiceActor, age); { this.hairStyle = hairStyle; } } info() { this.greet(); console.log(`声優は${this.voiceActor}です`); // 「髪型は〇〇です」と出力 console.log(`髪型は${this.breed}です`); console.log(`${this.age}歳です`); } } // 3つ目の引数に「"おかっぱ"」を渡す const tara = new IsonoFamily("さすがたかこ", 3, 'おかっぱ'); tara.info(); 以上です!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「HTMLはプログラミング言語か」問題に終止符を打つ

こんにちは。筆者はHTMLでプログラムを書けるプログラミング言語、その名も「The HTML Programming Language (THPL)」を作りました。なので、明らかにHTMLはプログラミング言語です。以下では、THPLについて説明します。 最初のプログラム HTMLプログラミング言語では、Hello, world!プログラムは次のように書くことができます。HTMLプログラムを実行するには、HTMLファイルをブラウザで開きます。そうするだけでHTMLプログラムが実行され、出力が表示されます。 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>My First HTML Program</title> <script src="https://unpkg.com/the-html-programming-language@0.1.0/browser.min.js"></script> </head> <body> <p><output>Hello, world!</output></p> </body> </html> 順に説明していきましょう。<body>までの以下の部分はおまじないです。 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>My First HTML Program</title> <script src="https://unpkg.com/the-html-programming-language@0.1.0/browser.min.js"></script> </head> <body> ただし、<title></title>の間の部分は好きに書くことができ、プログラムのタイトルを表します。分かりやすい名前を付けてあげましょう。また、scriptという部分にある@0.1.0というのはTHPLのバージョンを表します。プログラミング言語自体のバージョンを常に明示的に指定してあげることで、互換性などの問題が生じにくくなっています。 また、プログラムの最後の2行についてもおまじないです。ここの文言は常に固定です。 </body> </html> よって、このプログラムの本体は以下の1行となります。 <p><output>Hello, world!</output></p> ここに書かれているのはP文であり、これは<p>式</p>という構文を持ちます。意味は、中の式を実行することです。JavaScriptなどと同様にHTMLでは文は結果を持たず式は結果を持ちます。P文では、中の式を実行した結果は使われません。JavaScriptでいうところの 式; に相当する文ですね。 P文の中にあるのはOUTPUT式であり、これは<output>式</output>という構文を持ちます。意味は、中の式を評価し、その結果を出力します。OUTPUT式の結果は中の結果そのままです。上の例ではHello, world!という文字列を出力しています。御察しの通り、Hello, world!というプレーンテキストもHTMLでは式として扱われます。要するに文字列リテラルです。 変数を定義する HTML言語では変数を使うことができます。変数を定義するにはDL文を用います。DL文は次のような構文を持ちます。 <dl> <dt>変数名1</dt> <dd>式1</dd> <dt>変数名2</dt> <dd>式2</dd> <!-- ... --> </dl> このように、DL文の中では<dt>と<dd>という構文がペアになって現れます。dtで示された名前の変数が作られ、その中身はddに書かれた式の評価結果となります。 また、変数の中身を得るためには<var>変数名</var>という構文のVAR式を用います。 例(以下の例ではおまじない部分は省略します): <dl> <dt>str1</dt> <dd>Hello</dd> <dt>str2</dt> <dd>world</dd> </dl> <p> <output><var>str1</var>, <var>str2</var>!</output> </p> 関数を定義する HTMLではSECTION文を用いて関数を宣言できます。構文は<section title="関数名">関数本体</section>です。また、関数呼び出しにはABBR式を用います。こちらは<abbr title="関数名">引数</abbr>という構文です。引数を複数渡す場合は<br>で区切ります。 <section title="say"> <p> <output><slot name="0"></slot>, <slot name="1"></slot>!</output> </p> </section> <p> <abbr title="say">Hello<br />world</abbr> </p> この例ではsay関数にHelloとworldという2つの関数を渡しています。関数内では、受け取った引数はSLOT式を用いて参照することができます。最初の引数が0、次の引数が1……という名前を指定します。また、最初の引数は<slot></slot>というようにname構文を省略することができます。 また、関数名というのは実は変数名として解釈されます。上のプログラムでは、SECTION文は実際にはtitleという名前の変数を作りその中に関数オブジェクトを格納します。ABBR式では、title構文で関数を呼び出す方式のほかに、ABBR式の内部で、VARで取得した関数オブジェクトを指定する方法もあります。この場合、関数オブジェクトは引数よりも前に書かれ、関数オブジェクトと引数の間はやはり<br>で区切られます。つまり、上のプログラムの関数呼び出し部分は次のように書くことも可能です。 <abbr> <var>say</var><br />Hello<br />world </abbr> なお、関数の戻り値は関数内でP文の代わりにFOOTER文を実行することで指定できます。FOOTER文の中に書かれた式が実行され、その結果が関数の返り値となります。 算術演算 HTMLでは算術演算も可能ですが、あまり得意ではなくやや複雑なプログラムを書く必要があります。HTMLには算術演算用の演算子のようなものはなく、各演算が関数として提供されています。それらの関数はmath名前空間から取得する必要があります。math名前空間にアクセスするには、次のようなmath式を用います。次の例は、加算を表すplus関数をmath名前空間から取得する例です。 <math><plus /></math> このような関数は上述のABBR式を用いて呼び出すことができます。 <p> <output>1 + 2 + 3 = <abbr> <math><plus /></math><br> 1<br>2<br>3 </abbr></output> </p> <!-- 1 + 2 + 3 = 6 と表示される --> HTMLの値には文字列と数値がありますが、算術演算の結果は数値です。一方で、HTMLプログラムに書かれた1, 2, 3といったプレーンテキストは文字列として扱われます。これらが算術演算の関数に渡される際は自動的に数値に変換されます。明示的に数値に変換したい場合は、文字列から数値への変換を表すMETER式を用いて次のようにできます。 <p> <output>1 + 2 + 3 = <abbr> <math><plus /></math><br> <meter>1</meter><br><meter>2</meter><br><meter>3</meter> </abbr></output> </p> 条件分岐 HTMLでの条件分岐はRUBY式として提供されています。RUBY式はやや特殊な構文を持ち、JavaScriptで言うところのswitchに似た挙動をします。RUBY式は次のような構文を持ちます。 式 <ruby> 条件式1 <rt>分岐式1</rt> 条件式2 <rt>分岐式2</rt> <!-- ... --> 条件式n <rt>分岐式n</rt> <rt>else分岐 (任意)</rt> </ruby> このように、RUBY式は条件分岐の対象となる式に後置することで形成されます。ここではまず式が評価され、次に条件式1が評価されて両者が一致するかどうか確かめられます。一致した場合、分岐式1が評価され、その結果がRUBY式の結果となります。一致しなかった場合、次に条件式2が評価され、式の結果と条件式2の結果が一致すれば分岐式2が評価され……のように進行します。 RUBY式には、一番最後に条件式を伴わない<rt>構文を書くことができ、これはelse分岐です。これまでのどの条件にもマッチしなかった場合にこの分岐が実行されます。else分岐が無いRUBY文でどの条件にもマッチしなかった場合は、結果はnullとなります。 例えば、与えられた数値が偶数なら"even"を、奇数なら"odd"を返すodd_even関数は次のように定義できるでしょう。 <section title="odd_even"> <footer> <abbr> <math><rem /></math><br /> <slot></slot><br /> 2 </abbr> <ruby> <meter>0</meter> <rt>even</rt> <meter>1</meter> <rt>odd</rt> </ruby> </footer> </section> <p> <output>15 is <abbr title="odd_even">15</abbr>.<wbr /></output> </p> <p> <output>-2 is <abbr title="odd_even">-2</abbr>.<wbr /></output> </p> ここでのポイントは、RUBY式の条件分岐に先ほど紹介したMETER式が使われている点です。RUBY式の条件分岐の対象となっているのはその前のABBR式ですが、ここではmath名前空間から取り出したrem関数を実行しているので、その結果は数値です。RUBY式では文字列と数値の間の暗黙の変換は行われないため、このように条件として数値を与えないとマッチしてくれません。HTML初心者が引っかかりがちな罠の一つですので気をつけましょう。 ちなみに、この例では密かに<wbr />という構文のWBR式が登場しています。これは改行文字を表す式です。 実践編: FizzBuzz HTMLの言語機能は以上です。実践編として、FizzBuzzを書いてみました。HTMLの力試しをしたい方は自力で書いてみましょう。 ヒントとしては、ループの言語機能がありませんので、再帰関数を用いることになります。 以下がHTML言語によるFizzBuzzの実装です。1から100までループします。 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>FizzBuzz</title> <script src="https://unpkg.com/the-html-programming-language@0.1.0/browser.min.js"></script> </head> <body> <section title="fizzbuzz"> <dl> <dt>rem3</dt> <dd> <abbr> <math><rem /></math><br /> <slot></slot><br />3 </abbr> </dd> <dt>rem5</dt> <dd> <abbr> <math><rem /></math><br /> <slot></slot><br />5 </abbr> </dd> <dt>text</dt> <dd> <abbr> <math><eq /></math><br /><var>rem3</var><br /><meter>0</meter></abbr ><abbr> <math><eq /></math><br /><var>rem5</var><br /><meter>0</meter></abbr ><ruby> <span>00</span> <rt><slot></slot></rt> <span>10</span> <rt>Fizz</rt> <span>01</span> <rt>Buzz</rt> <span>11</span> <rt>FizzBuzz</rt> </ruby> </dd> </dl> <p> <output><var>text</var><wbr /></output> </p> <footer> <slot></slot> <ruby> <meter>100</meter> <rt><span></span></rt> <rt ><abbr title="fizzbuzz"> <abbr ><math><plus /></math><br /><slot></slot><br />1</abbr > </abbr></rt > </ruby> </footer> </section> <p> <abbr title="fizzbuzz"><meter>1</meter></abbr> </p> </body> </html> おおよそこれまで解説した知識で読み解くことができますが、一つだけ新たな要素としてSPAN式が登場しています。これは基本的には括弧( )のようなもので、中の式を評価してそのまま返すだけの式です。ただ、HTML言語では特徴的な使われ方をされることがあります。 まず、<span></span>のような空のSPAN式が許可されています。これは空文字列を返す式となります。式を書くべき所に何も書かないのは構文エラーとなるので、何もしたくないが式を書く必要がある場合に重宝されます。 また、よく見ると以下の箇所でRUBY式の条件式としてSPAN式で囲まれた文字列が使用されています。 <ruby> <span>00</span> <rt>...</rt> </ruby> これはHTMLパーサーの特徴的な挙動によるものです。HTMLパーサーは空白文字だけからなるプレーンテキストを無視しますが、次のようにSPANが無いと、RUBY文の条件式が"(改行) 00(改行) "のような文字列であると認識され、本来"00"にマッチさせたいところマッチしなくなってしまいます。 <ruby> 00 <rt>...</rt> </ruby> SPAN式を使うことで<ruby>と<span>の間のテキストが文字列の一部ではなく無視するべき空白であるとパーサーに認識されます。SPAN式はこのような用途で使うこともできます。 まとめ 皆さんもHTMLで楽しいプログラミングライフを! HTML言語への機能追加提案やプルリクエストなども歓迎です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

消費税計算~JS側~

今回は、JS側での消費税計算をする関数を作り、それを表に出力できるようにしました。 HTML側 index.html <table class="tax-answer" id="tax-answer" border="1"> <caption>結果一覧</caption> <tr> <th>何月何日</th><th>式</th><th>結果</th> </tr> <tr> <th>データ</th><th>データ</th><th id="total1">データ</th> </tr> <tr> <th>データ</th><th>データ</th><th id="total2">データ</th> </tr> </table> 今回は、結果の欄に出力できるようにしていきます。 index.js const tax = 0.1; function totalTax(taxNot) { const total = taxNot + taxNot * tax; return total; } document.getElementById('total1').textContent = totalTax(3000); document.getElementById('total2').textContent = totalTax(400); document.getElementByIdでHTMLのid属性を取得します。そして、textContent( これは開始タグから終了タグまでを書き換えてくれます)にtotalTax(値);で関数を呼び出し、代入します。 これで、データを書き換えることができました。 今回の場合は、ユーザーが計算した値を計算したいので、入力イベント発生から書き換えて行きます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript - Strictモード(厳格モード)下における「スクリプトの連結」について

結論を先にご覧になりたい方は、こちらをご覧ください。 Strictモード下における「スクリプトの連結」には要注意 Mozillaは開発者向けの技術解説を行っており、Strictモードに関する説明もあります。 その中では、Strictモードのスクリプトと非Strictモードのスクリプトとの「連結」に関し、 Strict モードのスクリプトと非 Strict モードのスクリプトを連結することを考えてみてください。連結後のスクリプト全体が strict になるのです! これは逆も言えます。非 Strict のスクリプトと Strict のスクリプトを連結すると非 Strict になります。 という注意がなされています。 そして、注意喚起とともに、 どうしても必要な場合は、機能ごと1に Strict を有効にすることを検討してください。 という対策も示されています。 すなわち、Strictモードのスクリプトと非Strictモードのスクリプトとを混在させる場合、「連結」されたスクリプトではStrictモードまたは非Strictモードのいずれかに統一されてしまい、時に開発者の意図しない挙動を示す場合があるため注意が必要である、という注意喚起です。 ところで、この「スクリプトの連結」とは、どのような場合を指すのでしょうか? 「スクリプトの連結」という語は、あまり一般的でないと思われます2。 将来的にECMAScriptで新たな構文が導入された際にも、Strictモードで実装したスクリプトであれば、非Strictモードで実装した場合よりも移行が容易であることが予想されます。 そのため、スクリプトを新たに作成する場合、Strictモードのもとで実装を行うほうが好ましいと思われます。 しかしながら、非Strictモードにより実装されたスクリプトが、数多、現役で稼働していることも想像に難くありません。 そのような環境では、「Strictモードのスクリプト」と「非Strictモードのスクリプト」との共存が不可欠であるはずです。 そして、そのような場面においてこそ、「どのような共存方法が『スクリプトの連結』に該当するのか?」を理解しておくことが大切であると考えます。 「スクリプトの連結」に該当するのはどのような状況で、逆に該当しないのはどのような状況であるのかが明らかでないと、Strictモード/非Strictモードのスクリプトを共存させる際、実装者の意図しないエラーやバグが生まれかねないからです。 筆者は、そのような動機から本記事を執筆するに至りました。 ぜひ本記事により、「スクリプトの連結」というあいまいな語を明確な理解に変換する手助けができれば幸いです。 「スクリプトの連結」とは何か? 「スクリプトの連結」はあいまいな語ですが、前提条件として、「少なくとも2つ以上のスクリプトが存在する」ということは、詳しい説明をせずとも受け入れていただけると思います3。 では、2つ以上のスクリプトがどのように共存していると「連結」に該当するのでしょうか? ここでは、HTMLファイルにJavaScriptによるスクリプトを埋め込む場合を想定して、その挙動を確かめてみます。 一般に、HTMLファイルへJavaScriptを埋め込む方法には、 インラインスクリプト形式 example.html <script type="text/javascript"> func () { /* 略 */ } </script> 外部スクリプト形式 example.html <script type="text/javascript" src="func.js"></script> func.js func () { /* 略 */ } の2通りがあります。 また、1つのHTMLファイルから2つのスクリプトを呼び出す場合、 インラインスクリプト同士 example.html <script type="text/javascript"> funcA () { /* 略 */ } </script> <script type="text/javascript"> funcB () { /* 略 */ } </script> 外部スクリプト同士 example.html <script type="text/javascript" src="funcA.js"></script> <script type="text/javascript" src="funcB.js"></script> インラインスクリプトと外部スクリプト example.html <script type="text/javascript"> funcA () { /* 略 */ } </script> <script type="text/javascript" src="funcB.js"></script> のようなパターンがあり得ます。 Mozillaによる注意喚起がなされているのは、「Strictモード/非Strictモードの共存」に対してです。 そこで、まずは「1つのHTMLファイルから2つのスクリプトを呼び出す」ことが、「スクリプトの連結」に該当するかを検証します。 (下記の検証1~3では、Microsoft Windows 10 (64ビット)上で稼働するMozilla Firefox 88.0による検証の結果を示します。JavaScriptおよびStrictモードの挙動は、ブラウザ間で差異が生じることがあります。そのため、ほかのブラウザでは異なる結果が得られる場合もありますが、ご了承ください。) 検証1:「1つのHTMLファイルから2つのスクリプトを呼び出す」ことは、「スクリプトの連結」に該当するか? つぎの表4にあげる8パターンに分けて、検証を行ってみます。 調査に使用するコードは、つぎの2つのスクリプトです。 (調査の結果を分かりやすくするため、DOMContentLoadedイベントに対するリスナーを2箇所に分けて記載しています。HTMLの構文としては、あまり好ましい方法ではありませんが、ご了承ください。) strict.js "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); non_strict.js /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); 注目すべき点は、非Strictモード「non_strict.js」の変数nonStrictMessageが、 const文 let文 var文 のいずれをも利用せずに登場している点です。 非Strictモード下においては、この場合、変数nonStrictMessageはグローバル変数として宣言されてしまいます。 ただし、このような挙動はStrictモード下では発生せず、const文・let文・var文のいずれをも伴わない変数の初期化はエラーとして扱われ、該当箇所でスクリプトの実行が停止します。 したがって、Strictモード「strict.js」と非Strictモード「non_strict.js」との共存をさせた際、エラーが発生せず全てのスクリプトの実行が完了すれば、「strict.js」の先頭でStrictモードの宣言を行っているにも関わらず「non_strict.js」が非Strictモード下で実行されることがわかります。 (逆に、変数nonStrictMessageの初登場箇所で「non_strict.js」の実行が停止した場合は、「non_strict.js」がStrictモード下で実行されたことがわかります。) さて、検証に用いるファイルは次のように準備します。 1つ目のスクリプトがStrictモードであるパターンA・C・E・Gでは、「strict.js」・「non_strict.js」の順に、それぞれインラインスクリプトもしくは外部スクリプトとして埋め込みます。 また、1つ目のスクリプトが非StrictモードであるパターンB・D・F・Hでは、「non_strict.js」・「strict.js」の順に、それぞれインラインスクリプトもしくは外部スクリプトとして埋め込みます。 検証1の結果 上記A~Hの8パターンについて、実際に実行したところ、すべてのパターンにおいてエラーは発生しませんでした。 (詳細な結果は割愛させていただきます。なお、実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) すなわち、記載場所がインラインであるか外部であるかに依らず、scriptタグで隔てられている場合、Mozillaの注意喚起にある「スクリプトの連結」には該当しません。 したがって、scriptタグで隔てられている限り、各スクリプトがStrictモード/非Strictモードのいずれであるかを注意する必要はありません。 では、scriptタグによる隔離を解いた場合、どのような挙動がみられるでしょうか? 検証2:「2つのインラインスクリプトを1つのscriptタグ内にまとめて記載する」ことは、「スクリプトの連結」に該当するか? 下記のようなコードを実行してみます。 verification2/strict_non_strict.html <script type="text/javascript"> "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); </script> 検証2の結果 2つのインラインスクリプトを1つのscriptタグ内にまとめて記載し、実際に実行したところ、エラーが発生しました。 (実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) 具体的には、変数strictMessageの値「ほえるライオン」のみアラートされ、変数nonStrictMessageの初登場箇所において、 Uncaught ReferenceError: assignment to undeclared variable nonStrictMessage のようなエラーメッセージがコンソールに出力されました。 このエラーは、スクリプトのすべてがStrictモードとして実行されたために発生したものです。 したがって、「2つのインラインスクリプトを1つのscriptタグ内にまとめて記載する」ことは、「スクリプトの連結」に該当します。 すなわち、1つのscriptタグ内に「Strictモードのスクリプト」と「非Strictモードのスクリプト」とを記載する場合、スクリプト全てに「Strictモードの構文」が用いられているか注意する必要があります。 追加検証:1つのscriptタグ内に「非Strictモード」・「Strictモード」の順に記載した場合は? verification2/non_strict_strict.html <script type="text/javascript"> /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); "use strict"; // JavaScriptの文法上、無視されます。 const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); </script> 変数nonStrictMessageの値「とまどうペリカン」および変数strictMessageの値「ほえるライオン」の両方がアラートされました。 1つのscriptタグ内に「非Strictモード」・「Strictモード」の順に記載した場合、そのスクリプト全てが「非Strictモード」で実行されます5。 したがって多くの場合、エラーは発生しないでしょう。 しかしながら、「Strictモード」での実行を意識して実装されたスクリプトが「非Strictモード」下で実行されてしまうため、実装時に意図しなかった挙動がみられる恐れ6があります。 検証3:「2つの外部スクリプトを1つのファイル内にまとめて記載する」ことは、「スクリプトの連結」に該当するか? 下記のようなコードを実行してみます。 verification3/strict_non_strict.html <script type="text/javascript" src="strict_non_strict.js"></script> verification3/strict_non_strict.js "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); 検証3の結果 2つの外部スクリプトを1つのファイルにまとめた上で1つのscriptタグにより呼び出したところ、検証2と同じくエラーが発生しました。 (実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) この結果は、検証2と同様に、スクリプトのすべてがStrictモードとして実行されたことを示しています。 したがって、「2つの外部スクリプトを1つのファイル内にまとめて記載する」ことも、「スクリプトの連結」に該当します。 すなわち、1つの外部スクリプトファイル内に「Strictモードのスクリプト」と「非Strictモードのスクリプト」とを記載する場合、スクリプト全てに「Strictモードの構文」が用いられているか注意する必要があります。 追加検証:1つの外部スクリプトファイル内に「非Strictモード」・「Strictモード」の順に記載した場合は? verification3/non_strict_strict.html <script type="text/javascript" src="non_strict_strict.js"></script> verification3/non_strict_strict.js /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); "use strict"; const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); 変数nonStrictMessageの値「とまどうペリカン」および変数strictMessageの値「ほえるライオン」の両方がアラートされました。 すなわち、1つの外部スクリプトファイル内に「非Strictモード」・「Strictモード」の順に記載した場合、そのスクリプト全てが「非Strictモード」で実行されます5。 検証2と同じく、意図せず「非Strictモード」下で実行されてしまうことにより、実装時に意図しなかった挙動がみられる可能性のあることに注意が必要です。 本記事のまとめ 「スクリプトの連結」とは、2つ以上のインラインスクリプトまたは外部スクリプトを1つに集約することです。 具体的には、下記の例1・例2が該当します。 例1 「インラインスクリプトの連結」 連結前 before.html <script> // Strictモード "use script"; const funcA = () => { /* 略 */ } </script> <script> // 非Strictモード const funcB = () => { /* 略 */ } </script> 連結後 after.html <script type="text/javascript"> // scriptタグ内のすべてがStrictモードとして扱われる。 "use script"; const funcA = () => { /* 略 */ } // 非Strictモードとして実装された部分であるため、funcBでエラーが発生する可能性があります。 // 実際の連結時には、funcBがStrictモードに対応した構文になっているか確認が必要です。 const funcB = () => { /* 略 */ } </script> 例2 「外部スクリプトの連結」 連結前 before.html <script type="text/javascript" src="A.js"></script> <script type="text/javascript" src="B.js"></script> A.js // Strictモード "use script"; const funcA = () => { /* 略 */ } B.js // 非Strictモード const funcB = () => { /* 略 */ } 連結後 agter.html <script src="AB.js"></script> AB.js /* AB.js:「A.js」と「B.js」とを連結 */ // ファイル全体がStrictモードとして扱われます。 "use script"; const funcA = () => { /* 略 */ } // 非Strictモードとして実装された部分であるため、funcBでエラーが発生する可能性があります。 // 実際の連結時には、funcBがStrictモードに対応した構文になっているか確認が必要です。 const funcB = () => { /* 略 */ } 補遺 - 実用上注意が必要である場面の一例 フレームワークによりスクリプトが自動連結される場合 Ruby on Rails はRuby言語によるウェブアプリケーションフレームワークであり、ビューファイル(ユーザインターフェイスを記述するファイル)にJavaScriptを埋め込むことが可能です。 そして、そのRuby on Railsには「アセットパイプライン」と呼ばれる機能があります。 アセットパイプライン アセットパイプラインは、JavaScriptやCSSを、自動で圧縮したうえで連結する機能です7。 アセットパイプラインのメリットの1つは、自動的にJavaScriptやCSSを連結・圧縮するため、クライアントによるHTTPリクエスト回数の減少、サーバ・クライアント間のファイル転送サイズの減少を実現でき、ウェブページをより高速に表示できる点です。 そのようなメリットを得られる一方、本記事の扱ってきた「スクリプトの連結」がアセットパイプラインにより自動的に行われることも事実です。 このとき、実装者がアセットパイプラインの存在を認識せず、意図せずスクリプトが連結された場合、本記事で扱った「Strictモード/非Strictモードのスクリプトの連結」による問題が容易に発生しえます。 すなわち、Ruby on Railsによるウェブアプリケーションの実装において、JavaScriptを埋め込む場合、 アセットパイプラインにより、スクリプトの連結が行われること Strictモードのスクリプトと非Strictモードのスクリプトを共存させる場合には、注意が必要であること に注意して利用する必要があります。 おそらく「機能ごと」は「関数ごと」と読みかえるべきだと思われます。Mozilla 英語版"Strict mode"の、"function-by-function"という語が対応します。すなわち、スクリプト全体に対してStrictモードを適用するのでなく、関数ごとにStrictモードを有効にすることを検討するよう提案しています。 ↩ 「スクリプトの連結」における「連結」という語は、Mozilla 英語版"Strict mode"の"concatenating"および"concatenate"の和訳として用いられています。JavaScriptにおいて"concatenate"という語は、Stringオブジェクトのconcat()メソッドのような「文字列の連結」、あるいはArrayクラスのconcat()メソッドのような「配列の連結」などに用いられることが一般的であるように思われます。 ↩ 1つのスクリプトをくりかえし実行することを指す場合、「ループ」や「再帰」という語の方がふさわしいと思われます。 ↩ Microsoft 365 PowerPointにより筆者が作成しました。使用しているフォントは「IPAexゴシック」です。項目「記載場所」の「内」はインラインスクリプトを、「外」は外部スクリプトを表します。項目「Strictモード」の丸印は当該スクリプトにおいてStrictモードが有効になっていることを、バツ印は当該スクリプトにおいてStrictモードが無効になっていることを表します。 ↩ 「"use strict";」は、スクリプトの先頭または関数ブロック内の先頭のどちらか以外では効力を有しません。 ↩ たとえば、変数名のタイプミスにより意図せず新たなグローバル変数が宣言されてしまうバグを作りこむことが考えられます。Strictモードで実行されていれば、そのバグによりエラーが発生するため早く気づくことが可能ですが、非Strictモードでは前述のとおりエラーになりません。 ↩ https://guides.rubyonrails.org/asset_pipeline.html ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript - Strictモード(厳格モード)下における「スクリプトの連結」とは何か?

結論を先にご覧になりたい方は、こちらをご覧ください。 Strictモード下における「スクリプトの連結」には要注意 Mozillaは開発者向けの技術解説を行っており、Strictモードに関する説明もあります。 その中では、Strictモードのスクリプトと非Strictモードのスクリプトとの「連結」に関し、 Strict モードのスクリプトと非 Strict モードのスクリプトを連結することを考えてみてください。連結後のスクリプト全体が strict になるのです! これは逆も言えます。非 Strict のスクリプトと Strict のスクリプトを連結すると非 Strict になります。 という注意がなされています。 そして、注意喚起とともに、 どうしても必要な場合は、機能ごと1に Strict を有効にすることを検討してください。 という対策も示されています。 すなわち、Strictモードのスクリプトと非Strictモードのスクリプトとを混在させる場合、「連結」されたスクリプトではStrictモードまたは非Strictモードのいずれかに統一されてしまい、時に開発者の意図しない挙動を示す場合があるため注意が必要である、という注意喚起です。 ところで、この「スクリプトの連結」とは、どのような場合を指すのでしょうか? 「スクリプトの連結」という語は、あまり一般的でないと思われます2。 将来的にECMAScriptで新たな構文が導入された際にも、Strictモードで実装したスクリプトであれば、非Strictモードで実装した場合よりも移行が容易であることが予想されます。 そのため、スクリプトを新たに作成する場合、Strictモードのもとで実装を行うほうが好ましいと思われます。 しかしながら、非Strictモードにより実装されたスクリプトが、数多、現役で稼働していることも想像に難くありません。 そのような環境では、「Strictモードのスクリプト」と「非Strictモードのスクリプト」との共存が不可欠であるはずです。 そして、そのような場面においてこそ、「どのような共存方法が『スクリプトの連結』に該当するのか?」を理解しておくことが大切であると考えます。 「スクリプトの連結」に該当するのはどのような状況で、逆に該当しないのはどのような状況であるのかが明らかでないと、Strictモード/非Strictモードのスクリプトを共存させる際、実装者の意図しないエラーやバグが生まれかねないからです。 筆者は、そのような動機から本記事を執筆するに至りました。 ぜひ本記事により、「スクリプトの連結」というあいまいな語を明確な理解に変換する手助けができれば幸いです。 「スクリプトの連結」とは何か? 「スクリプトの連結」はあいまいな語ですが、前提条件として、「少なくとも2つ以上のスクリプトが存在する」ということは、詳しい説明をせずとも受け入れていただけると思います3。 では、2つ以上のスクリプトがどのように共存していると「連結」に該当するのでしょうか? ここでは、HTMLファイルにJavaScriptによるスクリプトを埋め込む場合を想定して、その挙動を確かめてみます。 一般に、HTMLファイルへJavaScriptを埋め込む方法には、 インラインスクリプト形式 example.html <script type="text/javascript"> function func() { /* 略 */ } </script> 外部スクリプト形式 example.html <script type="text/javascript" src="func.js"></script> func.js function func() { /* 略 */ } の2通りがあります。 また、1つのHTMLファイルから2つのスクリプトを呼び出す場合、 インラインスクリプト同士 example.html <script type="text/javascript"> function funcA() { /* 略 */ } </script> <script type="text/javascript"> function funcB() { /* 略 */ } </script> 外部スクリプト同士 example.html <script type="text/javascript" src="funcA.js"></script> <script type="text/javascript" src="funcB.js"></script> インラインスクリプトと外部スクリプト example.html <script type="text/javascript"> function funcA() { /* 略 */ } </script> <script type="text/javascript" src="funcB.js"></script> のようなパターンがあり得ます。 Mozillaによる注意喚起がなされているのは、「Strictモード/非Strictモードの共存」に対してです。 そこで、まずは「1つのHTMLファイルから2つのスクリプトを呼び出す」ことが、「スクリプトの連結」に該当するかを検証します。 (下記の検証1~3では、Microsoft Windows 10 (64ビット)上で稼働するMozilla Firefox 88.0による検証の結果を示します。JavaScriptおよびStrictモードの挙動は、ブラウザ間で差異が生じることがあります。そのため、ほかのブラウザでは異なる結果が得られる場合もありますが、ご了承ください。) 検証1:「1つのHTMLファイルから2つのスクリプトを呼び出す」ことは、「スクリプトの連結」に該当するか? つぎの表4にあげる8パターンに分けて、検証を行ってみます。 調査に使用するコードは、つぎの2つのスクリプトです。 (調査の結果を分かりやすくするため、DOMContentLoadedイベントに対するリスナーを2箇所に分けて記載しています。HTMLの構文としては、あまり好ましい方法ではありませんが、ご了承ください。) strict.js "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); non_strict.js /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); 注目すべき点は、非Strictモード「non_strict.js」の変数nonStrictMessageが、 const文 let文 var文 のいずれをも利用せずに登場している点です。 非Strictモード下においては、この場合、変数nonStrictMessageはグローバル変数として宣言されてしまいます。 ただし、このような挙動はStrictモード下では発生せず、const文・let文・var文のいずれをも伴わない変数の初期化はエラーとして扱われ、該当箇所でスクリプトの実行が停止します。 したがって、Strictモード「strict.js」と非Strictモード「non_strict.js」との共存をさせた際、エラーが発生せず全てのスクリプトの実行が完了すれば、「strict.js」の先頭でStrictモードの宣言を行っているにも関わらず「non_strict.js」が非Strictモード下で実行されることがわかります。 (逆に、変数nonStrictMessageの初登場箇所で「non_strict.js」の実行が停止した場合は、「non_strict.js」がStrictモード下で実行されたことがわかります。) さて、検証に用いるファイルは次のように準備します。 1つ目のスクリプトがStrictモードであるパターンA・C・E・Gでは、「strict.js」・「non_strict.js」の順に、それぞれインラインスクリプトもしくは外部スクリプトとして埋め込みます。 また、1つ目のスクリプトが非StrictモードであるパターンB・D・F・Hでは、「non_strict.js」・「strict.js」の順に、それぞれインラインスクリプトもしくは外部スクリプトとして埋め込みます。 検証1の結果 上記A~Hの8パターンについて、実際に実行したところ、すべてのパターンにおいてエラーは発生しませんでした。 (詳細な結果は割愛させていただきます。なお、実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) すなわち、記載場所がインラインであるか外部であるかに依らず、scriptタグで隔てられている場合、Mozillaの注意喚起にある「スクリプトの連結」には該当しません。 したがって、scriptタグで隔てられている限り、各スクリプトがStrictモード/非Strictモードのいずれであるかを注意する必要はありません。 では、scriptタグによる隔離を解いた場合、どのような挙動がみられるでしょうか? 検証2:「2つのインラインスクリプトを1つのscriptタグ内にまとめて記載する」ことは、「スクリプトの連結」に該当するか? 下記のようなコードを実行してみます。 verification2/strict_non_strict.html <script type="text/javascript"> "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); </script> 検証2の結果 2つのインラインスクリプトを1つのscriptタグ内にまとめて記載し、実際に実行したところ、エラーが発生しました。 (実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) 具体的には、変数strictMessageの値「ほえるライオン」のみアラートされ、変数nonStrictMessageの初登場箇所において、 Uncaught ReferenceError: assignment to undeclared variable nonStrictMessage のようなエラーメッセージがコンソールに出力されました。 このエラーは、スクリプトのすべてがStrictモードとして実行されたために発生したものです。 したがって、「2つのインラインスクリプトを1つのscriptタグ内にまとめて記載する」ことは、「スクリプトの連結」に該当します。 すなわち、1つのscriptタグ内に「Strictモードのスクリプト」と「非Strictモードのスクリプト」とを記載する場合、スクリプト全てに「Strictモードの構文」が用いられているか注意する必要があります。 追加検証:1つのscriptタグ内に「非Strictモード」・「Strictモード」の順に記載した場合は? verification2/non_strict_strict.html <script type="text/javascript"> /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); "use strict"; // JavaScriptの文法上、無視されます。 const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); </script> 変数nonStrictMessageの値「とまどうペリカン」および変数strictMessageの値「ほえるライオン」の両方がアラートされました。 1つのscriptタグ内に「非Strictモード」・「Strictモード」の順に記載した場合、そのスクリプト全てが「非Strictモード」で実行されます5。 したがって多くの場合、エラーは発生しないでしょう。 しかしながら、「Strictモード」での実行を意識して実装されたスクリプトが「非Strictモード」下で実行されてしまうため、実装時に意図しなかった挙動がみられる恐れ6があります。 検証3:「2つの外部スクリプトを1つのファイル内にまとめて記載する」ことは、「スクリプトの連結」に該当するか? 下記のようなコードを実行してみます。 verification3/strict_non_strict.html <script type="text/javascript" src="strict_non_strict.js"></script> verification3/strict_non_strict.js "use strict"; // Strictモード const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; // Strictモード下ではエラー発生 window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); 検証3の結果 2つの外部スクリプトを1つのファイルにまとめた上で1つのscriptタグにより呼び出したところ、検証2と同じくエラーが発生しました。 (実際に検証に用いたHTML/JavaScriptファイルはこちらに保管してあります。) この結果は、検証2と同様に、スクリプトのすべてがStrictモードとして実行されたことを示しています。 したがって、「2つの外部スクリプトを1つのファイル内にまとめて記載する」ことも、「スクリプトの連結」に該当します。 すなわち、1つの外部スクリプトファイル内に「Strictモードのスクリプト」と「非Strictモードのスクリプト」とを記載する場合、スクリプト全てに「Strictモードの構文」が用いられているか注意する必要があります。 追加検証:1つの外部スクリプトファイル内に「非Strictモード」・「Strictモード」の順に記載した場合は? verification3/non_strict_strict.html <script type="text/javascript" src="non_strict_strict.js"></script> verification3/non_strict_strict.js /* 非Strictモード */ const pelicanPuzzled = () => { nonStrictMessage = "とまどうペリカン"; window.alert(nonStrictMessage); } window.addEventListener("DOMContentLoaded", pelicanPuzzled); "use strict"; const lionBarked = () => { const strictMessage = "ほえるライオン"; window.alert(strictMessage); } window.addEventListener("DOMContentLoaded", lionBarked); 変数nonStrictMessageの値「とまどうペリカン」および変数strictMessageの値「ほえるライオン」の両方がアラートされました。 すなわち、1つの外部スクリプトファイル内に「非Strictモード」・「Strictモード」の順に記載した場合、そのスクリプト全てが「非Strictモード」で実行されます5。 検証2と同じく、意図せず「非Strictモード」下で実行されてしまうことにより、実装時に意図しなかった挙動がみられる可能性のあることに注意が必要です。 本記事のまとめ 「スクリプトの連結」とは、2つ以上のインラインスクリプトまたは外部スクリプトを1つに集約することです。 具体的には、下記の例1・例2が該当します。 例1 「インラインスクリプトの連結」 連結前 before.html <script> // Strictモード "use script"; const funcA = () => { /* 略 */ } </script> <script> // 非Strictモード const funcB = () => { /* 略 */ } </script> 連結後 after.html <script type="text/javascript"> // scriptタグ内のすべてがStrictモードとして扱われる。 "use script"; const funcA = () => { /* 略 */ } // 非Strictモードとして実装された部分であるため、funcBでエラーが発生する可能性があります。 // 実際の連結時には、funcBがStrictモードに対応した構文になっているか確認が必要です。 const funcB = () => { /* 略 */ } </script> 例2 「外部スクリプトの連結」 連結前 before.html <script type="text/javascript" src="A.js"></script> <script type="text/javascript" src="B.js"></script> A.js // Strictモード "use script"; const funcA = () => { /* 略 */ } B.js // 非Strictモード const funcB = () => { /* 略 */ } 連結後 after.html <script src="AB.js"></script> AB.js /* AB.js:「A.js」と「B.js」とを連結 */ // ファイル全体がStrictモードとして扱われます。 "use script"; const funcA = () => { /* 略 */ } // 非Strictモードとして実装された部分であるため、funcBでエラーが発生する可能性があります。 // 実際の連結時には、funcBがStrictモードに対応した構文になっているか確認が必要です。 const funcB = () => { /* 略 */ } 補遺 - 実用上注意が必要である場面の一例 フレームワークによりスクリプトが自動連結される場合 Ruby on Rails はRuby言語によるウェブアプリケーションフレームワークであり、ビューファイル(ユーザインターフェイスを記述するファイル)にJavaScriptを埋め込むことが可能です。 そして、そのRuby on Railsには「アセットパイプライン」と呼ばれる機能があります。 アセットパイプライン アセットパイプラインは、JavaScriptやCSSを、自動で圧縮したうえで連結する機能です7。 アセットパイプラインのメリットの1つは、自動的にJavaScriptやCSSを連結・圧縮するため、クライアントによるHTTPリクエスト回数の減少、サーバ・クライアント間のファイル転送サイズの減少を実現でき、ウェブページをより高速に表示できる点です。 そのようなメリットを得られる一方、本記事の扱ってきた「スクリプトの連結」がアセットパイプラインにより自動的に行われることも事実です。 このとき、実装者がアセットパイプラインの存在を認識せず、意図せずスクリプトが連結された場合、本記事で扱った「Strictモード/非Strictモードのスクリプトの連結」による問題が容易に発生しえます。 すなわち、Ruby on Railsによるウェブアプリケーションの実装において、JavaScriptを埋め込む場合、 アセットパイプラインにより、スクリプトの連結が行われること Strictモードのスクリプトと非Strictモードのスクリプトを共存させる場合には、注意が必要であること に注意して利用する必要があります。 おそらく「機能ごと」は「関数ごと」と読みかえるべきだと思われます。Mozilla 英語版"Strict mode"の、"function-by-function"という語が対応します。すなわち、スクリプト全体に対してStrictモードを適用するのでなく、関数ごとにStrictモードを有効にすることを検討するよう提案しています。 ↩ 「スクリプトの連結」における「連結」という語は、Mozilla 英語版"Strict mode"の"concatenating"および"concatenate"の和訳として用いられています。JavaScriptにおいて"concatenate"という語は、Stringオブジェクトのconcat()メソッドのような「文字列の連結」、あるいはArrayクラスのconcat()メソッドのような「配列の連結」などに用いられることが一般的であるように思われます。 ↩ 1つのスクリプトをくりかえし実行することを指す場合、「ループ」や「再帰」という語の方がふさわしいと思われます。 ↩ Microsoft 365 PowerPointにより筆者が作成しました。使用しているフォントは「IPAexゴシック」です。項目「記載場所」の「内」はインラインスクリプトを、「外」は外部スクリプトを表します。項目「Strictモード」の丸印は当該スクリプトにおいてStrictモードが有効になっていることを、バツ印は当該スクリプトにおいてStrictモードが無効になっていることを表します。 ↩ 「"use strict";」は、スクリプトの先頭または関数ブロック内の先頭のどちらか以外では効力を有しません。 ↩ たとえば、変数名のタイプミスにより意図せず新たなグローバル変数が宣言されてしまうバグを作りこむことが考えられます。Strictモードで実行されていれば、そのバグによりエラーが発生するため早く気づくことが可能ですが、非Strictモードでは前述のとおりエラーになりません。 ↩ https://guides.rubyonrails.org/asset_pipeline.html ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript基礎】キーボード入力時に処理をする

JavaScriptにおける「キーボード入力時に処理するイベント」について、自己の忘備録としてまとめています。 参考URL 参考著書 文字を入力するたびに処理をする キーの入力時に発生するイベントはkeydown、keyup、keypressで監視することができます。 イベント名 発生タイミング keydown キーが押されたとき keyup キーが離されたとき keypress 文字を生成するキーが押されたとき <!-- HTML --> <textarea class="textarea"></textarea> // JS const textarea = document.querySelector('.textarea'); // キーが押されたとき textarea.addEventListener('keydown', () => { console.log('キーが押されました。'); }); // 文字が入力された textarea.addEventListener('keypress', () => { console.log('文字が入力されました。'); }); // キーが離されたとき textarea.addEventListener('keyup', () => { console.log('キーが離されました。'); }); 入力されたキーを調べる KeyboardEventオブジェクトのプロパティを調べることで、入力されたキーの種類を判定できます。keydownやkeyupイベントと組み合わせて使用します。 プロパティ 意味 型 キーボードイベント.key 押されたボタンの値 文字列 キーボードイベント.code 押されたボタンのコード 文字列 キーボードイベント.altKey Altキーが押されたかどうか 真偽値 キーボードイベント.ctrlKey Ctrlキーが押されたかどうか 真偽値 キーボードイベント.shiftKey Shiftキーが押されかどうか 真偽値 キーボードイベント.metaKey metaキー※が押されたかどうか 真偽値 キーボードイベント.repeat キーを押しっぱなしにしているかどうか 真偽値 キーボードイベント.isComposing 入力が未確定かどうか※ 真偽値 ※metaキーは、Macであれば⌘キーを指す。 ※全角で日本語を入力中に、確定しない状態であればtrueとなる。 // JS const textarea = document.querySelector('.textarea'); // キーが押されたとき textarea.addEventListener('keyup', (event) => { // 出力例は「a」を押したととき console.log(event.key); //'a' console.log(event.code); //'KeyA' console.log(event.altKey); // false console.log(event.ctrlKey); // false console.log(event.shiftKey); // false console.log(event.metaKey); // false console.log(event.repeat); // false console.log(event.isComposing); // false }); どのキーが押されたを判定するには、キーコードの数値を使用します。 const handleKeyDown = (event) => { // キーコード(どのキーが押されたか)を取得 const keyCode = event.keyCode; if (keyCode === 39) { console.log('右キーが押されました。'); } }; window.addEventListener('keydown', handleKeyDown);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Kawaii がかわいい

React Kawaii という React コンポーネントのお試し。 10 Trending projects on GitHub for web developers - 23rd April 2021 で紹介されていたもの。 React Kawaii 使い方 以下で。 npm install --save react-native-svg npm install --save react-kawaii かわいいのは題名のとおりさておいて、React ってこういう感じなのかあ というのが分かるところが面白かったかもしれない。 書いたのこれだけ。 import { Planet } from 'react-kawaii' import { Cat } from 'react-kawaii' import { Ghost } from 'react-kawaii' //中略 <Planet size={90} mood="blissful" color="#FDA7DC" /> <Planet size={90} mood="lovestruck" color="#FDA7DC" /> <Planet size={90} mood="blissful" color="#FDA7DC" /> <Cat size={110} mood="excited" color="#596881" /> <Cat size={110} mood="happy" color="#596881" /> <Cat size={110} mood="excited" color="#596881" /> <Ghost size={100} mood="excited" color="#83D1FB" /> <Ghost size={100} mood="happy" color="#83D1FB" /> <Ghost size={100} mood="ko" color="#83D1FB" /> こうなる React メモ 以上お楽しみいただければさいわいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ある配列が別の配列の部分配列になっているか判定する【javascript】

概要 ある配列が別の配列の部分配列になっていることを判定する関数を作りました。 例えば、[1,2]は[1,2,3,4]の部分配列になっているので、そういうときにtrueを返すことを目指しました。 実装 組み込み関数等で簡単にやる方法が見つからなかったので(もしご存知でしたら教えて下さい)、JSON.stringifyで文字列に直してから、includeで判定するようにしました。 ただし厳密に部分文字列のときだけtrueを返すようにはなっていないので、許容できる場合のみ使用してください。 //subarrayがarrayの部分配列になっているか判定する function isSubarray(subarray, array){ //subarrayの長さが1のときはincludeで判定する if(subarray.length == 1){ return array.includes(subarray[0]); } //subarrayの長さが1以上のとき、json文字列に直して比較する let subarray_str = JSON.stringify(subarray).slice(1,-1);//前後のカッコを除くためsliceする let array_str = JSON.stringify(array).slice(1,-1);//前後のカッコを除くためsliceする return array_str.includes(subarray_str); } console.log(isSubarray([1,2],[1,2,3,4]));//true console.log(isSubarray([1,3],[1,2,3,4]));//false console.log(isSubarray([1],[1,2,3,4]));//true console.log(isSubarray([1],[12,2,3,4]));//false console.log(isSubarray([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray("1,2", [1,2,3,4]));//true。引数がlistじゃないときは正しく機能しません console.log(isSubarray([1,2],["1,2",3,4]));//true。意地悪な配列を考えると誤った結果を返させる事ができるので注意してください。 別解 より厳密に判定したい場合は、subarrayの最初の要素がarrayに含まれているかをまず確認し、含まれていればその位置から、subarrayの長さだけsliceしたarrayの部分配列とsubarrayを比較するとよいです。 function isSubarray2(subarray, array){ //subarrayが空のとき常にtrue if(subarray.length == 0){ return true; } let i = array.length - subarray.length + 1; let subarray_str = JSON.stringify(subarray); //arrayがsubarrayより短ければfalse if(i<=0) return false; while(i--){//今回はwhileで書いているが、forでも別にいいと思う if(array[i]!=subarray[0])continue; //もしsubarray[0]と同じ文字があれば、そこからsubarray長さ分だけsliceして比較 let array_str = JSON.stringify(array.slice(i,i+subarray.length)); if(subarray_str == array_str) return true; } return false; } console.log(isSubarray2([1,2],[1,2,3,4]));//true console.log(isSubarray2([1,3],[1,2,3,4]));//false console.log(isSubarray2([1],[1,2,3,4]));//true console.log(isSubarray2([1],[12,2,3,4]));//false console.log(isSubarray2([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray2("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray2("1,2", [1,2,3,4]));//false。subarrayがstringでarrayがlistのときはfalseになります。 console.log(isSubarray2([1,2],["1,2",3,4]));//false あるいは部分配列を比較するときにJSON.stringifyをせずに1要素ずつ比較してもよいです。(@akebi_mh 様のコメントを参考にさせていただきました。ありがとうございました) function isSubarray3(subarray, array){ //subarrayが空のとき常にtrue if(subarray.length == 0){ return true; } let i = array.length - subarray.length + 1; //arrayがsubarrayより短ければfalse if(i<=0) return false; while(i--){ for(let j=0;j<subarray.length;j++){ if(array[i+j]!=subarray[j]) break; if(j == subarray.length-1) return true; } } return false; } console.log(isSubarray3([1,2],[1,2,3,4]));//true console.log(isSubarray3([1,3],[1,2,3,4]));//false console.log(isSubarray3([1],[1,2,3,4]));//true console.log(isSubarray3([1],[12,2,3,4]));//false console.log(isSubarray3([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray3("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray3("1,2", [1,2,3,4]));//false。subarrayがstringでarrayがlistのときはfalseになります。 console.log(isSubarray3([1,2],["1,2",3,4]));//false ただし、isSubarray2やisSubarray3だと二重のリストに対して上手く判定できないケースがあります(リスト同士の比較は参照先の比較になって異なると判定されるため)。 console.log(isSubarray2([[1,2],3],[[1,2],3,4]));//falseになってしまう console.log(isSubarray3([[1,2],3],[[1,2],3,4]));//falseになってしまう これを改善するためには、要素同士の比較をするときに、必ずJSON.stringifyするようにします。ただしisSubarray2やisSubarray3よりも遅くなるケースが多くなります。 function isSubarray4(subarray, array){ //subarrayが空のとき常にtrue if(subarray.length == 0){ return true; } let i = array.length - subarray.length + 1; let subarray_str = JSON.stringify(subarray); //arrayがsubarrayより短ければfalse if(i<=0) return false; while(i--){ //arrayのi番目からsubarray長さ分だけsliceして比較 let array_str = JSON.stringify(array.slice(i,i+subarray.length)); if(subarray_str == array_str) return true; } return false; } console.log(isSubarray4([1,2],[1,2,3,4]));//true console.log(isSubarray4([1,3],[1,2,3,4]));//false console.log(isSubarray4([1],[1,2,3,4]));//true console.log(isSubarray4([1],[12,2,3,4]));//false console.log(isSubarray4([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray4("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray4("1,2", [1,2,3,4]));//false。subarrayがstringでarrayがlistのときはfalseになります。 console.log(isSubarray4([1,2],["1,2",3,4]));//false console.log(isSubarray4([[1,2],3],[[1,2],3,4]));//true isSubarray4はループが最後までいくと遅くなるので、isSubarrayでtrueの場合だけ、isSubarray4でより厳密に比較するような処理をしても良いかもしれません。 function isSubarray5(list1,list2){ if(isSubarray(list1,list2)){ return isSubarray4(list1, list2); }else{ return false; } } console.log(isSubarray5([1,2],[1,2,3,4]));//true console.log(isSubarray5([1,3],[1,2,3,4]));//false console.log(isSubarray5([1],[1,2,3,4]));//true console.log(isSubarray5([1],[12,2,3,4]));//false console.log(isSubarray5([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray5("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray5("1,2", [1,2,3,4]));//false。subarrayがstringでarrayがlistのときはfalseになります。 console.log(isSubarray5([1,2],["1,2",3,4]));//false console.log(isSubarray5([[1,2],3],[[1,2],3,4]));//true あるいは要素にobjectが含まれるときだけ、文字列化しておいてから、isSubarray3を実行する処理でも良いと思います。 function isSubarray6(subarray, array){ //要素にobjectがある場合、文字列化しておく let onedim_subarray = [] for(let v of subarray){ if(typeof v == "object") onedim_subarray.push(JSON.stringify(v)); else onedim_subarray.push(v); }; //要素にobjectがある場合、文字列化しておく let onedim_array = [] for(let v of array){ if(typeof v == "object") onedim_array.push(JSON.stringify(v)); else onedim_array.push(v); }; return isSubarray3(onedim_subarray, onedim_array); } console.log(isSubarray6([1,2],[1,2,3,4]));//true console.log(isSubarray6([1,3],[1,2,3,4]));//false console.log(isSubarray6([1],[1,2,3,4]));//true console.log(isSubarray6([1],[12,2,3,4]));//false console.log(isSubarray6([],[12,2,3,4]));//true。subarrayが空のときは常にtrueです console.log(isSubarray6("12","1234"));//true。subarrayとarrayが両方stringのときは一応機能します。 console.log(isSubarray6("1,2", [1,2,3,4]));//false。subarrayがstringでarrayがlistのときはfalseになります。 console.log(isSubarray6([1,2],["1,2",3,4]));//false console.log(isSubarray6([[1,2],3],[[1,2],3,4]));//true 速度比較 以下のコードをGoogleChromeで実行してみました。 let pair0 = [[1,2,3], [1,2,3,4,5,6,7,8,9,10]] //ループの最後で見つかるケース(うしろから比較していることに注意) let pair1 = [[8,9,10], [1,2,3,4,5,6,7,8,9,10]] //ループの最初で見つかるケース(うしろから比較していることに注意) let pair2 = [[0,1,2], [1,2,3,4,5,6,7,8,9,10]] //先頭マッチもせず見つからないケース let pair3 = [[1,2,3], [0,0,0,0,0,1,1,1,1,1]] //先頭マッチを5回するが見つからないケース let pair4 = [[1,2,3], [1,1,1,1,1,1,1,1,1,1]] //先頭マッチを10回するが見つからないケース let pair5 = [[[0],[2]], [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]] //二重リストで見つからないケース let pair6 = [[[9],[10]], [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]] //二重リストでループの最初で見つかるケース let loop = 100000; let pairs = [pair0,pair1,pair2,pair3,pair4,pair5,pair6] for(let i=0;i<pairs.length;i++){ console.time("isSubarray_pair"+i); for(let j=0;j<loop;j++)isSubarray(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray_pair"+i); console.time("isSubarray2_pair"+i); for(let j=0;j<loop;j++)isSubarray2(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray2_pair"+i); console.time("isSubarray3_pair"+i); for(let j=0;j<loop;j++)isSubarray3(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray3_pair"+i); console.time("isSubarray4_pair"+i); for(let j=0;j<loop;j++)isSubarray4(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray4_pair"+i); console.time("isSubarray5_pair"+i); for(let j=0;j<loop;j++)isSubarray5(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray5_pair"+i); console.time("isSubarray6_pair"+i); for(let j=0;j<loop;j++)isSubarray6(pairs[i][0],pairs[i][1]) console.timeEnd("isSubarray6_pair"+i); console.log(""); } 結果は以下のとおりです。 //ループの最後で見つかるケース(うしろから比較していることに注意) isSubarray_pair0: 79.200927734375 ms isSubarray2_pair0: 64.299072265625 ms isSubarray3_pair0: 9.739013671875 ms isSubarray4_pair0: 257.19189453125 ms isSubarray5_pair0: 316.3779296875 ms isSubarray6_pair0: 26.1240234375 ms //ループの最初で見つかるケース(うしろから比較していることに注意) isSubarray_pair1: 86.56494140625 ms isSubarray2_pair1: 54.39794921875 ms isSubarray3_pair1: 1.077880859375 ms isSubarray4_pair1: 54.43310546875 ms isSubarray5_pair1: 110.537841796875 ms isSubarray6_pair1: 12.309814453125 ms //先頭マッチもせず見つからないケース isSubarray_pair2: 54.322021484375 ms isSubarray2_pair2: 24.364013671875 ms isSubarray3_pair2: 2.1181640625 ms isSubarray4_pair2: 235.083984375 ms isSubarray5_pair2: 54.5458984375 ms isSubarray6_pair2: 11.88671875 ms //先頭マッチを5回するが見つからないケース isSubarray_pair3: 56.10498046875 ms isSubarray2_pair3: 108.228759765625 ms isSubarray3_pair3: 2.509033203125 ms isSubarray4_pair3: 235.684814453125 ms isSubarray5_pair3: 55.550048828125 ms isSubarray6_pair3: 12.787841796875 ms //先頭マッチを10回するが見つからないケース isSubarray_pair4: 61.6123046875 ms isSubarray2_pair4: 237.735107421875 ms isSubarray3_pair4: 3.067138671875 ms isSubarray4_pair4: 234.406982421875 ms isSubarray5_pair4: 59.468994140625 ms isSubarray6_pair4: 13.0390625 ms //二重リストで見つからないケース isSubarray_pair5: 210.897216796875 ms isSubarray2_pair5: 59.349853515625 ms //二重リストでは結果を間違うことがあるので注意 isSubarray3_pair5: 8.093994140625 ms //二重リストでは結果を間違うことがあるので注意 isSubarray4_pair5: 545.741943359375 ms isSubarray5_pair5: 188.7587890625 ms isSubarray6_pair5: 295.174072265625 ms //二重リストでループの最初で見つかるケース isSubarray_pair6: 187.844970703125 ms isSubarray2_pair6: 58.2529296875 ms //二重リストでは結果を間違うことがあるので注意 isSubarray3_pair6: 6.530029296875 ms //二重リストでは結果を間違うことがあるので注意 isSubarray4_pair6: 104.947998046875 ms isSubarray5_pair6: 297.10205078125 ms isSubarray6_pair6: 251.386962890625 ms ざっくりと以下のことがわかります。 二重リストを考慮しない場合 isSubarray3(要素同士をシンプルに比較)が判定も厳密で、ダントツ速い 二重リストを考慮し、低い確率で誤判定することを許容する場合 isSubarray(全体を文字化してincludeで判定)が安定して速い 二重リストを考慮し、かつ、結果の厳密さを求める場合 isSubarray6(objectの要素を文字化してからisSubarray3で比較)が安定して速い isSubarray4(arrayの部分文字列を文字化して厳密に比較)はループの最初でマッチするときは速い isSubarray5 (isSubarrayで大雑把に判定してからisSubarray4)は、結果がfalseのときは速い したがって、リストが一重であることがわかっているなら、isSubarray3を使い、そうでなければ、isSubarray6を使うのがいいかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む