20190709のJavaScriptに関する記事は30件です。

【pugのコメント】HTML方式のコメントとスラッシュの使い分け

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

pugのコメントにはHTML方式のコメントではなくスラッシュを使おう

pugのコメントにはHTMLのコメントの
<!-- コメント -->
のような形と、
// コメント
というスラッシュを使ったコメントがあります。

使い分けについてです。
// コメントを使うと、その行の階層以下のpugもコメントとなります。
つまり、// コメントはその行の階層以下もコメントにしたいときに使うと良いです。一時的にコメントアウトしたい場合がよく当てはまると思います。

<!-- コメント -->はより柔軟に使えます。

この記事が参考になれば幸いです。

終わりに

Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MaterializecssのCarouselを使用して、不特定多数の@変数sをeachで表現する

概要

TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
その一部のページでMaterializecssのCarouselを使用し、users_controller.rb
で定義した@usersをそれぞれ"carousel-item"で表現したので紹介します。

MaterializecssのCarouselとは

画像をくるくると回せる機能です。
Image from Gyazo
https://materializecss.com/carousel.html

自分が作成したページ紹介

Image from Gyazo
画像が自動で切り替わる方法は
こちらを参照していただけば幸甚です(ついでにイイね:yum:)

作成する前提

MaterializecssがCDNで読み込めている

編集するファイル

・コントローラーファイル
・ビューファイル

コントローラーファイル

user_contoroller.rb
    def 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まで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

ひとこと

最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

変わらない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の中身の一部で変化しないエレメントも外側にくくりだされていて、けっこう積極的に処理しているんだなと感じた次第でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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は慣れると読みやすいし、書きやすいのですが、いつ使うのかを判断するのがなかなか難しいかったです。

ある程度はパターンなのかもしれませんが、一つの処理に時間がかかってその他の処理が止まってしまうのを防ぐために並行処理を行うようなので、その視点を持って書いてみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeafletとFlaskでシンプルなWebGISをつくった話

1hz9g-wru3l.gif

はじめに

オープンソースのGISにはQGISというド定番・大正義が存在しますが、インストールの手間、投影法など、若干敷居が高いです(初心者にとって)。ウェブ上で公開されている多数のオープンGISデータ(国土数値情報など)の内容をすぐに確認出来る、シンプルなWebGISがあれば便利なんじゃないかと思い開発したのがVanillaGISです。

VanillaGIS

Vanilla GIS
GitHub - Kanahiro/vanilla-gis

使用例(北海道のバス・鉄道路線図)

スクリーンショット 2019-06-23 16.52.56.png

技術概要

主な使用ライブラリ

  • 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マップに追加します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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レンダラーを選択
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で読み込んでアンカーリンクを踏ませた事にするというわけですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pixi.jsでつくるスワイプスタイルのクイズアプリ[その2]

前回に引き続きクイズアプリを作っていきます。

前回タイトル画面を作成したので、今回ゲーム内の画面を作っていきます。
イメージしている画面と処理フローは下記のような感じです。

画面構成

前回、横長でタイトル画面を作成しましたが、やはりスマホで遊ぶことを前提にしたので、
縦長にしました。一旦500x800で作成。

↓画面
quiz_image.png

処理フロー

非常に一般的な処理フローかなと思います。
↓処理フロー
quiz_flow2.png

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;
      }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

electronメモ

electronメモ

electronでHello Worldアプリ作成

$ npm i electron -g
$ npm i electron-packager -g
$ vi main.js
const 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というアプリケーションが作成されます。

参考

https://qiita.com/umamichi/items/6ce4f46c1458e89c4cfc

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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();
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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();
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

本格JavaScript記号プログラミング(2) 記号プログラム変換器を作ろう

おことわり:
前回の記事を投稿したところ、「モバイル端末で読もうとすると、ページを開いた瞬間にブラウザがフリーズして落ちる」というお声を頂きました。
単行で1万文字超にも及ぶ記号プログラムにシンタックスハイライトがかかることで、大量のspanタグが生成されて高負荷となるようです。
対策として、横幅が画面に収まらないほどの巨大な記号プログラムについてはハイライトを行わない、または記事中には掲載せず、代わりにGistへのリンクを記載するようにしています。


第1回ではJavaScript記号プログラミングについての基礎的な考え方を中心として取り上げました。
そのため、実際に記号だけでコードを書く場面では、「このようにすればできますね」というように方向性のみを示すにとどめた箇所が多くあります。
今回の記事では、JavaScriptプログラムを記号化するプログラムの作成を題材として、前回の記事をなぞりながら、実際に記号だけで書かれたプログラムを扱っていきます。

レギュレーション

