20210506のJavaScriptに関する記事は20件です。

document.get~~~の違い

JavaScriptからHTML要素を変更したい時、まずその要素を取得すると思います。 しかしその要素を取得する方法はgetElementByIdやgetElementsByClassNameなど様々な方法があります。しかし、それらは若干返すものが異なり、いざ操作しようと思ったときに困ることがしばしばあります。 今回は、DOMで要素を取得したときに返す値についてまとめていきます。 1つ目 Elementオブジェクト これが返ってくるものは - getElementById - querySelector などがあります。 例えばこんなHTMLがあったとします。 <body> <h1 id="title">タイトル</h1> <ul> <li class="list">リスト1</li> <li class="list">リスト2</li> <li class="list">リスト3</li> </ul> </body> そのHTML要素を取得するためのJSファイルがあるとします。 console.log(document.getElementById("title")); // => <h1 id="title">タイトル</h1> console.log(document.querySelector("li")); // => <li class="list">リスト1</li> 実際に取得するとこのように表示されます。これはHTMLオブジェクトであり、変数などに渡してあげればそのままaddEventListnerなどでイベント処理を書くことも可能です。 getElementByIdはIdを取得するがIdは一意のため、QuerySelectorは該当する1番目の要素を返すためこれが成り立ちます。 2つ目 HTMLコレクション これが返ってくるものは - getElementsByClassName - getElementsByTagName などがあります。 試しにgetElementsByClassNameで要素を取得してみます。(HTMLファイルは最初の物を参照します) console.log(document.getElementsByClassName("list")); //=> HTMLCollection {0: HTMLLIElement, 1: HTMLLIElement, 2: HTMLLIElement, length: 3, item: ƒ item()…} HTMLコレクションを簡単に言うと要素の集合体です。この中に該当する要素が入っています。(今回だとlistクラスを持つものです。) このコレクションから要素を取得するにはindexやidなどで取得することが出来ます。 console.log(document.getElementsByClassName("list")[0]); //=> <li class="list">リスト1</li> console.log(document.getElementsByClassName("list")["middle"]); //=> <li class="list" id="middle">リスト2</li> このように配列のようなHTMLコレクションから指定すればHTMLオブジェクトとして取り出すことが出来、イベント処理などが出来るようになります。 3つ目 NodeListコレクション これが返ってくるのは - querySelectorAll が主なメソッドです。 console.log(document.querySelectorAll("li")); //=>NodeList {0: HTMLLIElement, 1: HTMLLIElement, 2: HTMLLIElement, entries: ƒ entries(), keys: ƒ keys()…} NodeListはHTMLコレクションとほぼ変わりません。強いて上げるならHTMLコレクションは動的でありNodeListコレクションは静的であることが挙げられます。複雑なDOM操作をしない限りそこまで気にする必要もないと思います。 結局どれで取得するのがいいの? 現状一番ベターなのはgetElementByIdだと思います。最後に紹介したquerySelectorAllなどは非常に高機能ですが、その分低速になりやすいです。 逆にgetElementByIdなどは一意のIdを取得するのでとても高速です。 以上の事から対応できる範囲でgetElement.ByIdで処理して、それではきついような高度な取得をしたいときにはquerySelectorなどを使うのがオススメです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クライアント側でjsonファイルの書き出し/読み込みを行う

