- 投稿日:2019-07-09T23:32:48+09:00
【pugのコメント】HTML方式のコメントとスラッシュの使い分け
こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。
pugのコメントにはHTML方式のコメントではなくスラッシュを使おう
pugのコメントにはHTMLのコメントの
<!-- コメント -->
のような形と、
// コメント
というスラッシュを使ったコメントがあります。使い分けについてです。
// コメントを使うと、その行の階層以下のpugもコメントとなります。
つまり、// コメントはその行の階層以下もコメントにしたいときに使うと良いです。一時的にコメントアウトしたい場合がよく当てはまると思います。
<!-- コメント -->はより柔軟に使えます。この記事が参考になれば幸いです。
終わりに
Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/
- 投稿日:2019-07-09T22:57:11+09:00
MaterializecssのCarouselを使用して、不特定多数の@変数sをeachで表現する
概要
TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
その一部のページでMaterializecssのCarouselを使用し、users_controller.rb
で定義した@usersをそれぞれ"carousel-item"で表現したので紹介します。MaterializecssのCarouselとは
画像をくるくると回せる機能です。
https://materializecss.com/carousel.html自分が作成したページ紹介
画像が自動で切り替わる方法は
こちらを参照していただけば幸甚です(ついでにイイね)
作成する前提
MaterializecssがCDNで読み込めている
編集するファイル
・コントローラーファイル
・ビューファイルコントローラーファイル
user_contoroller.rbdef show user=User.find(params[:id]) @users=User.where(training: user.training) end上の例では、表示しているuserページのuserのtrainingと同じtrainingを持っている人を
@usersで定義してます。ビューファイル
show.html.erb<div class="card"> <h6>この人と同じトレーニングが好きな人</h6> <div class="carousel #fafafa grey lighten-5" id="recommend_user" > <% users.each do |user| %> <a class="carousel-item" href= "/users/<%= user.id %>" data-id ="<%=user.id %>" > <%=user.nickname%> <img src=""> <%= image_tag image(user),class:'circle' %> </a> <%end%> </div> </div>eachメソッドで"carousel-item"ごと表現しています。
名前とプロフ画像を描画してます。最後に
この記事を書いた目的
・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)筆者について
TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思いますひとこと
最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!
- 投稿日:2019-07-09T22:57:06+09:00
変わらないReact VDOMをBabelで最適化
不思議なもので、少し前に悩んでいたことを解決するためのBabelプラグインにちょうどめぐりあうことができました。
JSXの中身
Reactを書くときに使うJSXですが、実際にJavaScriptとして処理する際には
React.createElementという関数(リファレンス)に変換されます。// before (<p> 文章 <a href="https://qiita.com/">Qiita</a> </p>) // after React.createElement("p", null, "文章", React.createElement("a", { href: "https://qiita.com/" }, "Qiita" ) );
<br />のような表記を見ればリテラルのようにも思えますが、実態としては関数呼び出しになっています。さらに、React.createElementの中身は引数チェックをして、与えられた引数をオブジェクトに詰め込む程度のシンプルなもので、特にキャッシュなどはなされないので、<br />のようなごくシンプルなVDOMエレメントであっても、呼び出すたびに新しいものが作られます。毎回生成するデメリット
もちろん、
React.createElementは、本物のDOMの生成と比べれば、ずっと軽い処理です。とはいえ、全く同じ仮想DOMを何度も生成すると、以下のような点で無駄が発生します。
React.createElementの実行時間- 各回ごとに別なオブジェクトが生成するので、
- メモリを消費し、ガベージコレクタにより負荷をかける
- 比較しても一致しないので、そのVDOMを別なコンポーネントの引数にした場合に
React.memoが効かなくなる自分が少し前にした投稿では、最後のデメリットが気になったのでした。
Babelで解決
何気なくBabelのChangeLogを読んでいたところ、
@babel/plugin-transform-react-constant-elementsというプラグインの存在を知りました。これは、JSXが同じスコープ内の変数を使っていない場合に。できるだけ上位のスコープ(完全に何も変わらない場合はトップスコープ)まで引き上げる、というものです、書かれていた例// before const Hr = () => { return <hr className="hr" />; }; // after const _ref = <hr className="hr" />; const Hr = () => { return _ref; };手動でこのような作業をやっていたので、喜び勇んで
yarn addしたところで、望み通りの結果が得られるようになりました。ただ、(もちろん有害になることではなさそうとはいえ)childrenの中身の一部で変化しないエレメントも外側にくくりだされていて、けっこう積極的に処理しているんだなと感じた次第でした。
- 投稿日:2019-07-09T21:35:45+09:00
Fetch APIでデーターを取得しながらPromiseとasyc/awaitを学んだまとめ
シンプルな例で2つの書き方をまとめる
MDNなどのリファレンスはとても参考になるのですが、例題が少し難しく感じる・・・ということで、シンプルにPromiseとAsync/Awaitの記法をメモしておきます。
Fetch APIを使う時が一番Promiseを使っている(?)気がするので、便利なjsonplaceholderを使ってサンプルを作成します。
Promise
fetch('https://jsonplaceholder.typicode.com/photos') .then(response => response.json()) .then(postDate => console.log(postDate)) .catch(err => console.error(err));Chrome Developerツールを開いて、上記をconsoleへコピペすると、データが取得できる。5000件も!
おまけ
取り扱いが大変な時は、sliceを使って、件数を制限することも可能。
(sliceなどのシンタックスを覚えるのは簡単ですが、どのような場面で使えるのかという視点をなかなか持てなかったので、知った時は結構嬉しかったですw・・・)fetch()の結果はPromiseで返されます。
fetch('https://jsonplaceholder.typicode.com/photos') .then(response => response.json()) .then(postsDate => console.log(postsDate.slice(0, 50))) // 50件のみ表示 .catch(err => console.error(err));async/ await
次にES8で登場したasync/awaitを使ったfetchのしかた。
const getPostData = async () => { const response = await fetch('https://jsonplaceholder.typicode.com/photos'); const postsData = await response.json(); console.log(postsData); }; getPostData();
アロー関数の場合、asyncをどこに書くべきかわからなかったのですが、何パターンか書き方を見ると、置き場所がわかるかと思います。// ES5 async function getPostData() { const response = await fetch('https://jsonplaceholder.typicode.com/photos'); const postsData = await response.json(); console.log(postsData); } getPostData(); const getPostData = async function () { // 省略 } // アロー関数 const getPostData = async () => { // 省略 }"function"の前にasync!!!
エラー
fetchを使う時は
.catch(err)でエラー処理を書けるのですが、async/awaitの時は・・・・??
trycatchを使います!const getPostData = async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/photos'); const postsData = await response.json(); console.log(postsData); } catch (err) { console.error(err); } }; getPostData();
catch (err) { /* ここにエラー時の処理を */ }感想
Node, Express, MongoDBの勉強をしている際、データベースとのやりとりでasync/awaitが多用されていました。
asyncは慣れると読みやすいし、書きやすいのですが、いつ使うのかを判断するのがなかなか難しいかったです。
ある程度はパターンなのかもしれませんが、一つの処理に時間がかかってその他の処理が止まってしまうのを防ぐために並行処理を行うようなので、その視点を持って書いてみようと思います。
- 投稿日:2019-07-09T20:33:01+09:00
LeafletとFlaskでシンプルなWebGISをつくった話
はじめに
オープンソースのGISにはQGISというド定番・大正義が存在しますが、インストールの手間、投影法など、若干敷居が高いです(初心者にとって)。ウェブ上で公開されている多数のオープンGISデータ(国土数値情報など)の内容をすぐに確認出来る、シンプルなWebGISがあれば便利なんじゃないかと思い開発したのがVanillaGISです。
VanillaGIS
Vanilla GIS
GitHub - Kanahiro/vanilla-gis使用例(北海道のバス・鉄道路線図)
技術概要
主な使用ライブラリ
- Leaflet(JavaScript)
- Flask(Python)
フロントエンド
Leafletと以下の外部プラグインを使用しています。
- Leaflet.Control.Custom
- Leaflet.Control.Draw
- Leaflet.Control.Appearance(自作プラグイン)
バックエンド
- Flask
- SQLite
- SQLAlchemy
技術詳細
バックエンド(Flask)
前提としてRESTful APIを意識し、URLはリソースを参照する形式になっています。
/(ルート)
- GET
地図画面の表示。
- POST
外部ファイル(.zip(シェープファイル), .geojson)を投げるとjsonデータを返します。
.zipファイルの中身はシェープファイル(.shp, .shx, dbf)である必要があります。
pyshpモジュールを利用し、内部的にjsonに変換しています(LeafletはGeoJSONを読み込めるため)。
オープンデータで提供されるシェープファイルの多くはShift-JISエンコーディングですが、UTF-8やその他のエンコードである場合も踏まえ、通常利用されるすべてのエンコードに対応させています。Pythonでデコードする際、エンコードが相違しているとUnicode Errorが発生します。事前に主要なエンコードをリストにしておき、デコードでエラーが発生しなくなるまで各エンコードを順番にトライします(以下の記事で紹介しています)。シェープファイルをGeoJSON形式に変換する - Qiita
/user_map/
- GET
ユーザーが作成し保存したカスタムマップの、レイヤーデータ以外を返します。
- PUT
ユーザーが作成し保存したカスタムマップの、レイヤーデータを返します。
GETで返すと、レイヤーデータが読み込むまでページ全体の読み込みが完了しないため、GETとはレスポンスを分離しています。Fetchにより非同期でレスポンスを取得します。
- POST
ユーザーが作成したカスタムマップをDBに保存します(公開されているアプリでは実装していません)。
/export
これだけnot RESTfulです。
- POST
ユーザーが作成し保存したレイヤーを投げるとjsonデータが返ってきます。
フロントでは、返ってきたデータを.geojsonとしてダウンロードさせます。FlaskとJavaScriptのFetch通信でダウンロードさせる方法 - Qiita
フロントエンド
シングルページアプリケーションで、常にindex.htmlが表示されます。
/user_map/では、index.htmlにDBから取得したデータを渡しています。
Leafletと外部プラグイン(各種Control)を組み合わせる事でGUIを実装しています。使用しているControl
- L.control.scale
純正プラグイン。地図の縮尺を表示。
- L.control.custom
外部プラグイン。htmlを記述する事でかなり自由にGUIを作成可能。
- L.control.draw
外部プラグイン。手書き図形の作図機能。
- L.control.Appearance
自作プラグイン。純正のL.control.layerと、外部プラグインのL.control.Opacityを参考に作成。
ベースマップ切り替え、オーバーレイのオンオフ、レイヤーごとの透過度設定、オーバーレイごとの色設定、オーバーレイの削除。
各レイヤーをひとつのプラグインで管理したくて作成(Chromeで動作確認済み)。
※レイヤーのプロパティにcolorを設定しないと、色設定機能は動作しないなど、まだ未完成です。GitHub - Kanahiro/Leaflet.Control.Appearance
外部ファイル読み込み
ウィンドウにドラッグドロップする事で、ファイルを読み込みます。
対応している形式は.zip(.shp, .shx, .dbfのアーカイブ)か.geojsonです。
すべてFetchでサーバーに投げて、返ってきた.jsonをLeafletマップに追加します。
- 投稿日:2019-07-09T20:16:04+09:00
Leaflet.jsのパフォーマンス改善について
はじめに
Leafletは実装が簡単でカスタマイズ性も高く、非常に使いやすいWeb地図ライブラリですが、デフォルトの設定だと、多数の地物で構成されるベクターレイヤーの描画に、パフォーマンス上の問題がありました。要はポリゴンが多くなると途端に動作が重くなります。色々調べたところ、ある程度改善したのでその方法を示します。
オプションをいじるだけ
https://stackoverflow.com/questions/23745436/leaflet-js-with-a-vector-layer-is-very-slow
たどり着いたのがこのStackoverflowのスレッド。マップのpreferCanvasオプションを変えてみろ、と言っています。実装
https://leafletjs.com/reference-1.5.0.html#global
公式ドキュメントによれば、preferCanvasオプションにより、SVGレンダラーかCanvasレンダラーか、どちらで地物を描画するか設定可能とのこと。デフォルト値はfalseで、SVGレンダラーに設定されている。一般にどっちが重いか軽いか、わからないがとりあえず試してみると、劇的に改善した。つまりCanvasによるレンダリングの方が軽いということですね。var map = L.map('map_container',{ preferCanvas:true, //trueとし、Canvasレンダラーを選択 });
- 投稿日:2019-07-09T20:14:33+09:00
FlaskとJavaScriptのFetch通信でダウンロードさせる方法
はじめに
Flaskのレスポンスによってファイルをダウンロードさせる方法は以下のリンクのとおり3つあるようです。
https://qiita.com/5zm/items/760000cf63b176be544cしかしながら、Fetch通信によるPOSTに対するresponseにはこれらの方法だけでは対応出来ませんでした。解決した方法を以下に載せておきます。
前提
①Fetch通信で、Flaskサーバに対しPOSTリクエスト
②Flaskサーバでデータを処理し、ファイルを生成、responseヘッダに添付
③Fetchでresponseを受け取って、ダウンロードしたい③で引っかかっていました。単なるinputによるPOSTとは勝手が違うようで、responseはメモリ上で受けるだけです。
解決方法
fetch("/export", { method:"POST", body:formdata //データを添付 }) .then(response => response.blob()) //blobで読み込む .then(blob => { //DOMでダウンロードファイルを添付したアンカー要素を生成 let anchor = document.createElement("a"); anchor.download = String(Date.now()) + '.geojson' anchor.href = window.URL.createObjectURL(blob); //アンカーを発火 anchor.click(); }) .catch(function(err) { console.log("err=" + err); });要はresponseをblobで読み込んでアンカーリンクを踏ませた事にするというわけですね。
- 投稿日:2019-07-09T20:11:56+09:00
Pixi.jsでつくるスワイプスタイルのクイズアプリ[その2]
前回に引き続きクイズアプリを作っていきます。
前回タイトル画面を作成したので、今回ゲーム内の画面を作っていきます。
イメージしている画面と処理フローは下記のような感じです。画面構成
前回、横長でタイトル画面を作成しましたが、やはりスマホで遊ぶことを前提にしたので、
縦長にしました。一旦500x800で作成。処理フロー
Code it.
まだ模索中なので、随時タッチイベントの処理は変えるかもしれません。
画面の切り替え(?)
本来ならばちゃんとシーンを切り替えようの実装をしたほうがよいが、PIXIでのシーン切り替えのベストメソッドがわからないかつ
そんなにシーンが切り替わる仕様ではないので、切り替えの瞬間はオブジェクトを全削除して、同じstage?で再配置するようにしました。removeAll(?)とかいう関数があったがなぜか動かないので、今のところはいったんループ回して対応。
function destroySceneObjects() { //要素全削除 removeChildrenAllとかつかえないポイ。 for (var i = app.stage.children.length - 1; i >= 0; i--) { app.stage.removeChildAt(i); } }タイトルクリック時の処理
button .on('click', function () { //alert('button clicked'); //一旦部品をすべて除去 destroySceneObjects(); gameScene(); })gameSceneにメイン画面で使うスプライトたちをいれこんでいく。今日はまず金の斧(=QuestionImage)を配置してみる。
function gameScene() { console.log("すたーと"); setQuestionImage(container); }メイン画面におくスプライトたち
今回は金の斧のイメージのみおく。
function setQuestionImage(container) { var quiz_texture = PIXI.Texture.from('img/quiz_img.png'); var questionImage = new PIXI.Sprite(quiz_texture); questionImage.anchor.x = 0.5; questionImage.anchor.y = 0.5; //配置は直感できめている。 questionImage.position.x = app.screen.width / 2 - questionImage.width / 2; questionImage.position.y = 300 + questionImage.height / 2; touchQuestionImage(questionImage); app.stage.addChild(questionImage); }タッチイベントたち
function touchQuestionImage(questionImage) { questionImage.interactive = true; questionImage.buttonMode = true; questionImage .on('mousedown', onDragStart) .on('touchstart', onDragStart) // ドロップ(ドラッグを終了) .on('mouseup', onDragEnd) .on('mouseupoutside', onDragEnd) .on('touchend', onDragEnd) .on('touchendoutside', onDragEnd) // ドラッグ中 .on('mousemove', onDragMove) .on('touchmove', onDragMove) function onDragStart(event) { this.data = event.data this.alpha = 0.5 this.dragging = true } function onDragEnd() { this.alpha = 1 this.dragging = false // set the interaction data to null this.data = null // 判定 if (this.position.x > 280) { alert("〇の方向"); nextQuestion();} if (this.position.x < 180) { alert("×の方向"); nextQuestion();} } function onDragMove() { if (this.dragging) { var newPosition = this.data.getLocalPosition(this.parent) this.position.x = newPosition.x this.position.y = newPosition.y } } function onButtonOver() { this.isOver = true; if (this.isdown) { return; } this.texture = textureButtonOver; } }
- 投稿日:2019-07-09T20:03:56+09:00
electronメモ
electronメモ
electronでHello Worldアプリ作成
$ npm i electron -g $ npm i electron-packager -g$ vi main.jsconst electron = require('electron'); const app = electron.app; const BrowserWindow = electron.BrowserWindow; let mainWindow = null; app.on('ready', () => { // mainWindowを作成(windowの大きさや、Kioskモードにするかどうかなどもここで定義できる) mainWindow = new BrowserWindow({width: 400, height: 300}); // Electronに表示するhtmlを絶対パスで指定(相対パスだと動かない) mainWindow.loadURL('file://' + __dirname + '/index.html'); // ChromiumのDevツールを開く mainWindow.webContents.openDevTools(); mainWindow.on('closed', function() { mainWindow = null; }); });$ vi index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> </head> <body> Hello World! </body> </html>Mac用に実行ファイルを生成
$ electron -v v5.0.6$ electron-packager . electron-sample --platform=darwin --arch=x64 --electron-version=5.0.6これでelectron-sample.appというアプリケーションが作成されます。
参考
- 投稿日:2019-07-09T20:01:45+09:00
JQuery初心者のためのメモ
はじめに
この記事はhtmlやcssに多少知識はありjavascriptも慣れてはきたが、
$とか出てくると途端にわからなくなる人向けの絶妙な記事です。
自分のメモに近いので分かりにくかったらすみません。
ほんとは画像とか交えながらまとめたかったんですけどね…。JQueryとは
javascriptのライブラリでhtmlやcssをjavascript上で簡単に扱えるようにするものです。
定義の基本
以下のように$をつけることでJQueryのオブジェクトとして定義できる。
// 定義の仕方1 var $hoge; // 定義の仕方2 var hoga; $(hoga).なんかしらのメソッドhtmlに記述されたclassやidを指定して変数として扱う場合は以下のとおり。
cssと一緒でクラスには.を、idには#をつける。$('.some_class').なんかしらのメソッド ; // クラスで指定 $('#some_id').なんかしらのメソッド ; // idで指定 $('li').なんかしらのメソッド ; // htmlタグで指定よく使うメソッド
初期化関数(コンストラクタ的なやつ)
ページを開いた際に最初に実行される初期化関数の定義は下記のとおり。
最初は慣れないけども、書き方として覚える。$(function() { // ここに書いた内容がページを開いた際に実行される });クリックしたら走る関数
htmlに記述された何かがクリックされたときに処理を行う際の記述。
$('.some_class').click(function() { // 'some_class'をクリックした際に実行される処理 });クリックされた「これ!」を指定する場合は変数
$(this)を用いる。
例えば下記のようなボタンがいくつか配置された場合があるとする。<div class="index-btn">1</div> <div class="index-btn">2</div> <div class="index-btn">3</div>クリックした「これ!」を指定する場合は以下の通り。
// クラス"index-btn"の中でクリックしたものを$clickedElementに代入 $('.index-btn').click(function() { var $clickedElement = $(this); });マウスを乗せたら走る関数
あるhtmlのタグ上にマウスが乗っているとき実行される処理は
hover関数によって記述できる。$('.some-class').hover(function(){ // 'some_class'の上にマウスが乗っているとき実行される処理 });基本的にはマウスが離れたときの処理も以下のようにして記述する。
$('.some-class').hover( function(){ // 'some_class'の上にマウスが乗っているとき実行される処理 }, function(){ // 'some_class'の上からマウスが離れたとき実行される処理 } );クラスの追加・削除
addClass('new_class')でクラスを追加できる。
このときは.や#は必要ないので注意。
例えばクリックした要素に新しいクラスを追加する場合は以下の通り。$('.some_class').click(function() { $('.some_class').addClass('new_class'); });逆に削除は
removeClass('some_class')で行える。クラス等の属性の値を取得
そもそもhtmlにおけるclassやidを「属性 (attributes)」と呼ぶ。
属性を指定し、その値を所得するには関数attr()を使う。// <a>タグのURL情報を取得 var url = $('a').attr('href');htmlリストタグのインデックス番号の取得
htmlの
<li>にはインデックスという概念が存在する。
配列のインデックスと一緒で0から始まるもの。<li class="fisrt_list">インデックス0</li> <li class="second_list">インデックス1</li> <li class="third_list">インデックス2</li>上記の例の場合、関数
index()でインデックスを取得できる。var listIndex = $('li').index($('.second_list'));また、クリックした「これ!」のインデックスは以下のとおり。
$('li').click(function() { var $clickedIndex = $('li').index($(this)); });htmlリストタグの長さの取得
配列の
lengthメソッドと同じようなノリで<li>の長さを取得できる。var theLastIndex = $('list_class').length - 1要素の表示・非表示
$('.some_class').show()で表示。
$('.some_class').hide()で非表示。
例えば、スライドのインデックス番号に応じてボタンを非表示にしたいとき。
htmlを以下のようなものとすると、<div class="change-btn prev-btn">← 前へ</div> <div class="change-btn next-btn">次へ →</div>jsファイルの中身は以下の通り。
$(function() { // はじめに全てのボタンを表示 $('.change-btn').show(); // スライド番号に応じてボタンを非表示 if (slideIndex == 0) { $('.prev-btn').hide(); } else if (slideIndex == 3) { $('.next-btn').hide(); } });htmlで表示される文字列を編集・取得
text()関数を用いることで文字列関連の操作を行える。// <h1>タグの文字列を書き換える場合 $('h1').text('変更しちゃった'); // <h1>タグからの文字列の取得 var text = $('h1').text();フォームで使うメソッド
送信ボタンを押したら走るメソッド
送信ボタンが押された際の処理の記述を
submit関数で行うことができる。
例えばフォームの送信ボタンを以下のようにhtmlで記述したとする。<form id="form"> <!-- 回答フォームに関する記述 --> <button type="submit" class="btn btn-submit">送信</button> </form>この送信ボタンが押されたとき実行される処理は以下のように記述する。
$('#form').submit(function() { // 送信ボタンが押されたときに行う処理の記述 });選択肢から選んだ要素を取得
htmlではタグを使って選択フォームを生成できる。<form id="form"> <select id="select-form" class="select-form"> <option value="0">選択してください</option> <option value="1">1. 犬派</option> <option value="2">2. 猫派</option> <option value="3">3. それ以外</option> </select> <button type="submit" class="btn btn-submit">送信</button> </form>選択された値は
val()関数によって取得できる。$('#form').submit(function() { var selectedValue = $('#select-form').val(); });入力フォームに記載された内容を取得
選択フォームとほぼ同じ。
htmlにおける入力フォームの生成は以下のとおり。<form id="form"> <textarea id="text-form"></textarea> <button type="submit" class="btn btn-submit">送信</button> </form>入力された値も
val()関数によって取得できる。$('#form').submit(function() { var textValue = $('#text-form').val(); });
- 投稿日:2019-07-09T20:01:45+09:00
jQuery初心者のためのメモ
はじめに
この記事はhtmlやcssに多少知識はありjavascriptも慣れてはきたが、
$とか出てくると途端にわからなくなる人向けの絶妙な記事です。
自分のメモに近いので分かりにくかったらすみません。
ほんとは画像とか交えながらまとめたかったんですけどね…。JQueryとは
javascriptのライブラリでhtmlやcssをjavascript上で簡単に扱えるようにするものです。
定義の基本
以下のように$をつけることでJQueryのオブジェクトとして定義できる。
// 定義の仕方1 var $hoge; // 定義の仕方2 var hoga; $(hoga).なんかしらのメソッドhtmlに記述されたclassやidを指定して変数として扱う場合は以下のとおり。
cssと一緒でクラスには.を、idには#をつける。$('.some_class').なんかしらのメソッド ; // クラスで指定 $('#some_id').なんかしらのメソッド ; // idで指定 $('li').なんかしらのメソッド ; // htmlタグで指定よく使うメソッド
初期化関数(コンストラクタ的なやつ)
ページを開いた際に最初に実行される初期化関数の定義は下記のとおり。
最初は慣れないけども、書き方として覚える。$(function() { // ここに書いた内容がページを開いた際に実行される });クリックしたら走る関数
htmlに記述された何かがクリックされたときに処理を行う際の記述。
$('.some_class').click(function() { // 'some_class'をクリックした際に実行される処理 });クリックされた「これ!」を指定する場合は変数
$(this)を用いる。
例えば下記のようなボタンがいくつか配置された場合があるとする。<div class="index-btn">1</div> <div class="index-btn">2</div> <div class="index-btn">3</div>クリックした「これ!」を指定する場合は以下の通り。
// クラス"index-btn"の中でクリックしたものを$clickedElementに代入 $('.index-btn').click(function() { var $clickedElement = $(this); });マウスを乗せたら走る関数
あるhtmlのタグ上にマウスが乗っているとき実行される処理は
hover関数によって記述できる。$('.some-class').hover(function(){ // 'some_class'の上にマウスが乗っているとき実行される処理 });基本的にはマウスが離れたときの処理も以下のようにして記述する。
$('.some-class').hover( function(){ // 'some_class'の上にマウスが乗っているとき実行される処理 } function{ // 'some_class'の上からマウスが離れたとき実行される処理 } );クラスの追加・削除
addClass('new_class')でクラスを追加できる。
このときは.や#は必要ないので注意。
例えばクリックした要素に新しいクラスを追加する場合は以下の通り。$('.some_class').click(function() { $('.some_class').addClass('new_class'); });逆に削除は
removeClass('some_class')で行える。クラス等の属性の値を取得
そもそもhtmlにおけるclassやidを「属性 (attributes)」と呼ぶ。
属性を指定し、その値を所得するには関数attr()を使う。// <a>タグのURL情報を取得 var url = $('a').attr('href');htmlリストタグのインデックス番号の取得
htmlの
<li>にはインデックスという概念が存在する。
配列のインデックスと一緒で0から始まるもの。<li class="fisrt_list">インデックス0</li> <li class="second_list">インデックス1</li> <li class="third_list">インデックス2</li>上記の例の場合、関数
index()でインデックスを取得できる。var listIndex = $('li').index($('.second_list'));また、クリックした「これ!」のインデックスは以下のとおり。
$('li').click(function() { var $clickedIndex = $('li').index($(this)); });htmlリストタグの長さの取得
配列の
lengthメソッドと同じようなノリで<li>の長さを取得できる。var theLastIndex = $('list_class').length - 1要素の表示・非表示
$('.some_class').show()で表示。
$('.some_class').hide()で非表示。
例えば、スライドのインデックス番号に応じてボタンを非表示にしたいとき。
htmlを以下のようなものとすると、<div class="change-btn prev-btn">← 前へ</div> <div class="change-btn next-btn">次へ →</div>jsファイルの中身は以下の通り。
$(function() { // はじめに全てのボタンを表示 $('.change-btn').show(); // スライド番号に応じてボタンを非表示 if (slideIndex == 0) { $('.prev-btn').hide(); } else if (slideIndex == 3) { $('.next-btn').hide(); } });htmlで表示される文字列を編集・取得
text()関数を用いることで文字列関連の操作を行える。// <h1>タグの文字列を書き換える場合 $('h1').text('変更しちゃった'); // <h1>タグからの文字列の取得 var text = $('h1').text();フォームで使うメソッド
送信ボタンを押したら走るメソッド
送信ボタンが押された際の処理の記述を
submit関数で行うことができる。
例えばフォームの送信ボタンを以下のようにhtmlで記述したとする。<form id="form"> <!-- 回答フォームに関する記述 --> <button type="submit" class="btn btn-submit">送信</button> </form>この送信ボタンが押されたとき実行される処理は以下のように記述する。
$('#form').submit(function() { // 送信ボタンが押されたときに行う処理の記述 });選択肢から選んだ要素を取得
htmlではタグを使って選択フォームを生成できる。<form id="form"> <select id="select-form" class="select-form"> <option value="0">選択してください</option> <option value="1">1. 犬派</option> <option value="2">2. 猫派</option> <option value="3">3. それ以外</option> </select> <button type="submit" class="btn btn-submit">送信</button> </form>選択された値は
val()関数によって取得できる。$('#form').submit(function() { var selectedValue = $('#select-form').val(); });入力フォームに記載された内容を取得
選択フォームとほぼ同じ。
htmlにおける入力フォームの生成は以下のとおり。<form id="form"> <textarea id="text-form"></textarea> <button type="submit" class="btn btn-submit">送信</button> </form>入力された値も
val()関数によって取得できる。$('#form').submit(function() { var textValue = $('#text-form').val(); });
- 投稿日:2019-07-09T19:34:35+09:00
本格JavaScript記号プログラミング(2) 記号プログラム変換器を作ろう
おことわり:
前回の記事を投稿したところ、「モバイル端末で読もうとすると、ページを開いた瞬間にブラウザがフリーズして落ちる」というお声を頂きました。
単行で1万文字超にも及ぶ記号プログラムにシンタックスハイライトがかかることで、大量のspanタグが生成されて高負荷となるようです。
対策として、横幅が画面に収まらないほどの巨大な記号プログラムについてはハイライトを行わない、または記事中には掲載せず、代わりにGistへのリンクを記載するようにしています。
第1回ではJavaScript記号プログラミングについての基礎的な考え方を中心として取り上げました。
そのため、実際に記号だけでコードを書く場面では、「このようにすればできますね」というように方向性のみを示すにとどめた箇所が多くあります。
今回の記事では、JavaScriptプログラムを記号化するプログラムの作成を題材として、前回の記事をなぞりながら、実際に記号だけで書かれたプログラムを扱っていきます。レギュレーション
記号プログラムの制約は、前回に引き続き以下のようなものとします。
- プログラム中に使用してよいのは、
[]()+=の6種類の記号のみ。- ブラウザとnode.jsの両方で動作しなければならない。
- よって、明示的な
windowやglobalへのアクセスは禁止。globalThis1も(この記事を書いている途中に殆どの処理系で実装が進みましたが)まだ存在しないということにしておきます。- 実行環境は記事執筆時点でのnode.jsとChromeの最新安定版とします。
記号プログラム変換器を作る
今回は、記号プログラミングの実践も兼ねて「JavaScriptプログラムを受け取り、記号化されたプログラムに変換するプログラム」を作ってみます。
ただし、まずは「とりあえず動く」ものを目指すことにします。
記号プログラミングの常としてものすごい長さのプログラムになるかもしれませんが、今回はそのあたりは特に気にせず進めていきます。仕様と方針
作るプログラムの仕様は、以下のようにします。
- JavaScriptで書かれたプログラムを文字列として入力する
[]()+=の6種類の記号のみを使ったプログラムを文字列として出力する- 出力結果をJavaScriptとして実行すると、入力として与えたプログラムを実行した場合と同じ処理をする
入力文字列を1文字ずつ記号で表現して、最後に
evalに通すことにすれば、実装が楽そうでいいですね。そうしましょう。
JavaScriptプログラム中にはどのような文字が現れるかわからないので、文字ごとに対応する記号表現を用意するのは大変です。
そこで、String.prototype.codePointAt()で全ての文字を単なる整数に変換してしまいましょう。
そして、記号プログラムの実行時にString.fromCodePoint()でもともとの文字列を復元し、それをevalで実行させます。たとえば、
1+1を実行する場合、"1"と"+"のコードポイント値はそれぞれ49,43ですから、"1+1"という文字列は49, 43, 49という3つの数値の列で表すことができます。
よって、eval(String.fromCodePoint(49, 43, 49));というプログラムで、
1+1を実行して計算結果である2を得ることができますね。
これを記号で表現したコードが生成できれば、どのような入力が来ても大丈夫そうです。ただし、実のところ前回の記事で解説した内容ではメソッド呼び出し時に2つ以上の引数を渡すことが困難です。
手っ取り早く済みそうなのは、以下のような形式で書いてしまうことです2。eval(eval("String.fromCodePoint(49, 43, 49)"));
evalに加えて"eval","String.fromCodePoint"という文字列、数値、および"'","(",")",","の文字が記号で表現できれば、このようなプログラムを記号で書くことができます。
今回は、このような方針で記号プログラム変換器を作っていきます。
evalを得る前回の記事のおさらいをしましょう。
[]["find"]["constructor"]("return eval")()で
evalへの参照が得られるので、このコード中の文字列をすべて記号で表現したものがevalに対応する記号プログラムとなります。
ここに含まれている文字は、半角スペースとacdefilnorstuvです。
これらの文字が全て記号で表現できれば、+で足していくことで"find","constructor","return eval"を構築できます。この記事中で数百文字・数千文字の記号コードを全部提示していると紙面がいくらあっても足りませんので、
https://gist.github.com/Tatamo/5762f14941889675c4932972ed17deb8#file-eval-js
にevalを得るまでのコードを記載しています。
実際に実行することで、記号で書かれたevalが出力されます。曰く、// === eval [][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]][[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[[]==[]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[]][+[]][++[+[]][+[]]]+[[+[]==[]]+[]][+[]][++[++[+[]][+[]]][+[]]]+[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[]][+[]][++[+[]][+[]]]]([[+[]==[]]+[]][+[]][++[+[]][+[]]]+[[+[]==[]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[]][+[]][++[++[+[]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][++[+[]][+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]+[++[+[]][+[]]]]+[[+[]==[]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[+[]][+[]]][+[]]+[++[++[++[+[]][+[]]][+[]]][+[]]]]+[[[]==[]]+[]][+[]][++[+[]][+[]]]+[[[]==[]]+[]][+[]][++[++[+[]][+[]]][+[]]])()2,077文字らしいです。これを手で書くのは少し苦労するかもしれませんね。
"String.fromCodePoint"を得るこちらも前回の記事で取り上げた内容と被っていますが、
String.fromCodePoint(またはString.fromCharCodeを得るのは少し複雑な過程を経る必要がありました。
今回はString.fromCodePointというメソッドそのものではなく、文字列としての"String.fromCodePoint"を用意する必要がありますが、基本的な方法は変わりません。Object.getOwnPropertyNames(String).sort()[1]というコードを実行して、
"fromCharCode"の文字列を取得することにします。3
また、"String"という文字列はString.nameから得ることができます。これらを文字列を用いたプロパティアクセスの形で書き直して、
""["constructor"]["name"]+"."+[]["entries"]()["constructor"]["getOwnPropertyNames"](""["constructor"])["sort"]()[1]ここに含まれる文字は、
acegimnoprstuwyNOP.です。
この中で、入手にやや難がある文字は"p","w","P"でしょう。
これらは、以下のようにして取得する必要がありました。const p = (25).toString(36); // "p" const w = (32).toString(36); // "w" const P = (Function("return async "+Function())()()+"")[8] // "[object Promise]"[8] === "P"ここで、
"toString"を構築するための"String"は""["constructor"]["name"]で取得でき、他の文字は既に取得済みのものです。以上の内容をもとに、
"String.fromCodePoint"の記号表現を得るためのコードは
https://gist.github.com/Tatamo/5762f14941889675c4932972ed17deb8#file-fromcodepoint-js
となります。
これを実行してみると、22,564文字の記号プログラムが出力されます。さすがに長いですね。自然数を記号に変換する
次は、実行したいプログラムのそれぞれの文字のコードポイント値を記号の形で表現することを考えましょう。
とりあえずインクリメントで作ってみる
0は+[]で表現することができ、++[n][0]と書けばn+1を得ることができるのでした。
よって0以上の整数は帰納的に構築可能であり、これに従うと、自然数を記号表現に変換する処理は以下のように書くことができます。function convertNumber(n) { const zero = "+[]"; if (n === 0) return zero; return `++[${convertNumber(n - 1)}][${zero}]`; }
eval(convertNumber(n))とした結果がnとなりますが、このプログラムには以下の3つの問題があります。
- 出力結果が長すぎる
- $n=20000$ ぐらい4で
convertNumber(n)がスタックオーバーフローを起こす- それより前に、$n=800$ ぐらいで
eval(convertNumber(n))がスタックオーバーフローを起こす1.の問題として、数値をインクリメントするのに9文字必要になるので、単純に作りたい数を9倍した数(に
0を表現するための3文字分を足した数)がその数値を表現するためのコードの文字数になります。
そもそも記号プログラムを書いている時点でコードがものすごい長さになるのは織り込み済みですが、しかし100を表現するのに900文字では、さすがに割に合わない気がします。2.は単純に再帰呼び出しをネストしすぎてコールスタックを使い果たすために起きるエラーであり、何も考えずに再帰関数を書いた際に起きがちな問題ですね。
ES2019がリリースされたというのに、未だにES2015仕様である末尾再帰最適化をサポートする主要な処理系がSafariしかない5ので、この関数を末尾再帰の形に書き直したとしても問題は解決しません。再帰を使わずにループで書き直せば1.の問題は解決する、と思いきや、結局3.の問題が生じます。
convertNumber(n)で記号コードの生成に成功しても、それをevalするときに配列の添字アクセスがネストしすぎてしまうため、やはりスタックオーバーフローが発生してしまいます。6一桁ずつ生成し、文字列として結合する
そこで、はじめからすべて数値として作るのではなく、一桁ずつ個別に生成したのち、文字列として繋ぎ合わせて目的の数を作ることにします。
たとえば42を作る場合、"4"+"2"で"42"を作り、+"42"とすれば目的の42を得ることができますね。
さらに記号プログラム的な書き方に寄せると、+[[4]+[2]]と書くことができます。(
[4]+[2]の部分では、二項演算+によって[4]と[2]が暗黙的にプリミティブに変換され、それぞれ"4"と"2"になって7文字列の結合が行われます。つまり+["42"]になりますが、hint値"number"のもとでの配列からプリミティブへの変換は、まず["42"].valueOf()が["42"]自身を返すために失敗し、フォールバックとして["42"].toString()が呼び出されることになり、結局["42"]は暗黙的に"42"に変換されるため、+"42"が評価されて42になります。8 9)そのように書き直したのが、以下のコードになります。10
function convertNumber(n) { const str = [...n.toString(10)] .map(c => Number.parseInt(c, 10)) .map(d => `[${"++[".repeat(d)}+[]${"][+[]]".repeat(d)}]`) .join("+"); return `+[${str}]`; }
eval(convertNumber(42))などと実行してみれば、正しく42が返ることがわかります。
convertNumber(Number.MAX_SAFE_INTEGER)のように大きな数を与えても問題なく実行できますね。ただし、ここでは単項演算子
+で数値型に再変換していますが、配列の添え字アクセスで使用する場合などは文字列のままでも全く問題ない(a[1]とa["1"]は同じ)ため、わざわざ数値に戻す必要がないことも多いです。
今回の場合も、必要とされているのは"42"のような「数値を表す文字列」なので、return `+[${str}]`;のかわりにreturn str;で文字列として返すだけで十分です。11
以後、この記事ではconvertNumber(n)は[4]+[2]のようにして得られる文字数をそのまま返す実装とみなします。残りの文字を集める
"("と")"は適当な関数を文字列キャストすることで取得できます。[]["find"]+[] // === "function find() { [native code] }" [[]["find"]+[]][0][13] // === "(" [[]["find"]+[]][0][14] // === ")"
","は、要素が複数ある配列をjoin()するか、文字列に変換することで得ることができます。12[]["constructor"](2)+[] // Array(2)+"" === ","これらの記号表現は、
// === "(" [[][[[][[]]+[]][+[]][++[++[++[++[+[]][+[]]][+[]]][+[]]][+[]]]+[[][[]]+[]][+[]][++[++[++[++[++[+[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[]]+[]][+[]][++[+[]][+[]]]+[[][[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[+[]][+[]]+[++[++[++[+[]][+[]]][+[]]][+[]]]] // === ")" [[][[[][[]]+[]][+[]][++[++[++[++[+[]][+[]]][+[]]][+[]]][+[]]]+[[][[]]+[]][+[]][++[++[++[++[++[+[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[]]+[]][+[]][++[+[]][+[]]]+[[][[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[+[]][+[]]+[++[++[++[++[+[]][+[]]][+[]]][+[]]][+[]]]](+[[++[++[+[]][+[]]][+[]]]]) // === "," [[][[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[[]==[]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[]][+[]][++[+[]][+[]]]+[[+[]==[]]+[]][+[]][++[++[+[]][+[]]][+[]]]+[[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]+[]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[[+[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[][[[[]==[]]+[]][+[]][+[]]+[[+[]==[]]+[+[]]+[][+[]]+[]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[][+[]]][+[]][++[+[]][+[]]+[+[]]]+[[][+[]]+[]][+[]][++[++[+[]][+[]]][+[]]]]][+[]][++[+[]][+[]]+[+[]]]+[[+[]==[]]+[]][+[]][++[+[]][+[]]]](++[++[+[]][+[]]][+[]])+[]][+[]]となります。
完成
さて、これで
eval(eval("String.fromCodePoint(49, 43, 49)"));を実行するために必要なものが揃いましたね。
あとは、入力プログラムを受け取って、コードポイントの列に変換して、さらに記号に変換することができれば完成です。
/** * @returns {string} */ function convertNumber(n) { // ここでは上の例とは異なり、数値に再キャストせずに文字列のまま返している return [...n.toString(10)] .map(c => Number.parseInt(c, 10)) .map(d => `[${"++[".repeat(d)}+[]${"][+[]]".repeat(d)}]`) .join("+"); } // 実際にはすべて記号だけで書かれた表現を入れる const _eval = "eval"; // ここだけevalするとeval自身になる、他は文字列となることに注意 const _String_dot_fromCodePoint = '"String.fromCodePoint"'; const _comma = '","'; const _lparen = '"("'; const _rparen = '")"'; function convertProgram(src){ const codes = [...src] .map(c => c.codePointAt()) .map(convertNumber) .join(`+${_comma}+`); return `${_eval}(${_eval}(${_String_dot_fromCodePoint}+${_lparen}+${codes}+${_rparen}))`; }ここではコード長の制約から、本来記号で表現するところを元の表現で書いています。
実際に[]+=()の6種類の記号のみからなる記号プログラムを出力するコードは、
https://gist.github.com/Tatamo/5762f14941889675c4932972ed17deb8#file-convertprogram-js
に掲載しています。convertProgram('console.log("Hello, World!");');で
console.log("Hello, World!");というプログラムを記号のみで表したものが得られます。
(https://gist.github.com/Tatamo/5762f14941889675c4932972ed17deb8#file-helloworld-js )
得られた出力をコピーしてJavaScript処理系に投げるか、またはeval(convertProgram('console.log("Hello, World!");'));とすれば、コンソールに
Hello, World!と表示されます。結果
出力された
Hello, World!プログラムの長さを数えてみたところ、60,831文字でした。
前回の記事冒頭に記載したものは14,178文字でしたから、大幅に悪化している気がしますね?今回作成した変換器で出力されるプログラムは、
eval(eval("String.fromCodePoint(1,2,3,...)"));という形のコードを記号だけで書いたものでした。
このコードのそれぞれの部分の文字数を見てみると、
式 文字数 eval2,077 "String.fromCodePoint"22,564 "("224 ","1,123 ")"233 数字 1桁あたり3~84文字程度 のようになっています。
数字部分は、先述したように一桁ずつ生成してから文字列として結合しており、一桁ごとの文字数は、その数字を $d$ とすると $3+9d$ 文字となります。
ASCII文字に含まれる文字ならコードポイント値は128未満で収まりますから、ほとんどの場合で一文字ごとの文字数は多くても200文字と考えることができそうです。
"String.fromCodePoint"部分にかなりの文字数を要していますが、全体で見れば6万文字のうちの4割未満に収まっています。
どうやら今回のコードでは、","の文字がプログラム長の肥大化を招いているようです。
文字を一文字表現するたびに","で繋いでいくことになりますから、変換元のプログラムの長さが長くなるにつれて","を表す部分の割合がどんどん高くなっていくことが予想されます。
変換元のプログラムの長さが1文字増えるごとに、出力されるプログラムの長さが1,000文字以上増えることになりますから、これでは数値を短く表現できるようにした意味がありませんね。まとめ・次回予告13
今回の記事では、実際に記号のみでJavaScriptプログラムを記述する過程を見ていくとともに、プログラムを記号化するプログラムを(記号プログラムではないJavaScript上で)実装していきました。
扱うプログラムが複雑になるにしたがって、記号プログラムを人力で記述していくのは困難になってくるため、「記号プログラムを文字列として出力するプログラム」を用意しておくことは重要になります。とはいえ今回は第1回で示した概念の実証という意味合いが強く、結果として得られた変換器は出力するプログラムの大きさという点で大いに改善の余地があります。
そこで次回は、文字数短縮をはじめとした記号プログラミング(および記号プログラムを出力するプログラムのプログラミング)におけるテクニックの紹介などを書いてみたいと思っています。
また、そろそろ「記号プログラム」「文字列化したJavaScriptプログラム」「文字列化した記号プログラム」あたりの高階的な概念が頻出し始め、ごちゃごちゃになってきて混乱を招くので、いい感じに表現できるモデルが欲しい気もします。いよいよ皆さんも記号化されたJavaScriptプログラムが書けるようになってきたことと思いますので、次回はより黒魔術と呼べるような闇の深いコードを提示できるように進捗を生んでいきたいと考えています。
おまけ
前回、記号プログラムコンパイラを記号プログラムで書くとか言っていたような気がしますね。
先程作った https://gist.github.com/Tatamo/5762f14941889675c4932972ed17deb8#file-convertprogram-js の全体をクロージャで囲って、(function(){ // ここから記号プログラム変換器 const maping = ... ... function convertProgram(src){ ... } // ここまで return convertProgram; })()こういう感じのプログラムを作って、
const program = "(function(){ const mapping = ...; ...; return convertProgram;})()"文字列にしてから14自分自身に食べさせて、
convertProgram(program); // 記号で書かれた記号プログラム変換器できましたね。
敢えてソースコードを示すことはしませんが、やってみたところ6MB程度(6,000,000文字オーバー)の記号プログラムが生成されました。
eval(eval(convertProgram(program))('console.log("Hello, World!");'));を実行してみたところ、結果が返るまでに数秒かかりましたが正しく
Hello, World!とコンソールに表示されました。
というわけで、使えたものではありませんが「記号プログラムで書かれた記号プログラムコンパイラ」が作成できました。めでたし。
https://github.com/tc39/proposal-global, ?globalThis?と?global?と?this? ↩
他に、
eval(String.fromCodePoint(49)+String.fromCodePoint(43)+String.fromCodePoint(49));という形式で書くことも考えられます。とはいえこれは出力コードの長さがとんでもないことになりそうなので、今回は採用しないことにします。 ↩前回の記事でも指摘した点ですが、この実装の動作は
Stringオブジェクトに存在する他のメソッドの名前に依存するため、将来に渡って正しく動作する保証はありません。今回は簡単のためこのようなコードにしていますが、この時点で手に入っている文字のみで確実に"C"を得られる方法として、Object.getOwnPropertyNames(String).join("").split("").filter(eval("Function.bind(undefined,(undefined+[])[0])")("return [true][u.codePointAt()-67]"))[0]を提示しておきます。 ↩スタックに割り当てられている領域のサイズは処理系によって異なります。node.js(V8)のデフォルトは984KBで、本記事ではこれを基準としています。 ↩
https://kangax.github.io/compat-table/es6/#test-proper_tail_calls_(tail_call_optimisation) ↩
とはいっても800前後でスタックオーバーフローになるのは結構意外でした。配列の添字アクセスって普通の関数呼び出しの数倍以上もコールスタックを消費するんでしょうか? ↩
配列
aをhint値"default"または"string"でプリミティブに変換すると、a.toString()が実行され、a.join()の結果が返ります。 https://www.ecma-international.org/ecma-262/#sec-array.prototype.tostring ↩べつに真面目に読まなくていいです ↩
蛇足ですが、変換する数字のうち最も上の桁だけは配列で囲わずに
1+[2]+[3]+...とすることができます。最初の+演算子の右辺は文字列となるため問題なく"123..."になり、これで2文字節約できます。ほかの桁で[]を外すと、+が複数個連続してしまうためにエラーとなります。 ↩むしろ、前者では先頭に単項演算子の
+が存在するため、返り値に対して前から+二項演算子を適用しようとするとa++xのような形となってしまい、10と同様にエラーが発生するおそれがあるため注意が必要です。このような場合、文字数は増えてしまいますがa+[+x][0]やa+(+x)のような形で囲うことで、意図しない+の連続を防ぐことができます。 ↩7と同様に
Array.prototype.join()が呼び出されますが、Array(2)よりlengthは2となっており、また0番目と1番目の要素はundefinedとして扱われますが、undefinedとnullはjoin()の処理では空文字列に置換されるため、結果として","が返ります https://www.ecma-international.org/ecma-262/#sec-array.prototype.join ↩2本目を書くのに3ヶ月も空けたのにまだ次回作を書く気があるんですか???(大幅に間が空いてしまい申し訳ないです、第3回も気長にお待ちください) ↩
書いたクロージャに名前をつけるなどしておき、文字列に型変換すれば文字列化された実装が得られます ↩
- 投稿日:2019-07-09T19:22:15+09:00
ページ遷移時の「このサイトを離れますか?」を意地でも表示しない方法
フォームの入力値を編集したときなどに表示される「このサイトを離れますか?」というポップアップを非表示にする方法です。
色々と調べましたが、
Object.definePropertyの setter で検知して値を上書きすると確実でした。Object.defineProperty(window, 'onbeforeunload', { set(newValue) { if (typeof newValue === 'function') window.onbeforeunload = null; } });ちなみに
if (typeof newValue === 'function')等のチェックがないと変更を検知し続けて無限ループになり、スタックが大変なことになるで気をつけましょう。また、もし他に良い方法をご存知の方がいたらコメントで教えてください?
参考
- 投稿日:2019-07-09T18:29:52+09:00
イテレータ
JavaScript本格入門(ISBN 978-4774184111)で基礎からJavaScriptを勉強するシリーズです。
今回はChapter5からイテレータについてです。イテレータ
オブジェクトの内容を列挙するための仕組みを備えたオブジェクトのことです。
例えばArrayオブジェクトはイテレータを備えているので、for...ofで呼ぶたびに指しているオブジェクトが変わり、走査することが出来ます。
let team = ['Giorno', 'Bucciarati', 'Mista', 'Fugo', 'Narancia', 'Abbacchio']; for(let d of team) { console.log(d); }実行結果Giorno Bucciarati Mista Fugo Narancia Abbacchioイテレータを備えた組み込みオブジェクトには以下のようなものがあります。
- 投稿日:2019-07-09T17:59:06+09:00
Hello JavaScript!
JavaScript本格入門(ISBN 978-4774184111)で基礎からJavaScriptを勉強するシリーズです。今回はChapter1からです。そもそもJavaScriptとは?という当たり前だけど大事な話。
JavaScriptの特徴
下記のような特徴があるようです。
- スクリプト言語
- 自然言語に近くてとっつきやすいということらしいです
- インタープリター型言語
- 命令を逐次マシン語に変更しながら動作する
- 色々な環境で動作する
- サーバ側であったりクライアント側であったり
ECMAScriptとは
ECMAScriptはJavaScriptの標準のことです。(ESと略したりします)
JavaScriptは、数あるブラウザによりそれぞれで仕様が異なったり方言が乱立していたという歴史があるようです。
そこでEcma Internationalという組織がなんとかせんといかんと言って標準化を始めたということのようです。執筆時点での最新版は ECMAScript 9(ECMAScript 2018) です。
どこから学ぼうかという気持ちになりますが、ECMAScript6(ECMAScript2015)がこれまでの標準化の中で非常に大きな変更となったようですので筆者はここから勉強することにしました。
ES2015で何が追加されたかについて、詳しくは ECMAScript6の新機能について が参考になると思います。
Hello world!
console.log('Hello world!');
- 投稿日:2019-07-09T17:22:29+09:00
配列の値をすっきり比較するやり方まとめ
目的と前提
- 実装するときにいつもひと手間かかってしまうので、配列の比較ロジックの備忘録です。
- ES2015(ES6)のバージョンを想定。
比較パターン
(1) オブジェクトと配列を比較 → 値が重複するオブジェクトをそのまま取り出す
const array = ["test1", "test2", "test3", "test4"]; const objectArray = [ {param: "ちがうよ"}, {param: "ちがうよ"}, {param: "test1"}, {param: "ちがうよ"}, {param: "test2"} ]; const newArray = []; for (const targetValue of array) { const sameValue = objectArray.find(obj => obj.param === targetValue); newArray.push(sameValue); } console.log(newArray); //[ { param: 'test1' }, { param: 'test2' } ](2)オブジェクトと配列を比較 → 値が重複するオブジェクトの値を取り出す
const newValueArray = newArray.map(data => data.param); console.log(newValueArray); // [ 'test1', 'test2' ](3) 配列どうしを比較 → 重複する値を取り出す
const compareArray = ["test1", "test2"]; const arrayOfDupulicates = array.filter(value => compareArray.includes(value)); console.log(arrayOfDupulicates); //[ 'test1', 'test2' ](4) 配列どうしを比較 → 重複しない値を取り出す
const arrayRemovedDupulicates = array.filter(value => !compareArray.includes(value)); console.log(arrayRemovedDupulicates); //[ 'test3', 'test4' ]
- 投稿日:2019-07-09T16:25:18+09:00
iOS SafariのouterHeight, outerWidthが知らないうちに使えるようになっていた
前まで、iOS Safariで
outerHeightやouterWidthを取ると0になる、という現象があったのですが、新しめのシミュレーターで確認したところとれました。どうやらiOS12.2あたりから使えるようです。iOS Simulator での確認結果
確認用HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> </body> <script> var h = document.createElement("div"); var w = document.createElement("div"); h.innerHTML = outerHeight; w.innerHTML = outerWidth; document.body.appendChild(h); document.body.appendChild(w); </script> </html>
- 投稿日:2019-07-09T16:02:52+09:00
【Vue】メニュー周りの権限制御を行う
概要
VueなどでSPAを構築する場合、大体こんな感じのメニューがあると思います。
クリックされた項目でURLをルーティングして内容を変化させるやつですね。
権限制御を行う
このメニューで、以下のような権限制御を行う場合の例をご紹介します。
(※バックエンドについては今回省略します。)
- ユーザーのロールに設定された権限によりメニューの項目を出し分けたい
- 使用する権限を持っていない場合、項目は非表示とする
- URL直打ちでも開けないようにしたい
環境
- Vue 2.6
- VueRouter 3.0
- Vuex 3.0
- Vue Router Multiguard
- VueRouterのbeforeEnternに使用可能なヘルパー関数を用意してくれます
- URLごとにナビゲーションガードを行うために使用します
- https://github.com/atanas-angelov-dev/vue-router-multiguard#readme
テーブル
テーブルはこんな感じです
ユーザーはロールを1つ持ち、ロールは権限を複数持つ構成となっています。ユーザーテーブルのレコード例
ユーザーコード パスワード ロールコード admin ******* admin user1 ******* writer user2 ******* readonly ロール権限のレコード例
ロールコード 権限 admin menu1 admin menu2 admin menu3 writer menu1 writer menu2 readonly menu1 権限を取得
ストア
- ユーザーに紐付いた権限を保持します。
action-types.jsexport const GET_MENU_FLGS = "GET_MENU_FLGS"; export const ACTION = { GET_MENU_FLGS };mutation-types.jsexport const UPDATE = "UPDATE"; export const MUTATION = { UPDATE };menu-store.jsimport { MUTATION } from "./mutation-types"; import { ACTION } from "./action-types"; import axios from "axios"; const state = { //権限 menuFlgs: {} }; const getters = { menuFlgs: state => { return state.menuFlgs; } }; const actions = { //ログインユーザーのメニュー使用権限を取得します。 async [ACTION.GET_MENU_FLGS]({ commit }) { await axios .get("menus") .then(res => { commit(MUTATION.UPDATE, { menuFlgs: res.data }); }) .catch(err => { throw err; }); } }; const mutations = { [MUTATION.UPDATE](state, { menuFlgs }) { state.menuFlgs = menuFlgs; } }; export default { state: state, getters: getters, actions: actions, mutations: mutations };権限のJSONはこのようなイメージです。
menuFlgs: [ { menu1: true }, { menu2: true }, { menu3: true } ];VueRouter
- ①URLごとに権限を持っているかを判定します
- 権限がない場合は遷移をキャンセルします
- ②ストアに権限が保持されていない場合、取得します
router.jsimport Vue from "vue"; import Router from "vue-router"; import multiguard from "vue-router-multiguard"; import store from "/store/store.js"; import { GET_MENU_FLGS } from "/store/menu/action-types"; Vue.use(Router); /* * * ①ナビゲーションガード * */ const menu1Guard = (to, from, next) => { if (store.getters.menuFlgs.menu1) { next(); } else { next(false); } }; const menu2Guard = (to, from, next) => { if (store.getters.menuFlgs.menu2) { next(); } else { next(false); } }; const menu3Guard = (to, from, next) => { if (store.getters.menuFlgs.menu3) { next(); } else { next(false); } }; /* * * ルート定義 * */ const router = new Router({ routes: [ { path: "/menu1", //① beforeEnter: multiguard([menu1Guard]), component: () => import("@/components/pages/menu1/menu1Template") }, { path: "/menu2", //① beforeEnter: multiguard([menu2Guard]), component: () => import("@/components/pages/menu2/menu2Template") }, { path: "/menu3", //ナビゲーションガードを複数設定することもできます。 beforeEnter: multiguard([menu1Guard, menu2Guard, menu3Guard]), component: () => import("@/components/pages/menu3/menu3Template") } ] }); /* * * ②権限を取得します。 * */ router.beforeEach(async (to, from, next) => { if (!Object.keys(store.getters.menuFlgs).length) { await store.dispatch(GET_MENU_FLGS); } next(); }); export default router;メニュー表示
- ①メニューを配列で定義しておきます。
- ②ストアの権限から、使用可能なメニューのみ表示します
<template> <v-list> <v-list-tile v-for="menu in availableMenus" :key="menu.id"> <router-link :to="menu.url">{{menu.title}}</router-link> </v-list-tile> </v-list> </template> <script> export default { data() { return { //①メニューマスタ menuMaster: [ { id: 1, title: "メニュー1", url: "/menu1" }, { id: 2, title: "メニュー2", url: "/menu2" }, { id: 3, title: "メニュー3", url: "/menu3" } ] }; }, computed: { //②使用可能なメニューで絞り込みます availableMenus() { let available = []; for (const menu of this.menuMaster) { if (this.isAvailable(menu.url)) { available.push(menu); } } return available; } }, methods: { //ログインユーザーがメニューの使用権限を持っているかを判定します。 isAvailable(url) { const menuName = url.slice(1); return this.$store.getters.menuFlgs[menuName]; } } }; </script>まとめ
- Vue Router Multiguardを使うとナビゲーションガードが扱いやすくなりました
- 権限をどのタイミングで取得するかは賛否両論ありそうです
- 投稿日:2019-07-09T15:31:12+09:00
フォーム処理で扱う正規表現
フォームの処理で、「郵便番号じゃないから書き直して!」「ふりがなじゃないから書き直して!」というのをやろうとしたとき、正規表現というものが必要になりました。
ちょっと練習もかねて少しみてみます。正規表現って?
http://gimite.net/help/devas-ja/all_regex.html
ここにある記号たちをうまいこと組み立てて行きます!
JavaScriptで正規表現を表す際には以下の二種類の構文になります。var str = /正規表現/オプション; //リテラル var str = new RegExp('正規表現','オプション'); //コンストラクターちなみにオプションというのは主に以下の種類があります。
g・・・グローバルリサーチ(一致する全ての文字列を返します。)
i・・・大文字・小文字を区別しない
m・・・複数行検索(^と$が各行の先頭末尾にマッチ)
s・・・sを改行文字と一致するようにするレッツ!正規表現
/...a/
こちらの.は任意の一文字を表すので、
abca,3iGa,bbbaなどが当てはまります。
/a*b+/
*は直前文字の0回以上の繰り返し、+は直前文字の1回以上の繰り返しです。
b,abbb,aaab,bbbbなどになりますね。
「****は庭だよ。」は「ハニワだよ」という意味の可能性があるですね。省略可能なのがみそです。
/みみ?ず/
?の直前の文字は省略可能です。
みみず か みず が検索できますね。
/^はるが/
これは先頭が「はるが」のものの身を検索します。
/$落ちた/
これは行末が落ちたのみを検索します。
/^はるが.*落ちてきた$/
はるが二階から落ちてきた、などがヒットします。(元ネタ重力ピエロ)
.と*のコンビ技です。
/ヒトカゲ|ゼニガメ|フシギダネ/
これはヒトカゲ、ゼニガメ、フシギダネが検索できます。
囲われたどれかに一致したら検索できます。
/[桃金喰]太郎/
桃太郎、金太郎、喰太郎が一致します。どれかの文字がヒットしたら検索できます。
/[0-9][A-Z]/
2B,9S,などがヒットします。A2はヒットしません...(元ネタニーアオートマタ)
このように複数文字をまとめてセットすることもできます。
/[0-9]{3}/
{3}は直前を三回繰り返すということ。
050,214,555とかがヒットします。
/[0-9]{2,}
こちらは2桁以上の数字になる。
04,210,43508510926とかがヒット。
/[^abc]
abc以外の文字にマッチ。定義済みの文字クラス
\d・・・[0-9]と同じ意味。数字。
\D・・・[^0-9]と同じ意味。数字以外。
\s・・・空白文字。[\t\n\x0B\f\r]
\S・・・空白文字以外[^\s]使用例
郵便番号を表してみましょう。
/^\d{3}-\d{4}$/
^と$が指定されてるので完全一致ですね。
\dは数字を表すので、数字3桁-数字4桁という意味になっています!
- 投稿日:2019-07-09T15:28:30+09:00
JavaScript 非同期処理の基礎
はじめに
最近のWebアプリでは、WebAPI を用いてクライアント側から JSON データを取得してそれらを描画する...
という流れが一般的になってきています。WebAPIをクライアント側で使用するためには、JavaScript による非同期処理の実装は避けて通ることはできないでしょう。
今回、非同期処理の基本的な概念と実装方法を学習しましたので、その内容を記録しておきます。
※誤った記述等がありましたら、ご教授いただけると嬉しいです!
非同期処理
以下のような、引数で渡されたスクリプトを読み込む関数があったとする。
function loadScript(src) { let script = document.createElement('script'); script.src = src; document.head.append(script); }これは任意の JavaScript ファイルを読み込むための関数であり、呼び出されると
<script src="...">タグが<head></head>タグ内に追加され、ファイルの読み込み処理が行われる。// 呼び出し loadScript('/my/script.js');ファイルの読み込み処理はファイルサイズや通信状態によって時間がかかるため
読み込み処理のうしろに別の処理が控えていた場合、ファイル読み込みの完了を待たずに処理される。loadScript('/my/script.js'); console.log('hogeeee!'); // loadScript関数 の完了を待たずに処理が開始するこのファイル読み込み処理のように、
呼び出されてもすぐに完了せず、同期的に処理が進行しないような処理を 非同期処理 と呼ぶ。例えば、loadScript 関数で読み込まれるスクリプト内の関数を、後続処理で利用する場合は
スクリプトの読み込みが完了するまで待たなければならない。スクリプトの読み込み中に呼び出すとエラーになってしまう。
loadScript('/my/script.js'); // myFunction() を含むスクリプトを読み込み myFuction(); // スクリプトの読み込みが終わってないので、エラーが発生する。:「そんな関数は存在しません!」これを解決するためには、loadScript に対して「読み込みが終わったら実行する関数」をもたせる仕組みを導入する必要がある。
callback 関数
loadScript 関数の第2引数に callback関数 を追加する。
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(); // この行を追加 document.head.append(script); }※
onloadはHTMLのページやスクリプトファイル、画像ファイル等の読み込み完了後に
何らかの操作をしたいときに利用するイベントハンドラ。
ファイルの読み込みが完了したタイミングで実行したい処理を定義できる。上記の例では、
script.onloadに callback 関数を呼び出すための無名関数を設定しているので
スクリプトの読み込みの完了後に callback の処理が呼び出されるようになる。以下は呼び出しのイメージ。
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); // callback関数 に script を渡す。 document.head.append(script); } loadScript('/my/script.js', script => { // 受け取った script のパスを表示する。 alert(`${script.src} is loaded`); });エラー処理
上記の例ではスクリプト読み込み時に発生するエラーに対応できていない。
対応するには、callback 関数の引数にエラー情報を追加し、エラーを追跡する。function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; // 正常時 script.onload = () => callback(null, script); // エラー発生時 script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } loadScript('/my/script.js', function(error, script) { if (error) { // エラー処理 } else { // スクリプトの読み込みが成功 } });スクリプトの読み込みに成功した場合(onload)は
callback(null, script)が呼び出され、
失敗した場合(onerror)はcallback(error)が呼び出される。callback関数の第1引数は error オブジェクトが渡される。エラーが発生したときのために予約しておき、
エラー発生時は error オブジェクトを、正常時はnullを渡すことでエラー発生を判定できる。
第2引数以降は正常時の結果データを得るための引数であり、任意の個数設定できる。このことから、単一の callback関数はエラー報告と正常時の結果渡しの両方の用途で使用される。
これは コールバックベース の非同期プログラミングのスタイルであり、
上記のように callback関数の第1引数にエラーを設定するスタイルを エラーファーストなコールバック と呼ぶ。callback 地獄
例えば
/my/script.jsを読み込んだ後に、/my/script2.jsを読み込み、
その読み込み後に/my/script3.jsを読み込みたい場合、loadScriptの コールバック関数で再びloadScriptを呼び出すことで実現できる。loadScript('/my/script.js', function(script) { loadScript('/my/script2.js', function(script) { loadScript('/my/script3.js', function(script) { // 1 -> 2 -> 3 の順番で呼び出される。 }); }) });これに先程のエラー処理を加えてみる。
loadScript('/my/script.js', function(error, script) { if (error) { handleError(error); } else { loadScript('/my/script2.js', function(error, script) { if (error) { handleError(error); } else { loadScript('/my/script3.js', function(error, script) { if (error) { handleError(error) } else { // ... } }); } }); } });ネストが増え、複雑な見た目になってしまった。
上記は読み込むファイルも3ファイルのみなのでまだ良いが、これが4, 5, 6 ...と増えたときの事を考えると
コールバックが何重にも積み重なってしまい、コードの可読性・保守性が一気に低下して制御不能になる。
このことを コールバック地獄 と呼ぶ。次のように全てのアクションを単一の関数によってステップ化することで、
ある程度この問題を軽減できる。loadScript('/my/script1.js', step1); function step1(error, script) { if (error) { handleError(error); } else { // ... loadScript('/my/script2.js', step2); } } function step2(error, script) { if (error) { handleError(error); } else { // ... loadScript('/my/script3.js', step3); } } function step3(error, script) { if (error) { handleError(error); } else { // ... } };上記の書き方は一見整理されているように見えるが、コードがバラバラに分散され読みにくくなる。
また、step関数はこのコールバック地獄のためだけの関数であり、外部から使用されることもなく
名前空間を汚すだけである。このような事象を回避するための方法として、promise や async/await などが用意されている。
まとめ
非同期処理とコールバック関数について理解を深めることができました。
最後に出てきた promise や async/await について、これから学習していきますので、
またこのような形で残しておきたいと思います。実際のアプリを実装するにはもう少し掛かりそうです。。
- 投稿日:2019-07-09T14:57:29+09:00
Vueをマウントできなくて奮闘
プログラミング歴3か月くらいの初心者です、自分のメモ用。
bladeファイル内になどを記載して、コンポーネントを挿入しても
app.js:38008 [Vue warn]: Failed to mount component: template or render function not defined. found in ---> <コンポーネント名> <Root>上記のエラーが発生する。
app.jsにもしっかり記載している。
Vue.component('my-component', require('./components/MyComponent.vue'));いろいろな記事を見て、webpack.config.jsに追記だの devコマンドを実行したかだのいろいろ対処法が
書かれていたがどれでも解決できないところ以下の記事を見つけた。どうやら app.jsで
Vue.component('my-component', require('./components/MyComponent.vue').default);最後に.defaultを追記するだけでした。(意味は知らない)
- 投稿日:2019-07-09T14:41:58+09:00
Next.js 9で3分で1からTypeScriptのReactサーバーとGraphQLサーバを立てよう
概要
やり方
1. 必要なパッケージをインストール
terminalyarn add apollo-server-micro graphql micro next@latest react@latest react-dom@latest yarn add -D typescript @types/react2. テキトーにメインページを作る
pages/index.tsxを作り、テキトーにメインページを立ちあげる。拡張子をtsxにしておくと、Next.js 9は勝手にTypescriptのtsconfig.jsonを用意してくれる。速さ⚡️を求めるならば、
tsconfig.jsonを書く前に、サーバーを立ち上げる。テキトーなメインページの例?
pages/index.tsxexport default ()=>( <p>ほげ</p> )Reactのコンポーネントを
export defaultすればそれが配信される。画像などのファイルは/static配下に単に配置すれば配信される。サーバー立ち上げ
terminalyarn next初回のターミナルのログの例
terminal~/W/next9 graphql ❯❯❯ yarn next yarn run v1.15.2 warning package.json: No license field $ '/███████████/██████████/next9 graphql/node_modules/.bin/next' [ wait ] starting the development server ... [ info ] waiting on http://localhost:3000 ... We detected TypeScript in your project and created a tsconfig.json file for you. Your tsconfig.json has been populated with default values. [ info ] bundled successfully, waiting for typecheck results ... [ ready ] compiled successfully (ready on http://localhost:3000)※ Next.js 9からサーバーでプレレンダリングできていると、右下に⚡️マークがつくようになりました。
3. テキトーにGraphQLサーバーを作る
/pages/api配下にはAPIのエンドポイントを生やせる。GraphQLのモックサーバを立てよう。/pages/api/index.tsimport { ApolloServer, gql } from 'apollo-server-micro'; // ここからGraphQLのモックの定義 const typeDefs = gql` type Query { hello: String } `; const resolvers = { Query: { hello: () => 'world', }, }; const apolloServer = new ApolloServer({ typeDefs, resolvers }); // ここから先はNext.jsが読みに行く領域 export default apolloServer.createHandler({path:"/api"}); export const config = { api: { bodyParser: false, }, };サーバー再起動するまでもなく、APIが立ち上がる。
??嬉しい??
ここから、ApolloClientを入れるなり、モックじゃないGraphQLサーバーをこしらえるなりすると良い。
終わり
ポイント
tsconfig書く前に、サーバー立ち上げてしまおう。
Next.js 9からは、
tsxをNext.jsが見えるどこかにおけば、勝手に以下のtsconfig.jsonが用意されてしまう。tsc --initしたのが書き変わるのでさっさと立ち上げたいなら、nextを先にする方が早い。短絡的に言えば、
pages/index.tsx書けばオッケー。tsconfig.json{ "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "exclude": [ "node_modules" ], "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ] }apollo-serverはmicro用のやつを使う。
pages/api配下で (req, res)を受け取る関数をexport defaultすれば、APIサーバが立つという寸法。export default (req, res) => { res.setHeader('Content-Type', 'application/json') res.statusCode = 200 res.end(JSON.stringify({ name: 'Nextjs' })) }デフォルトのres,req(ただし幾らかミドルウェアが入っている)をいじるより、Zeit▲のmicroでマイクロサービスを
/pages/apiにもさもさ生やせば、賑やかになると思う。これはご機嫌。参考:next.js/examples/api-routes-micro/
ということで、
apollo-server-ナンチャラは、apollo-server-microを使うっぽいなあってのは、API Routeの見た目にわかった。しかし、GraphQLサーバを立たせるに当たって2点ハマりポイントがあった。ハマり1. graphQLサーバのパスをちゃんと指定する。
apollo-serverはデフォルトでlocalhost:3000/graphqlにサーバーを立たせようとする。一方で、Next.js 9はAPIを単純な設定ならば
localhost:3000/api配下にしか生やせない。したがって、apollo-server-microのcreateHandlerにオプションを指定する。const apolloServer = new ApolloServer({ typeDefs, resolvers }); const handler = apolloServer.createHandler({path:"/api"});この
handlerをexport defaultすれば、graphQLサーバが立つもんだと思ったらそうはいかない。ハマり2. export const config={option}ちゃんとする
export default handler;したところで
GraphQL Playgroundが動いているので、見た感じAPIが機能しているように思えるが、スキーマをダウンロードできない、クエリを投げられないで、まともに動いていない。これは、
Next.js 9が自動で設定しているbodyParserが原因らしい。数時間時間を潰してしまった。
Next.js 9のAPI routesは備え付けで少しのミドルウェア(cokkieやquery、bodyをreqに生やすことができるやつ)が入っており、その中で、bodyParserはデフォルトでonであり、reqをパースしてしまう。しかし、API配下で、
Next.js 9に設定を伝える方法があるのでこのようにすると、クエリも投げられるし、スキーマもダウンロードできるようになる。以下のconfigは決め打ちで。export default apolloServer.createHandler({path:"/api"}); export const config = { api: { bodyParser: false, }, };参考 next.js/examples/api-routes-graphql/
まとめ
- 恐ろしく楽チンにNextのサーバーとGraphQLのサーバを立てられるようになった。
- 投稿日:2019-07-09T14:40:12+09:00
JavaScript本格入門 勉強メモ
JavaScript本格入門(ISBN 978-4774184111)で基礎からJavaScriptを勉強するシリーズです。各セクションで興味深かったり、学んだ内容を自分なりに整理してまとめています。
ここは各セクションのまとめページです。
タイトル 概要 Chapter1 Hello JavaScript! ECMAScriptって? Chapter2 変数と定数について varを使うべき?let使うべき?などを簡単にまとめました Chapter3 Symbolオブジェクトについて Symbolオブジェクトを使ったことがなかったので調べて使い道をまとめました Chapter4 関数について アロー記法だったりfunction命令だったりぱっとしてなかったところを勉強しました Chapter5-1 setterとgetterについて アクセサーメソッドの実装を行ってみました Chapter5-2 イテレータ for...ofとイテレータを使って便利なループを実装します Chapter7 非同期処理の設計・実装 非同期処理や同期処理を実装する方法を学びました Chapter8 コーディング規約 JavaScriptの有名どころのコーディング規約について調べ、ソースコードを自動チェックする環境を準備してみました
- 投稿日:2019-07-09T14:28:15+09:00
JavaScriptのMapをディープコピーする
- 投稿日:2019-07-09T12:40:53+09:00
関数が存在していたら実行する
/* ★共通部 各ページの起動時読み込み関数 */ if(typeof init == 'function') { init(); } /* 状況により存在有無が変わる関数 */ function init(){ … }
- 投稿日:2019-07-09T12:30:03+09:00
Array<Object> な値を reduceしてObjectにする
はじめに
例えば、以下のようなSQLでDBからレコードを取得してきた場合
SELECT a, SUM(CASE WHEN case = 'b' THEN 1 ELSE 0 END) AS b, SUM(CASE WHEN case = 'c' THEN 1 ELSE 0 END) AS c FROM table GROUP BY a ;戻り値はこんな感じで取得できたりする。
src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }]group by a ごとの集計(SQL結果そのまま)と
group by しない合計を出したい場合を考えた時に、
単純にSQLを2発投げることもできるが、JS側で足し算すればいいじゃん?とも思った。
(ただし、足し算するレコードが多い場合は、SQLでやった方が早い気もする。
今回は100〜200レコードしか取得されない前提。)JavaScriptのArrayには強力なメソッドがいくつもあり、
その中の一つ、reduceがかなりいい仕事をしてくれるのだが[1,2,3].reduce((p,c)=>{ return p + c }) // 6Object同士の足し算だとポンコツな結果が返ってくる(いや、ポンコツなのは仕様を理解できていない自分なのだと思うが)
[{a:1},{a:2},{a:3}].reduce((p,c)=>{ return p + c }) // {a:6} を期待するが、実際はこれ // [object Object][object Object][object Object]どうやら、Objectの場合は、ちょっと工夫が必要らしい
[{a:1},{a:2},{a:3}].reduce((p,c)=>{ return {a:p.a + c.a} }) // {a:6}結論
※追記 コメントでこのような書き方もあると教えていただきました!
だいぶスマートですね!src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }] keys = ["a","b","c"] keys.map(k=>( {[k]: src.map(e=>e[k]).reduce((p,c)=>(p+c))} )).reduce((p,c)=>({...p,...c}))で、完成したのがこれ。
/** * Array[Object]型の全要素を足し上げし、 * Object型で返す。 * @param {Array<Object>} src * @return Object */ reduceObjectArray(src) { // src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }] let result = {} Object.keys(src[0]).forEach((name) => { result[name] = src.reduce((p, c) => { return p + c[name] }, 0) }) return result // result = { a: 11, b: 22, c: 33 } };関数名があれだが
解説
Object.keys(src[0]).forEach((name) => {で、要素名(今回はaとbとc)分繰り返し、
result[name] = src.reduce((p, c) => { return p + c[name] }, 0)で、resultという連想配列(let result = {}で定義)に
1要素ずつreduceした結果を入れていく。
- 投稿日:2019-07-09T11:05:12+09:00
WebAssembly でセルオートマトン
WebAssemblyの練習に、セル・オートマトンの有名な問題ライフゲーム(Game of Life)を実装してみました。
ライフゲームのルール
Wikipediaより、典型的と思われるルールを採用しました。
誕生
死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
生存
生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
過疎
生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
過密
生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。結果
まず出力結果から。ここで、nはキャンバス1辺に含まれるセルの数。初期条件は、市松模様からスタートしています。デモは https://cellular-automaton-webassembly.herokuapp.com/ にあげてあります。
n = 20
n = 50
n = 100
Cargo.toml
主に3つのcrateを読ませます。
- wasm-bindgen - JavaScript連携
- web-sys - HtmlElementの操作に使用
- lazy_static - セルの状態等を格納するベクトルをグローバル変数化し、クロージャー内で使用するために使用
[package] name = "wasm" version = "0.1.0" authors = ["snst.lab <snst.lab@gmail.com>"] edition = "2018" [lib] crate-type = ["cdylib"] [dependencies] lazy_static = "0.2.1" [dependencies.wasm-bindgen] version = "0.2.45" [dependencies.web-sys] version = "0.3.4" features = [ 'Document', 'Window', 'Element', 'Node', 'HtmlElement', 'DocumentFragment', 'CssStyleDeclaration', 'Event', 'EventTarget', 'MouseEvent', ]
Rust側
とりあえず主要箇所のみ抜粋。ソースはGitHubにあげています。
ヘッダー部分
ポイントとして、
RcとRefCellをrequest_animation_frameの呼び出しに、RwLockをlazy_staticに使用します。extern crate wasm_bindgen; use std::rc::Rc; use std::cell::RefCell; use web_sys::{HtmlElement, DocumentFragment}; use wasm_bindgen::{prelude::*, JsCast}; #[macro_use] extern crate lazy_static; use std::sync::RwLock;グローバル変数の定義
Vec<HtmlElement>をRwLockに渡すと、以下のエラーとなります。error[E0277]:
*mut u8cannot be sent between threads safely
help: withinweb::web_sys::HtmlElement, the traitstd::marker::Sendis not implemented for*mut u8回避策として、
Vec<String>をRwLockに渡して各セル要素のクラス名を保持しています。lazy_static! { static ref CELLS: RwLock<Vec<String>> = RwLock::new(Vec::new()); //セル要素のクラス名を保持するベクトル static ref LIFE: RwLock<Vec<u16>> = RwLock::new(Vec::new()); //セルの状態を保持するベクトル static ref LIFE_TEMP: RwLock<Vec<u16>> = RwLock::new(Vec::new()); //セルの状態を一時保管するベクトル static ref RUNNING: RwLock<bool> = RwLock::new(false); //アニメーションの稼働状態を保持 }構造体の定義
wasm-bindgenの作法で書いていきます。ちなみに、
Document::query_selector()やWindow::request_animation_frame()はweb-sysで用意されている関数をラップした関数です。#[wasm_bindgen] pub struct CellularAutomaton{ canvas: HtmlElement, //描画範囲のHTML要素。HtmlElementはCopy Treatを実装していないため、pubは外す pub n: isize, //描画範囲1辺に含まれるセルの数 pub N: isize, //全セル数 pub size_of_cell: f64, //セルの幅(px) } #[wasm_bindgen] impl CellularAutomaton{ /** コンストラクタで各プロパティを初期化。N, size_of_cellは計算で求めるため、とりあえずの初期値 */ #[wasm_bindgen(constructor)] pub fn new() -> CellularAutomaton { CellularAutomaton { canvas: Document::query_selector(".canvas"), n : 20, N : 400, size_of_cell:10.0 } } /** N, size_of_cellの計算、各メソッドの呼び出し */ pub fn start(&mut self) -> Result<(), JsValue>{ self.N = self.n.pow(2); self.size_of_cell = self.canvas.client_width() as f64 /self.n as f64; CellularAutomaton::draw_canvas(self).expect("failed to draw canvas"); CellularAutomaton::initialize(self).expect("failed to initialize"); Ok(()) } /** Canvasの描画。DocumentFragmentを使って一気に追加。 */ fn draw_canvas(&mut self) -> Result<(), JsValue> { self.canvas.style().set_property("height", &(self.canvas.client_width().to_string()+"px"))?; let fragment : DocumentFragment = DocumentFragment::new().unwrap(); /** 各グローバル変数を書き込み用に呼び出し。 */ let mut life = LIFE.write().unwrap(); let mut life_temp = LIFE_TEMP.write().unwrap(); let mut cells = CELLS.write().unwrap(); for i in 0..(self.N as usize){ let cell:HtmlElement = Document::create_element("div"); let class_name:String = "cell".to_owned() + &i.to_string(); cell.set_class_name(&("cell ".to_owned()+&class_name)); cell.style().set_property("width", &(self.size_of_cell.to_string() + "px"))?; cell.style().set_property("height", &(self.size_of_cell.to_string() + "px"))?; fragment.append_child(&cell)?; /** まず全セルを0で初期化。 */ (*life).push(0); (*life_temp).push(0); (*cells).push(".".to_owned() + &class_name); } self.canvas.append_child(&fragment); Ok(()) } /** 初期状態として市松模様を与え、アニメーションスタート */ fn initialize(&self) -> Result<(), JsValue>{ let mut life = LIFE.write().unwrap(); let mut life_temp = LIFE_TEMP.write().unwrap(); let cells = CELLS.write().unwrap(); for i in 0..(self.N as usize){ if ((i/(self.n as usize))%2==0 && i % 2 == 0) || ((i/(self.n as usize))%2==1 && i % 2 == 1){ (*life)[i] = 1; (*life_temp)[i] = 1; let cell:HtmlElement = Document::query_selector(&(*cells)[i as usize]); cell.style().set_property("background-color", "deeppink").expect("failed to set property"); } } let mut running = RUNNING.write().unwrap(); (*running) = true; CellularAutomaton::run(self.n,self.N); Ok(()) } /** request_animation_frameで15フレーム毎(1秒に約4回)に世代遷移のメソッドevaluateを呼び出す。 ブール値runningは中断や再開をコントロールするためのフラグとして使用。 */ fn run(n:isize, N:isize) -> Result<(), JsValue>{ let f = Rc::new(RefCell::new(None)); let g = f.clone(); let mut frame = 0; *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { let runnning = RUNNING.read().unwrap(); if *runnning { if frame % 15 == 0 {CellularAutomaton::evaluate(n,N).expect("failed to evaluate");} frame += 1; Window::request_animation_frame(f.borrow().as_ref().unwrap()); } }) as Box<FnMut()>)); Window::request_animation_frame(g.borrow().as_ref().unwrap()); Ok(()) } /** 世代遷移のメソッド */ fn evaluate(n:isize, N:isize) -> Result<(), JsValue>{ /** cellsを読み取り専用で、lifeとlife_tempは書き込み用に呼び出す。 */ let cells = CELLS.read().unwrap(); let mut life = LIFE.write().unwrap(); let mut life_temp = LIFE_TEMP.write().unwrap(); /** セルiの周囲の生きたセルの数から、上記ルールに従って次世代のセルの生死を判定 */ for i in 0..N { let top_right: isize = i - n + 1; let top: isize = i - n; let top_left: isize = i - n - 1; let left: isize = i - 1; let bottom_left: isize = i + n - 1; let bottom: isize = i + n; let bottom_right: isize = i + n + 1; let right: isize = i + 1; /** around = セルiの周囲の生きたセルの数 */ let around : u16 = (if top_right < 0 { 0 } else { (*life)[top_right as usize] } ) + (if top < 0 { 0 } else { (*life)[top as usize] } ) + (if top_left < 0 { 0 } else { (*life)[top_left as usize] } ) + (if left < 0 { 0 } else { (*life)[left as usize] } ) + (if bottom_left >= N { 0 } else { (*life)[bottom_left as usize] } ) + (if bottom >= N { 0 } else { (*life)[bottom as usize] } ) + (if bottom_right >= N { 0 } else { (*life)[bottom_right as usize] } ) + (if right >= N { 0 } else { (*life)[right as usize] } ); (*life_temp)[i as usize] = (*life)[i as usize]; if (*life)[i as usize] == 0 && around == 3 { let cell:HtmlElement = Document::query_selector(&(*cells)[i as usize]); cell.style().set_property("background-color", "deeppink").expect("failed to set property"); (*life_temp)[i as usize] = 1; } else if (*life)[i as usize] == 1 && (around == 2 || around == 3) { continue; } else if (*life)[i as usize] == 1 && (around <= 1 || around >= 4) { let cell:HtmlElement = Document::query_selector(&(*cells)[i as usize]); cell.style().set_property("background-color", "lightgray").expect("failed to set property"); (*life_temp)[i as usize] = 0; } } /** 上記の判定が全て終わったら、life_tempの状態をlifeにコピーする。 */ for i in 0..(N as usize){ (*life)[i] = (*life_temp)[i]; } Ok(()) } }
JavaScript側
WebAssemblyは非同期呼び出しする必要があるため、async即時関数でラップします。
init関数で初期化してから上記CellularAutomaton構造体を呼び出します。※
wasm.js、wasm_bg.wasmのパスは環境によって変えて下さい。import { CellularAutomaton as CA , default as init } from '../wasm/pkg/wasm.js'; (async() =>{ await init('./src/wasm/pkg/wasm_bg.wasm'); const ca: CA = new CA(); ca.start(); })().catch(() => 'Failed to load wasm.');参考にさせていただいたサイト
https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html
https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.HtmlElement.html
https://qiita.com/nacika_ins/items/cf3782bd371da79def74
https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0
- 投稿日:2019-07-09T10:24:20+09:00
javascriptを便利メソッド[forEach,map,filter,find,every,some,reduce] 使い方について
記事を書こうと思った経緯
javascriptのスキルはwebのライブラリやフレームワークを扱う上で必須のスキルのため。
以前にjavascriptを学んだが、だいぶ時間がたってしまったので、最初から学びたいと思いました。今回使用した教材
Udemy JavaScriptエンジニアのためのES6完全ガイド 講師 Ken Fukuyamaさん
ES6とは
ECMAScript(標準規格)の6番目のバージョンのこと。次世代javascript。ES6とES2015は同じもの。
注意事項
EC6は全てのブラウザで動くわけではないので、そのままでは動かないブラウザも多い。エラーになる場合も多い。そのため、Babelというツールを使ってES5のコードに変換してあげる必要がある。配列メソッド
配列の便利メソッド一覧 forEach map filter find every some reduce forEach
forEach.js//数字の配列 var numbers = [1, 2, 3, 4, 5]; //合計を保持する変数 var sum = 0; //配列の数字を合計に一つずつ足していく numbers.forEach(function(number){ sum += number; }); sum; //=> 15map (配列のある値を指定したい場合に使用する)
map.js配列の値を2倍にする処理 var numbers = [1, 2, 3, 4, 5]; //新しい配列を用意する var doubledNumbers = []; //数字を2倍にした値を配列に戻す var doubled = numbers.map(function(number) { return bumber * 2; }); doubled; //=>[2,4,6,8,10] //returnは必ずつけるfilter (データの一覧から必要な情報を選択して絞り込みたい場合に使用する)
filter.jsvar products = [ { name: 'トマト', type: '野菜', quantity: 0, price: 6 }, { name: 'りんご', type: 'フルーツ', quantity: 10, price: 16 }, { name: 'なす', type: '野菜', quantity: 20, price: 6 }, { name: 'レモン', type: 'フルーツ', quantity: 3, price: 6 } ]; //種類がフルーツ、量が1個より多く、値段が9より小さいものを配列から取り出す products.filter(function(product){ return product.type === 'フルーツ' && product.quantity > 1 && product.price < 9; }); //=>[{"name":"レモン","type":"フルーツ","quantity":3,"price":6}]find(特定のデータを取り出す場合に使用する)
find.jsvar users = [ { name: '鈴木' }, { name: '佐藤' }, { name: '田中' } ]; var users; //配列からuser.nameが田中を見つけて取り出す users.find(function(user) { return user.name === '田中'; }); //=>{"name":"田中"} //findでは最初に見つけた要素を取り出すevery(条件を全て検証し全て条件があてはまるとき。 論理積)
every.jsvar computers = [ { name: 'ASUS', ram: 25 }, { name: 'TOSHIBA', ram: 14 }, { name: 'mac', ram: 5 } ]; //全てのramが15以上であるか? computers.every(function(computer) { return computer.ram>= 15; }); //=>Falsesome(条件を全て検証して、どれか一つでも条件が当てはまる場合。 論理和)
some.jsvar computers = [ { name: 'ASUS', ram: 25 }, { name: 'TOSHIBA', ram: 14 }, { name: 'mac', ram: 5 } ]; //一つでもramが15以上であるか? computers.some(function(computer) { return computer.ram>= 15; }); //=>Truereduce(プロパティの集約)
redue.jsvar numbers = [ 3, 5, 4 ]; var sum = 0; numbers.reduce(function(sum, number) { return sum + number; }, 0); //0は初期値。 //=> 12
- 投稿日:2019-07-09T10:24:20+09:00
javascript 便利メソッド[forEach,map,filter,find,every,some,reduce] 使い方について
記事を書こうと思った経緯
javascriptのスキルはwebのライブラリやフレームワークを扱う上で必須のスキルのため。
以前にjavascriptを学んだが、だいぶ時間がたってしまったので、最初から学びたいと思いました。今回使用した教材
Udemy JavaScriptエンジニアのためのES6完全ガイド 講師 Ken Fukuyamaさん
ES6とは
ECMAScript(標準規格)の6番目のバージョンのこと。次世代javascript。ES6とES2015は同じもの。
注意事項
EC6は全てのブラウザで動くわけではないので、そのままでは動かないブラウザも多い。エラーになる場合も多い。そのため、Babelというツールを使ってES5のコードに変換してあげる必要がある。配列メソッド
配列の便利メソッド一覧 forEach map filter find every some reduce forEach
forEach.js//数字の配列 var numbers = [1, 2, 3, 4, 5]; //合計を保持する変数 var sum = 0; //配列の数字を合計に一つずつ足していく numbers.forEach(function(number){ sum += number; }); sum; //=> 15map (配列のある値を指定したい場合に使用する)
map.js配列の値を2倍にする処理 var numbers = [1, 2, 3, 4, 5]; //新しい配列を用意する var doubledNumbers = []; //数字を2倍にした値を配列に戻す var doubled = numbers.map(function(number) { return bumber * 2; }); doubled; //=>[2,4,6,8,10] //returnは必ずつけるfilter (データの一覧から必要な情報を選択して絞り込みたい場合に使用する)
filter.jsvar products = [ { name: 'トマト', type: '野菜', quantity: 0, price: 6 }, { name: 'りんご', type: 'フルーツ', quantity: 10, price: 16 }, { name: 'なす', type: '野菜', quantity: 20, price: 6 }, { name: 'レモン', type: 'フルーツ', quantity: 3, price: 6 } ]; //種類がフルーツ、量が1個より多く、値段が9より小さいものを配列から取り出す products.filter(function(product){ return product.type === 'フルーツ' && product.quantity > 1 && product.price < 9; }); //=>[{"name":"レモン","type":"フルーツ","quantity":3,"price":6}]find(特定のデータを取り出す場合に使用する)
find.jsvar users = [ { name: '鈴木' }, { name: '佐藤' }, { name: '田中' } ]; var users; //配列からuser.nameが田中を見つけて取り出す users.find(function(user) { return user.name === '田中'; }); //=>{"name":"田中"} //findでは最初に見つけた要素を取り出すevery(条件を全て検証し全て条件があてはまるとき。 論理積)
every.jsvar computers = [ { name: 'ASUS', ram: 25 }, { name: 'TOSHIBA', ram: 14 }, { name: 'mac', ram: 5 } ]; //全てのramが15以上であるか? computers.every(function(computer) { return computer.ram>= 15; }); //=>Falsesome(条件を全て検証して、どれか一つでも条件が当てはまる場合。 論理和)
some.jsvar computers = [ { name: 'ASUS', ram: 25 }, { name: 'TOSHIBA', ram: 14 }, { name: 'mac', ram: 5 } ]; //一つでもramが15以上であるか? computers.some(function(computer) { return computer.ram>= 15; }); //=>Truereduce(プロパティの集約)
redue.jsvar numbers = [ 3, 5, 4 ]; var sum = 0; numbers.reduce(function(sum, number) { return sum + number; }, 0); //0は初期値。 //=> 12
- 投稿日:2019-07-09T10:17:20+09:00
indexOfとspliceメソッドを使った削除方法 JavaScript 【Snippets】
概要
JavaScriptで特定の要素を削除(配列から取り除く)方法がいくつかあるので、今回は
spliceの書き方例をメモします。コード
index.jsconst userLists = [ { id: 1, name: 'Mike' }, { id: 2, name: 'Taro' }, { id: 3, name: 'John' } ]; const user = userLists.find(user => user.id === 3); // 'John'を取得 const index = userLists.indexOf(user); // 'John'は2番目のユーザー userLists.splice(index, 1); // 2番目から、1つの要素を取り除きます console.log(userLists); // 残りの要素(ユーザー)は{ id: 1, name: 'Mike' }, { id: 2, name: 'Taro' }※
userLists.find(user => user.id === 3)の「3」は、Node.jsでreq.params.idで「3」を取得したと想定しています。削除するまでの流れ
これらの流れを言葉にして読み上げると、記憶に残りやすかったのでメモ。
- 配列リストから特定の要素を取得する 例:
{ id: 3, name: 'John' }- indexOfで配列の何番目の要素か調べる。 例:
name: 'John'は2番目のユーザーsplice(index, 1)で、index番目の要素を1つ取り除く- 残った要素(ユーザー)は
{ id: 1, name: 'Mike' }, { id: 2, name: 'Taro' }です。