記号プログラムの制約は、前回に引き続き以下のようなものとします。

  • プログラム中に使用してよいのは、[]()+=の6種類の記号のみ。
  • ブラウザとnode.jsの両方で動作しなければならない。
    • よって、明示的なwindowglobalへのアクセスは禁止。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つの問題があります。

  1. 出力結果が長すぎる
  2. $n=20000$ ぐらい4convertNumber(n)がスタックオーバーフローを起こす
  3. それより前に、$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,...)"));

という形のコードを記号だけで書いたものでした。
このコードのそれぞれの部分の文字数を見てみると、

文字数
eval 2,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!とコンソールに表示されました。
というわけで、使えたものではありませんが「記号プログラムで書かれた記号プログラムコンパイラ」が作成できました。めでたし。


  1. https://github.com/tc39/proposal-global, ?globalThis?と?global?と?this? 

  2. 他に、eval(String.fromCodePoint(49)+String.fromCodePoint(43)+String.fromCodePoint(49));という形式で書くことも考えられます。とはいえこれは出力コードの長さがとんでもないことになりそうなので、今回は採用しないことにします。 

  3. 前回の記事でも指摘した点ですが、この実装の動作はStringオブジェクトに存在する他のメソッドの名前に依存するため、将来に渡って正しく動作する保証はありません。今回は簡単のためこのようなコードにしていますが、この時点で手に入っている文字のみで確実に"C"を得られる方法として、 Object.getOwnPropertyNames(String).join("").split("").filter(eval("Function.bind(undefined,(undefined+[])[0])")("return [true][u.codePointAt()-67]"))[0]を提示しておきます。 

  4. スタックに割り当てられている領域のサイズは処理系によって異なります。node.js(V8)のデフォルトは984KBで、本記事ではこれを基準としています。 

  5. https://kangax.github.io/compat-table/es6/#test-proper_tail_calls_(tail_call_optimisation) 

  6. とはいっても800前後でスタックオーバーフローになるのは結構意外でした。配列の添字アクセスって普通の関数呼び出しの数倍以上もコールスタックを消費するんでしょうか? 

  7. 配列aをhint値"default"または"string"でプリミティブに変換すると、a.toString()が実行され、a.join()の結果が返ります。 https://www.ecma-international.org/ecma-262/#sec-array.prototype.tostring 

  8. べつに真面目に読まなくていいです 

  9. JavaScriptのプリミティブへの変換を完全に理解する 

  10. 蛇足ですが、変換する数字のうち最も上の桁だけは配列で囲わずに1+[2]+[3]+... とすることができます。最初の+演算子の右辺は文字列となるため問題なく"123..."になり、これで2文字節約できます。ほかの桁で[]を外すと、+が複数個連続してしまうためにエラーとなります。 

  11. むしろ、前者では先頭に単項演算子の+が存在するため、返り値に対して前から+二項演算子を適用しようとするとa++xのような形となってしまい、10と同様にエラーが発生するおそれがあるため注意が必要です。このような場合、文字数は増えてしまいますがa+[+x][0]a+(+x)のような形で囲うことで、意図しない+の連続を防ぐことができます。 

  12. 7と同様にArray.prototype.join()が呼び出されますが、Array(2)よりlengthは2となっており、また0番目と1番目の要素はundefinedとして扱われますが、undefinednulljoin()の処理では空文字列に置換されるため、結果として","が返ります https://www.ecma-international.org/ecma-262/#sec-array.prototype.join 

  13. 2本目を書くのに3ヶ月も空けたのにまだ次回作を書く気があるんですか???(大幅に間が空いてしまい申し訳ないです、第3回も気長にお待ちください) 

  14. 書いたクロージャに名前をつけるなどしておき、文字列に型変換すれば文字列化された実装が得られます 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ページ遷移時の「このサイトを離れますか?」を意地でも表示しない方法

フォームの入力値を編集したときなどに表示される「このサイトを離れますか?」というポップアップを非表示にする方法です。

色々と調べましたが、Object.definePropertyの setter で検知して値を上書きすると確実でした。

Object.defineProperty(window, 'onbeforeunload', {
  set(newValue) {
    if (typeof newValue === 'function') window.onbeforeunload = null;
  }
});

ちなみに if (typeof newValue === 'function') 等のチェックがないと変更を検知し続けて無限ループになり、スタックが大変なことになるで気をつけましょう。

また、もし他に良い方法をご存知の方がいたらコメントで教えてください?

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イテレータ

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

イテレータを備えた組み込みオブジェクトには以下のようなものがあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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!');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列の値をすっきり比較するやり方まとめ

目的と前提

  • 実装するときにいつもひと手間かかってしまうので、配列の比較ロジックの備忘録です。
  • 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' ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS SafariのouterHeight, outerWidthが知らないうちに使えるようになっていた

前まで、iOS SafariでouterHeightouterWidthを取ると0になる、という現象があったのですが、新しめのシミュレーターで確認したところとれました。どうやらiOS12.2あたりから使えるようです。