はじめに クライアント側(JavaScript)で連想配列をjson形式のファイルに書き出し、読み込みする方法を簡単に書きたいと思います。 今回クライアント側はJavaScript(Vue.js)を使用しています。 実装イメージ 見た目のイメージはこんな感じです。 ボタンの色などは別ファイルで設定しているので今回は省略しています。 ファイルの書き込み、読み込みボタンがあり、 書き込みボタンを押すと、jsonファイルがダウンロード 読み込みボタンを押すと、ファイル選択画面がブラウザで開き、ファイルを選択できる。 *ファイル選択後の処理は今回は書いていません 実装 example.vue <template> <div class="example"> <v-row justify="end"> <!-- 書き出しボタン --> <v-col cols="6"> <v-btn color="primary" outlined block @click="dataExport">書き出し</v-btn> </v-col> <!-- 読込ボタン --> <v-col cols="6"> <v-btn id="inportBtn" width="154px" color="primary" @click="btnClick">読み込み</v-btn> <input type="file" style="display: none" @change="dataImport" ref="input" accept="application/json" /> </v-col> </v-row> </div> </template> <script> export default { data: () => ({ exampleData: [ // jsonファイルに書き出ししたいデータ { "name": "太郎", "age": 3 }, { "name": "花子", "age": 5 }] }), methods: { dataExport() { // dataには const blob = new Blob([JSON.stringify(this.exampleData, null, ' ')], { type: 'application/json', }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'exportData.json'; // 出力するファイルの名前 link.click(); link.remove(); }, dataImport() { const ref: any = this.$refs.input; // 選択したファイル const file = ref.files[0]; console.log(file); // 何かエラーチェックを行う場合は以下に // その他処理(API呼ぶなど) }, btnClick() { // 中にあるinput要素を取得 const ref: any = this.$refs.input; // dataImportを実行 ref.click(); }, } </script> 解説 読み込みボタン example.vue ... 省略 ... <!-- 読込ボタン --> <v-col cols="6"> <v-btn id="inportBtn" width="154px" color="primary" @click="btnClick">読み込み</v-btn> <input type="file" style="display: none" @change="dataImport" ref="input" accept="application/json" /> ... 省略 ... <script> export default { 省略 ... }), methods: { dataImport() { const ref: any = this.$refs.input; // 選択したファイル const file = ref.files[0]; console.log(file); // 何かエラーチェックを行う場合は以下に // その他処理(API呼ぶなど) }, btnClick() { // 中にあるinput要素を取得 const ref: any = this.$refs.input; // dataImportを実行 ref.click(); }, } </script> btn要素とは別にinput要素を配置。ボタンをクリックすると、 btnClick()が呼ばれ、その中でbtn要素の中のinput要素にアクセス。 アクセスしたinput要素のclick(=dataImport())を呼ぶ と言った流れになります。 今回はファイルアップロードのボタンにcssを付けたかったので、上のような実装にしましたが、 下のシンプルなファイル選択ボタンでいい場合は以下のコードで十分です。 <input type="file" accept="application/json"> 書き込みボタン example.vue ... 省略 ... <!-- 書き出しボタン --> <v-col cols="6"> <v-btn color="primary" outlined block @click="dataExport">書き出し</v-btn> </v-col> ... 省略 ... <script> export default { data: () => ({ exampleData: [ // jsonファイルに書き出ししたいデータ { "name": "太郎", "age": 3 }, { "name": "花子", "age": 5 }] }), methods: { dataExport() { // JSON.stringifyで文字列に変換 const blob = new Blob([JSON.stringify(this.exampleData, null, ' ')], { type: 'application/json', }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'exportData.json'; // 出力するファイルの名前 link.click(); link.remove(); }, } </script> 書き出しボタンを押すとdataExport()が呼ばれます。 今回書き出す対象のデータはdataにあるexampleDataを使っています。 URL.createObjectURLで、blobに対してアクセスができるURLを作成しています。 このURLはブラウザを閉じるまでメモリに残ってしまうので、最後に.removeを使って、削除しています。 このボタンをクリックすると、ブラウザはexportData.jsonというファイルがダウンロードされます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxtjs × Netlify Functionsを始める際のメモ

まずはNuxtjs yarn create nuxt-app <project-name> axios にはチェックを入れておく cd <project-name> yarn dev Netlify Functionsを使えるように インストール npm i netlify-lambda package.jsonに追記 "scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate", "serve": "netlify dev" }, serveの行を追加した 毎回 'netlify dev' で実行でもいいけどやっぱり忘れるし… netlify コマンドがないって言われたら npm install netlify-cli -g ちなみにnpxだと失敗した 試しに実行する際は、 npm run serve で開発していくとよかった Netlify Functionsも実行できるし、Nuxtもローカルで動いた tomlファイルの作成 netlify.toml という名前のファイルを新しく作って、 [dev] command = "yarn dev" [build] command = "yarn generate" publish = "dist" functions = "functions" としてみた。 (たぶん余計なものとかまだ削れきれてないだろうなぁ) functionsフォルダの作成 functionsフォルダを作成し、中にNetlify Functionsのコードを書いていく 例えば 'hello-world.js' というファイル名で hello-world.js exports.handler = function (event, context, callback) { callback(null, { statusCode: 200, body: 'Hello, World', }) } npm run serve で自動的に立ち上がった 'localhost:8888' にNuxtのページが見れる 各functionsへは http://localhost:8888/.netlify/functions/<functionのファイル名> 例えば http://localhost:8888/.netlify/functions/hello-world URL内にドットがあると嫌です? まぁまぁ、Nuxtのページとバッティングせずにいいじゃないですか… どうしてもって場合は多分tomlファイル内での設定です。がんばってください(未来への自分へ…)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

数当てゲームの改良版

数当てゲームを改良して、正解、不正解でブラウザ上に表示する文言を変えました <!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>数当てゲーム</title> <style> h1 { color:plum; font-size:100px; } h2 { color:#4f7911; font-size:50px; } body { background-color:whitesmoke; } </style> </head> <body id = "test"> <header> <div class="container"> <h1>数当てゲーム</h1> <h2>数を当ててみよう</h2> </div><!-- /.container --> </header> <main> <div class="container"> <section> </section> </div><!-- /.container --> </main> <footer> <div class="container"> </div><!-- /.container --> </footer> <script> 'use strict'; let message; let count = 1; let flag = 0; //乱数を発生させる。0~49まで数字がランダムに表示されるので1を足す。 const number = Math.floor(Math.random() * 50) + 1; //数当てゲームは5回挑戦できるようにする。 while (count <=5){ const answer = parseInt(window.prompt('数当てゲーム。1〜50の数字を入力してね。5回まで挑戦できます')); window.alert(count + "回目です。") //正解、数が小さい、数が大きい、1から50以外の数字が入った時の条件式 if(answer === number) { message = 'あたり!'; window.prompt(count + "回目で正解しました。おめでとうございます!"); document.querySelector("h1").innerHTML = "<h1>数当てゲーム、正解です!</h1>"; document.querySelector("h2").innerHTML = "<h2>おめでとうございます!</h2>"; flag = 1; break; } else if((answer < number) && (answer >= 1)) { message = '残念でした!もっと大きい'; } else if((answer > number) && (answer <=50)) { message = '残念でした!もっと小さい'; } else { message = '1〜50の数字を入力してね。'; } //入力した数字に応じてメッセージを出力する。 window.alert(message); // count = count + 1; } //3回挑戦して不正解だったら正解をアラートで表示し、HTMLに表示するメッセージを変更する。 if (flag === 0){ window.alert("正解は" + number + "です"); document.querySelector("h1").innerHTML = "<h1>残念でした!</h1>"; document.querySelector("h2").innerHTML = "<h2>また挑戦してください!</h2>"; } </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Slickの基本的な使い方

スライダー&カルーセルを実装するモジュールでjQueryプラグインで広く知られるslickのReact版。 React Slick 導入方法 インストール npm install react-slick --save or yarn add react-slick cssの設置 設置場所はcreat-react-appで作成している時のpublic/index.html相当のheadタグ内に以下を記述する。 ・CDN <link rel="stylesheet" type="text/css" charset="UTF-8" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" /> ・インストールする場合はインストール後にimportする npm install slick-carousel --save or yarn add slick-carousel ↓ import "~slick-carousel/slick/slick.css"; import "~slick-carousel/slick/slick-theme.css"; オプション オプション名 type default 説明 accessibility bool true tabキー、矢印キーによる操作を可能にするかどうか adaptiveHeight bool false スライドの高さを自動で調整するかどうか afterChange func default スライド移動後のコールバック関数 appendDots func default ドット部分にカスタムテンプレートを適用できる、customPagingと同じ働き arrows bool true スライドの左右にある矢印の表示・非表示 asNavFor ref undefined 別のスライダーとの同期を可能にする autoplaySpeed int 3000 自動でスライドするときのスピード autoplay bool false 自動でスライドするかどうか beforeChange func null スライド移動前のコールバック関数 centerMode bool false 表示しているスライドを真ん中にもってくるか(複数枚表示時) centerPadding string 50px スライドとスライドの間隔 className string "" スライダー内のdivに任意のクラス名を付与できる customPaging func ドット部分にカスタムテンプレートを適用できる dotsClass string 'slick-dots' dotに任意のクラス名を付与できる dots bool default ドットの表示・非表示 draggable bool true デスクトップ上でのドラッグによるスクロールの有効にするか cssEase(easing) string 'linear アニメーションのイーシングの変更 fade bool default スライドの切り替えをfadeにするか focusOnSelect bool false 選択したスライドまで動くか(複数枚表示) infinite bool true スライダーを無限にスライドできるか initialSlide int 0 初期表示のスライドを指定できる lazyLoad ondemand/progressive null 画像やコンポーネントを遅延ロードする onEdge func null onInit func null componentWillMountにあたるもの onLazyLoad func null 遅延ロード後のコールバック関数 onReInit func null componentDidUpdateにあたるもの onSwipe func null スワイプによるスライド変更後のコールバック関数 pauseOnDotsHover bool bool ドットにホバーしているときに自動スライドを止めるか pauseOnFocus bool false スライドを選択したときに自動スライドを止めるか pauseOnHover bool true スライドにホバーしているときに自動スライドを止めるか responsive array null 任意のブレイクポイントで何枚スライドを表示するかなどの指定ができる rows integer 1 行数の指定 rtl bool false スライドの順番を逆にする slide string 'div' スライドの要素を変更できる slidesPerRow int 1 グリッドモード時に表示するスライドの数(rowsを指定したときのみ有効) slidesToScroll int 1 一度のスクロールで何枚スライドするか slidesToShow int 1 一度に表示するスライドの数 speed int 500 スライドするスピード swipeToSlide bool false ドラッグ、スワイプでのスクロールを有効にするか swipe bool true スワイプでのスライドの変更の有効/無効 touchMove bool true ドラッグやスワイプでスライダーが動くか touchThreshold int 5 スライドを変更する際のドラッグ、スワイプの値の指定(数字を大きくすればすこしのドラッグ、スワイプで次のスライドにいく) useCSS bool true CSS Transitionsの有効/無効 useTransform bool true CSS Transformの有効/無効 variableWidth bool false スライドごとのwidthの指定の有効/無効 vertical bool false 縦向きスライドの有効/無効 ※onEdgeのみ公式の説明や検索しても使用方法がわかりませんでした... ※公式ではeasingとなっていますが、githubのissueを確認したところ正しくはcssEaseのようです。 メソッド メソッド名 args default 説明 slickGoTo index, dontAnimate null, false 引数に現在のスライドの順番を取れる,dotAnimateをtrueにするとアニメーションがなくなる slickNext none none 次スライドにいく slickPause none none autoplayを停止する slickPlay none none autoplayを有効にする slickPrev none none 前のスライドにいく サンプル 簡単な実用例 app.js import Slider from "react-slick"; function App() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 1, slidesToScroll: 1, arrow: true, }; const items = [ {title: "name1", img: 'http://placehold.it/500x200'}, {title: "name2", img: 'http://placehold.it/500x200'}, {title: "name3", img: 'http://placehold.it/500x200'}, {title: "name4", img: 'http://placehold.it/500x200'}, {title: "name5", img: 'http://placehold.it/500x200'}, {title: "name6", img: 'http://placehold.it/500x200'}, ] return ( <div className="App"> <p>simple slider</p> <div className="container"> <Slider {...settings}> {items && items.map(item => { return ( <div> <img src={item.img} /> <p>{item.title}</p> </div> ) })} </Slider> </div> </div> ); } export default App; 簡単な説明 一行目でSliderをインポート。これで該当要素をラップすることでスライダーとして機能します。 定数のsettingsにはオブジェクト型でオプションを記述し、Sliderに渡してあげます。 また業務ではapiなどを使用しデータを取得後に、表示させるようなことが多いと思ったので、ダミーデータは連想配列の形にしてみました。 1点注意なのはitems &&の部分が無いと初期表示がバグってしまう事象があったので、データを取得してきて表示するような場合は忘れないように記述するといいと思います。 またcssですが、上記にあるオプションを使用して、ドット部分などに任意のクラス名をつけてcssを適用してもいいですし、デフォルトのクラスのままでもcssは反映されます。 もっとシンプルな例や上記のオプションやメソッドを使用した公式サンプルを見てみてもいいと思います。 https://react-slick.neostack.com/docs/example/simple-slider 補足 公式sampleにあるAsNavForですが3つ以上のスライダーを同期させたい場合は以下のようにするとできました。 constructor(props) { super(props); this.state = { nav1: null, nav2: null, nav3: null, }; } componentDidMount() { this.setState({ nav1: this.slider1, nav2: this.slider2, nav3: this.slider3, }); } <Slider className="background-slider" asNavFor={this.state.nav2} ref={slider => (this.slider1 = slider)} {...settings1}> ... </Slider> <Slider className="content-slider" asNavFor={this.state.nav3} ref={slider => (this.slider2 = slider)} {...settings2}> ... </Slider> <Slider className="person-slider" asNavFor={this.state.nav1} ref={slider => (this.slider3 = slider)} {...settings3}> ... </Slider>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

与えた数字が3の倍数または3を含めるときにテンションがおかしくなる関数を作成する

与えた数字(giveNumber)が3の倍数または3がつくときはyeahと返す、それ以外の場合はその数字を返す(ナベ○ツ関数の作成) 考えたこと ①3の倍数と3の倍数以外の場合分け =>if文使う ②3を含む数字をどう判断するか =>数字のままでは判断できないので文字に直せばいけるかも =>includes使えばいけそう! const giveNumber1 =19; const giveNumber2 =31; const giveNumber3 =54; function returnNumber(num){ let str = String(num) if(num % 3 ===0 || str.includes("3")){ console.log("yeah"); }else{ console.log(num) } } returnNumber(giveNumber1); returnNumber(giveNumber2); returnNumber(giveNumber3); //出力結果 //19 //yeah //yeah
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1+2+3+…と足される数を1ずつ加算し、和が1000を超えるまでの加算値と、合計値を表示せよ。

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>1+2+3+…と足される数を1ずつ増やしながら足していき、和が1000を超えるときの足される数値と、合計値を表示してください。</title> </head> <body> <!-- 1+2+3+…と足される数を1ずつ増やしながら足していき、 和が1000を超えるときの足される数値と、合計値を表示してください。 --> <script> var sum = 0; var i = 0; while (sum <= 1000) { i++; document.write('<p>' + '= ' + sum + ' +' + '【' + i + '】' + '<p>'); sum = sum + i ; //sum += i でもOK document.write('<p>' + sum + '</p>'); } document.write('<h1>' + '合計:' + sum + '<h1>'); </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】1+2+3+…と1ずつ加算し、【和】が1000を超えた時までの加算値と、合計値を表示せよ。

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>1+2+3+…と足される数を1ずつ増やしながら足していき、和が1000を超えるときの足される数値と、合計値を表示してください。</title> </head> <body> <!-- 1+2+3+…と足される数を1ずつ増やしながら足していき、 和が1000を超えるときの足される数値と、合計値を表示してください。 --> <script> var sum = 0; var i = 0; while (sum <= 1000) { i++; document.write('<p>' + '= ' + sum + ' +' + '【' + i + '】' + '<p>'); sum = sum + i ; //sum += i でもOK document.write('<p>' + sum + '</p>'); } document.write('<h1>' + '合計:' + sum + '<h1>'); </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】1から100までの間で、3の倍数(3〜99)の数だけを足した合計値を表示せよ

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>1から100までの間で、3の倍数の数だけを足した合計値</title> </head> <body> <script> var sum = 0; for (var i = 1; i <= 100; i++) { if (i % 3 === 0) { document.write('<p>' + sum + ' + ' + '【' + i + '】' + '</p>'); sum = sum + i ; //sum += i でもOK document.write('<p>' + '= ' + sum + '<p>'); } } document.write('<h1>' + '合計:' + sum + '<h1>'); </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非同期処理を理解したい!!

はじめに 非同期処理の理解にすごく困ったので、勉強した結果をまとめました! 基本的なところから書いたので、誰でも読んでいただけると思います! ですが、自分の理解をまとめたものなので、もしかしたら間違っている箇所があるかもしれません... なお、説明中のコードは全てnode.jsで実行しました。 非同期処理とは 非同期処理というのは、簡単に言うと、処理をバックグラウンドで行うこと、です。 これに対して同期処理は、処理を1個1個順番に行っていきます。 例えば、下の画像は、自分がLGTMした記事一覧なのですが、これを表示するためには、 ・データベースからLGTMした記事の情報を取得 ・取得した結果を表示 という2つの処理を行う必要があります。 これを同期処理で行うと、データベースからデータを取得するように依頼して、結果が帰ってきたら画面を表示するという流れになります。でも、それだと結果が帰ってくるまでユーザは真っ白の画面を見ることになってしまいます... 非同期処理では、データベースからデータを取得するように依頼して、その処理をバックグラウンドでやってもらいます。バックグラウンドでやってもらっている間に、ページの枠組みだけ表示します。 リロードすると、一瞬だけ下の画像のようになると思います。真っ白な画面で待つよりも、こっちの方が良いですよね! TwitterやYouTubeも、リロードすると初めの一瞬、枠組みだけ表示されます。非同期処理は意外と身近にありますね! コールバック地獄 上で見たように、非同期処理はバックグラウンドで処理を行うのですが、その分、順序を制御するのが大変になります。 突然ですが、問題です。下のコードの出力はどうなるでしょう? import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) //hoge.txtにはhogeと書いてあります console.log(fileData) コードを上から読むと、3行目で変数fileDataを定義して、4行目でhoge.txtを読み込んで、fileDataにその結果を入れています。5行目でfileDataを出力しています。 うーん、出力結果はhogeかなあ... って思うと間違いで、このコードの出力結果はなんとundefinedになってしまいます。 なぜかと言うと、ファイル読み込みは、CPUにとって非常に時間がかかる処理で、非同期で実行されるようになっているからです。4行目で、ファイル読み込みのリクエストを開始して、5行目で結果を出力しようとしているのですが、5行目の時点では、バックグラウンドで行われているファイル読み込みがまだ終わっていないのです。 どうしたら期待通り動いてくれるでしょうか? setTimeout関数を使って、1秒間待ってから出力してみましょう。 import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) //hoge.txtにはhogeと書いてあります setTimeout(()=>console.log(fileData), 1000) //1秒間待ってからconsole.log(fileData)をします このコードはちゃんとhogeを出力してくれます。1秒間待っているので、その間にファイル読み込みの処理が終了しているんですね。(実際は1秒よりずっと早くファイル読み込みは終了します) これは自分がハマったところなんですが、setTimeout関数の第一引数は関数です。つまり、 x setTimeout(console.log(fileData), 1000) o setTimeout(()=>console.log(fileData), 1000) です。 ちなみに、setTimeout関数中の()=>console.log(fileData)のように、一定時間経ってから呼び出される関数のことをコールバック関数と呼びます。(コールバック関数は他の関数に引数として渡す関数、という説明がほとんどですが、非同期処理の文脈では、一定時間経ってから呼び出される、という解釈の方が、コールバックという言葉にも合っているかなあと思っています...) もっと複雑な例を考えてみましょう。ファイルが3つあって、そのファイルに書かれているテキストを連結して出力する、という処理を考えます。 さっきと同様、同期処理(処理が1個1個順番に行われる)のお気持ちだと、コードは以下のようになります。 import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) //hoge.txtにはhogeと書いてあります fs.readFile('fuga.txt', 'utf-8', (err, data)=>{fileData+=data}) //fuga.txtにはfugaと書いてあります fs.readFile('piyo.txt', 'utf-8', (err, data)=>{fileData+=data}) //piyo.txtにはpiyoと書いてあります console.log(fileData) hogefugapiyoと出力して欲しいのですが、このコードの出力結果はもうお分かりの通り、undefinedとなってしまいます。では、最後の出力をsetTimeout関数を使って書き換えてみましょう。 import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) //hoge.txtにはhogeと書いてあります fs.readFile('fuga.txt', 'utf-8', (err, data)=>{fileData+=data}) //fuga.txtにはfugaと書いてあります fs.readFile('piyo.txt', 'utf-8', (err, data)=>{fileData+=data}) //piyo.txtにはpiyoと書いてあります setTimeout(()=>console.log(fileData), 1000) なんと今度は出力結果がhogepiyofugaとなってしまいました! (hogefugapiyoとなる時もあります) これはどうしてかというと、hoge.txtを読み込む非同期処理、fuga.txtを読み込む非同期処理、piyo.txtを読み込む非同期処理がほぼ同時にスタートしていて、どの非同期処理から終了するかが分からないからです。hogepiyofugaが出力された場合、piyo.txtを読み込む非同期処理が、fuga.txtを読み込む非同期処理よりも早く終わってしまったということになります。 では、hoge.txtの読み込みが開始してから1秒後に、fuga.txtの読み込みを開始して、さらにその1秒後にpiyo.txtの読み込みを開始して、さらにその1秒後に結果を出力、というコードを書いてみましょう。 import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) setTimeout(()=>{ fs.readFile('fuga.txt', 'utf-8', (err, data)=>{fileData+=data}) setTimeout(()=>{ fs.readFile('piyo.txt', 'utf-8', (err, data)=>{fileData+=data}) setTimeout(()=>{ console.log(fileData) }, 1000) }, 1000) }, 1000) このコードであれば、hogefugapiyoと正しく出力されます。ですが、このコード読みやすいでしょうか...? 非同期処理を記述するたびに、インデントが深くなってしまいます。これをコールバック地獄と言います。このように、setTimeoutで非同期処理の流れを制御するのには無理がありそうです。これを解決するために、考え出されたのがPromiseです。 Promise まず、Promiseの説明をしたいと思います。その後で、Promiseを使って、前述のコールバック地獄がどう解決できるのかを見ます。 Promiseは非同期処理の状態を持つオブジェクトです。 といっても何のことか分からないので、とりあえず、Promiseオブジェクトの作成から見ていきましょう! Promiseオブジェクトは例えば以下のように作成できます。 new Promise(resolve=>{ //非同期処理 //非同期処理が正常に終了したらresolveを呼ぶ }) resolve=>{...}というのは高階関数(関数を引数とする関数)になっています。なんで高階関数で初期化するんだろう...とか考えたのですが、よく分かりませんでした! とにかくこういう書き方をする!って覚えた方が良いかもしれません。 hoge.txtを読み込むPromiseオブジェクトは以下のようになります。 new Promise(resolve=>{ fs.readFile('hoge.txt', 'utf8', (err, data)=>resolve(data)) }) 先程、Promiseは非同期処理の状態を持つオブジェクトと言いました。この非同期処理の状態には3つあります。 ・pending : 初期状態 処理が成功も失敗もしていない状態 ・fullfilled : 処理が成功した状態 ・rejected : 処理が失敗した状態 Promiseオブジェクトの作成時点ではpendingです。resolveを呼ぶと、pendingからfullfilledになります。(rejectedについては後で説明します) hoge.txtの読み込み結果を出力する先程のプログラム import fs from 'fs' let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) //hoge.txtにはhogeと書いてあります console.log(fileData) これをPromiseを使って書き換えると、以下のようになります。 import fs from 'fs' new Promise(resolve=>{ fs.readFile('hoge.txt', 'utf-8', (err, data)=>resolve(data)) }).then((fileData)=>console.log(fileData)) このコードはちゃんと、hogeを出力してくれます。 ちょっと読みにくいので、Promiseオブジェクトを返す関数を定義してみましょう。 import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>resolve(data)) }) } fileRead('hoge.txt').then((fileData)=>console.log(fileData)) thenというのは、Promiseオブジェクトが持っているメソッドです。thenの中にはPromiseオブジェクトがfullfilledになった時に呼ぶ関数を書きます。 また、resolveに値を渡すと、その値をthenの中に記述した関数の引数にすることができます。この例で言うと、resolve(data)のdataが、関数(fileData)=>console.log(fileData)の引数になっています。 resolveに渡すのは変数じゃなくても良くて、 import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>resolve('ファイル読み込み終了')) }) } fileRead('hoge.txt').then((message)=>console.log(message)) このようにすると、ファイル読み込み終了という文字列が、関数(message)=>console.log(message)の引数になります。 では次に、非同期処理に失敗した時の処理を考えてみましょう。 ファイル読み込みの例で言うと、指定したファイルが存在しない時や、ファイルから取り出したテキストに不正な文字が入っていた時のことを考えます。非同期処理に失敗することを考慮する場合、Promiseオブジェクトは以下のように作成します。 new Promise((resolve, reject)=>{ //非同期処理 //非同期処理が正常に終了したらresolveを呼ぶ 失敗したらrejectを呼ぶ }) rejectを加えると、hoge.txtを読み込むPromiseオブジェクトは以下のようになります。 new Promise((resolve, reject)=>{ fs.readFile(path, 'utf8', (err, data)=>{ if (err) reject('ファイルが見つかりません') else resolve(data) }) }) resolveはPromiseオブジェクトの状態をpendingからfullfilledにするのでした。rejectはPromiseオブジェクトの状態をpendingからrejectedにします。Promiseオブジェクトの状態がrejectedになった時の処理は、thenメソッドの第2引数に書きます。 import fs from 'fs' function fileRead(path) { return new Promise((resolve, reject)=>{ fs.readFile(path, 'utf8', (err, data)=>{ if (err) reject('ファイルが見つかりません') else resolve(data) }) }) } fileRead('hogehoge.txt').then((fileData)=>console.log(fileData), (errorMessage)=>console.log(errorMessage)) 今回はhoge.txtではなく、存在しないhogehoge.txtを読み込むようにしています。こうすると、rejectが実行されます。rejectに渡しているファイルが見つかりませんという文字列が、thenの第二引数である、関数(errorMessage)=>console.log(errorMessage)の引数になります。 また、.then(func1, func2)という記述は、.then(func1).catch(func2)と書くこともできます。 import fs from 'fs' function fileRead(path) { return new Promise((resolve, reject)=>{ fs.readFile(path, 'utf8', (err, data)=>{ if (err) reject('ファイルが見つかりません') else resolve(data) }) }) } fileRead('hogehoge.txt') .then((fileData)=>console.log(fileData)) .catch((errorMessage)=>console.log(errorMessage)) こっちの方が読みやすいですね! 以上がPromiseの大まかな説明になります。 では、前に見た、3つのファイルを読み込んで、書かれているテキストを連結して出力する、という処理をPromiseを使って書いてみましょう。(簡単のためにresolveだけ考えます)これにはPromiseチェーンと呼ばれるものを使います。 import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>resolve(data)) }) } let fileData fileRead('hoge.txt').then((data)=>{ fileData = data return fileRead('fuga.txt') }).then((data)=>{ fileData += data return fileRead('piyo.txt') }).then((data)=>{ fileData += data console.log(fileData) }) thenが連続で呼ばれていますね。 まず1個目のthenはfileRead('hoge.txt')で返されるPromiseオブジェクトが実行するメソッドです。2個目のthenは1個目のthen内に書かれたfileRead('fuga.txt')で返されるPromiseオブジェクトが実行するメソッドです。3個目のthenも同様です。 このように、then内の関数でPromiseオブジェクトを返すようにすることで、非同期処理を連続して書けるようになります。 setTimeoutで非同期処理を制御して書くと、 let fileData fs.readFile('hoge.txt', 'utf-8', (err, data)=>{fileData=data}) setTimeout(()=>{ fs.readFile('fuga.txt', 'utf-8', (err, data)=>{fileData+=data}) setTimeout(()=>{ fs.readFile('piyo.txt', 'utf-8', (err, data)=>{fileData+=data}) setTimeout(()=>{ console.log(fileData) }, 1000) }, 1000) }, 1000) このように、コールバック地獄という、インデントが深くなっていく問題がありました。でも、今回Promiseを使ったことにより、 let fileData fileRead('hoge.txt').then((data)=>{ fileData = data return fileRead('fuga.txt') }).then((data)=>{ fileData += data return fileRead('piyo.txt') }).then((data)=>{ fileData += data console.log(fileData) }) このように、スッキリ書けるようになりました。これなら、書きやすいし、読みやすいですね! Promise万歳!! async, await async, awaitを使うと、Promiseチェーンをもっと簡潔に書くことができます。まず、非同期処理を順番に実行する関数を定義します。この時、asyncという修飾子をつけます。 async function func() { //非同期処理を順番に実行 } asyncをつけた関数の中では、await <Promiseオブジェクト>とすることで、非同期処理を順番に実行できます! import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>{resolve(data)}) }) } async function fileReads() { let fileData fileData = await fileRead('hoge.txt') fileData += await fileRead('fuga.txt') fileData += await fileRead('piyo.txt') console.log(fileData) } fileReads() 同期処理のように記述することができました! resolveに渡された値がawaitの戻り値になっていますね。 ここで気をつけたいのが、asyncをつけた関数の戻り値はPromiseオブジェクトになるという点です。初め、自分は上のコードを、 import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>{resolve(data)}) }) } async function fileReads() { let fileData fileData = await fileRead('hoge.txt') fileData += await fileRead('fuga.txt') fileData += await fileRead('piyo.txt') return fileData } console.log(fileReads()) このように書いていたのですが、出力結果はhogefugapiyoではなく、Promise { <pending> }になってしまいます。なので、thenメソッドを使って値を取り出す必要があります。 import fs from 'fs' function fileRead(path) { return new Promise(resolve=>{ fs.readFile(path, 'utf8', (err, data)=>{resolve(data)}) }) } async function fileReads() { let fileData fileData = await fileRead('hoge.txt') fileData += await fileRead('fuga.txt') fileData += await fileRead('piyo.txt') return fileData } fileReads().then((fileData)=>console.log(fileData)) おわりに 非同期処理についての自分の理解をまとめてみました。まだ分かっていないところもたくさんあるのですが、基本は抑えられたかなと思っています。誤り等ございましたら、ご指摘いただけると幸いです。 参考 ・あんどうやすし著 ハンズオン JavaScript ・JavaScriptの非同期処理を理解する その1 〜コールバック編〜 https://knowledge.sakura.ad.jp/24888/ ・小学生でもわかるasync/await/Promise入門【JavaScript講座】 https://www.youtube.com/watch?v=kbKIENQKuxQ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】0~2のランダムな数値を3つ取得し、「それぞれの数値」と「最も数値が大きい変数」の情報を表示する。※「最も数値が大きい変数」が複数ある場合は複数表示

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>if&amp;論理演算子</title> </head> <body> <script> // var rand1 = Math.floor(Math.random() * 3); // var rand2 = Math.floor(Math.random() * 3); // var rand3 = Math.floor(Math.random() * 3); var rand1 = 11; var rand2 = 12; var rand3 = 11; // 出た数字 document.write('<p>rand1: ' + rand1 + '</p>'); document.write('<p>rand2: ' + rand2 + '</p>'); document.write('<p>rand3: ' + rand3 + '</p>'); // ----------------------------------------------------------------- //3つがすべて同じ値か? // ----------------------------------------------------------------- if ( rand1 === rand2 && rand2 === rand3 ) { document.write('<p>3つは同じ値です</p>'); // ----------------------------------------------------------------- // rand1とrand2が同じ値か? // ----------------------------------------------------------------- } else if ( rand1 === rand2 ){ if ( rand2 >rand3) { document.write('<p>一番値が大きいのはrand1とrand2です</p>'); }else { document.write('<p>一番値が大きいのはrand3です</p>'); } // ----------------------------------------------------------------- // rand1とrand3が同じ値か? // ----------------------------------------------------------------- } else if (rand1 === rand3) { if ( rand3 >rand2) { document.write('<p>一番値が大きいのはrand1とrand3です</p>'); }else { document.write('<p>一番値が大きいのはrand2です</p>'); } // ----------------------------------------------------------------- // rand2とrand3が同じ値か? // ----------------------------------------------------------------- } else if (rand2 === rand3) { if ( rand3 >rand1) { document.write('<p>一番値が大きいのはrand2とrand3です</p>'); }else { document.write('<p>一番値が大きいのはrand1です</p>'); } // ----------------------------------------------------------------- // rand1が一番大きい値か? // ----------------------------------------------------------------- } else if ( rand1 > rand2 && rand1 > rand3 ) { document.write('<p>一番値が大きいのはrand1です</p>'); // ----------------------------------------------------------------- // rand2が一番大きい値か? // ----------------------------------------------------------------- } else if ( rand2 > rand3 && rand2 > rand1 ) { document.write('<p>一番値が大きいのはrand2です</p>'); // ----------------------------------------------------------------- // rand3が一番大きい値か? // ----------------------------------------------------------------- } else if ( rand3 > rand2 && rand3 > rand1 ) { document.write('<p>一番値が大きいのはrand3です</p>'); // ----------------------------------------------------------------- } </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Promiseをもうちょっと理解する

はじめに PromiseとはES2015で導入された機能(オブジェクト)で、Promiseを使う事で非同期処理の取り扱いが容易になります。有名どころではaxiosもPromiseで出来ており、サーバとのHTTP通信を非同期処理で行いますが、使えるけど何をやっているのかいまいちわからない、というモヤモヤした部分がずっとありました。 このモヤモヤを個人的に払拭するために記事を作成しました。Promiseを理解には「非同期処理」と「コールバック」について理解する必要があると思いますので、そこから始めます。 非同期処理とは 物事を順番にこなすことを同期処理と言います。例えば豆腐とネギの味噌汁を作る場合、 1. 豆腐を切る(1分) 2. ネギを切る(2分) 3. お湯を沸かす(3分) 4. 出汁をとる(2分) 5. 豆腐とネギをお湯で煮る(3分) 6. 味噌を溶かす(2分) の工程があった時に、1→2→3→4→5→6と一つずつ順番に行うと、13分かかる計算になりますが、1,2と3,4を並行して行うと所要時間は10分で済みます。 この場合、豆腐とネギを切っている合間に行うお湯を沸かす工程が非同期処理になります。 コールバックとは コールバックとは非同期処理の完了後に実行する関数で、お湯が沸いたあと出汁をとる部分がコールバックに該当します。 ここまでのところをコードで表現すると、以下のようになります。 boilWaterはお湯を沸かす関数、makeSoupは出汁をとるコールバック関数です。8行目で引数として渡したmakeSoupをboilWater関数内の処理完了後に呼び出しています。 cooking.js const boilWater = (callback) => { console.log('3.お湯を沸かす') setTimeout(() => {callback()},1000) } const makeSoup = () => { console.log('4.煮干しで出汁をとる(コールバック)') } boilWater(makeSoup) console.log('1.豆腐を切る') console.log('2.ネギを切る') 実行結果は以下の通りです。実行順序に注目してください。 実行結果 3.お湯を沸かす 1.豆腐を切る 2.ネギを切る 4.煮干しで出汁をとる(コールバック) Promiseで書き換えてみる 上記のコードはPromiseを用いて書き換えることが可能です。試しに書き換えてみます。 cooking.js const boilWater = new Promise((resolve, reject) => { console.log('3.お湯を沸かす') setTimeout(() => {resolve()},1000) }) const makeSoup = () => { console.log('4.煮干しで出汁をとる(コールバック)') } boilWater.then(makeSoup) console.log('1.豆腐を切る') console.log('2.ネギを切る') 変更箇所は以下2点で、実行結果は同じになります。 boilWaterをPromiseインスタンスで定義している コールバック(makeSoup)をthenメソッドの引数として渡している Promiseインスタンス内の引数であるresolve/rejectはそれぞれインスタンス内に記述した処理(console.log('3.お湯を沸かす'))が成功/失敗した場合のコールバックになります。 また.thenはコールバックを渡すためのメソッドで.thenを使う事でPromiseチェーン(後述)という記述ができるようになります。これはPromiseならではの仕様で、Promiseを使うメリットの1つです。 Rejectを使う console.logは失敗しないので成功パターンしか記述していませんでしたが、コードを少し書き換えて失敗パターンを追加してみます。 失敗パターンとして、沸騰させていたお湯が途中で吹きこぼれるケースを考えます。お湯の温度が上がりすぎて吹きこぼしてしまった場合、吹きこぼしを雑巾で拭かないといけませんね。 cleanSpills(吹きこぼしを拭く)で非同期処理失敗時に呼び出されるコールバック関数を定義し、.catchメソッドを使ってPromiseに失敗時のコールバック関数を渡します。 cooking.js const boilWater = new Promise((resolve, reject) => { console.log('3.お湯を沸かす') const rand = Math.random() if(rand < 0.5){ // 処理成功 setTimeout(() => {resolve()},1000) } else { // 処理失敗 setTimeout(() => {reject()},1000) } }) const makeSoup = () => { console.log('4.煮干しで出汁をとる(成功時のコールバック)') } const cleanSpills = () => { console.log('吹きこぼしを掃除する(失敗時のコールバック)') } boilWater.then(makeSoup).catch(cleanSpills) console.log('1.豆腐を切る') console.log('2.ネギを切る') 実行結果 ///処理成功 3.お湯を沸かす 1.豆腐を切る 2.ネギを切る 4.煮干しで出汁をとる(成功時のコールバック) ///処理失敗 3.お湯を沸かす 1.豆腐を切る 2.ネギを切る 吹きこぼしを掃除する(失敗時のコールバック) ここでcooking.jsの21行目に注目してください。 boilWater.then(makeSoup).catch(cleanSpills) .thenと.catchを数珠つなぎに記述しています。このような書き方ができるのは、.thenは戻り値として新しいPromiseインスタンスを返すためです。同じ理由で.catchの後に.thenを続けることもできます。これらもPromiseならではの仕様と言えます。 Promiseチェーン 上記の通り.thenは戻り値として新しいPromiseを生成するため、数珠つなぎにすることで複数の非同期処理を順番に処理させることが可能です。これをPromiseチェーンと言います。 豆腐を切る(1分) ネギを切る(2分) お湯を沸かす(3分) 出汁をとる(2分) 豆腐とネギをお湯で煮る(3分) 味噌を溶かす(2分) ここで1~6の処理をPromiseで定義し直すことで、以下のようなシナリオをPromiseチェーンで順序立てて実行することができます。 1,2と3を並列で実行する 両方の処理が完了したら、4→5→6の順番に処理を行う 3~6のいずれかの処理の途中で失敗(吹きこぼし)したら、処理を止めて吹きこぼしを掃除する。 cooking.js // 3. お湯を沸かす const boilWater = new Promise((resolve, reject) => { console.log('3.お湯を沸かす') const rand = Math.random() if(rand < 0.9){ // 処理成功 setTimeout(() => { resolve() },1000) } else { // 処理失敗 setTimeout(() => {reject()},1000) } }) // 1. 豆腐を切る + 2. ネギを切る const cutFoods = new Promise((resolve, reject) => { console.log('1.豆腐を切る') console.log('2.ネギを切る') setTimeout(() => {resolve()} ,1000) }) // 4. 出汁をとる const makeSoup = () => { return new Promise((resolve, reject) => { console.log('4.煮干しで出汁をとる') const rand = Math.random() if(rand < 0.9){ // 処理成功 setTimeout(() => { resolve() },1000) } else { // 処理失敗 setTimeout(() => {reject()},1000) } }) } // 5. 豆腐とネギをお湯で煮る const boilFoods = () => { return new Promise((resolve, reject) => { console.log('5.豆腐とネギをお湯で煮る') const rand = Math.random() if(rand < 0.9){ // 処理成功 setTimeout(() => { resolve() },1000) } else { // 処理失敗 setTimeout(() => {reject()},1000) } }) } // 6.味噌を溶かす const dissolveMiso = () => { return new Promise((resolve, reject) => { console.log('6.味噌を溶かす') const rand = Math.random() if(rand < 0.9){ // 処理成功 setTimeout(() => { resolve() },1000) } else { // 処理失敗 setTimeout(() => {reject()},1000) } }) } // 吹きこぼしを掃除する(失敗時のコールバック) const cleanSpills = () => { console.log('吹きこぼしを掃除する(失敗時のコールバック)') } Promise.all([cutFoods,boilWater]).then(() => { return makeSoup() }).then(() => { return boilFoods() }).then(() => { return dissolveMiso() }).catch(cleanSpills) 実行結果 3.お湯を沸かす 1.豆腐を切る 2.ネギを切る 4.煮干しで出汁をとる 5.豆腐とネギをお湯で煮る 6.味噌を溶かす cooking.jsの最終行について補足します。 Promise.all([cutFoods,boilWater]).then(() => { return makeSoup() }).then(() => { return boilFoods() }).then(() => { return dissolveMiso() }).catch(cleanSpills) 冒頭のPromise.all()はPromiseの静的メソッドで引数で渡した配列内のPromiseの完了を待ちます。複数のPromise完了を待ち受ける時に便利です。両方の処理が成功した場合は.then、いずれかの処理が失敗した場合には.catchが呼び出されます。 ここでは1,2と3を並行で処理して、両方が完了したら次の処理(4)へ移行という部分を担っています。 また.thenのコールバックにPromiseをreturnする関数を無名関数で渡すことで、それぞれ個別に定義したPromise同士を繋げて処理を作ることができます。 以上 参考文献 MDN JavaScript Primer
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptで型推論やりすぎな足し算をする関数を作ってみた

こんにちは。最近TypeScriptの型レベルプログラミングにハマっています。 今回実装する「型推論やりすぎ足し算」をする関数はこちらです。 既出だったらごめんなさい。 (画像が読み込めない時用のコード) // 実装省略 declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>; const a = add(12, 34); // testの型が46になる a の型が 46 になり、型レベルでも足し算をして、その結果を返します。 Add<T, U> という型は用意されていないので、これを工夫して作ります。 TypeScriptには、リテラル型というのがあり、 "hello" や 10 、 true などの値も型となりえます。 このようにして、型レベルでも足し算を行うのが、この「やりすぎ」関数となっています。 ちなみに、普通にジェネリクスを使って実装しても、返り値は number となってしまいます。 function add2<T extends number, U extends number>(a: T, b: U) { return a + b; } const b = add2(12, 34); // 型はnumber この a + b という演算が返す型はリテラル型ではなく number にされてしまうのですね。 使用したTypeScriptのバージョン TypeScript 4.2.3 型レベルプログラミングは、TypeScriptのバージョンの違いをもろに受けるので、気を付けてください。 足し算部分の実装 まず、型 Add<T, U> の実装ですが、ここでは簡単に載せます。 type Repeat<T extends number, R extends any[] = []> = R["length"] extends T ? R : Repeat<T, [any, ...R]>; type Add<T extends number, U extends number> = [...Repeat<T>, ...Repeat<U>]["length"]; Repeatで任意長(ただし再帰制限のため45程度まで)のTupleを作って、それを用いて足し算します。Tupleの長さが数値リテラル型で返ってくることから、それを足し算結果とします。 え、どうしてわざわざTupleを用いているの?などの背景の説明については、 ブログの記事で詳しく説明したので、そちらをご覧ください。 参考はいつもお世話になっているこのサイトのページです。 なお、再帰制限を回避してもっと大きな数まで扱いたい場合は、 Multiple<1, ...> を用いる手があります。内部的に文字列操作を行っているのです。この実装はややこしく、ちょっとテーマとそれるので、先ほどの記事を参照してください。 関数部分の実装 後は、これを関数の定義に含めれば完成です。 declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>; T,Uは実は引数に応じてリテラル型に推論してくれるみたいです。助かった。 これで、冒頭の関数が実装できました。 実装を含める場合は、次のようにします。 a + b は number 型となってしまうので、 as any as が必要です。 function add<T extends number, U extends number>(a: T, b: U): Add<T, U> { return a + b as any as Add<T, U>; } まとめ 型レベルプログラミングが、実際のプログラミングにも役に立ってくるともっと楽しいですね。 この関数の使いどころがあるかどうかはわかりませんがw 今回のプログラムは、こちらで試すことができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ToDoリスト

概要 JSでToDoリストを作成しました。 inputに入力された値を読み取り、HTMLに反映。 完了タスクを削除するまでが一連の流れとなります。 HTML index.html <main> <div class="title">リスト</div> <div class="todo-list"> <ul> </ul> </div> </main> <section> <input class="form" type="text"> <button class="btn">追加</button> </section> HTMLは上記になります。 今回追加するToDoタスクはul要素に追加していきます。 JS main.js const btn = document.querySelector('.btn'); btn.addEventListener('click',() =>{ const ul = document.querySelector('ul'); const li = document.createElement('li'); const input = document.querySelector('.form'); li.textContent = input.value; ul.appendChild(li); input.value = ''; const span = document.createElement('span'); span.textContent = '✔'; span.classList.add('span'); li.appendChild(span); span.onclick = function() { const listen = confirm('削除しますか'); if(listen === true) { ul.removeChild(li); } } }); 1,イベント要素の取得 今回のイベントはボタンをクリックしたときになります。button要素をquerySelectorで取得して、定数btnに保存します。 要素を取得後、addEventListenerでイベントを登録します。 これでイベントを取得することが出来たので、処理を書いていきます。 2,親要素の取得と要素の追加 今回ToDoリストを追加するのはliタグです。liタグの親要素はulになります。なので、ulタグを取得します。 親要素取得後、liタグをcreateElementで生成します。これで空のliタグが作られました。 要素が無いので、取得します。ToDoリストにはinputタグからの値を取得します。 定数inputにquerySelectorでクラスformを取得。 空のli.textContentにinput.valueを代入します。valueで値を読み込みます。 定数ulに定数liをapendChildします。 追加後、input要素を空にするためinput.valueに「''」を代入します。 上記の流れで、ToDoリストを追加することが出来ました。次に、完了したタスクを削除できるよう実装します。 3,JSで生成された要素の削除 完了タスクを削除する実装です。 まず。liタグに「✔」を追加するためにspanタグを生成します。上記と同様にcreateElementを使用します。要素の追加も上記の通りです。 先ほどと同様の手順で要素を生成すると、タスク追加時にタスクの右隣に✔がついている段階です。 目的としては、✔をクリックしたときに削除することです。 イベントを登録します。 main.js const spanBtn = querySelector('span'); spanBtn.addEventListener('click',()=>{ console.log(spanBtn); }); 上記のように記述すると spanBtn is not defined とerrorが出ます。DOM操作では、直接HTMLを操作しているわけではありません。したがって、HTML上にはspanタグはありません。 上記のようなerrorが発せられると言うわけです。 そこで下記のように記述します。 main.js span.onclick = function() { const listen = confirm('削除しますか'); if(listen === true) { ul.removeChild(li); } } //定数名.イベント =関数; そして削除する前に条件分岐でユーザーに確認します。 trueであれば親要素ulから子要素liを削除します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラス・オブジェクト指向について

オブジェクト指向 オブジェクト指向を言葉で説明するのは非常に難しく、色々なサイトで様々な説明をされていますが、簡単に行ってしまえば、オブジェクトとはモノという意味で、何でも入れられる箱という考え方もされます。 オブジェクトというデータの入れ物があり、その中には様々なデータが入ります。 オブジェクトにはどんなデータも入れることができます。 数字やテキスト、配列はもちろん、メソッドなどの機能も入れることができます。 オブジェクト const userData = { name:'taro', age:30, } クラスとは クラスの概念は多くのオブジェクト指向のプログラム言語で共通で使える概念なので、覚えてしまえば多くの言語で応用が可能です。 クラスは、簡単に言えばモノの設計図です。 オブジェクトはモノと言いましたが、クラスはモノの設計図という考え方です。 オブジェクト = モノ クラス = モノの設計図 クラス class Users { constructor(name, age) { this.name = name; this.age = age; } text(){ return '私の名前は'+this.name+'です。今年で'+this.age+'歳になります。'; } } インスタンス化 クラス(設計図)を元に実際にオブジェクト(モノ)を生み出すことをインスタンス化といいます。 newを使ってクラスを呼び出すことでインスタンス化を行います。 少しややこしいですが、一旦下のようなやり方で実際のものを作り、「user」という変数に格納すると思ってください。 インスタンス化 const user = new Users('太郎', 22); コンストラクタについて コンストラクタとは、クラスのインスタンスを作るときに必ず実行される処理です。 よく使われるのは引数をクラスの中の変数やオブジェクトに代入したりするのに使います。 また、クラスの中で、メソッドを複数定義して、それを呼び出しても同じことができます。 インスタンス化するときに定義した引数はコンストラクタで受け取ることができます。 一例 class Users { constructor(name, age) { this.name = name; this.age = age; } text(){ return '私の名前は'+this.name+'です。今年で'+this.age+'歳になります。'; } } var user = new Users('太郎', 22); console.log( user.name ); console.log( user.age ); console.log( user.text()); //結果 //太郎 //22 //私の名前は太郎です。今年で22歳になります。 クラスの中のthisとは クラスの中で使われている「this」とは何か。 この場合のthisはクラスそのものを指します。 上記のコンストラクタで、「this.name」というクラスの変数の中にデータを入れて、クラス内で使うことができるようになります。 クラスを使うメリット クラスを使うメリットとしては、いつでも何回でも再利用が可能ということです。 また、クラスはコンストラクタ以外のメソッドは、メソッド自体を呼び出さないと起動しないため、使いたい動きと使いたくない動きを分けて使うことができます。 クラスの作り方 javascriptを例にクラスの作り方を説明します。 基本的にはPHPや他の言語でもクラスの構成はあまり変わりません。 クラスの宣言 クラスを使用するときに気をつけることは、必ずクラスを使用する前に、クラス宣言を行うことが必要となります。 クラスの宣言 class Users { // クラスの内容 } コンストラクタの定義 コンストラクタは引数を取ることができます。 引数はインスタンス化するときに渡される引数が、そのまま引き渡されます。 それをクラス内のオブジェクトに格納することができます。 ここではnameとageという変数を用意してそれぞれ格納します。 その際にthisをつけるのを忘れないようにしましょう。 thisは「クラス自体」を参照します。 つまりこのクラスの中の変数nameとageを示し、このクラス内ではどこでも使うことができます。 コンストラクタ class Users { constructor(name, age) { this.name = name; this.age = age; } } メソッドの定義 次にテキストを返すメソッドを定義します。 コンストラクタで定義した変数を使って自己紹介の文章を作成して、returnで返します。 これでインスタンス化したオブジェクトから、このtextというメソッドを呼び出して使うことができます。 使うときは「インスタンスオブジェクト名.メソッド名()」の形で使いましょう。 メソッド class Users { constructor(name, age) { this.name = name; this.age = age; } text(){ return '私の名前は'+this.name+'です。今年で'+this.age+'歳になります。'; } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

任意の色相に対して最低コントラストを満たす色を計算する

この記事は、Webデザインでユーザに任意のテーマカラーを選択させるときなどを想定しています。 一般に言う「色の種類」とは、HLS色空間やHSV色空間の「色相」によって決定されます。 色相は、上の画像を見ていただけると分かる通り、0-360の数値で表すことができます。 HLS色空間では、「色相、彩度、輝度」によって色が決定されるので、彩度と輝度を固定して、色相のみをユーザに選択させればいいと考えることができます。 それを実装したものが以下のGIFになります。 しかし、実際に見てみると、赤〜青にかけては文字が見えやすい一方で、黄〜緑では文字が見えづらいことがわかります。 これは、背景色(#fff)とのコントラストが低くなることが原因です。なぜ色によってコントラストが変化するかというと、色の相対輝度にはRGBそれぞれについて重みがあり、白とコントラストが高いのは青 > 赤 > 緑です。黄色は赤と緑の中間色なので、上記の画像で黄〜緑の文字が見えづらいことにも納得できます。 相対輝度(L)の定義 {\displaystyle L=0.2126\times R+0.7152\times G+0.0722\times B} 詳しくはWIkipedia Help:配色のコントラスト比が参考になります。 では、先ほどのように彩度と輝度を固定して色相のみを変化させるのではなく、彩度のみを固定し、指定された色相に対して一定のコントラストを満たす輝度を求めてみましょう。 赤〜青は最初とほとんど変わりませんが、黄〜緑が見えやすくなったのが分かります。 JavaScriptの関数は以下の通りです。(chroma-jsという色操作が簡単にできるJavaScriptのライブラリを使用しています。) // 背景色、色相、彩度、満たしたいコントラスト比を渡すと、それを満たす色(16進数)を返す const getChromaFromHue = ( baseColor: string, hue: number, saturation: number, minContrast: number ): chroma.Color => { const BC = chroma(baseColor) let left = 0 let right = 1 let count = 0 while (right - left > 0.01) { const middle = Math.floor(((left + right) / 2) * 100) / 100 const color = chroma(hue, saturation, middle, 'hsl') if (chroma.contrast(BC, color) > minContrast) { left = middle } else { right = middle } if (count > 5) break count += 1 } return chroma(hue, saturation, left, 'hsl').hex() } const changeThemeColor = (hue: number) => { setThemeColor({ theme: getChromaFromHue('#fff', hue, 0.7, 1.7), themeAA: getChromaFromHue('#fff', hue, 0.5, 4.5), themeAAA: getChromaFromHue('#fff', hue, 0.4, 7), themeLight: getChromaFromHue('#fff', hue, 0.5, 1.05), }) } 関数内の処理としては、指定されたコントラストを満たす最高の輝度を二分探索で求めています。 (二分探索せずに求める式があれば教えてください) ここで、コントラスト比 > 4.5を満たすものはアクセシビリティ適合レベルAA、コントラスト比 > 7.0を満たすものはアクセシビリティ適合レベルAAAです。文字色のコントラストはAA以上は満たすか、テーマをAA以上に変更するオプションなどは用意するといいでしょう。 AAを満たすボタンを設置した例
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Svelte-kitでSPAモードを有効にする

環境 svelte-kit: 1.0.0-next.99 adapter-static: 1.0.0-next.8 TL;DR adapter-staticを使います。 import preprocess from 'svelte-preprocess'; import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ export default { // Consult https://github.com/sveltejs/svelte-preprocess // for more information about preprocessors preprocess: preprocess(), kit: { // hydrate the <div id="svelte"> element in src/app.html target: '#svelte', adapter: adapter({ fallback: 'index.html', }), }, }; Svelte-kit Svelte向けのNext.js的なフレームワークは、これまではSapperなるものが主流だったようですが、Svelte-kitなるものに置き換えようと開発中のようです、今はベータ版という位置付け。 このSvelte-kitはデフォルトだとSSRになる訳ですが、シンプルにSPAでやりたい。しかし出始めだけあって情報がなかなかない。うまくいかなくても仕様なのか不具合なのかわからない(でもそれも面白い)。とりあえずStaticなビルドが出来るようになったのでメモしておきます。 Svelte-kitのadapter Svelte-kitにはadapterなる概念があり、ホスティング環境に応じてこのadapterを切り替えてビルドすれば、ソースは変えずに複数の環境に対応出来る、という事らしいです(サーバーに依存するSSRならではの対応でしょうか)。 adapterはVercelやNetlifyなどのホスティングサービスから、単純にNode.jsサーバー向けのものなど様々あり、そのなかにstaticなるadapterがあります。名前のとおり静的ホスティング向けのものです。 adapter-static
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】フィルタ後のデータを取得して貼り付け

前提 GAS(V8)を使用して、スプレッドシートのフィルタ後のデータを取得。その後、別シートに貼り付けする方法を備忘録として記載。 手順 /** * Rawシートの最終列に1がセットされているデータのみを抽出してDupliシート(新規作成)に貼り付ける。 */ function onlyDupliRaw() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const ssRaw = ss.getSheetByName('Raw'); let lastRow = ssRaw.getLastRow(); let lastCol = ssRaw.getLastColumn(); // フィルタを設定 let rule = SpreadsheetApp.newFilterCriteria() .whenNumberEqualTo(1) // 数値1と同値を抽出 .build(); ssRaw.getDataRange().createFilter().setColumnFilterCriteria(lastCol, rule); /** * 「Dupli」シートに「Raw」からフィルタ後のデータを張り付ける。 */ ss.insertSheet('Dupli'); const ssDupli = ss.getSheetByName('Dupli'); let spreadsheetId = ss.getId(); let sheetId = ssDupli.getSheetId(); // フィルタしたデータを「Dupli」シートにペースト let url = "https://docs.google.com/spreadsheets/d/" + spreadsheetId + "/gviz/tq?tqx=out:csv&gid=" + sheetId + "&access_token=" + ScriptApp.getOAuthToken(); // API使用のためのOAuth認証 let res = UrlFetchApp.fetch(url); // HTTPクライアント用のクラス let array = Utilities.parseCsv(res.getContentText()); // CSVテキストを二次元配列で取得 let lastFilterdCol = array[1].length; let lastFilterdRow = array.length; // 二次元配列arrayをDupliシートに貼り付け ssDupli.getRange(1 ,1 ,lastFilterdRow, lastFilterdCol).setValues(array); } 補足 isRowHiddenByFilter(rowPosition)メソッドを使用する方法もあるが、スピードの問題から断念。 https://developers.google.com/apps-script/reference/spreadsheet/sheet#isrowhiddenbyfilterrowposition 手順で紹介した方法が早い。 ※logtime測定済み。 参考 https://teratail.com/questions/220476 https://gist.github.com/tanaikech/053d3ebbe76fa7c0b5e80ea9d6396011
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Svelte-kitでSPAモードを有効にする方法

環境 svelte-kit: 1.0.0-next.94 adapter-static: 1.0.0-next.8 TL;DR adapter-staticを使いますが、現状のコードだとエラーになるので以下のとおり書くことで回避できます。 const preprocess = require('svelte-preprocess'); const adapter = function ({ pages = 'build', assets = pages, fallback = null, } = {}) { /** @type {import('@sveltejs/kit').Adapter} */ const adapter = { name: '@sveltejs/adapter-static', async adapt(utils) { utils.copy_static_files(assets); utils.copy_client_files(assets); await utils.prerender({ fallback, all: !fallback, dest: pages, }); }, }; return adapter; }; /** @type {import('@sveltejs/kit').Config} */ module.exports = { // Consult https://github.com/sveltejs/svelte-preprocess // for more information about preprocessors preprocess: preprocess(), kit: { // hydrate the <div id="svelte"> element in src/app.html target: '#svelte', adapter: adapter({ fallback: 'index.html', }), }, }; Svelte-kit Svelte向けのNext.js的なフレームワークは、これまではSapperなるものが主流だったようですが、Svelte-kitなるものに置き換えようと開発中のようです、今はベータ版という位置付け。 このSvelte-kitはデフォルトだとSSRになる訳ですが、シンプルにSPAでやりたい。しかし出始めだけあって情報がなかなかない。うまくいかなくても仕様なのか不具合なのかわからない(でもそれも面白い)。とりあえずStaticなビルドが出来るようになったのでメモしておきます。 Svelte-kitのadapter Svelte-kitにはadapterなる概念があり、ホスティング環境に応じてこのadapterを切り替えてビルドすれば、ソースは変えずに複数の環境に対応出来る、という事らしいです(サーバーに依存するSSRならではの対応でしょうか)。 adapterはVercelやNetlifyなどのホスティングサービスから、単純にNode.jsサーバー向けのものなど様々あり、そのなかにstaticなるadapterがあります。名前のとおり静的ホスティング向けのものです。 adapter-static これを使えばいいんですが、サンプルのままだとビルドできません。サンプルの記法はES-Moduleですが、svelte.config.cjsは拡張子のとおりCommon-JSだからです。かと言ってCommon-JS記法でimportしようとすると別のエラーで蹴られます(おそらくpackage.jsonの問題)。なので、adapter-static/index.jsでexportしている関数自体をconfigに書き込んだのが冒頭のコードです、これで期待通り動作します。 おまけ:ページ単位のprerender(SSG) svelte-kitでは、各コンポーネントファイルでprerender変数を宣言する事で、それぞれの単位でビルド方法を設定する事ができます。 <script context="module" lang="ts"> export const prerender = true; </script> adapter-staticを設定したうえで、上記のとおり宣言しておけば、これを記述したページはhtmlファイルが書き出されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あまりヒットしないjQuery、attrメソッドの使い方

jQuery学習中に検索であまり見かけないattrメソッドの使い方をしたのでメモ。 script.js var hoge = $($('セレクタ').attr('属性')); なるほど、という感じだがこのようにattr()の取得結果をさらに$()で囲ってあげることでその要素を指定できる。 余談 attrの意味 attrの読み方が分からずとりあえずアッターと呼んでいたが、せっかくなので元の意味を調べてみた。 属性の英語 attribution; attribute 参考:https://ejje.weblio.jp/content/属性 誰かにアッターメソッドとか言う前でよかった… これからはちゃんとアトリビュートと呼びます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む