- 投稿日:2022-02-01T22:12:18+09:00
HTML+JavaScript で Unicode 文字表
Unicode の何処にどんな文字が入っているのか確認するための HTML+JavaScript です。 静的な HTML ファイルを作ると、サイズが巨大になってしまうため、JavaScript で動的にテーブルを生成します。 以下、スクリーンショットです。 「更新」をクリック後にブラウザが暫く応答しないことがあります。 動作確認は Google Chrome バージョン: 97.0.4692.99(Official Build) (x86_64) で行っています。 viewer.html <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unicode 文字表</title> <style> table { border: solid 1px #ccc; border-collaspe: collaspe; border-spacing: 0; } th, td { border: solid 1px #ccc; padding: 6px; } th { background: #eee; } .left { text-align: left; } .center { text-align: center; } .right { text-align: right; } </style> </head> <body> <h1>Unicode 文字表</h1> <table> <tr> <th colspan="4">Basic Multilingual Plane</th> <th colspan="4">Supplementary Multilingual Plane</th> <th colspan="4">Other Plane</th> </tr> <tr> <td><label><input type="radio" name="alloc" value="0000-00FF" checked>ASCII & Latin-1 Compatibility</label></td> <td>0000</td><td>-</td><td>00FF</td> <td rowspan="2"><label><input type="radio" name="alloc" value="10000-107FF">General Script</label></td> <td rowspan="2">10000</td><td rowspan="2">-</td><td rowspan="2">107FF</td> <td><label><input type="radio" name="alloc" value="20000-2FFFF">Supplementary Ideographic</label></td> <td>020000</td><td>-</td><td>02FFFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="0100-058F">General Script</label></td> <td>0100</td><td>-</td><td>058F</td> <td><label><input type="radio" name="alloc" value="30000-3FFFF">Plane 3</label></td> <td>030000</td><td>-</td><td>03FFFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="0590-08FF">General Script (RTL)</label></td> <td>0590</td><td>-</td><td>08FF</td> <td><label><input type="radio" name="alloc" value="10800-10FFF">General Script (RTL)</label></td> <td>10800</td><td>-</td><td>10FFF</td> <td rowspan="8"><label><input type="radio" name="alloc" value="40000-DFFFF" disabled>Plane 4 〜 13</label></td> <td rowspan="8">040000</td><td rowspan="8">-</td><td rowspan="8">0DFFFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="0900-1FFF">General Script</label></td> <td>0900</td><td>-</td><td>1FFF</td> <td><label><input type="radio" name="alloc" value="11000-11FFF">General Script</label></td> <td>11000</td><td>-</td><td>11FFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="2000-2BFF">Punctuation & Symbols</label></td> <td>2000</td><td>-</td><td>2BFF</td> <td rowspan="4"><label><input type="radio" name="alloc" value="12000-15FFF">Cuneiform & Hieroglyphic</label></td> <td rowspan="4">12000</td><td rowspan="4">-</td><td rowspan="4">15FFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="2C00-2DFF">General Script</label></td> <td>2C00</td><td>-</td><td>2DFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="2E00-2E7F">Supplemental Punctuation</label></td> <td>2E00</td><td>-</td><td>2E7F</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="2E80-33FF">CJK Miscellaneous</label></td> <td>2E80</td><td>-</td><td>33FF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="3400-9FFF">CJKV Unified Ideographs</label></td> <td>3400</td><td>-</td><td>9FFF</td> <td><label><input type="radio" name="alloc" value="16000-16FFF">General Script</label></td> <td>16000</td><td>-</td><td>16FFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="A000-ABFF">General Script (Asia & Africa)</label></td> <td>A000</td><td>-</td><td>ABFF</td> <td><label><input type="radio" name="alloc" value="17000-1BBFF">Ideographic Script</label></td> <td>17000</td><td>-</td><td>1BBFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="AC00-D7FF">Hangul Syllabels</label></td> <td>AC00</td><td>-</td><td>D7FF</td> <td><label><input type="radio" name="alloc" value="1BC00-1CFFF">General Script</label></td> <td>1BC00</td><td>-</td><td>1CFFF</td> <td style="color: #f00;"><label><input type="radio" name="alloc" value="E0000-E0FFF" disabled>Plane 14 (Ignorable)</label></td> <td>0E0000</td><td>-</td><td>0E0FFF</td> </tr> <tr> <td style="color: #f00;"><label><input type="radio" name="alloc" value="D800-DFFF" disabled>Surrogate Codes</label></td> <td>D800</td><td>-</td><td>DFFF</td> <td><label><input type="radio" name="alloc" value="1D000-1E7FF">Symbols</label></td> <td>1D000</td><td>-</td><td>1E7FF</td> <td><label><input type="radio" name="alloc" value="E1000-EFFFF" disabled>Plane 14 (Reserved)</label></td> <td>0E1000</td><td>-</td><td>0EFFFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="E000-F8FF">Private Use</label></td> <td>E000</td><td>-</td><td>F8FF</td> <td><label><input type="radio" name="alloc" value="1E800-1EFFF">General Script (RTL)</label></td> <td>1E800</td><td>-</td><td>1EFFF</td> <td><label><input type="radio" name="alloc" value="F0000-FFFFF">Plane 15 (Private Use)</label></td> <td>0F0000</td><td>-</td><td>0FFFFF</td> </tr> <tr> <td><label><input type="radio" name="alloc" value="F900-FFFF">Compatibility & Specials</label></td> <td>F900</td><td>-</td><td>FFFF</td> <td><label><input type="radio" name="alloc" value="1F000-1FFFF">General Script</label></td> <td>1F000</td><td>-</td><td>1FFFF</td> <td><label><input type="radio" name="alloc" value="100000-10FFFF">Plane 16 (Private Use)</label></td> <td>100000</td><td>-</td><td>10FFFF</td> </tr> </table> <div> </div> <table> <tr> <th>幅</th> <td> <label><input type="radio" name="width" value="16" checked>16</label> <label><input type="radio" name="width" value="32">32</label> <label><input type="radio" name="width" value="64">64</label> <label><input type="radio" name="width" value="128">128</label> <label><input type="radio" name="width" value="256">256</label> </td> <th><input id="button" type="button" name="generate" value="更新" onclick="onUpdate();"></th> </table> <hr> <table id="ctab"> </table> <script type="text/javascript"> <!-- function setTable(start, end, width) { const table = document.getElementById('ctab'); if (false) { const tr = document.createElement('tr'); const th = document.createElement('th'); th.innerText = 'UTF-8'; th.setAttribute('colspan', (width + 1).toString()); tr.appendChild(th); table.appendChild(tr); } { const tr = document.createElement('tr'); tr.appendChild(document.createElement('th')); const s = ((width == 16) ? -1 : -2); for (let i = 0; i < width; i++) { const th = document.createElement('th'); th.innerText = ('00' + i.toString(16).toUpperCase()).slice(s); tr.appendChild(th); } table.appendChild(tr); } { for (let y = start; y < end; y += width) { const tr = document.createElement('tr'); const th = document.createElement('th'); th.innerText = ('000000' + y.toString(16).toUpperCase()).slice(-6); tr.appendChild(th); for (let x = 0; x < width; x++) { const td = document.createElement('td'); td.setAttribute('class', 'center'); const code = x + y; if (code < 0x10000) td.innerText = String.fromCharCode(code); else { const h = code >> 16; const l = code & 0xffff; const code1 = 0xd800 | ((h - 1) << 6) | (l >> 10); const code2 = 0xdc00 | (l & 0x3ff); td.innerText = String.fromCharCode(code1, code2); } tr.appendChild(td); } table.appendChild(tr); } } } function updateTable() { const inp_range = document.querySelector("input[name='alloc']:checked").value; const inp_width = document.querySelector("input[name='width']:checked").value; const [start, end] = inp_range.split('-').map((v) => Number('0x' + v)); const width = Number(inp_width); setTable(start, end + 1, width); } function onUpdate() { const table = document.getElementById('ctab'); while (table.firstChild != undefined) table.removeChild(table.firstChild); updateTable(); } onUpdate(); --> </script> </body> </html>
- 投稿日:2022-02-01T18:33:49+09:00
「JavaScript=非同期言語」は間違い?
1.基本は同期処理 「javascriptは非同期言語だ」「時間のかかる処理は後回しになる」と書かれた記事をいくつか目にしたことが有る。 自分も以前はそんな感じの認識を持っていた。 しかし、非同期言語かどうかはともかく、「時間のかかる処理は後回しになる」は明確に間違いだ。 基本的には時間のかかる処理であっても同期処理(上から順に処理)される。 例として以下のコードを見て欲しい。 console.log("while前") start = Date.now() while(true){ if( Date.now() - start > 3000){ console.log("3秒経過"); break; } } console.log("while後") //"while前" //"3秒経過" //"while後" 上記コードを実際に実行するとコンソールには以下の順で表示される。 ・while前 ・3秒経過 ・while後 3秒もかかるwhileの処理を待ってから、console.log("while終了後")が実行されていることがわかる。 もうひとつ似たような例を用意してみた。 console.log("for前") for(let i = 0;i <= 1000; i++){ if(i == 1000){console.log("1000")}; } console.log("for後") //"for前" //"1000" //"for後" こちらも同様で、コンソールに表示される順番は「for前」→「1000」→「for後」となる。 つまり、時間のかかる処理でも後回しにせずに同期的に(上から順に)処理していることがわかる。 2.setTimeout等が特殊なだけ setTimeoutを使用して上記2つのコードと似たようなことを行ってみる console.log("setTimeout前") setTimeout(function(){ console.log("3秒経過") },3000) console.log("setTimeout後") //"setTimeout前" //"setTimeout後" //"3秒経過" この場合、コンソールに表示される順番は「setTimeout前」→「setTimeout後」→「3秒経過」となり setiTimeoutの処理を待たずにconsole.log("setTimeout後")が実行されていることがわかる。 そして、こうなる理由は「時間のかかる処理を後回しにしているから」ではなく「setTimeoutのような非同期処理をする関数を使用しているから」が正しい。 その裏付けとなるのが以下のコードだ。 setTimeout(function(){ console.log("setTimeout終了") },0) start = Date.now() while(true){ if( Date.now() - start > 3000){ console.log("while終了"); break; } } //"while終了" //"setTimeout終了" コードを見てもわかるとおり、whileの処理には3秒かかるのに対し、setTimeoutは0秒に設定してある。 もし「時間のかかる処理が後回しになる」のであればコンソールの表示順はsetTimeout終了→while終了となるはずだが、 実際に実行するとwhile終了→setTimeout終了となる。 これは、非同期処理は同期処理の後に実行される、という処理順序があるからだ。 ↓そのあたりわかりやすく解説されているサイト様 https://note.affi-sapo-sv.com/js-processing-difference.php#jseventloop 3.ajax、axios、promise、async/await 非同期処理をする関数等の代表的なものには、setTimeoutの他にajaxやaxios等がある。 そして、そういった「非同期処理」を「同期的に実行したい場合」に使うのがpromiseやasync/await。 4.「JavaScript=非同期言語」は間違い? 非同期言語の定義がはっきりしないので、わかりません。 ただ、非同期処理もできるからと言ってシングルスレッドの言語を「非同期言語」と呼ぶのには少し違和感があります。 ↓参考にさせて頂いたとてもわかりやすいサイト様 https://dodosu.hatenablog.jp/entry/2021/01/17/212236
- 投稿日:2022-02-01T16:45:34+09:00
ウェブサイト作成用備忘録・22号:ローディングアニメーションの疑問点について
今回も他のwebサイトのソースコードを読み取っている時に感じた疑問点について備忘録として記録しておこうと思います。 webページを開いた時、画像や動画を読み込んでからwebページの表示を行う為に、ローディングアニメーションを実装しているサイトが多いのですが、自分が見つけたソースコードはこのような内容でした。 <script> const loaded = () => { //ローディングアニメーションを終了させる処理 } // 全てのコンテンツを読み込んだらloadedを発火させる window.addEventListener("DOMContentLoaded", loaded, false); // 10秒経過したらloadedを発火させる settimeout(loaded, 10000); </script> 当時の自分はローディングアニメーションの事を ページ内のコンテンツが全て読み込まれた状態でないと閲覧者にサイト内のコンテンツをしっかりアピールできない大掛かりなサイトに実装するもの だと思っていました。 なので、 10秒経過したら読み込みが終わっていなくてもコンテンツを表示するなら、初めからローディングアニメーションは必要ないのでは? という疑問が浮かびました。 そこで、ローディングアニメーションが実装される背景から調べてみる事で以下の事が分かってきました。 ページの読み込みに時間がかかり、真っ白なページがずっと表示されていると直帰率が高まる(閲覧者が不安になる) 具体的にはページの読み込み時間が3秒を超えたあたりで閲覧者の直帰率が大幅に上昇する ページの読み込みの間にローディングアニメーションを表示することで現在のページの状態を閲覧者に伝える事で直帰を防ぐ効果がある(閲覧者の不安を解消する) 更に、「待ち時間」そのものについての資料も調べてみる事にしました。 調べた中でwebサイトとローディングアニメーションに関係する内容は下記6つです 何もしないで過ごす待ち時間は長く感じる スマホやPCのディスプレイでwebサイトを開くときは、ディスプレイがwebサイトに占有されている為、読み込み時間の間は閲覧者は何もすることが無い状態になっている 本来のサービスの前後に付随する待ち時間は長く感じる webサイトのコンテンツが本来のサービスだとすると、ウェブサイトの読み込み時間は本来のサービスの前に付随している待ち時間になる 不安があると待ち時間は長く感じる webサイトを開いた時に画面が真っ白のまま待たされると、閲覧者はwebサイトがこのまま表示されなかったらどうしようと不安になる 不確定な待ち時間は長く感じる 仮にローディングアニメーションを設定していたとしても、現在の読み込みの状況が分からないままずっと待たされる様な場合は余計に待ち時間が長く感じられる 理由が分からない待ち時間は長く感じる webサイトを開いた時に画面が真っ白のまま待たされると、今ページを読み込んでいるから表示されないのか、自分の端末や電波の調子が悪いのから表示されないのか等の原因が分からない 独りで待つときは待ち時間が長く感じる スマホやPCでwebサイトを開く時は高確率で一人の時だと考えられる ↓参考記事 上記の内容を調べた事でローディングアニメーションの役割は ページの読み込み時間が3秒を超える前に、ローディングアニメーションを挟むことで閲覧者の直帰率にブレーキをかける ページの読み込みを行っているという事をわかりやすく閲覧者に伝える事で、閲覧者の待ち時間の理由を明確にする ページ内のコンテンツが全て読み込んでから表示する事で閲覧者にサイト内のコンテンツをしっかりアピールする という事なんだと思うようになりました。 (※更に、ローディングアニメーション中に閲覧者がミニゲームで遊べるようにしたり、今の読み込み状況を表示する機能を付ければ更に心理的に効果的なローディングアニメーションになるとは思いますが、そこまでやるならページの読み込み時間を短縮する方に力を入れた方が良いような気もします) (※ちなみに、自分が実際にローディングアニメーションを実装しようとした時、予めローディングアニメーションを表示している状態から始まるwebサイトで、ローディングが終了したらローディングアニメーションを終了させる処理を実行させたい時、コンテンツの読み込みを始めてからローディングの終了用関数を発火させるまでの読み込み中にエラーが発生した場合、コンテンツの読み込みが終わらずにローディング画面から進まない不具合が発生した事があったので、万が一不具合が発生した時の為に、一定のタイミングで強制的にローディングを切り上げる機能はあった方が安全なのかもしれません)
- 投稿日:2022-02-01T15:58:30+09:00
【progate】javaScriptレッスンの備忘録
初めに progateのjavaScriptレッスンをやりました。メモとしてやった内容をまとめておきます。 内容 コンソール console.log()のかっこの中身をコンソールに出力する。 //Hello Worldを出力 console.log("Hello World"); console.log('Hello World'); //3と出力 console.log(3); //1と出力 console.log(3-2); //35と出力 console.log("3"+"5"); 変数 変数宣言し、変数は再代入できる。 let name = "sato"; 定数 定数宣言すると、再代入が不可能になる。 const name = "sato"; テンプレートリテラル 連結は「+」を用いる以外の方法もある。 const name = "sato"; //こんにちは、satoさんと出力 console.log(`こんにちは、${name}さん`); if if文で条件判定する。 // a===b → aとbが等しい // a!==b → aとbが異なる //単純なif,else文 if(条件式){ }else{ } //たくさんの条件を書く場合 if(条件式){ }else if(条件式){ }else{ } switch どの値に該当するかを判定しそれぞれ該当する動作をする。 const color = "赤" switch(color){ case"赤": console.log("ストップ!");//この場合この行が実行される。 break; case"緑": console.log("前進!"); break; } while,for ループ作業に使う。 while(条件式){ } for(変数の定義;条件式;変数の更新){ } //例:1~100まで出力 for(let i = 1; i <= 100, i++){ console.(i); } 配列 それぞれのインデクス番号を使って値を更新できる。 //配列宣言:インデクス番号は左から0,1,2 const number = [1, 2, 3]; //[1,2,3]と出力 console.log(number); //3と出力(配列の長さ) console.log(number.length); オブジェクト 配列と同じく複数のデータをまとめて管理できる。 ただオブジェクトはそれぞれの値にプロパティと呼ばれる名前を付けて管理する。 値は更新できる。 // {プロパティ1:値1, プロパティ2: 値2} const item = {name:"手裏剣", price:300}; //{name:"手裏剣", price:300}と出力 console.log(item); //手裏剣と出力 console.log(item.name); オブジェクト+配列 const items = [ {name:"手裏剣", price:300}, //インデクス番号0 {name:"忍者刀", price:1200} //インデクス番号1 ]; //1200と出力 console.log(item[1].price); undefined undefinedは特別の値で、値が定義されていないことを意味する。 const number = [1, 2, 3]; //undefinedと出力 console.log(number[5]); if(何か === undefined){ //何かが定義されていないの時の処理 } 複雑なオブジェクト const character = { name:"にんじゃ", age:14, favorite:{ food:"ラーメン",//favoriteプロパティの値がオブジェクトになっている。 color:"青", }, }; //{food:"ラーメン", color:"青"}と出力 console.log(character.favorite); 関数 普通の関数 const 定数名 = function(){ //処理 }; //関数の呼び出し 定数名(); アロー関数 同じように関数定義できて、ES6から導入される新しい書き方。 const 定数名 = () =>{ //処理 }; const 定数名 = (引数名) =>{ //処理 }; //値が引数名に代入され呼び出す 定数名(値); //戻り値 const add = (a,b) =>{ //処理 return a + b; }; オブジェクトの中の関数 const 定数名 = { プロパティ名:()=>{ //処理 } }; 定数名.プロパティ名();//関数の呼び出し クラス クラス名は基本的に大文字から始める class Animal{ constructor(name,age){ this.name = name; this.age = age; } greet(){ console.log("こんにちは!"); } info(){ console.log(`名前は${this.name}です`); } } const animal = new Animal("sato",3); animal.greet();//こんにちは!と出力 animal.info();//名前を出力 継承 親クラスのメソッド使用可能。 class Dog extends Animal{ getHumanAge(){ return this.age*7; } } const dog = new Dog("kuro",4); console.log(dog.getHumanAge());//28 子クラスに親クラスと同じメソッドが存在する場合子クラスのメソッドが呼び出される。これはオーバーライドという。また、コンストラクタをオーバーライドする場合最初にsuper()が必要。 class Dog extends Animal{ constructor(name,age,breed){ super(name,age); this.breed = breed;//子クラス独自のもの } } ファイルの分割 複数のファイルでコード管理する。 以下のようにファイル分けする場合、関連付ける必要がある。 script.js dog.js animal.js animal.js class Animal{ } export default Animal; //Animalクラスをほかのファイルでも使用できる設定。 //1ファイルにつき1つの値のみ使えます。 dog.js import Animal from "./animal";//インポートして使えるようにする。 名前付きエクスポート デフォルトエクスポートと違い複数の定数やクラスを指定してエクスポートできる。(もちろん1つでも大丈夫) dogData.js const dog1 = new Dog("レオ",4,"チワワ"); const dog1 = new Dog("ベン",2,"プードル"); export{dog1, dog2}; sprict.js import {dog1, dog2} from "./dogData"; dog1.info(); dog2.info(); パッケージ chalk:コンソール出力文字に色をつける import chalk from "chalk"; console.log(chalk.yellow("hello"));//黄色文字表示 readline-sync:コンソールに値を入力できる import readlineSync from "readline-sync"; const name = readlineSync.question("名前を入力してください:"); //コンソールでは一旦とまり、文字入力するとnameに代入。 console.log(`${name}と入力されました`); const age = readlineSync.questionInt("年齢を入力してください:"); //整数を入力させる場合はquestionIntを使う 配列を操作するメソッド push 配列の最後にあたらあしい要素追加。 const n =[1,2,3]; n.push(4); console.log(n);//[1,2,3,4] forEach 配列の中の要素を1つずつ取り出し同じ処理を行う。 下のように引数に渡される関数はコールバック関数という。 const n =[1,2,3]; n.forEach( //アロー関数 (n)=>{ console.log(n); } ); //1,2,3の順に表示 find コールバック関数の処理部分に記述した条件式に合う1つ目の要素を配列の中から取り出すメソッドである。 const n =[1,3,5,7]; const foundNumber = n.find((n)=>{ return n > 3;//条件 }); console.log(fondNumber);//5と出力 filter 条件に合う要素のみを取り出して新しい配列を作成する。 const n =[1,3,5,7]; const filtedNumbers = n.filter((n)=>{ //条件に合う要素がfiltedNumbersに配列として代入される。 return n > 3; }); console.log(filtedNumbers);//[5,7] map 配列内のすべての要素に処理を行い、その戻り値から新しい配列を作成する。 const n =[1,2,3]; const doubledNumbers = numbers.map((n)=>{ //配列の中身をすべて2倍にする。 return n*2; }); console.log(doubledNumbers);//[2,4,6] コールバック関数 引数に関数を渡す。この引数に渡される関数をコールバック関数という。 //関数printWanko const printWanko = () =>{ console.log("わんこ"); }; const call = (callback) =>{ callback(); //コールバック関数を呼び出すときは()をつける //わんこと出力される }; call(printWanko);//コールバック関数を渡すときは()をつ 関数の扱い方として、 関数名() → 関数が呼び出される 関数名 → 関数の定義そのもの 直接引数の中で定義した関数 直接引数の中で関数を定義できる。 const call = (callback) =>{ callback(); //わんこと出力される }; call(()=>{ console.log("わんこ"); }); 普通の関数と同じように引数を渡すこともできる。 const introduce = (callback) =>{ callback("わんこ") }; introduce((name)=>{ console.log(name); });
- 投稿日:2022-02-01T14:28:15+09:00
Webの勉強はじめてみた その27〜モジュールごとの実装とデータベース〜
N予備校「プログラミング入門Webアプリ」を受講しています。 今回は第3章20節〜26節です。 承認されたユーザーだけが使える匿名掲示板の作成。 気をつけたい箇所や気付いた点だけをまとめました。 設計の進め方 システム要件の定義 UIの設計: ページに何をどう配置するか URIの設計: パスの作り方(RESTful) モジュールの設計 URIとモジュール リクエストを具体的な処理に振り分けることをルーティング、 リクエストに対し具体的な処理をする関数を リクエストハンドラという サーバーを起動するもの リクエストを処理するもの ルーティングを行うもの など、それぞれ役割を決めて実装する。 メインとなる機能から実装していくのが良い。 処理を実装する前にそれぞれの機能などをコメントする。 覚えておきたいこと テンプレートエンジンなどを使う場合、のちにhtmlになるものはviewsディレクトリに格納する。 以下はPOSTされた時の処理 // POSTの処理 let body =[]; req.on('data', (chunk) => { body.push(chunk); }).on('end', ()=>{ // クエリから値を取得 body = Buffer.concat(body).toString(); const params = new URLSearchParams(body); const content = params.get('content'); console.info(`投稿されました: ${content}`); }); querystringが非推奨になったのでURLSearchParamを使う。URIエンコードされた文字列のデコードも同時に行うので、decodeURIComponentはいらない。 認証機能 http-authモジュールの、ファイルを利用した認証 users.htpasswd admin:apple guest1:1234 guest2:5678 const basic = auth.basic({ realm: 'Enter username and password.', file: './users.htpasswd' }) Basic認証では、特定のURLにアクセスした際、ステータスコード 401 - Unauthorized を返すことでログアウトされる。 function handleLogout(req, res){ handleStatus(req, res, 401, 'ログアウトしました'); } function handleNotFound(req, res){ handleStatus(req, res, 404, 'ページが見つかりません'); } function handleBadRequest(req, res){ handleStatus(req, res, 400, '未対応のメソッドです'); } /** * HTTPレスポンスの共通処理 * @param {Object} req HTTP Request * @param {Object} res HTTP Response * @param {Number} statusCode * @param {String} msg Message */ function handleStatus(req, res, statusCode, msg){ res.writeHead(statusCode, { 'Content-Type' : 'text/plain; charset=utf-8' }); res.end(msg); } データベース 今回はPostgreSQLをsequelizeモジュールで操作。 docker-compose.yml services: app: depends_on: - db db: image: postgres:12 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: secret_board TZ: "Asia/Tokyo" docker-compose.ymlにdbというサービス名でPostgreSQLのコンテナを追加。 仮想環境上でappとdbの二つのサーバーが稼働する状態。 Sequelize yarn add sequelize@6.5.0 yarn add pg@8.5.1 yarn add pg-hstore@2.3.3 データベースの構成を設定することを、データモデリングという post.js // sequelizeの基本設定 const { Sequelize, DataTypes } = require('sequelize'); // データベース全体の設定 const sequelize = new Sequelize( // IDとパスワードを渡す 'postgres://postgres:postgres@db/secret_board', { // 起動ログなどのログを出力しない logging: false } ); // データモデリング const Post = sequelize.define( // テーブル名 'Post', { // データ定義 id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, content: { type: DataTypes.TEXT }, postedBy: { type: DataTypes.STRING }, trackingCookie: { type: DataTypes.STRING } }, { // テーブル名の固定 freezeTableName: true, // createdAt, updatedAtを自動追加 timestamps: true } ); // このファイルの起動時にデータベースの設定を同期する Post.sync(); // データモデリングしたオブジェクトをモジュールとして公開 module.exports = Post; insert const Post = require('./post'); Post.create({ //登録したいデータ }).then(() => { //登録後に実行してほしい処理 }) 非同期処理となる(Promiseオブジェクトが返ってくる)ので、thenメソッドを利用する。 コンソール上でのデータ確認 dbコンテナへ入る docker-compose exec db bash su postgres : ユーザーの変更(今回はpostgresというユーザー) psql : Postgres用コンソールに入る ¥c secret_borad : データベースに接続(今回はsecret_boardというデータベース) ¥q : 終了 select データの全件取得 // データの全件取得 Post.findAll({order:[['id', 'DESC']]}).then((posts) => { res.end( pug.renderFile('./views/posts.pug', { posts }) ); }); renderFileの引数に変数を指定することで、pugで参照可能となる。 delete destroyで指定したキーのデータを削除 投稿者自身か管理者のみが削除できるようにする function handleDelete(req, res){ switch(req.method){ case 'POST': let body = []; req.on('data', (chunk) => { body.push(chunk); }).on('end', () =>{ body = Buffer.concat(body).toString(); const params = new URLSearchParams(body); const id = params.get('id'); Post.findByPk(id).then((post) => { if(req.user === post.postedBy || req.user === 'admin'){ post.destroy().then(() => { handleRedirectPosts(req, res); }) } }) }) break; default: util.handleBadRequest(req, res); break; } } curlなどで第三者がdeleteを送る可能性があるため、サーバー側でもユーザーのチェックをすること。 実行するとデータベースから実際にデータを削除することを物理削除、実際にデータを削除する代わりに「削除された」ことを表すフラグを立ててデータが削除されたとみなすことを論理削除という。 削除処理は慎重に。実用的にも論理削除が良い。 データベースの永続化 dockerを破棄するとデータも破棄されるので、PC側の別の場所にファイルを保存する。 docker-compose.yml db: image: postgres:12 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: secret_board TZ: "Asia/Tokyo" volumes: - ../secret-board-db:/var/lib/postgresql/data PC側: ../secret-board-db docker側: var/lib/postgresql/data docker起動時に同期させる トラッキングCookie ユーザーの行動を追跡するために付与される Cookie のことをトラッキング Cookie と呼ぶ。 cookiesというnpmモジュールを利用する yarn add cookies@0.8.0 const Cookies = require('cookies'); const trackingIdKey = 'tracking_id'; // TrackingCookie const cookies = new Cookies(req, res); addTrackingCookie(cookies); function addTrackingCookie(cookies){ // Cookieが存在しない場合 if(!cookies.get(trackingIdKey)){ // ランダムな数値を登録 const trackingId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); // 有効期限を翌日(24時間後)まで const tomorrow = new Date(Date.now() + (1000 * 60 * 60 * 24)); cookies.set(trackingIdKey, trackingId, {expires: tomorrow}); } } MAX_SAFE_INTEGER : JavaScriptで扱える最大値 PUG側 each post in posts - let isPostedAdmin = (post.postedBy === 'admin') if isPostedAdmin h3 #{post.id} : 管理人 ★ else h3 #{post.id} : ID:#{post.trackingCookie} p!= post.content p 投稿日時: #{post.createdAt} - let isAdmin = (user === 'admin') if isAdmin p 投稿者: #{post.postedBy} - let isDeletable = (user === post.postedBy || isAdmin) if isDeletable form(method="post" action="/posts?delete=1") input(type="hidden" name="id" value=post.id) button(type="submit") 削除 hr each in 渡されてきたコレクションをループ。 - pugテンプレート内にJavaScriptを直接記述することができる。 p!= post.content pugテンプレート内で文字列に含まれたタグを認識させる。 まとめ モジュールごとの実装、わかりにくかったのは名前のせいもあるのかな。 formからのPOSTメソッド、データベースのPost、データベースモデリングのpost.js、URIクエリのposts。 データベースからの削除は、なるほどと思った。 実際に削除するとなるとほかのテーブルも意識しないといけないので、実務的にも慎重になる事案。 しかし設計の重要性というのをつくづく感じる今日この頃。
- 投稿日:2022-02-01T12:02:49+09:00
「リファレンス読めばわかるよ」からの脱出 ~誰も教えてくれないリファレンスの読み方~
背景 昨今は、プログラミングスクールの流行で、プログラマー見習いが量産されています。 スクール卒業生曰く、スクールではある決まった流れに沿ってのみプログラミングを体験していくため、Ruby on RailsでECコマースは作れるけど、反復処理(forやwhileなど)や制御フロー(ifやSwitchなど)といった基本的な処理を知らないという方が多いらしいです。(諸説あり) そういった方から、「主さんはどうしてそんなに色々なことを知っているんですか?」なんて聞かれたりもします。 でも正直、私がすごいエンジニアというわけではなく、普通にリファレンスで質問の答えを探しているだけなんですよね。 だからこそ、アドバイスのつもりで言ってしまいがちなのが、 「リファレンス読めばわかるよ」 自分も初心者の頃よく思っていました。「そもそもリファレンスが読めないんだけど...」 という事で、今回は初心者キラーのリファレンス読めばわかるよから皆様を救うべくとりあえずなんとなくリファレンスが読めることを目標に、誰もが一度は目にしたであろうMDN Web DocsからJavaScriptのリファレンスを読んでいこうと思います 補足: 今回は初心者の方向けの記事になります。 細かい表現などおかしな点ありますが、ある程度は目を瞑っていただけたら幸い。。 とはいえ、初心者への表現としてそぐわない点ありましたら教えていただけたら嬉しいです。 まずは基礎知識(知ってる人は読み飛ばし可) データ型を知ろう 人は、「0.24」と書かれていれば数値、「あいうえお」と書かれていれば文字だとわかります。 数値は計算ができるけど、文字は計算できない。こんなことも当たり前です。 さて、ここで問題です。「二十四 + 六」の答えはなんでしょうか?、、、、そうです、答えは「三十」です。 日本人なら特別疑問もないと思います。でも、漢字が読めない人の視点に立つとどうでしょうか? 二十四も六も数値ではなく文字と認識されそうですよね? つまり、上記の計算が成り立つのは、漢数字も数値の一種というあらかじめ決められたルールが存在しているからです。 言葉というのはあらかじめ決められたルールに則って使われます。 それはプログラミング言語でも同じです。 コンピュータが理解できるように決められた言葉のルール = データ型を教えてあげる必要があります。 よく使われるデータ型 コンピュータの世界ではデータ型は、よくこのような言葉で表されます。 日本語 データ型 文字 String 整数 Number, Integerなど 少数 Float 真偽値 Boolean 配列 Array オブジェクト Object JavaやVBAなどプログラマが型を指定する必要がある言語(静的型付言語)ではデータ型は普通の概念ですが、JavaScriptやRubyなど、基本的にプログラマが型を指定しない言語(動的型付言語)を使っている場合には馴染みが薄いかもしれませんね。 とはいえ、動的言語もデータ型という概念が存在しないわけではなく、コンピュータ側が自動でデータ型を予測しているのです。 補足: JavaScript等でも、誤ったデータ型によるエラーがあります。 デバッグなどで、Type ofなどを利用してデータ型を確認するようにしましょう。 今後開発を進めていく際に、自分が今扱っている変数はどんなデータ型なのか意識しながら開発を進めるとエラーの少ないコードが書けると思います。 プロパティとメソッド 各データ型はそれぞれに決まった性質を持っています。 こう聞くと難しそうですが、例えば数値は計算ができる、 言葉には文字数があるなど思い浮かべるといくつか固有の性質があることに気づくと思います。 このような性質の中でも、値として表されるものをプロパティ、何かしらの処理をメソッドと呼びます。 プログラミング言語におけるデータ型はそれぞれ固有のプロパティやメソッドを持っています。 例えば、String(文字)型はlength(文字数)というプロパティやslice(切り取り)というメソッドなど、あらかじめ決められた性質を持っています。 このことをひとまずは意識しておいてください String.js const tubuyaki = '本日のお昼ごはんもコンビニ弁当です。'; //String型 console.log( tubuyaki.length ); // 18 console.log( tubuyaki.slice(9, 15) ); // 'コンビニ弁当' 補足: メソッドやプロパティの話をする際にはオブジェクトやクラスの話が必要かとは思いますが、今回は割愛しています。 これらを勉強すると今回の内容はより理解できると思いますので、ぜひ勉強してみてください。 大枠を掴もう ライブラリなどの関数を使ったりする場合、その関数自体がどう作られているか知らなくても普通に使えると思います。 関数やメソッドを使う際は、インプット(引数)に対してアウトプット(返値)が何になるかのみが重要で、スクリプトの中身はブラックボックスとして扱われていると思います。 リファレンスに書かれている関数やメソッドにも同じことが言えます。 リファレンスを読む際に注目すべきはどのようなデータ型の引数に対して、どのようなデータ型の返値が帰ってくるか、そして返ってきたデータ型はどんなプロパティやメソッドを持っているかなのです。 実践:リファレンスを読んでみよう さて、ここまでの内容が理解できたあなたには、既にリファレンスを理解する基礎があります。 この先の実践では、まず引数のデータ型と返値のデータ型に注目してみていきます。 Array.prototype.push() 配列のメソッドとして使われるpush関数を見ていきましょう。 push.js const array = [0, 1, 2, 3]; array.push(4); return array; // [0, 1, 2, 3, 4] リファレンスの解説に書かれている内容を引用するとpushメソッドは配列の末尾に要素を追加するメソッドです。 ①構文 まず最初に確認するのは構文です。 構文とはメソッドを使う際の使い方=ルールです。 斜体のarrはpushを適用する配列を指しています。 pushは配列のメソッドなので、配列以外のデータ型では使えないことに注意しましょう。 [element1[,...[, elementN]]]の部分は分かりにくいので単純化すると下記のようになります。 push.js arr.push(引数); つまり、[element1[,...[, elementN]]] = 引数(Input)です。 この場合、引数[element1[,...[, elementN]]は複数個のelementを引数として持てることを表しています。 example.js const arr = [3, 4, 5]; arr.push(2); // arr = [3, 4, 5, 2] arr.push(0, 1); // arr = [3, 4, 5, 2, 0, 1] ②引数と返値 次に確認するのは引数と返値です。引数 elementNは配列の末尾に追加したい要素です。 文章内にデータ型を表す文章がないため、elementNはあらゆるデータ型(Any)を指定することができます。 push.js const arr = []; arr.push(1); arr.push('a'); arr.push([1, 2], {a: 1, b: 2}); return arr; // [1, 'a', [1, 2], {a: 1, b: 2}] そして、返値を見てみましょう。 pushメソッドは普段配列に要素を追加するだけの目的で使われることが多いため、返値を見たことがある方は少ないかもしれません。 感覚的には、要素が追加された配列が返ってきたりするのかな?と思ったりしませんか? さて、pushメソッドを格納した変数resultには一体何が格納されているのでしょうか? 答えは、メソッドが呼び出されたオブジェクト(配列)の新しい length プロパティです。 push.js const arr = [0, 1, 2, 3]; const pushed = arr.push(4); // arr = [0, 1, 2, 3, 4] console.log(pushed); // 5 console.log(arr.length); // 5 その他の項目 解説や例などは、pushメソッドをより詳しく説明している部分ですが、pushメソッドの使い所や実際の使用例を見たい場合などに読むものなので、読まなくても問題はありません。 引数と返値、そして構文が理解できたあなたは、もうpushメソッドを使いこなせます。 結局ここに書いてあること 最終的にまとめると、pushメソッドは引数としてAny、返値としては新しい配列のlengthプロパティ(Number型)が得られることがわかりました。 得られたNumber型のプロパティやメソッドを利用することもできます。 advanced.js const animals = ['dog', 'cat', 'fish']; const newAnimalNumber = animals.push('bird'); console.log(`新しい動物はNo. ${newAnimalNumber}です。`); //新しい動物はNo. 4です。 Array.prototype.pop() せっかくなのでもう一つメソッドを見ていきましょう。 今回も同じく配列のメソッドとして使われるpop関数を見ていきます。 pop.js const plants = ['cauliflower', 'cabbage', 'kale', 'tomato']; const poped = plants.pop(); console.log(plants); // ["cauliflower", "cabbage", "kale"] console.log(poped); // "tomato" popメソッドは、配列から最後の要素を取り除き、その要素を返します。このメソッドは配列の長さを変化させます こちらもpushと同じく、まずは①構文を確認します。 pop.js arrName.pop(); では、次に引数を確認しましょう。 と、いきたいところですが、popメソッドには引数がありません。 なので②返値を見ていきましょう。 返値は、配列の最後の要素、配列が空だった場合は、undefinedになります。 つまり、返値はpopメソッドで取り除いた要素ということです。(要素のない配列でpopメソッドを使った場合はundefindが返されます) pop.js const alphabets = ['a', 'b']; const poped1 = alphabets.pop(); const poped2 = alphabets.pop(); const poped3 = alphabets.pop(); console.log(poped1); // "b" console.log(poped2); // "a" console.log(poped3); // undefind なるほどなるほど、これでpopメソッドも使い方がわかりましたね。 こちらもpush同様、返値を利用したプログラムを書くこともできます。 advanced.js const animals = ['dog', 'cat', 'fish', 'dragon']; const lastAnimal = animals.pop(); console.log(`最新の動物は${lastAnimal}です。文字数は${lastAnimal.length}です。`); //最新の動物はdragonです。文字数は6です。 Arrayが出来ること ここまででリファレンスの読み方は大体わかってきたかと思います。 ところで各ページに書いてあることは理解できたかもしれませんが、そもそも配列というデータ型はどんなメソッドやプロパティを持っているのか網羅的に知りたいと思いませんか? そんな時に確認するのが③Related Topicsのプロパティとメソッドです プロパティとメソッド 先にも書いた通り、ArrayにはArrayのプロパティやメソッド、StringにはStringの、NumberにはNumberの、各々のデータ型が持つプロパティやメソッドはあらかじめ組み込まれています。 そして、Arrayに組み込まれている内容は、③プロパティとメソッドに全て記載されています。 ここから自分の見たい内容っぽい名前のメソッドやプロパティのページを見れば、pushやpop同様のページに辿り着けます。 一方でArray以外のデータ型のページはどう行けばいいの?という疑問もあると思います。 そう言った時はこちらの目次ページから該当のデータ型を探しましょう。 例えばpushメソッドの返値であるNumber型のページはこちらになります。 この先のページにも同様にRelated Topicsがあるのでこちらからプロパティやメソッドを確認していけます。 まとめ 返値のデータ型のプロパティとメソッドを確認し続けることで、自分が今どうしてこのコードを書いているかが明確にわかるようになると思います。 リファレンスとデータ型、そしてメソッドとプロパティを意識するだけでもプログラミングの世界は一気に広がります。 よりしっかりとした理解を求める方はぜひ、クラスやプロトタイプ、オブジェクトといったキーワードを勉強してみてください。 それでは、皆さんも自分の力で様々なリファレンスに挑戦してみましょう!
- 投稿日:2022-02-01T11:09:54+09:00
javascriptで演算子のオーバーロードを可能にした話
先日、Javascriptで演算子のオーバーロードを可能にする operator-overload.js というnpmパッケージをリリースしたので、開発に至った理由や仕様について書いていこうと思います。 モチベーション 最近Qiitaで見つけたPythonのコードを自分でJavascriptに移植していた時に、 コードの途中でNumpyを使った処理を見つけたのがきっかけで、Javascriptでも もっと簡単に行列演算を記述できたらいいなーと思うようになりました。 が、Javascriptでは演算子のオーバーロードが言語仕様上不可能なので、 普通の演算のように行列演算を行うことができません。 そこで、トランスパイラを自作する、という考えに至りました。 演算子のオーバーロードとは この章では、そもそも演算子のオーバーロードってなんぞや、という方のための簡単な解説なので、知っている方は読み飛ばしていただいて構いません。 突然ですが、C++で std::cout << ... というコードを見たことはないでしょうか。 本来<<は左ビットシフトのための演算子ですが、 この場合は文字列出力(というよりパイプ)を意味しています。 これは、<<演算子がオーバーロードされ、動作が変更されているためです。 ほかにも前の章で上げたNumpyでは、 x = np.array([0, 2, 4]) x = x / 2 print(x) # 実行結果:[0, 1, 2] のようにベクトルや行列に対して四則演算ができたり、行列同士で演算なんてこともできたりします。 これらのように、演算子のオーバーロードは、演算する型によって演算子の動作を変更することで、 より簡潔なコードを記述することが可能にします。 作ったもの npm - operator-overload.js GitHub 使い方 インストール npm initは説明しませんので、各自お願いします。 $ npm install --save operator-overloadjs CLIの使用のためにpackage.jsonの編集も必要です。 "scripts": { "build": "overload -d ./src -o ./dist", // ディレクトリごとトランスパイルする場合 "transpile": "overload" // 後でファイル名を直接指定してトランスパイルする場合 }, "type": "module" コード この例では、"+"演算子で結合ができるカスタム配列クラスを作ります。 src/customArray.js import _overload from "operator-overloadjs"; class CustomArray extends Array { [_overload.plus](a) { return this.concat(a); } } const array_a = new CustomArray(); const array_b = new CustomArray(); array_b.push(1, 1, 3); array_a.push('a', 'b', 'c'); console.log(array_a + array_b); もちろんこのままでは動作しないので、CLIを使ってトランスパイルします。 $ npm run build ディレクトリごとではなく、ファイル名を直接指定してトランスパイルすることもできます。 $ npm run transpile -- ./src/customArray.js -o ./dist/customArray.js 出力されたコードは、このようにトランスパイルされます: import _overload from "operator-overloadjs"; class CustomArray extends Array { [_overload.plus](a) { return this.concat(a); } } const array_a = new CustomArray(); const array_b = new CustomArray(); array_b.push(1, 1, 3); array_a.push('a', 'b', 'c'); - console.log(array_a + array_b); + console.log((array_a)[_overload.plus](array_b)); Note: ここまでで出てきた_overload.plusという変数には、シンボルが入っています。 そのため、ほかのプロパティとバッティングすることはありません トランスパイルされたコードを実行してみましょう。 $ node ./dist/customArray.js CustomArray(6) [ 'a', 'b', 'c', 1, 1, 3 ] ちゃんと動作しましたね。 ここで注意してほしいのは、この例でオーバーロードしたのはCustomArrayクラスの"+"演算子であって、 Arrayクラスの"+"演算子ではないということです。 つまり、new Array()で生成した配列や、[0, 1, 2]のような配列リテラルで生成した配列では "+"演算子で結合することはできない、ということです。 もし、ビルドインなクラスやほかのライブラリのクラスの演算子をオーバーロードしたい場合は、次のような方法を使うことができますが、 一種のプロトタイプ汚染であり、ライブラリなどの動作に支障をきたす可能性があるので、なるべく控えることをお勧めします。 Object.defineProperty(Array.prototype, _overload.plus, { value: function(a) { return this.concat(a); } }); オーバーロードが可能な演算子の一覧 単項演算子の場合はthisだけですが、左辺と右辺がある場合は一つ目の引数に右辺が渡されます。 演算子 識別子 a + b _overload.plus a - b _overload.minus a * b _overload.multiply a / b _overload.divide a % b _overload.modulus + a _overload.unaryplus - a _overload.unaryminus ~ a _overload.bitnot a & b _overload.bitand a | b _overload.bitor a ^ b _overload.bitxor ! a _overload.not a && b _overload.and a || b _overload.or a == b _overload.equal a != b _overload.notequal a === b _overload.identityequal a !== b _overload.identitynotequal a[b] _overload.index おまけ ここまではほとんどreadme.mdの和訳みたいなものだったので、 せっかくなので機能が少ないどころかほぼないNumpyもどきを実装してみます。 src/numjs.js import _overload from "operator-overloadjs"; class njArray extends Array { [_overload.plus](a) { const newArray = new njArray(); for (let i = 0; i < this.length; i++) { newArray.push(this[i] + a); } return newArray; } [_overload.minus](a) { const newArray = new njArray(); for (let i = 0; i < this.length; i++) { newArray.push(this[i] - a); } return newArray; } [_overload.multiply](a) { const newArray = new njArray(); for (let i = 0; i < this.length; i++) { newArray.push(this[i] * a); } return newArray; } [_overload.divide](a) { const newArray = new njArray(); for (let i = 0; i < this.length; i++) { newArray.push(this[i] / a); } return newArray; } [_overload.unaryminus]() { const newArray = new njArray(); for (let i = 0; i < this.length; i++) { newArray[i] = - this[i]; } return newArray; } [_overload.index](a) { if (typeof a === 'number') { return Array.from(this)[a]; } else if (a instanceof Array) { const newArray = new njArray(); for (let i = 0; i < a.length; i++) { newArray.push(Array.from(this)[a[i]]); } return newArray; } } } const a = new njArray(0, 1, 2, 3); console.log(`a + 1 = ${a + 1}`); console.log(`a - 1 = ${a - 1}`); console.log(`a * 1 = ${a * 2}`); console.log(`a +/ 1 = ${a / 2}`); const index = [1, 3] console.log(`a[index]= ${a[index]}`); わざわざArray.from()でArrayクラスに変換しているところですが、 インデックスが数値の時は本来の動作をする関係上、ここでthis[a]としてしまうとトランスパイル後のコードでは自分自身を呼び出して無限ループに陥ってしまうためです。 $ npm run build > numjs@1.0.0 build > overload -d src -o dist $ node dist/numjs.js a + 1 = 1,2,3,4 a - 1 = -1,0,1,2 a * 1 = 0,2,4,6 a + / 1 = 0,0.5,1,1.5 a [ i n d e x ] = 1,3 見事配列に対する数値の四則演算に成功しました。 が、なぜか表示がおかしいようですね。(文字の間に無駄に空白が入っている) トランスパイル後のコードを見てみると、 dist/numjs.js console.log(` a + 1 = ${ (a)[_overload.plus](1) } `); console.log(` a - 1 = ${ (a)[_overload.minus](1) } `); console.log(` a * 1 = ${ (a)[_overload.multiply](2) } `); console.log(` a + / 1 = ${ (a)[_overload.divide](2) } `); const index = [1 , 3]; console.log(` a [ i n d e x ] = ${ (a)[_overload.index](index) } `); トランスパイル後の時点で空白が入っているようですね・・・ また一つバグを発見しました・・・ (後で修正します) 最後に ここまで読んでくださった方、ありがとうございます。 LGTM・コメントお待ちしております。 また、実際にnpmでインストールして使ってみてくださると光栄です。 さらに、このパッケージを使ってNumpyのようなライブラリを開発してくださると筆者が喜びます。
- 投稿日:2022-02-01T10:54:01+09:00
Stripe Checkoutの決済URLを、任意の時間またはタイミングで期限切れにする
Stripe Checkoutで発行する決済URLは、顧客のセッションごとに作成されます。 そしてデフォルトでは、この決済URLは、「決済が完了する」か「発行から24時間経過する」ことで利用できなくなります。 しかし以下のようなケースでは、任意のタイミングで決済URLを無効化する必要があります。 タイムセールなど、特定の時間内に注文を完了させる必要のあるケース 在庫システムと連携し、「カゴ落ち」が発生した場合に確保した在庫を解放したいケース 料金改訂などで、旧料金での決済を任意の時間で受付終了したい場合 これらのケースに利用できるAPIとAPIパラメータについて、紹介します。 Checkoutセッション(決済URL)開始時に、有効期限を設定する場合 もっともシンプルな方法は、Checkoutセッションを作成する際のAPIパラメータに有効期限を設定する方法です。 以下のサンプルでは、day.jsを利用し、90分後に無効化されるセッションを作成しています。 import dayjs from 'dayjs' ... await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxx', quantity: 1 }], success_url: 'https://stripe.com', cancel_url: 'https://stripe.com', expires_at: dayjs().add(90, 'minutes').unix(), mode: 'payment' }) Tips: 1時間未満の有効期限は、セッション開始時の設定がNG なお、セッション開始時のAPIリクエストでは、「expires_atに1時間以上後の時間のみ設定できる」仕様であることに注意が必要です。 そのため、在庫の一時確保など、数分で無効化したいユースケースでは、後に紹介する「セッション終了APIの呼び出し」で対応しましょう。 Tips: 決まった時間でセッションを無効化する 以下のような実装にすることで、「20時をすぎると注文できなくなる商品」を提供することができます。 try { const endingTime = dayjs().hour(20).minute(0).second(0).millisecond(0) const session = await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxx', quantity: 1 }], success_url: 'https://stripe.com', cancel_url: 'https://stripe.com', expires_at: endingTime.unix(), mode: 'payment' }) return session } catch (e) { const { type, statusCode, param } = e // タイムセール終了時間を過ぎてからのリクエストを処理する if ( type === 'StripeInvalidRequestError' && statusCode == 400 && param === 'expires_at' ) { throw new Error('タイムセールは終了しました') } // それ以外のエラーはそのまま投げる throw e } ただし、このケースでは、以下のようなケースを考慮する必要があります。 終了時間経過後にAPIが呼び出されると、Stripe APIがエラーを返すため、エラーをハンドルして「セール時間が終了していること」をユーザーに知らせる必要がある 日付が変わると注文が可能になるため、営業時間開始前の注文を拒否する実装を追加するか否かを判断する AWS Lambdaなどのプラットフォームを利用する場合、タイムゾーンが現地時間に設定されているかを確認する 「終了1時間前」からのセッションもAPIエラーになるため、内部的には終了時間 + 1時間で設定する必要がある Tips: セッションの終了時間をユーザーに見せたい場合 「X時までにご注文ください」のように、そのセッションが終了するまでの時間をユーザーに表示させたい場合、APIレスポンスの値を利用することができます。 const session = await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxxxx', quantity: 1 }], success_url: 'https://stripe.com', cancel_url: 'https://stripe.com', expires_at: endingTime.unix(), mode: 'payment' }) return { session_id: session.id, // UNIXタイムスタンプをそのまま返す場合 expires_at: session.expires_at, // サーバー側でフォーマットしてから返したい場合 expires_at_string: dayjs.unix(session.expires_at).format('YYYY/MM/DD HH:MM'), url: session.url, } 手動で任意のタイミングにセッションを終了させる場合 「1時間以内にセッションを期限切れにさせたい場合」や「時間以外の要因でセッションを期限切れにしたい場合」などでは、別途セッションを期限切れにするAPIを呼び出します。 await stripe.checkout.sessions.expire(session.id) Checkoutセッション作成時にcronやAWS StepFunctionsなどにセッションIDを含めたジョブを、期限切れにさせたいタイミングを実行時間に設定して登録します。 そして、実行時間に上記のExpire APIを実行し、セッションを終了させることができます。 「Checkoutのセッションを作成したタイミング」のWebhookイベントが現時点で提供されておりませんので、すこし組み込みに工夫が必要な点に注意しましょう。 セッションを期限切れにさせた後の処理を実行させる方法 「セッションが有効な間は在庫を仮押さえするタイプの通販サイト」などでは、セッションを有効期限切れにさせた後に在庫を戻す必要があります。 この場合に利用できるのが「checkout.session.expiredのWebhookイベント」です。 Webhookを利用することで、ネットワークエラー時のリトライや自動化されたワークフローの一本化などが期待できます。 if (['checkout.session.expired'].includes(event.type)) { const session = event.data.object console.log(session) } Tips: checkout.session.expiredイベントに、カートの中身の情報を渡す 現在、Checkoutのセッション情報にはline_itemsのデータが含まれていません。 そのため、checkout.session.expiredイベントのデータのみを利用して在庫の操作を行いたい場合は、セッション作成時にデータをmetadataやRedis / AWS DynamoDBなどへ一時的に保存する操作が必要となります。 DynamoDBなどのKVSでは、以下のようにCheckoutのセッションIDをキーに保存する方法が手軽かと思います。 const session = event.data.object const data = await (new DynamoDB.DocumentClient()).get({ TableName: 'CartItem', Key: { session_id: session.id } }).promise() 以下はmetadataを利用した実装サンプルです。 const lineItems = [{ price: 'price_xxxx', quantity: 1 }] const metadata = {} lineItems.forEach(item => { metadata[item.price] = item.quantity }) const session = await stripe.checkout.sessions.create({ line_items: lineItems, success_url: 'https://stripe.com', cancel_url: 'https://stripe.com', mode: 'payment', metadata }) おわりに Stripe Checkoutを利用することで、表示内容だけでなく、今回のケースのような在庫やタイムセールなどのユースケースにも少ないコード・実装で対応することができます。 また、カゴ落ち時に顧客にリマインドメールを送付するための実装・設定方法についても機能・ドキュメントを用意しています。 ぜひCheckoutの機能をフル活用して、新しいセールス・プロモーションの企画・開発にお役立てください。 参考・関連ドキュメント [PR] Stripe開発者向け情報をQiitaにて配信中! 2021年12月よりQiitaにて、Stripe開発者のためのブログ記事更新を開始しました。 [Stripe Updates]:開発者向けStripeアップデート紹介・解説 ユースケース別のStripe製品や実装サンプルの紹介 Stripeと外部サービス・OSSとの連携方法やTipsの紹介 初心者向けのチュートリアル(予定) など、Stripeを利用してオンラインビジネスを始める方法について随時更新してまいります。 -> Stripe Organizationsをフォローして最新情報をQiitaで受け取る
- 投稿日:2022-02-01T02:22:10+09:00
PixiJS でサロゲートペアの文字の改行による文字化けを解消する
前置き フォームに入力された文字列を pixi.js で描画するものを作っているときに遭遇した事象です。 ちなみに僕自身、 pixi.js は reference 見ながらなんとなく実装できるくらいです。 何が起きた? const style = { fill: "#fff", fontSize: 40, wordWrap: true, breakWords: true, wordWrapWidth: 100, }; const message = new PIXI.Text("?????", style); container.addChild(message); // output: // ??� // �?? サロゲートペアの部分で分断されて出力されてしまいました。 対処 サロゲートペアの判定をする TextMetrics を取得する 文字列の長さを比較する (元の文字列・改行を考慮した文字列) 長さが合わなかったら wordWrapWidth を調整する // サロゲートペア判定 function stringToArray(str: string) { return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || []; } const text = "?????"; const style = { fill: "#fff", fontSize: 40, wordWrap: true, breakWords: true, wordWrapWidth: 100, }; // TextMetrics 取得 const textMetrics = PIXI.TextMetrics.measureText(text, style); // 文字列の長さ const beforeTextLength = stringToArray(text).length; // 行ごとの文字列の長さを足した数 let afterTextLength = textMetrics.lines.reduce((pv, v) => pv + stringToArray(v).length, 0); // output: 5 6 console.log(beforeTextLength, afterTextLength); // 文字サイズ分横幅を増やして試す let retry = style.fontSize; let wordWrapWidth = style.wordWrapWidth; // retry上限回数 または 文字列の長さが同じ になるまで試す while (retry > 0 && beforeTextLength !== afterTextLength) { retry -= 1; wordWrapWidth += 1; afterTextLength = PIXI.TextMetrics .measureText(text, { ...style, wordWrapWidth }) .lines.reduce((pv, v) => pv + stringToArray(v).length, 0); } const message = new PIXI.Text(text, { ...style, wordWrapWidth }); container.addChild(message); // output: // ??? // ?? 最初にこの事象に遭遇したときは、もっと複雑なことしないといけないのかなーと思ったんですが、意外となんとかなりました。
- 投稿日:2022-02-01T01:23:22+09:00
[JS]HTML要素の取得や書き換えをする
HTML要素の取得や書き換えをすることで、イベント発火させると表示されている文字を変更することができるようになります。 innerHTMLプロパティ JavaScriptにて、HTML要素の取得や書き換えを行うことができるプロパティ。 例 HTML <div class="contents" id="hoge">HTML</div> javascript const hoge = document.getElementById("hoge") //HTML要素の取得 console.log(hoge.innerHTML) // => HTML //HTML要素の書き換え hoge.innerHTML = "javascript" console.log(hoge.innerHTML) // => javascript 以上。 補足等ありましたらコメント頂けると幸いです。
- 投稿日:2022-02-01T01:01:51+09:00
ローカルストレージは安全に使用できるのか?
もう、2月。もうすぐ節分。 2022年2月3日の恵方は、「北北西」のようですね。 東京では、久しぶりに夕方グラっと地震がありドキッとしました。 QiitaでITセキュリティ関連の記事をご紹介しつつ、いざというときの家庭のセキュリティについては後回しに。 早めに見直さなければと思いつつ、また、明日と先延ばししています。 今年の抱負の一つが密かに、[何事もシフトレフトを目指す] も入っているのですが、、、 今回は、ローカルストレージのセキュリティについて「Is local Storage Safe to Use?」 のブログ翻訳をご紹介します。 ローカルストレージは安全に使用できるのか? リラン・タル 2020年1月30日 インターネットは、基本的なテキストのレイアウトにさえ苦労していた最初のウェブサイトから長い道のりを歩んできました。HTML5はハイパーテキストマークアップランゲージの最新の進化形で、ブラウザのプラグインを追加することなく、ウェブデベロッパが望むほぼすべてのことができるように設計されています。HTML5では、セマンティックな要素、グラフィックな要素、さらにはウェブストレージ(特にローカルストレージ)を利用したいくつかのトリックなど、さまざまな要素がデベロッパに提供されています。もちろん、ローカルストレージは安全に使えるのか、という疑問もあります。 ローカルストレージは、データベースやサーバーを必要としない、軽量なデータストレージとしてデベロッパの注目を集めています。それはとても素晴らしいことですが、それを使用することは毎回良いアイデアだと言えるのでしょうか?ここでは、Snykのスタッフの考えをご紹介します。 ローカルストレージとは? ローカルストレージは、クライアント、つまりユーザーのコンピュータにデータを保存するためのHTML5ウェブストレージオブジェクトです。ローカルに保存されたデータには有効期限がなく、削除されるまで存在し続けます。(これに対して、もう一つのHTML5ウェブストレージAPIであるセッションストレージは、ブラウザが閉じたときに保存されたデータを削除します) ローカルストレージは純粋なJavaScriptです。同様に、ユーザーのデバイス上にプレーンテキストのドキュメントを生成することに変わりはありませんが、ローカルストレージでは(cookieの4KBに対して)最大5MBのデータを保存することができます。これにより、コンテンツを管理してサーバーへのリクエストを減らしたり、ロード時間を短縮したりするなど、ローカルストレージの興味深い応用が可能になりました。 慎重に使えば、ローカルストレージは強力な軽量データストレージソリューションとなりますが、問題がないわけではありません。ここでは、保存対象によってはローカルストレージの使用が好ましくないと思われる理由をいくつか紹介します。 ローカルストレージの使用が好ましくない理由 ローカルストレージは、本質的にはcookieを使用するよりも安全ではありません。このことを理解していれば、セキュリティの観点からは重要ではないデータの保存にオブジェクトを使用することができます。しかし、ここにローカルストレージの使用を再考すべきいくつかの理由があります。 1. XSSに脆弱なサイトの場合、ローカルストレージは安全ではない ローカルストレージを使用することに対する最大の反対意見は、おそらくローカルストレージに関連するセキュリティの脆弱性でしょう。ローカル・ストレージは、cookieと同じ特徴を持ち、同じセキュリティ・リスクを含みます。そのひとつがクロスサイトスクリプティングで、cookieを盗み、ハッカーにユーザーになりすまさせてサイトにログインさせることができます。パスワードのような機密性の高いものをローカルストレージファイルに保存すると、実際にはハッカーは自分のブラウザのなかでcookieを読み込む必要がないため、プロセスが簡素化されます。 2. ローカルに保存されたデータをデベロッパが管理できない ローカルストレージでは、サーバー側のストレージ、つまりデベロッパがコントロールできるデータベースがありません。これにはいくつかの問題がありますが、その一つは、一度保存されたコードや情報をデベロッパが更新する手段がないことです。ユーザーは手動でファイルを削除する必要がありますが、そのためにはファイルを探す必要があります。あるいは、ブラウザのキャッシュを消去して、保存されたデータをすべて失わせる必要があります。 3. キャッシュを消去する ブラウザのキャッシュを定期的にクリアすることで、Cookieの機能をより効果的に発揮させることができます。また、ページが正しく読み込まれないなどのブラウザの問題をトラブルシューティングする際の最初のステップにもなります。これは、サイトの機能をサポートするためにローカルストレージを使用する場合の問題です。ユーザーがブラウザのキャッシュを消去してしまうと、その情報は永久に失われてしまいます。そのため、ローカルストレージは予備のデータベースとしての役割を果たします。 ローカルストレージの代替手段 情報が機密性の高いものかどうかに応じて、ローカルストレージの代わりとなるものがいくつかあります。ローカルストレージを使用したくないデベロッパは、以下のような選択肢があります。 機密情報にはサーバーサイドセッションを使用する サーバーにデータを保存することは、機密情報を扱う場合にいくつかの利点があります。第一に、デベロッパはそのセキュリティを保証することができ、個々のセッションをよりコントロールすることができます(必要に応じて即座に終了させることができます)。第二に、データはアーキテクチャ内で保護されているため、データが流出する可能性が低くなります。 非機密情報にはIndexedDBを選択する IndexedDBは、クライアントサイドのセッションを使用することにこだわるデベロッパにとって、ローカルに保存できるアプリケーションの構築を可能にします。ローカルストレージほど広くブラウザでサポートされているわけではありませんが、機密性のないデータをローカルに保存する場合には便利です。IndexedDBには、ローカルストレージに比べて、より多くの種類のデータを扱うことができる本格的なデータベースであるという大きな利点があります。 データストレージを賢く使う ローカルストレージをうまく活用すれば、サイトのパフォーマンスを向上させ、より軽量なアプリケーションを作ることができます。しかし、ローカルストレージは、パスワードや個人情報などの機密情報には決して使用しないでください。機密情報にローカルストレージを使用することで、本来、サイトのサーバーがすでに安全であれば避けることができるセキュリティリスクを生み出します。 暗号化やセキュリティが必要な情報については、サーバーサイド・セッションなど、効果的で手軽なソリューションがあります。サイバーセキュリティがこれほど大きな関心事となっている今、リスクを冒す必要はありません。 Snyk は、常に脆弱性を見つけて修正し、安全な状態を維持できるよう支援します。サインアップして、無料のアカウントを取得しましょう。 最後まで、読んでいただきありがとうございました! Contents provided by: Jesse Casman, Fumiko Doi, Content Strategists for Snyk, Japan, and Randell Degges, Community Manager for Snyk Global
- 投稿日:2022-02-01T00:14:54+09:00
JavaScriptの関数【関数宣言,アロー関数,無名関数,即時関数,コールバック関数,高階関数】
はじめに よくJavaScriptの基礎を理解せずに、ReactやTypeScriptに手を出して自滅する事例をよく耳にするので、同じ轍を踏まぬようJavaScritpの基礎をしっかり固めたいと思い今回の記事を執筆しました。 記事といっても自分自身のアウトプット理解用、備忘録なので気楽にみていただければ良いかと思います。 今回はJSの関数です。 JavaScriptの関数 ?=??2みたいな数学で習うものではなくて、 JSにおける関数というのは一連の処理のまとまりを指しますね。 関数の定義 そんな関数の作り方は大きく分けて2つ。 関数宣言と関数式に分けられます。 関数宣言 functionで関数を宣言します。 関数名の後に()をつけると関数を実行するという意味になります。 function 関数名(引数) { 処理内容 } ホイスティング 関数宣言は宣言されているコードより前に記述しても実行されるホイスティングと言われる機能があります。 hello(); function hello() { console.log("こんにちは") } // 出力結果 こんにちは helloを先に実行していますが、ちゃんとホイスティングで関数を巻き上げていますので、エラーになりません。 関数の定義と実行 関数を宣言して、そのあと実行するパターン。 // 関数の定義 function sum(a, b) { return a + b; } // 関数の実行 const answer = sum(1, 2); console.log(answer); 関数式 関数式は、function(引数){}という無名の関数を変数に定義したり、代入したりする記述方法になります。 変数 = function(引数) { 処理内容 } 関数宣言と関数式の違い 関数宣言の方が先に読み込まれる JavaScriptにおいては関数宣言の方が先に読み込まれます。 関数宣言は重複してもOK 関数宣言は同じ記述をしてもエラーにならず、後から書いた関数に上書きされます。 関数式の場合は重複した場合エラーになります。 // 関数宣言の場合 function hello() { console.log("こんにちは"); } function hello() { console.log("はじめまして"); } hello(); // 出力結果 はじめまして // 関数式の場合 const hello = function() { console.log("こんにちは"); } const hello = function() { console.log("はじめまして"); } hello(); // 出力結果 Uncaught SyntaxError: Identifier 'hello' has already been declared 関数のいろいろ 無名関数 無名関数はその名の通り、関数名がない定義の仕方です。 基本形 // 無名関数 const hello = function(){ console.log('hello') } // 関数宣言 function hello(){ console.log('hello') } 関数宣言はhelloという関数を宣言していますが、 無名関数ではhelloという変数に代入して関数を定義しています。 retrun(戻り値) JSでは、関数の戻り値はreturnで明示する必要があります。 function sum(a, b) { return a + b; } const a = 1 const b = 2 console.log(sum(a, b)); // 出力結果 3 const sum = function (a, b){ result = a + b; return result; } const answer = sum(1, 2); console.log(answer); // 出力結果 3 変数sumにfunction以下の関数定義を代入しています。 即時関数 即時関数は関数を定義すると同時に実行される構文です。 関数を定義してから呼び出すという手間を省くことができます。 基本形 // 即時関数 (function 変数(引数) { console.log(引数) })() 返り値 const sum = (function (a, b) { return a + b; }(1, 2)); console.log(sum); // 出力結果 3 即時関数を使う場合は、function()内の外側で変数を使用することができません。 関数の中で定義した変数は関数の中でしか使用できないスコープの原理を意識しなければなりません。 アロー関数 ES6で追加された、無名関数や即時関数において、より省略した記述をする場合に使います。 アロー関数の記法 アロー関数はfunction省いて() =>といった記述によって関数を定義します。 引数がない場合 // アロー関数 () => { 処理内容 }; // 無名関数 function() { 処理内容 }; 引数が一つの場合 // アロー関数 引数 => { 処理内容 }; // 無名関数 function(引数) { 処理内容 }; 引数が複数ある場合 // アロー関数 ( 引数1, 引数2 ) => { 処理内容 }; // 無名関数 function( 引数1, 引数2 ) { 処理内容 }; 基本形 まず同じ関数の定義を関数宣言と関数式で書いてみます。 // 関数宣言の書き方 function greeting(name) { return 'Hello, ' + name; } console.log(greeting('ホリトモ')); // 出力結果 Hello, ホリトモ // 関数式(無名関数)の書き方 const greeting = function(name) { return 'Hello, ' + name; } console.log(greeting('ホリトモ')); // 出力結果 Hello, ホリトモ これらを、アロー関数で書くことによって、functionやreturnを省略して端的にかけることができます。 functionのみ省略 const greeting = (name) => { return 'Hello, ' + name; } console.log(greeting('ホリトモ')); functionやreturnを省略 const greeting = name => 'Hello, ' + name; console.log(greeting('ホリトモ')); returnを省略する場合は{}も省略する必要があります。 アロー関数のメリット この記事ではサラッと書きますが、アロー関数のメリットとしては 端的に短い記法で書けること thisやargumentsを保持せず、関数定義時に決められる といったことがあげられます。 コールバック関数 コールバック関数は、他の関数に引数として渡す関数をいいます。 関数はオブジェクトとして捉えられるので、他の引数に渡せるということのようですね。 基本形 function saySomething(callback) { const result = callback(); console.log(`${result}、ホリトモ!`); } function hello() { return "こんにちは"; } saySomething(hello); // 出力結果>> こんにちは、ホリトモ! helloというコールバック関数をsaySomethingという関数の引数にしています。 そしてsaySomething関数内ではcallbackという引数で扱われます。 主に非同期処理を行う際に使用されます。 呼び出した関数の処理が終わったあとにコールバック関数として指定した関数を実行させたい場合に使用されます。 よくあるコールバック関数の使用例:setTimeout setTimeout関数はJSにあらかじめ搭載されている組み込み関数です。 第一引数で渡したコールバック関数を特定の秒数を待ってから実行させます。 function hello() { console.log("こんにちは"); } setTimeout( hello, 3000 ) // 3秒後に出力 // 出力結果 こんにちは helloというコールバック関数をsetTimeoutの第一引数にとっています。 また無名関数を使ってコールバック関数を定義することも可能です。 setTimeout( function() { console.log("こんにちは" ); }, 3000 ); // 出力結果 こんにちは 高階関数 参考:コールバック関数/高階関数を利用する コールバック関数とは反対に、別の関数を引数として受け取る関数のことを高階関数と呼びます。 function apple(price, func){ const name = 'りんご'; func(name, price); } const price = function(name, price){ console.log(name + ' の値段は ' + price + ' 円です。'); } apple(200, price); // 出力結果 りんご の値段は 100 円です。 appleという関数を呼び出す際に引数として数値のほかに別途定義した関数priceを渡しています。 関数appleでは受け取った関数をブロック内で呼び出しています。 このようにJavaScriptでは別途定義した関数を別の関数を呼び出す際に引数にして渡すことができます。 引数として渡される関数(price)のことをコールバック関数、別の関数を引数として受け取る関数(apple)を高階関数と呼びます。 まとめ 関数名 内容 用途 無名関数 関数名を定義しない書き方 関数名の重複を避けたい場合 即時関数 関数の定義と実行を同時にする スコープを限定する場合流用する可能性のない関数を定義する場合 アロー関数 無名関数を省略して書くようなもの thisやargumentsを保持させないようにする コールバック関数 別の関数に引数として渡す promiseや非同期処理など 高階関数 別の関数から引数として受け取る promiseや非同期処理など JSの基礎を理解するには関数をよく理解する必要があります。 他にも細かいルールがあるので、別記事で書いていきたいと思います。 ※記事内容は今後もアップデートしていきます。 (thisや非同期など書き足らない部分が多々あります、、、)