iOS Simulator での確認結果

スクリーンショット 2019-07-09 15.52.11.png

確認用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>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】メニュー周りの権限制御を行う

概要

VueなどでSPAを構築する場合、大体こんな感じのメニューがあると思います。
 2019-08-24 13.39.25.png

クリックされた項目でURLをルーティングして内容を変化させるやつですね。

権限制御を行う

このメニューで、以下のような権限制御を行う場合の例をご紹介します。
(※バックエンドについては今回省略します。)

  • ユーザーのロールに設定された権限によりメニューの項目を出し分けたい
  • 使用する権限を持っていない場合、項目は非表示とする
  • URL直打ちでも開けないようにしたい

環境

テーブル

テーブルはこんな感じです
 2019-08-26 21.32.16.png
ユーザーはロールを1つ持ち、ロールは権限を複数持つ構成となっています。

ユーザーテーブルのレコード例

ユーザーコード パスワード ロールコード
admin ******* admin
user1 ******* writer
user2 ******* readonly

ロール権限のレコード例

ロールコード 権限
admin menu1
admin menu2
admin menu3
writer menu1
writer menu2
readonly menu1

権限を取得

ストア

  • ユーザーに紐付いた権限を保持します。
action-types.js
export const GET_MENU_FLGS = "GET_MENU_FLGS";

export const ACTION = {
  GET_MENU_FLGS
};
mutation-types.js
export const UPDATE = "UPDATE";

export const MUTATION = {
  UPDATE
};
menu-store.js
import { 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.js
import 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を使うとナビゲーションガードが扱いやすくなりました
  • 権限をどのタイミングで取得するかは賛否両論ありそうです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォーム処理で扱う正規表現

フォームの処理で、「郵便番号じゃないから書き直して!」「ふりがなじゃないから書き直して!」というのをやろうとしたとき、正規表現というものが必要になりました。
ちょっと練習もかねて少しみてみます。

正規表現って?

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桁という意味になっています!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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関数はこのコールバック地獄のためだけの関数であり、外部から使用されることもなく
名前空間を汚すだけである。

このような事象を回避するための方法として、promiseasync/await などが用意されている。

まとめ

非同期処理とコールバック関数について理解を深めることができました。

最後に出てきた promiseasync/await について、これから学習していきますので、
またこのような形で残しておきたいと思います。

実際のアプリを実装するにはもう少し掛かりそうです。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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コマンドを実行したかだのいろいろ対処法が
書かれていたがどれでも解決できないところ以下の記事を見つけた。

https://stackoverflow.com/questions/49138501/vue-warn-failed-to-mount-component-template-or-render-function-not-defined-i

どうやら app.jsで

Vue.component('my-component', require('./components/MyComponent.vue').default);

最後に.defaultを追記するだけでした。(意味は知らない)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js 9で3分で1からTypeScriptのReactサーバーとGraphQLサーバを立てよう

概要

  • Next.js 9のAPI RouteにGraphQLのエンドポイントを生やします
  • Next.jsもGraphQLもTypescriptで書けます

やり方

1. 必要なパッケージをインストール

terminal
yarn add apollo-server-micro graphql micro next@latest react@latest react-dom@latest
yarn add -D typescript @types/react

2. テキトーにメインページを作る

pages/index.tsxを作り、テキトーにメインページを立ちあげる。拡張子をtsxにしておくと、Next.js 9は勝手にTypescriptのtsconfig.jsonを用意してくれる

速さ⚡️を求めるならば、tsconfig.jsonを書く前に、サーバーを立ち上げる。

テキトーなメインページの例?

pages/index.tsx
export default ()=>(
    <p>ほげ</p>
)

Reactのコンポーネントをexport defaultすればそれが配信される。画像などのファイルは/static配下に単に配置すれば配信される。

サーバー立ち上げ
terminal
yarn 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)

http://localhost:3000
スクリーンショット 2019-07-09 13.55.19.png

※ Next.js 9からサーバーでプレレンダリングできていると、右下に⚡️マークがつくようになりました。

3. テキトーにGraphQLサーバーを作る

/pages/api配下にはAPIのエンドポイントを生やせる。GraphQLのモックサーバを立てよう。

/pages/api/index.ts
import { 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が立ち上がる。

http://localhost:3000/api
スクリーンショット 2019-07-09 13.59.28.png

??嬉しい??

ここから、ApolloClientを入れるなり、モックじゃないGraphQLサーバーをこしらえるなりすると良い。

参考: Apollo Client + React 入門

終わり


ポイント

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-microcreateHandlerにオプションを指定する。

const apolloServer = new ApolloServer({ typeDefs, resolvers });

const handler = apolloServer.createHandler({path:"/api"});

このhandlerexport 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のサーバを立てられるようになった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の有名どころのコーディング規約について調べ、ソースコードを自動チェックする環境を準備してみました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのMapをディープコピーする

本題

コピー対象のMapを new Map() に放り込むだけ

const previousMap = new Map([[1, "hoge"], [2, "fuga"]]);
const newMap = new Map(previous);

console.log(previousMap === newMap); // false

なして必要か

Reactのstate上で set() するといかんので。
コピーしてから新しい方を更新して setState({myMap: new}) する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

関数が存在していたら実行する

/* ★共通部 各ページの起動時読み込み関数 */
if(typeof init == 'function') { 
 init();  
}

/* 状況により存在有無が変わる関数 */
function init(){
 
}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 
})
// 6

Object同士の足し算だとポンコツな結果が返ってくる(いや、ポンコツなのは仕様を理解できていない自分なのだと思うが)

[{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した結果を入れていく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebAssembly でセルオートマトン

 WebAssemblyの練習に、セル・オートマトンの有名な問題ライフゲーム(Game of Life)を実装してみました。

ライフゲームのルール

 Wikipediaより、典型的と思われるルールを採用しました。

誕生
 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
生存
 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
過疎
 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
過密
 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

結果

まず出力結果から。ここで、nはキャンバス1辺に含まれるセルの数。初期条件は、市松模様からスタートしています。デモは https://cellular-automaton-webassembly.herokuapp.com/ にあげてあります。

n = 20

cell20

n = 50

cell50

n = 100

cell100

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にあげています。

ヘッダー部分

 ポイントとして、RcRefCellrequest_animation_frameの呼び出しに、RwLocklazy_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 u8 cannot be sent between threads safely
help: within web::web_sys::HtmlElement, the trait std::marker::Send is 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.jswasm_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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
//=> 15

map (配列のある値を指定したい場合に使用する)

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.js
var 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.js
var users = [
  { name: '鈴木' },
  { name: '佐藤' },
  { name: '田中' }
  ];

var users;
//配列からuser.nameが田中を見つけて取り出す
users.find(function(user) {
           return user.name === '田中';
           });

//=>{"name":"田中"}
//findでは最初に見つけた要素を取り出す

every(条件を全て検証し全て条件があてはまるとき。 論理積)

every.js
var computers = [
  { name: 'ASUS', ram: 25 },
  { name: 'TOSHIBA', ram: 14 },
  { name: 'mac', ram: 5 }
  ];
//全てのramが15以上であるか?
computers.every(function(computer) {
  return computer.ram>= 15;
});

//=>False

some(条件を全て検証して、どれか一つでも条件が当てはまる場合。 論理和)

some.js
var computers = [
  { name: 'ASUS', ram: 25 },
  { name: 'TOSHIBA', ram: 14 },
  { name: 'mac', ram: 5 }
  ];
//一つでもramが15以上であるか?
computers.some(function(computer) {
  return computer.ram>= 15;
});

//=>True

reduce(プロパティの集約)

redue.js
var numbers = [ 3, 5, 4 ];
var sum = 0;

numbers.reduce(function(sum, number) {
   return sum + number;
}, 0);
//0は初期値。
//=> 12
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
//=> 15

map (配列のある値を指定したい場合に使用する)

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.js
var 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.js
var users = [
  { name: '鈴木' },
  { name: '佐藤' },
  { name: '田中' }
  ];

var users;
//配列からuser.nameが田中を見つけて取り出す
users.find(function(user) {
           return user.name === '田中';
           });

//=>{"name":"田中"}
//findでは最初に見つけた要素を取り出す

every(条件を全て検証し全て条件があてはまるとき。 論理積)

every.js
var computers = [
  { name: 'ASUS', ram: 25 },
  { name: 'TOSHIBA', ram: 14 },
  { name: 'mac', ram: 5 }
  ];
//全てのramが15以上であるか?
computers.every(function(computer) {
  return computer.ram>= 15;
});

//=>False

some(条件を全て検証して、どれか一つでも条件が当てはまる場合。 論理和)

some.js
var computers = [
  { name: 'ASUS', ram: 25 },
  { name: 'TOSHIBA', ram: 14 },
  { name: 'mac', ram: 5 }
  ];
//一つでもramが15以上であるか?
computers.some(function(computer) {
  return computer.ram>= 15;
});

//=>True

reduce(プロパティの集約)

redue.js
var numbers = [ 3, 5, 4 ];
var sum = 0;

numbers.reduce(function(sum, number) {
   return sum + number;
}, 0);
//0は初期値。
//=> 12
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

indexOfとspliceメソッドを使った削除方法 JavaScript 【Snippets】

概要

JavaScriptで特定の要素を削除(配列から取り除く)方法がいくつかあるので、今回はspliceの書き方例をメモします。

MDN: splice

MDN: indexOf

コード

index.js
const 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' }です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む