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

モーダルウインドウを背景固定にしてスクロールバー分のガタつき問題を解決しつつ、iOS Safariにも対応してしかもjQuery非依存にしたいワガママなアナタへ捧ぐ愛のコード

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

JavaScript 配列関係のコード集

JavaScriptの配列関係のコード集です。何かを作っていたときの副産物の塊です。

最初の要素を特別扱いして二次元配列→2要素配列の配列[[a, b, c, ...], [d, e, f, ...], ...]→[[a, b], [a, c], [d, e], [d, e], ...]

const items = [
    ["2020/05/31", "A", "B", "C"],
    ["2020/05/31", "D", "E", "F"],
    ["2020/06/01", "G", "H", "I"],
];
Array.from(items).flatMap((raw) => raw.slice(1).map(entry => [ raw[0], entry ]));
/*
(9) […]
0: Array [ "2020/05/31", "A" ]
1: Array [ "2020/05/31", "B" ]
2: Array [ "2020/05/31", "C" ]
3: Array [ "2020/05/31", "D" ]
4: Array [ "2020/05/31", "E" ]
5: Array [ "2020/05/31", "F" ]
6: Array [ "2020/06/01", "G" ]
7: Array [ "2020/06/01", "H" ]
8: Array [ "2020/06/01", "I" ]
length: 9
*/
  • Array.prototype.flatMapmapの結果をflatで処理した配列として取得できます。
  • Array.prototype.slice(N)で配列のN番目の要素から末尾までの部分を取得できます。

最初の要素を特別扱いして二次元配列→2要素配列の配列[[a, b, c, ...], [d, e, f, ...], ...]→[[b,a], [c, a], [e, a], [e, a], ...]

Array.from(items).flatMap((raw) => raw.slice(1).map(entry => [ entry, raw[0] ]));
/*
(9) […]
0: Array [ "A", "2020/05/31" ]
1: Array [ "B", "2020/05/31" ]
2: Array [ "C", "2020/05/31" ]
3: Array [ "D", "2020/05/31" ]
4: Array [ "E", "2020/05/31" ]
​5: Array [ "F", "2020/05/31" ]
6: Array [ "G", "2020/06/01" ]
​7: Array [ "H", "2020/06/01" ]
​8: Array [ "I", "2020/06/01" ]
length: 9
*/

最初の要素を特別扱いして二次元配列→2要素オブジェクトの配列[[a, b, c, ...], [d, e, f, ...], ...]→[{key1:a, key2:b}, {key1:a, key2:c}, {key1:d, key2:e}, {key1:d, key2:e}, ...]

const items = [
    ["2020/05/31", "A", "B", "C"],
    ["2020/05/31", "D", "E", "F"],
    ["2020/06/01", "G", "H", "I"],
];

Array.from(items).flatMap((raw) => raw.slice(1).map(entry => ({  key1: raw[0], key2: entry })));
/*
(9) […]
0: Object { key1: "2020/05/31", key2: "A" }
1: Object { key1: "2020/05/31", key2: "B" }
2: Object { key1: "2020/05/31", key2: "C" }
3: Object { key1: "2020/05/31", key2: "D" }
4: Object { key1: "2020/05/31", key2: "E" }
5: Object { key1: "2020/05/31", key2: "F" }
6: Object { key1: "2020/06/01", key2: "G" }
7: Object { key1: "2020/06/01", key2: "H" }
8: Object { key1: "2020/06/01", key2: "I" }
length: 9
*/
  • ラムダ式でオブジェクトリテラルを使うとき、直接() => {...}とすると関数と誤解されてエラーが発生します。() => {return {...};}または() => ({...})で対処できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめてのThree.js 9章 日記

はじめてのthree.js

新たに知った知識

  • 複数のモデルをアニメーションするの意外と簡単

        function render() {
            stats.update();
            // rotate the cube around its axes
            cube.rotation.x += controls.rotationSpeed;
            cube.rotation.y += controls.rotationSpeed;
            cube.rotation.z += controls.rotationSpeed;

            // bounce the sphere up and down
            step += controls.bouncingSpeed;
            sphere.position.x = 20 + (10 * (Math.cos(step)));
            sphere.position.y = 2 + (10 * Math.abs(Math.sin(step)));

            // scale the cylinder

            scalingStep += controls.scalingSpeed;
            var scaleX = Math.abs(Math.sin(scalingStep / 4));
            var scaleY = Math.abs(Math.cos(scalingStep / 5));
            var scaleZ = Math.abs(Math.sin(scalingStep / 7));
            cylinder.scale.set(scaleX, scaleY, scaleZ);

            // render using requestAnimationFrame
            renderer.render(scene, camera);
            requestAnimationFrame(render);

        }
  • tweeningでイージングなどがつけれる
  • trackball control
  • three.clockオブジェクトで時間通りに動く
  • morphtargetinfluenceは2つのオブジェクトをスムーズに変形移行させてくれる
        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({morphTargets: true, color: 0xff0000});

        // define morphtargets, we'll use the vertices from these geometries
        var cubeTarget1 = new THREE.BoxGeometry(2, 10, 2);
        var cubeTarget2 = new THREE.BoxGeometry(8, 2, 8);

        // define morphtargets and compute the morphnormal
        cubeGeometry.morphTargets[0] = {name: 't1', vertices: cubeTarget2.vertices};
        cubeGeometry.morphTargets[1] = {name: 't2', vertices: cubeTarget1.vertices};

気づいたこと

  • flycontrolとfirstpersoncontrolは結構酔う

wow moment

まだ解決していない点

  • animationmixerの役割

10章はこちら

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

【jQuery】for ループ内で Ajax で取得した値がうまく表示できない

実装したい機能

特定のクラスを持つエレメントから商品IDを取得し
それぞれの商品詳細ページからカテゴリを取得して
対応する商品IDを持つエレメントに表示する。

使用するコード

開発環境
jquery > 1.7.2

表示するページ

index.html
...
<ul>
 <li class="products pid_111">
  <p>商品名①</p>
  <p class="cate"><!--カテゴリを表示したい--></p>
 </li>
 <li class="products pid_222">
  <p>商品名②</p>
  <p class="cate"><!--カテゴリを表示したい--></p>
 </li>
 <li class="products pid_333">
  <p>商品名③</p>
  <p class="cate"><!--カテゴリを表示したい--></p>
 </li>
</ul>
...

商品詳細ページ

111.html
...
<div>
 <p>商品名①</p>
 <p class="p_cate">カテゴリ①</p><!--これを取得したい-->
</div>
...

書いてみたコード

まず、自分の知識で書いてみました。

product.js
var id_name = $('.products');

for (var i = 0; i < $(id_name).length; i++) {

   var id_sp = id_name.eq(i).attr('class').split(" ")[1];
   var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);

   (function(i){
      $.ajax({
         url: id + '.html',
         type: 'GET',
         dataType: 'html',
      })
      .done(function (data) {
         $('.' + id_sp).html($(data).find('.p_cate').text());
       })
    })(i);
}

実行結果

index.html
<ul>
 <li class="products pid_111">
  <p>商品名①</p>
  <p class="cate"></p>
 </li>
 <li class="products pid_222">
  <p>商品名②</p>
  <p class="cate"></p>
 </li>
 <li class="products pid_333">
  <p>商品名③</p>
  <p class="cate">カテゴリ③</p>
 </li>
</ul>

最後の商品にだけカテゴリが入ってしまいます。

コンソール実行

product.js
...
.done(function (data) {
   $('.' + id_sp).html($(data).find('.p_cate').text());
   console.log(data + ':' + id_sp);
})
/*
=> 結果
p_333:カテゴリ①
p_333:カテゴリ②
p_333:カテゴリ③
*/

ajax自体は正常に動いている様子です。
ループも商品の個数分回っています。
しかし、商品IDがすべて最後の商品のIDになっています。

考察

ajax処理が完了する前にループが回りきってしまうため
商品IDを取得するタイミングと商品カテゴリを取得するタイミングにずれがある?

追記

@netebakari 様にコメントを頂き
そもそも「クロージャの性質」によるものだとわかりました。
(コメント参照)

forループ内の1つ目の変数
var id_name = $('.products');
constletで宣言することで
forループ内でも変数(定数)を維持することができて
適応した商品IDを取得することができました。

動くコード

product.js
var id_name = $('.products');

for (var i = 0; i < $(id_name).length; i++) {
   const id_sp = id_name.eq(i).attr('class').split(" ")[1]; // var→constへ変更
   var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);

   $.ajax({
       url: id + '.html',
       type: 'GET',
       dataType: 'html',
   })
   .done(function (data) {
       $('.' + id_sp).html($(data).find('.p_cate').text());
   });
}

コンソールを実行

product.js
...
.done(function (data) {
   $('.' + id_sp).html($(data).find('.p_cate').text());
   console.log(data + ':' + id_sp);
})
/*
=> 結果
p_111:カテゴリ①
p_222:カテゴリ②
p_333:カテゴリ③
*/

今度こそ、想定の動きをしてくれました!

対策を調べる

グーグル先生に聞いてみたところ、大きく3つの対応策がありました。

ajaxを非同期にする

こちらは特に変化なしです。
これが原因ではないようです。

無名関数で囲う

すでに囲っていました。
これも原因ではないようです。

取得した値を配列に含んでeachで吐き出し

もう、これしかありません。
頑張って書いてみます。

product.js
var id_name = $('.products');
var arr = [  ];// <- 追加

for (var i = 0; i < $(id_name).length; i++) {

   var id_sp = id_name.eq(i).attr('class').split(" ")[1];
   var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);

   (function(i){
      $.ajax({
         url: id + '.html',
         type: 'GET',
         dataType: 'html',
      })
      .done(function (data) {
         var cate = $(data).find('.p_cate').text();// <- 変更
         arr.push( {p_id:id_sp, cate_name:cate} );
       })
   })(i);

   $.each(arr, function(index, value) { // <- 追加
       var find = '.' + value.p_id
       var cate_in = value.cate_name
       $(find).html(cate_in);
   });

}

コンソール実行

product.js
...
$.each(arr, function(index, value) {
   var find = '.' + value.p_id
   var cate_in = value.cate_name
   $(find).html(cate_in);
   console.log(find + ':' + cate_in);
});
/*
=> 結果
p_111:カテゴリ①
p_222:カテゴリ②
p_333:カテゴリ③
*/

うまく回っています!
商品IDとカテゴリが適合しています!

実行

index.html
<ul>
 <li class="products pid_111">
  <p>商品名①</p>
  <p class="cate">カテゴリ①</p>
 </li>
 <li class="products pid_222">
  <p>商品名②</p>
  <p class="cate">カテゴリ②</p>
 </li>
 <li class="products pid_333">
  <p>商品名③</p>
  <p class="cate">カテゴリ③</p>
 </li>
</ul>

しっかりと表示されました!
一安心です…笑

まとめ

一見単純な動きでも、ループや非同期通信が絡み合うと想定した動きにならないということを体感できました。
取得したデータを配列に格納して吐き出す、という考え方も知ることができました。
思考の幅がぐっと広がりました!

初学者が手探りで書いたコードなので、間違っている可能性も高いです。
もっと合理的な書き方をご存知の方は是非コメントを下さい!

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

【Vue】イメージをhoverすると扉が閉まるように背景が現れて文字が表示されるCSSアニメーション実装

スクリーンショット 2020-05-30 20.47.03.png

Vueバージョン確認

npm list vue

まずは上記コマンドでバージョンの確認

twinzlabo@0.1.0 /Users/twinzlabo

── vue@2.6.11

イメージをhoverするとカッコよく文字が表示されるCSSアニメーション実装

すでに上の方で確認してもらったかと思いますが、

特に変哲もない画像にスタイル修正を行うことで

画像をhoverするとスーッと白い背景+descriptionが現れるクールなアニメーション実装をしていきましょう
スクリーンショット 2020-05-30 20.47.16.png

デフォルトの上の画像をhoverしたら下の画像のように背景と文章が現れるアニメーションをカスタマイズしていきます
スクリーンショット 2020-05-30 20.47.03.png

この感じなかなかクールですよね

では早速コードをコピペしていきましょう

<template>
  <div class="container">
    <div class="bg-pic">
      <div class="pic" style="background-image : url('https://cdn.pixabay.com/photo/2017/01/17/23/05/valetta-1988455_1280.jpg');">
        <div class="screen-left"></div>
        <div class="screen-right"></div>
        <div class="fonts">
          <h1>Malta's capture</h1>
          <p>this is a photo in malta <br><br><br>Have A Good Time</p>
        </div>
      </div>
    </div>
  </div>
</template>
<style>
body {
  background-color: #E43;
}

.container {
  width  : 960px;
  margin : 0 auto;
}

.container:after{
  clear   : both;
  display : table;
  content : '';
}

.bg-pic {
  width  : 640px;
  height : 500px;
  margin : 20px;
  background-color: white;
  float  : left;
  cursor : pointer;
  box-shadow : 3px 3px 5px 0px rgba(0,0,0,0.5);
}

.pic {
  width  : 640px;
  height : 500px;
  position: relative;
  overflow: hidden;
  background-color: #102B46;
}

.fonts {
  background-color : #ffffff;
  width            : 640px;
  height           : 500px;
  padding          : 10px;
  top : 0;
  left: 0;
  font-family : georgia;
  color       : #888888;
  opacity : 0;
  transition : opacity .8s;
}

.fonts h1 {
  margin-top: 100px;
  margin-bottom : 40px;
}

.fonts p {
  font-size : 14px;
  font-style: italic;
  text-align: center;
  line-height : 20px;
}

.pic:hover .fonts {
  opacity : 1;
  transition : opacity .2s .3s;
}

.pic div {
  position : absolute;
}


/* screen open and close */

.screen-right , .screen-left{
  width : 50%;
  height: inherit;
  background-color : #ffffff;
  top : 0;
  transition : all .3s;
}

.screen-right {
  left : 100%;
}

.screen-left {
  right : 100%;
}

.pic:hover .screen-right {
  transition : all .3s;
  left : 50%;
}

.pic:hover .screen-left {
  transition : all .3s;
  right : 50%;
}
</style>

いかがでしたでしょうか?

画像をhoverするとアニメーションが実行されましたか?

下の記事では別の応用的な画像のデザイン方法を掲載しているので是非挑戦してみてください

以上です

より応用的でお洒落な画像一覧画面を作りたい方にはこちらの記事がおすすめです
【Vue/画像一覧をコピペだけ】イメージをhoverすると背景が閉じるように変化し文字が現れるアニメーション
スクリーンショット 2020-05-31 0.07.40.png

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

IMI住所変換コンポーネントでいろんな住所を正規化してみた

先日、経済産業省から「IMIコンポーネントツール」というものが公開されました。

IMIコンポーネントツール
https://info.gbiz.go.jp/tools/imi_tools/

今回、この中の「(1) 住所変換コンポーネント」をいろいろ使ってみたので、そのことについて投稿したいと思います。

住所変換コンポーネントについて、以下のブログで詳しく解説されています。

経産省発の npm モジュール!住所や電話番号の正規化、ジオコーディングなどができる IMI コンポーネントツールを試した!

住所変換コンポーネントは、簡単に言うとあいまいな住所表記を正規化してくれるツールと理解しています。
私は個人でLinked Open Addresses Japan(以下、住所LODと呼びます)という住所データをLinked Open Dataで公開するサイトを運営しています。

住所LODについて、以下のQiitaの投稿を参照してください。

緯度経度付き住所LODを作りました
https://qiita.com/uedayou/items/b6be807d36526593b4da

このサイトでも住所の正規化をしていますが、残念ながら不完全なもので、文字列としての住所からこの住所LODの当該住所のURLを取得しようとすると失敗してしまうことが多々ありました。
そこで、このIMIコンポーネントツールの住所変換コンポーネントで、さまざまな住所でどの程度、住所LODのURLが取得可能か試してみました。

使用する住所

ちょうど、DBpedia日本語版から取得した1万件ほどの住所(計10121件)を住所LODのURLに変換しようとしていましたので、このデータを使うことにしました。DBpediaのデータは、Wikipediaの編集データが元になっていますので、その多くが人手での入力であると思います。そのため住所の表記にもゆれが比較的あるデータになります。

都道府県別の変換対象住所数は以下のようになります(上位30件)。

住所 件数 住所 件数 住所 件数
東京都 870 大阪府 572 北海道 555
愛知県 533 兵庫県 422 神奈川県 402
千葉県 384 福岡県 381 長野県 271
広島県 263 京都府 256 埼玉県 255
三重県 250 静岡県 234 新潟県 225
富山県 201 福島県 197 岐阜県 195
岩手県 194 宮城県 185 岡山県 178
高知県 174 熊本県 169 山口県 168
青森県 160 愛媛県 156 秋田県 153
群馬県 150 奈良県 141 茨城県 136

住所変換コンポーネントで住所LODのURLを取得するスクリプト

変換に使用したスクリプト(node)は以下のようなものです。

index.js
const fs = require('fs');
const axios = require('axios');
const convert = require('imi-enrichment-address');

const getLoaUri = async (adr) => {
  const url = "http://uedayou.net/loa/"+encodeURI(adr);
  try {
    const res = await axios.get(url);
    return res.request.res.responseUrl;
  } catch (e) {
    console.error(`${e.response.status}: ${e.response.statusText}`);
  }
};

const getAddress = async (str) => {
  const jsonld = await convert(str);
  const obj = jsonld['住所'];
  let metadata, address;
  if ('メタデータ' in jsonld) {
    metadata = jsonld['メタデータ']['説明'];
    console.log(`説明: ${jsonld['メタデータ']['説明']}`);
  }
  if ('都道府県' in obj) {
    address = obj['都道府県']+(obj['市区町村']||"")
      +(obj['']||"")+(obj['町名']||"")
      +(obj['丁目']?obj['丁目']+"丁目":"")
      +(obj['番地']?obj['番地']+"番地":"")
      +(obj['']||"");
  }
  return {metadata,address};
};

(async()=>{
  const address = "霞が関2";
  console.log(`入力: ${address}`);
  const norm = await getAddress(address);
  console.log(`正規化: ${norm.address}`);
  if (norm.address) {
    const uri = await getLoaUri(norm.address);
    console.log(`住所LOD: ${decodeURIComponent(uri)}`);
  }
})();

スクリプトを動かすには axios と imi-enrichment-address をインストールする必要があります。

npm install axios
npm install https://info.gbiz.go.jp/tools/imi_tools/resource/imi-enrichment-address/imi-enrichment-address-2.0.0.tgz

node index.jsで実行すると以下のように住所「霞が関2」が正規化され、住所LODのURLを取得します。

入力: 霞が関2
正規化: 東京都千代田区霞が関2丁目
住所LOD: http://uedayou.net/loa/東京都千代田区霞が関二丁目

正規化・住所LOD URL取得精度

10121件の住所について、どれくらい正規化、住所LODのURLが取得できたかを集計しました。

件数 割合
正規化 9554 94.4%
URL変換 6750 70.7%

※URL変換の割合の分母は正規化に成功した数(9554)です。

住所変換コンポーネントでの正規化についてはかなり高い割合で成功しました。また、その正規化したデータから70% 住所LODのURLが取得できました。

住所変換コンポーネントによる正規化をせずに住所LODのURL取得をした結果と比較すると

件数
URL変換
(正規化あり)
6750
URL変換
(正規化なし)
4880

と正規化により、これまで失敗していた住所もたくさんURL取得できできるようになったことがわかりました。

住所変換コンポーネントで正規化すると、その住所の位置情報(緯度経度)も付与されます。どの程度付与されるかも調べてみました。

件数
正規化 9554
位置情報 9302

正規化された住所のほとんどに位置情報が付与されましたが、ごく一部付与されないものもありました。

最後に住所の正規化でのエラーメッセージをまとめました。

エラーメッセージ 件数
該当する町名が見つかりません 467
該当する地名が見つかりません 38
該当する市区町村名が見つかりません 31
該当する丁目が見つかりません 21
該当する地名が複数あります 6
地名コードに対応するインスタンスがありません 4

正規化・URL取得に失敗する場合

正規化できなかった住所の都道府県上位10件を集計しました。

住所 件数 割合
北海道 162 29.2%(162/555)
京都府 41 16.0%(41/256)
愛知県 39 7.3%(39/533)
奈良県 33 23.4%(33/141)
大阪府 29 5.1%(29/572)
岐阜県 26 13.3%(26/195)
高知県 23 13.2%(23/174)
長野県 23 8.5%(23/271)
埼玉県 21 8.2%(21/255)
福岡県 12 3.1%(12/381)
愛媛県 11 7.1%(11/156)

割合として、北海道、奈良、京都が比較的高い印象があります。京都は通り名がついた特殊な住所は正規化し難いことが影響しているのかも、と思いますが、北海道、奈良は詳細を見ないとよくわかりません。単純に書き間違いが多いのかもしれません。

住所LODのURL取得の失敗は以下の通りです。

住所 件数
北海道 272
長野県 171
青森県 135
福岡県 133
千葉県 111
岩手県 109
福島県 108
愛知県 108
新潟県 105
静岡県 101
三重県 96

正規化失敗より数が多くなっています。これは、住所LODは住所に「郡」を含めるのに対し、住所変換コンポーネントは郡を削除してしまうことが大きく、ランキングに大都市圏が少ない理由はそのあたりが影響しているようです。このあたりは住所LOD側での工夫が必要だと思います。

「京都府相楽郡精華町精華台1」を変換した結果
{
  '@context': 'https://imi.go.jp/ns/core/context.jsonld',
  '@type': '場所型',
  '住所': {
    '@type': '住所型',
    '表記': '京都府相楽郡精華町精華台1',
    '都道府県': '京都府',
    '都道府県コード': 'http://data.e-stat.go.jp/lod/sac/C26000',
    '市区町村': '精華町',
    '市区町村コード': 'http://data.e-stat.go.jp/lod/sac/C26366',
    '町名': '精華台',
    '丁目': '1'
  },
  '地理座標': { '@type': '座標型', '緯度': '34.752484', '経度': '135.786199' }
}

IMI住所変換コンポーネントを使ってみて

人手で入力された住所について、そのほとんど(94%)が正規化に成功し、高い確率で位置情報の取得もできました。当初の目的の住所LODのURL取得についても取得割合を大きく向上できました。
少しコードがかければその扱いも比較的容易ですし、今後多方面で活用できるツールになるのかなと思いました。

住所LODにIMI住所変換コンポーネントを組み込んでみた

早速、住所LODの表記ゆれ修正部分に住所変換コンポーネントを組み込んでみました。
今までよりも少しだけレスポンスに時間がかかりますが、これまでNot Foundになっていた住所が住所LODの該当ページに転送できるようになりました。

例えば、「霞が関2」ではこれまでは、住所LODのページは見れませんでしたが、IMI住所変換コンポーネントにより、http://uedayou.net/loa/東京都千代田区霞が関二丁目 に転送されるようになりました。

住所LOD「霞が関2」ページ

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

【Vue.js】ライフサイクルフック、イベント/キー修飾子のまとめ(コンポーネントもあるよ)

Vueまとめパート2

こちらの記事は、Adnan Babakan 氏によりDev.to上で公開された『 Vue cheat sheet 2 』の邦訳版です(原著者から許可を得た上での公開です)

原著をベースに説明の足りない部分は適宜、追記していく予定です。
(追記・改変の許可は得ています。)


Cover image for Vue cheat sheet 2

DEV.toコミュニティのみなさん、こんにちは!

これは『Vueまとめ』シリーズのパート2です。

一部の説明とコードサンプルは、Vue.js公式ウェブサイトから引用しています。

チートシート

ライフサイクルフック

ライフサイクルフック 実行されるタイミング
beforeCreate Vueインスタンスが初期化された後(Vueインスタンスが作成される前)
created Vueインスタンスが作成された後
beforeMount Vueインスタンスがマウントされる前
mounted Vueインスタンスがマウントされた後
beforeUpdate dataプロパティの値に変更があってDOMが更新される前
updated dataプロパティの値に変更があってDOMが更新された後
beforeDestroy Vueインスタンスが破棄される前
destroyed Vueインスタンスが破棄された後

ライフサイクルフック(Lifecycle Hook)

ライフサイクルフックとは、特定のイベント/タイミングで実行されるVueコンポーネント内の関数のこと。

beforeCreate

beforeCreate()はインスタンスが初期化された直後、データ(data)監視とイベント/ウォッチャ(watch)のセットアップの前に同期的に呼び出される。

const app = new Vue({
    beforeCreate () {
        console.log('Vueインスタンスが初期化されました!')
    }
})

created

created()はインスタンスの作成後に同期的に呼び出される。この段階では、インスタンスはオプション群の処理を完了している。つまり、データ(data)監視、算出プロパティ(computed)、メソッド(methods)、ウォッチャ(watch)、イベントコールバックのセットアップが完了している状態。ただし、マウントフェーズはまだ開始されておらず、$elプロパティはまだ利用できない。

const app = new Vue({
    created () {
        console.log('Vueインスタンスが作成されました!')
    }
})

beforeMount

マウントが始まる直前に呼び出される(レンダー関数が初めて呼び出される直前のタイミング)。1

const app = new Vue({
    beforeMount () {
        console.log('Vueインスタンスがマウントされようとしています!')
    }
})

mounted

インスタンスがマウントされた後に呼び出され、elは新しく作成されたvm.$elに置き換えられる。ルートインスタンスがドキュメント内要素にマウントされている場合、vm.$elmounted()が呼び出されるとドキュメント内要素になる。2

const app = new Vue({
    mounted () {
        console.log('Vueインスタンスがマウントされました!')
    }
})

beforeUpdate

DOMにパッチが適用される前に、データ(data)が変更されたときに呼び出される。これは、更新前に既存のDOMにアクセスするのに適したタイミングである。(たとえば、手動で追加されたイベントリスナーを削除する場合など)。3

const app = new Vue({
    beforeUpdate() {
        console.log('DOMが変更されようとしています!')
    }
})

updated

updated()はデータ変更によって仮想DOMが再レンダリングされてパッチが適用された後に呼び出される。

このフックが呼び出されると、コンポーネントのDOMが更新されるため、ここでDOMに依存する操作を実行できる。ただし、ほとんどの場合、このフック内で状態を変更すべきではない(無限更新ループになるため)。状態の変化に対応するには、代わりに算出プロパティ(computed)かウォッチャ(watch)を使うのが普通は良いとされる。4

const app = new Vue({
    updated() {
         console.log('DOMが更新されました!')
    }
})

beforeDestroy

Vueインスタンスが破棄される直前に呼び出される。この段階では、インスタンスはまだ完全に機能している。5

const app = new Vue({
    beforeDestroy() {
        console.log('Vueインスタンスが破棄されようとしています!')
    }
})

destroyed

destroyed()はVueインスタンスが破棄された後に呼び出される。このフックが呼び出されると、Vueインスタンスのすべてのディレクティブがバインド解除され、すべてのイベントリスナーが削除され、すべての子Vueインスタンスも破棄される。6

const app = new Vue({
    destroyed() {
        console.log('Vueインスタンスが破棄されました!')
    }
})

Events イベント

イベントは、HTMLにおける一つの要素おいて特定のアクションが実行されたときに呼び出される。

このシリーズの前の記事では、イベントについて基本的なことを説明したが、ここではもう少し詳しく説明する。

ちなみに@v-onの省略形なのでお忘れなく。

利用可能なイベント

v-onを使うと、すべてのJavaScriptイベントにアクセスできる。

<!-- ボタンに対して次のアクションが行われるとアラートが出る -->
<button @click="() => alert('Hello')">Do it</button>
<button @mouseover="() => alert('Hello')">Do it</button>
<button @mouseout="() => alert('Hello')">Do it</button>
<button @contextmenu="() => alert('Hello')">Do it</button>

フォームでsubmitイベントを発生させることもできる。

<form @submit="() => alert('This form is submitted')">
  <input type="text" />
</form>

イベント修飾子

イベント修飾子は、イベントの一部の動作を変更したり、イベントをより詳細に制御したりするために使用される。

イベント名の後の.に続けて修飾子を書く。

/* イベント修飾子の使い方 */
v-on:<イベント名>.<イベント修飾子>
// または
@<イベント名>.<イベント修飾子>
// 修飾子をチェーンして書くこともできる(書いた順に実行される)
@<イベント名>.<イベント修飾子1>.<イベント修飾子2>

.stop

<!-- クリックイベントの伝搬が止まる -->
<a v-on:click.stop="doThis"></a>

.prevent

<!-- submit イベントによってページがリロードされない -->
<form v-on:submit.prevent="onSubmit"></form>

.capture

<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div v-on:click.capture="doThis">...</div>

.once

.onceを使うと指定したクリックイベントは最大1回しかトリガーされない。

<!-- クリックイベントは1回だけ実行される -->
<a v-on:click.once="doThis"></a>

.self

<!-- event.target が要素自身のときだけ、ハンドラが呼び出される -->
<!-- 言い換えると子要素のときは呼び出されない -->
<div v-on:click.self="doThat">...</div>

.passive

<!-- `onScroll` が `event.preventDefault()` を含んでいたとしても -->
<!-- スクロールイベントのデフォルトの挙動(つまりスクロール)は -->
<!-- イベントの完了を待つことなくただちに発生するようになる -->
<div v-on:scroll.passive="onScroll">...</div>

キー修飾子/システム修飾子キー

Vueには、キーボードイベントを利用するときに、特定のキーをリスンするための修飾子が用意されている。

これらの修飾子は、keydown(キーを押す)またはkeyup(キーを押してから離す)などのいづれのキーイベントと一緒に使うことができる。

.enter

「enter」キーに反応をリスンする

<!-- Enterキーが押されたときにアラートする -->
<input @keydown.enter="() => alert('enterキーが押されました!')">

.tab

「tab」キーに反応をリスンする

<!-- Tabキーが押されたときにアラートする  -->
<input @keydown.tab="() => alert('tabキーが押されました!')">

.delete

「delete」キーと「backspace」キーの反応をリスンする。

<!-- deleteキーorbackspaceが押されたときにアラートする -->
<input @keydown.delete="() => alert('deleteキーが押されました!')">

.esc

「esc」キーの反応をリスンする。

<!-- escキーが押されたときにアラートする -->
<input @keydown.esc="() => alert('espキーが押されました!')">

.space

「space」キーの反応をリスンする。

<!-- spaceキーが押されたときにアラートする -->
<input @keydown.space="() => alert('spaceキーが押されました!')">

.up

「↑カーソル」キーの反応をリスンする。

<!-- ↑カーソルキーが押されたときにアラートする -->
<input @keydown.up="() => alert('↑カーソルキーが押されました!')">

.down

「↓カーソル」キーの反応をリスンする。

<!-- ↓カーソルキーが押されたときにアラートする -->
<input @keydown.down="() => alert('↓カーソルキーが押されました!')">

.right

「→カーソル」キーの反応をリスンする。

<!-- →カーソルキーが押されたときにアラートする -->
<input @keydown.right="() => alert('→カーソルキーが押されました!')">

.left

「←カーソル」キーの反応をリスンする。

<!-- ←カーソルキーが押されたときにアラートする -->
<input @keydown.left="() => alert('←カーソルキーが押されました!')">

.home

「home」キーの反応をリスンする。

<!-- homeキーが押されたときにアラートする -->
<input @keydown.home="() => alert('homeキーが押されました!')">

.end

「end」キーの反応をリスンする。

<!-- endキーが押されたときにアラートする -->
<input @keydown.end="() => alert('endキーが押されました!')">

.ctrl

「ctrl」キーの反応をリスンする。

<!-- ctrlキーが押されたときにアラートする -->
<input @keydown.ctrl="() => alert('ctrlキーが押されました!')">

.alt

「alt」キーの反応をリスンする。

<!-- altキーが押されたときにアラートする -->
<input @keydown.alt="() => alert('altキーが押されました!')">

.shift

「shift」キーの反応をリスンする。

<!-- shiftキーが押されたときにアラートする -->
<input @keydown.shift="() => alert('shiftキーが押されました!')">

.meta

「meta」キーの反応をリスンする。
(Mac:コマンドキー(⌘)、Windows:ウィンドウキー(⊞))

<!-- metaキーが押されたときにアラートする -->
<input @keydown.meta="() => alert('metaキーが押されました!')">

Custom key code カスタムキーコード

Vueが必要なキー修飾子を提供しない場合は、次のようにそのキーコードを使うことができる。

<!-- 
キーコード「49」が押されたときにアラートする
(キーコード「49」は、キーボード上部の「1」。テンキーの「1」ではない)
-->
<input @keydown.49="() => alert('キーコード「49」キーが押されました!')">

キー修飾子の組み合わせ

次のように、複合キーに対応するためにキー修飾子をチェーンできる。

<input @keydown.ctrl.shift="() => alert('ctrlキーとshiftキーが同時に押されました!')">

.exact

exactを使うと、必要なボタンだけを捕捉できる。他のボタンと一緒に使用した場合は捕捉されない。

exactを使用しない場合、次のコードでShiftキーを押しながらCtrlキーを押すと、正常に応答する。

<!-- Shiftキー+Ctrlキーでもアラートが出る -->
<input @keydown.ctrl="() => alert('やあ!')">

ただし、以下のコードを使用すると、ctrlだけを押さないと正常に応答しない。

<!-- ctrlキーだけを押す必要がある -->
<input @keydown.exact.ctrl="() => alert('やあ!')">

コンポーネント(Component)

コンポーネントは再利用可能なVueインスタンスであり、コードを簡素化・分割するために使用される。

次がVueコンポーネントの一例。

Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

できる限り、テンプレートとそれに使用されるデータが埋め込まれていて、他のVueコンポーネント内で使用できるようにする。

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

これは、互いに何の関係もない3つの異なるボタンをレンダリングします。それぞれが個別に機能し、それぞれが独自のcountデータを持っている。(コンポーネントを定義する場合、各インスタンスが独自のデータを持たずデータが共有される場合を除いて、dataはオブジェクトを返す関数である必要がある。)

単一ファイルコンポーネント(Single File Component)

単一ファイルコンポーネント(またはSFC)は、1つのファイルとしてまとまっている1つのコンポーネントのこと(すごい!)。

次のようによりまとまった構造になっている。

<template>
    <div>

    </div>
</template>

<script>
    export default {

    }
</script>

<style scoped>

</style>

コンポーネントの登録

コンポーネントを登録することにより、登録したコンポーネントをテンプレートで使用できる。

グローバルにコンポーネントを登録する

component()を実行することにより、コンポーネントはすべてのVueインスタンスで利用可能になる(Vueインスタンスが複数ある場合):

import Vue from 'vue'

Vue.component('my-component', require('/path/to/your/component'))

インスタンスのスコープを制限して登録する

次のようにすることで、コンポーネントは指定されたVueインスタンスでのみ使用可能になる。

import Vue from 'vue'

const myComponent = require('/path/to/your/component')

const app = new Vue({
    components: {
        myComponent
    }
})

遅延読み込みでコンポーネントを登録する

この方法は、コンポーネントをメインエントリファイルにバンドルしないため、非常に優れている。このため、Webサイトの読み込みが速くなり、必要なコンポーネントのみが必須になる。

import Vue from 'vue'

const myComponent = () => import('./components/myComponent ')

const app = new Vue({
    components: {
        myComponent 
    }
})

ついでに付け加えると、この登録方法では0などの番号で名前が付けられたコンポーネントが抽出される。webpackを使用している場合は、次のようにコンポーネントのファイル名を変更するためにマジックコメントを使用できる。

import Vue from 'vue'

const myComponent = () => import(/* webpackChunkName: "myComponent" */ './components/myComponent ')

let app = new Vue({
    components: {
        myComponent 
    }
})

props

コンポーネントは、HTMLタグの属性のように機能するpropsを持つことができます。

次のようなコンポーネントがあるとする。

Vue.component('blog-post', {
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

次のように使うことができる。

<blog-post post-title="hello!"></blog-post>

注:propsを定義する場合は、キャメルケースを使用して定義する方が適切ですが、使用する場合は、ケバブケースとして使用します。逆にVueでケバブケースで定義されたpropsをキャメルケースで使用することは可能だが、IDEがうまく認識できない可能性があるので注意。

単一ファイルコンポーネントには、propsを含めることもできる。

<template>
    <div>

    </div>
</template>

<script>
    export default {
           props: ['myProp'];
    };
</script>

<style scoped>

</style>

slots

slotsは、コンポーネント内に他の要素またはコンポーネントを埋め込むために使用される。

シンプルなslot

単純なslotは名前のないslotであり、次のようにテンプレートで使用される。

<a :href="url" class="strange-class"><slot></slot></a>

コンポーネント名がnavigation-linkurlがpropと想定して上記のテンプレートを定義した後、次のように使うことができる。

<navigation-link url="/my-profile">Your profile</navigation-link>

見てのとおりコンポーネントの内部にテキストがあり、それは<slot></slot>の代わりに挿入される。

名前付きslots

名前付きslotsは、名前なしスロットとは少し異なる。

次のbase-layoutというコンポーネントを定義する。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

これを次のように使うことができる。

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

v-slot:slot-nameのついたテンプレートは、コンポーネントで定義されたそれぞれのスロットに配置され、その他は名前のない<slot></slot>に配置される。

次のように名前のないslotには、実際にはdefaultという名前でアクセスできる。

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

スコープ付きslots

slotはその親propにアクセス可能だが、避けたい場合は次のようにslot propを定義できる。

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

そして使用するときは次のようにする。

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

このスロットはそのコンポーネントで利用可能なuserを使用し、親コンポーネントで利用可能なuserは使用しない。


  1. beforeMount()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  2. mounted()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  3. サーバーサイドでは最初1回のレンダリングのみ実行されるので、beforeUpdate()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  4. updated()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

  5. beforeDestroy()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

  6. destroyed()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

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

JavaScript Mapの覚書き

概要

JavaScriptのMapに関する覚書きです。

Mapの目的

Objectよりもキーと値の紐付け(マップ)を表すのに適切なオブジェクトを提供することです。MDNのMapが詳しいです。

Mapの初期化

デフォルトコンストラクタMap()で作成してsetで個別に代入することもできますが、配列リテラルでも初期化できます。

配列リテラルによる初期化
'use strict'
new Map([[0, 0], ["a", "a"], [0.1, 0.1]])
// Map(3) { 0 → 0, a → "a", 0.1 → 0.1 }

Mapの高階関数による処理

Mapの要素を取得するメソッドはentries()keys()values()ですが、いずれもイテレーターを返します。現状ではイテレーターはfiltermapのような高階関数を実装しないため、一度Arrayに変換してから高階関数を適用する必要があります。

キーがある型の要素からなるMapを作成する

typeof entry[0] === "string"stringを目的の型に置換して別の型でも適用できます。

string型の場合
'use strict'
const m = new Map([[0, 0], ["a", "a"], [0.1, 0.1]])
new Map(Array.from(m.entries()).filter(
    entry => typeof entry[0] === "string"))
// Map { a → "a" }

値がある型の要素からなるMapを作成する

typeof entry[1] === "string"stringを目的の型に置換して別の型でも適用できます。

string型の場合
'use strict'
const m = new Map([[0, 0], ["a", "a"], [0.1, 0.1]])
new Map(Array.from(m.entries()).filter(
    entry => typeof entry[1] === "string"))
// Map { a → "a" }

値を文字列へ変換したMapを作成する

'use strict'
const m = new Map([[0, 0], ["a", "a"], [0.1, 0.1]])
new Map(Array.from(m.entries()).map(
    entry => [entry[0], entry[1]?.toString()]))
// Map(3) { 0 → "0", a → "a", 0.1 → "0.1" }

entry[1]?.toString()?.は値がundefinedの場合の例外を防ぐために必要です。

値がundefinedの要素を除外したMapを作成する

'use strict'
// Map(4) { 0 → 0, a → "a", 0.1 → 0.1, undefined → undefined }
const m = new Map([[0, 0], ["a", "a"], [0.1, 0.1], ["undefined", undefined]])
new Map(Array.from(m.entries()).filter(
    entry => entry[1] !== undefined))
// Map(3) { 0 → 0, a → "a", 0.1 → 0.1 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10行ぷよぷよのソースコードを読む

プログラマをする上では10行程度のプログラムを読めるといいとのこと。上記記事のブックマークコメントから飛んで10行ぷよぷよなるものを目にしたので、ソースコードを解読してみました。
解説記事を探したのですが、見当たらなかったので書いてみてます。

10行ぷよぷよについて

javascriptで動くぷよぷよです。
開発者のPascalさんのサイトはもう残っていないとのことで、下記からソースを拝借しました。

https://zapanet.info/blog/item/1134

実行すると、一人用のとことんぷよぷよが遊べます。
そのまま実行しても怒られるので、後述しますがちょっとだけ手を加えてます。

GIF 2020-05-31 16-09-40.gif

ソースコード

ソースコードは以下です。

puyo.html
<body onKeyDown=K=event.keyCode-38 id=D><script>for(M=N=[i=113];--i;M[i-1]=i%8<2|i<8)function Y(){e++;if(e%=10)for(N=[K-2?K-50?h-=M[h+l-K]|M[h-K]?0:K:M[h+p]||(x=p,p=-l,l=x):e=0],K=0;++i<113;N[i]=M[i])N[h]=B>>2,N[h+l]=B%4-B%1+2;if(!e&&(h-=8,!g||M[h]+M[h+l])){C=[M=N];for(i=g=1;++i<103;!M[i]*n&&(M[i]=n,g=M[i+8]=0))n=M[i+8];for(;--i;){n=c=0;for(E=[i];g&M[i]>1&n>=c>>2;t>102|C[t]|M[i]-M[t]||(E[++n]=C[t]=t))t=p,p=-l,l=t,t+=E[c++>>2];for(;c>16&&n;)g=M[E[n--]]=0}B=g?Math.random(h=100,l=8,p=-1)*16+8:--e}for(i=104,S="";i--;S+=n--?n?"<a style=color:#"+(248*n)+">●"+"</a>":i%8?"":"■<br>":"_")n=N[i];D.innerHTML=S;M[100]*g||setTimeout(Y,50)}Y(g=h=e=9,l=p=K=0)</script></body>

これで動くんだ、すごい。
これじゃ何もわからないので、とりあえずbeautifyします。

puyo.html
<body onKeyDown=K=event.keyCode-38 id=D>
<script>

for (M = N = [i = 113]; --i; M[i - 1] = i % 8 <
    2 | i < 8)
function Y() {
    e++;
    if (e %= 10)
        for (N = [K - 2 ? K - 50 ? h -= M[h + l - K] | M[h - K] ? 0 : K : M[h + p] ||
                (x = p, p = -l, l = x) : e = 0
            ], K = 0; ++i < 113; N[i] = M[i]) N[h] = B >> 2, N[h + l] = B % 4 - B % 1 + 2;
    if (!e && (h -= 8, !g || M[h] + M[h + l])) {
        C = [M = N];
        for (i = g = 1; ++i < 103; !M[i] * n && (M[i] = n, g = M[i + 8] = 0)) n =
            M[i + 8];
        for (; --i;) {
            n = c = 0;
            for (E = [i]; g & M[i] > 1 & n >= c >> 2; t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t)) t = p, p = -l, l = t, t += E[c++ >> 2];
            for (; c > 16 && n;) g = M[E[n--]] = 0
        }
        B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e
    }
    for (i = 104, S = ""; i--; S += n-- ? n ? "<a style=color:#" + (248 * n) + ">●" + "</a>" : i % 8 ? "" : "■<br>" : "_") n = N[i];
    D.innerHTML = S;
    M[100] * g || setTimeout(Y, 50)
}
Y(g = h = e = 9, l = p = K = 0)

</script>
</body>

あっなんかこねくり回して最後にhtml書いてるなーくらいはわかるようになりました。
このまま実行すると6行目の function の宣言で怒られます。
昔はこれでよかったのかな?なので、functionの宣言を、想定した挙動であろうforのブロックに入れときます。

以降は解説に入ります。

解説

おおまかな流れ

コードにコメントを入れて、大まかな流れを説明します。

puyo.html
<body onKeyDown=K=event.keyCode-38 id=D>
<script>

// フィールドを初期化して枠のブロックを配置
for (M = N = [i = 113]; --i; M[i - 1] = i % 8 <
    2 | i < 8) {
    // コード削減のためループ内部でそのまま関数宣言
    function Y() {
        // タイマーをインクリメント
        e++;
        // キー操作受付(xで右回転/→で移動) + 操作ぷよ配置
        if (e %= 10)
            for (N = [K - 2 ? K - 50 ? h -= M[h + l - K] | M[h - K] ? 0 : K : M[h + p] ||
                    (x = p, p = -l, l = x) : e = 0
                ], K = 0; ++i < 113; N[i] = M[i]) N[h] = B >> 2, N[h + l] = B % 4 - B % 1 + 2;
        // 落下処理
        if (!e && (h -= 8, !g || M[h] + M[h + l])) {
            C = [M = N];
            // 下にぷよが存在しない場合に落下させる
            for (i = g = 1; ++i < 103; !M[i] * n && (M[i] = n, g = M[i + 8] = 0)) n =
                M[i + 8];
            // 同色で連結しているぷよをカウント、4連結以上で消滅させる
            for (; --i;) {
                n = c = 0;
                for (E = [i]; g & M[i] > 1 & n >= c >> 2; t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t)) t = p, p = -l, l = t, t += E[c++ >> 2];
                for (; c > 16 && n;) g = M[E[n--]] = 0
            }
            // 新しく落ちてくるぷよを生成
            B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e
        }
        // フィールドを描画
        for (i = 104, S = ""; i--; S += n-- ? n ? "<a style=color:#" + (248 * n) + ">●" + "</a>" : i % 8 ? "" : "■<br>" : "_") n = N[i];
        D.innerHTML = S;
        // ゲーム継続/終了判定
        M[100] * g || setTimeout(Y, 50)
    }
}
// ゲーム開始
Y(g = h = e = 9, l = p = K = 0)

</script>
</body>

以降は個々の処理を見ていきます。

変数

変数がわかると見通しが良さそうなので、先に一覧します。

変数 役割
M, N フィールド
K 数値
e タイマー
g 落下中フラグ
h 操作位置
l 従ぷよ現在位置
p 従ぷよ移動先位置
E 連結しているぷよ位置
C 連結しているぷよ位置
B 操作ぷよ用乱数

初期化処理

for (M = N = [i = 113]; --i; M[i - 1] = i % 8 <
    2 | i < 8) {
    function Y() {
...
}
Y(g = h = e = 9, l = p = K = 0)
変数 役割
M, N フィールド

ぷよぷよのフィールドに使うM, Nを1次元配列で初期化しています。左上から右下にデクリメントされています。
113はぷよぷよのフィールドである 6 * 12 に、左右下のブロックと上1列を足した 8 * 14 = 112--i でカウントダウンして使うための数字です。

配列の値が持つ意味を以下に書きます。

意味
0
1 ブロック
2-5 ぷよ(4色)

--iiを返すことを利用し、カウンタをcondition部に書いています。
final-expression部ではフィールドにブロック(M[i] = 1)を配置しています。

for loop の中は関数の宣言に使われています。
もちろん無駄に回数分呼ばれますが、コード量の削減を優先しています。

最後に、loop内で宣言した関数Yを実行しています。
初期化している変数については都度解説します。

キー操作受付

        e++;
        // キー操作受付(xで右回転/→で移動) + 操作ぷよ配置
        if (e %= 10)
            for (N = [K - 2 ? K - 50 ? h -= M[h + l - K] | M[h - K] ? 0 : K : M[h + p] ||
                    (x = p, p = -l, l = x) : e = 0
                ], K = 0; ++i < 113; N[i] = M[i]) N[h] = B >> 2, N[h + l] = B % 4 - B % 1 + 2;

if分の代わりに、論理演算子、参考演算子を多用しています。
見やすく改行していきます。

        e++;
        if (e %= 10)
            for (N = [
                K - 2 ?
                    K - 50 ?
                        h -= M[h + l - K] | M[h - K] ?
                            0
                            : K
                        : M[h + p] || (x = p, p = -l, l = x)
                    : e = 0
                ], K = 0;
                ++i < 113;
                N[i] = M[i])            
                N[h] = B >> 2, N[h + l] = B % 4 - B % 1 + 2;
変数 役割
M, N フィールド
K 数値
e タイマー
g 落下中フラグ
h 主ぷよ操作位置
l 従ぷよ現在位置
p 従ぷよ移動先位置
B 操作ぷよ用乱数

1つ1つ見ていきます。

        e++;
        if (e %= 10)
...

eはタイマーです。代入と判定を同時にしています。0の場合のみ操作を受け付けず、後述の落下処理に入ります。

次はキーコードの判定です。
キーコードについてはHTML側に書いてあるので、先に貼っておきます。

<body onKeyDown=K=event.keyCode-38 id=D>

拾ったキーコードを-38することで、Kには下記の値が入ります。

K キー
-1
1
2
50 x

これを踏まえると、こうなります。

            for (N = [
                K - 2 ?
                    K - 50 ?
                        // ←キー/→キーの場合
                        h -= M[h + l - K] | M[h - K] ?
                            0
                            : K
                        // xキーの場合
                        : M[h + p] || (x = p, p = -l, l = x)
                    // ↓キーの場合
                    : e = 0
                ], K = 0;

Nに代入する意味は特になく、コード削減のためにこう書いています。
ここではh, e, p, l をキー操作に合わせて更新しています。

まず↓キーの場合、e = 0 が実行されます。インクリメントを待たず、落下処理に移動します。
←キー/→キーの場合、 h -= -1 or 1 が実行されます。hは操作しているぷよの位置を表すので、左右に移動できます。
M[h + l - K] | M[h - K] は壁判定です。移動先が空(0)の場合だけ移動します。

xキーは回転操作です。ぷよが右に回転します。主ぷよの操作位置hは変わりませんが、従ぷよが回転してlが変わります。
lpは、このあとそれぞれl = 8, p = -1で初期化されています。
なので、xを押すごとに (8,-1), (-1,-8), (-8,1), (1,8)と遷移します。

                    // ↓キーの場合
                    : e = 0
                ], K = 0;
                ++i < 113;
                N[i] = M[i])            

今回もカウンタがcondtion部に書かれています。初期化を避けるため、同じiをインクリメントしたりデクリメントしたりしていきます。
final-expression部では雑に初期化したNをもとに戻しています。
ここのfor loopはなんとか削減出来るような気もちょっとします。

                ++i < 113;
                N[i] = M[i])            
                N[h] = B >> 2, N[h + l] = B % 4 - B % 1 + 2;

最後にN[h]を設定します。ここで出てくるBはのちのち出てきますが、下記で乱数がセットされています。

            B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e

h,l,pの初期化の処理を混ぜていますが、乱数に影響はありません。
ここでは8~24の乱数を生成しています。それぞれ主ぷよN[h]に4で割った商を、従ぷよn[h+l]に4で割った剰余を使って2-5であらわされる色ぷよを設定しています。

ここまでが操作処理です。

落下処理

        // 落下処理
        if (!e && (h -= 8, !g || M[h] + M[h + l])) {
            C = [M = N];
            // 下にぷよが存在しない場合に落下させる
            for (i = g = 1; ++i < 103; !M[i] * n && (M[i] = n, g = M[i + 8] = 0)) n =
                M[i + 8];
            // 同色で連結しているぷよをカウント、4連結以上で消滅させる
            for (; --i;) {
                n = c = 0;
                for (E = [i]; g & M[i] > 1 & n >= c >> 2; t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t)) t = p, p = -l, l = t, t += E[c++ >> 2];
                for (; c > 16 && n;) g = M[E[n--]] = 0
            }
            // 新しく落ちてくるぷよを生成
            B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e
        }

これも改行します。

        if (!e && (h -= 8, !g || M[h] + M[h + l])) {
            C = [M = N];
            for (i = g = 1; 
                ++i < 103;
                !M[i] * n && (M[i] = n, g = M[i + 8] = 0)) n = M[i + 8];

            for (; --i;) {
                n = c = 0;
                for (E = [i];
                    g & M[i] > 1 & n >= c >> 2;
                    t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t))
                    t = p, p = -l, l = t, t += E[c++ >> 2];

                for (; c > 16 && n;) g = M[E[n--]] = 0
            }
            B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e
変数 役割
M, N フィールド
e タイマー
g 落下中フラグ
l ぷよ隣接位置
p ぷよ隣接位置(next)
E 連結しているぷよ位置(リスト)
C 連結しているぷよ位置(フィールド)
B 操作ぷよ用乱数

1つ1つ見ていきます。

        if (!e && (h -= 8, !g || M[h] + M[h + l])) {

まず判定です。eはタイマーです。e %= 10 されているので、10回毎に e!trueになります。
タイマーの次は、操作位置hを1段下に移動(-8)します。これはほとんどtrueになります。
続いてgですが、これは落下中フラグです。0なら操作中となります。
M[h], M[h+l]は移動先の操作ぷよの位置です。移動先が共に空(0)なら0になります。

なのでこれは、なんらかのぷよが落下中または操作ぷよの落下先が空じゃなければ(接地していれば)処理開始、という判定になります。

        if (!e && (h -= 8, !g || M[h] + M[h + l])) {
            C = [M = N];
            for (i = g = 1; 
                ++i < 103;
                !M[i] * n && (M[i] = n, g = M[i + 8] = 0)) n = M[i + 8];

C を初期化していますが、まだ使いません。
for の処理は落下処理です。
全フィールドを下の段から走査し、M[i+8]に値が存在していてM[i]が空の場合に、M[i]=M[i+8]を実行します。
その際に、落下中フラグg0にします。
これで落下しうる全てのぷよが1段落下します。

            for (; --i;) {
                n = c = 0;
                for (E = [i];
                    g & M[i] > 1 & n >= c >> 2;
                    t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t))
                    t = p, p = -l, l = t, t += E[c++ >> 2];

                for (; c > 16 && n;) g = M[E[n--]] = 0

ここがぷよを消す処理です。

先に流れを説明します。
* 上から全フィールドを走査(i)
* M[i]が色ぷよの場合に周囲4マスを走査し
* 周囲に同色の未捜査のぷよがあったら、そのぷよを起点に周囲4マスを走査
* 未捜査のぷよが無くなったら終了し、4つ以上のぷよを走査していたらそれらを削除

中身を見ていきます。

まずiを例のごとくデクリメントして上からloopします。

珍しくcondition部がに純粋にconditionが書かれている気がします。
まずgの判定が入っているので、落下途中ではぷよを消さないようにしています。
M[i] > 1 なので、消すのは2-5の色ぷよだけです。
n >= c >> 2 は、後述しますが走査の完了を判定します。

condition部の次は中身である t = p, p = -l, l = t, t += E[c++ >> 2]; が実行されます。
t = p, p = -l, l = ttの位置を回転させています。
また、c++ >> 20-30になります。E[0]にはiが格納されていますので、ti周りに回転します。

final-expression部の t > 102 | C[t] | M[i] - M[t] || (E[++n] = C[t] = t) が実行されます。
C は走査済みのフィールドを格納します。
A | B || C は AとB両方がfalseの場合だけCが実行されるということなので、
tが102以下(画面外の13段目を含まない)で、未走査(C[t]false)で、iと同色(M[i] - M[t])の場合に、tを次の走査対象としてEに格納してC[t]を走査済みに、nをインクリメントします。

新しい走査対象を中心にまたtを回転させ、同様の処理を続けます。
新しい走査対象が見つからないときに、インクリメントのタイミングからcnの4倍になります。
これが走査完了条件となって、戦術のn >= c >> 2trueとなってループを抜けます。

最後に、for (; c > 16 && n;) g = M[E[n--]] = 0で、c > 16 すなわち4つ以上のぷよを走査した場合に、Eをループしてぷよを消します。

            B = g ? Math.random(h = 100, l = 8, p = -1) * 16 + 8 : --e

ぷよを消す処理まで終わったら、次の操作ぷよを生成します。
落下が完了(g)していれば操作ぷよに使う乱数を生成し、していなければ落下処理を続けるためにタイマーを戻します。
h=100は操作ぷよの出現位置です。

描画処理

        // フィールドを描画
        for (i = 104, S = ""; i--; S += n-- ? n ? "<a style=color:#" + (248 * n) + ">●" + "</a>" : i % 8 ? "" : "■<br>" : "_") n = N[i];
        D.innerHTML = S;

HTMLを描画します。
フィールドであるNをループして、上からタグを生成しています。端まで要った場合(i%8==0)に改行するくらいです。

終了処理

        // ゲーム継続/終了判定
        M[100] * g || setTimeout(Y, 50)

落下が完了していて、かつ操作ぷよの出現位置が空でない場合はゲームオーバーです。
そうでない場合は、再び関数を実行して次のフレームに移動します。

ということで、ぷよぷよがなんと動きました。すごい。

終わりに

ちょうどぷよぷよをネタにコードを書こうと思っていたところにこれを見つけて、実装の参考にもなるし見てみるか...と思ったのがきっかけでした。だいぶ参考になった。
読んでみてちょっと論理演算子に強くなった気がします。楽しかったです。

CodeGolfとかやってる人たちならもっともっと短くなるんだろうか。
気が向けば挑戦してみたいです。

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

Vibrant.jsで画像内の特徴的な色を抽出する

概要

Vibrant.jsというライブラリを使って、画像や写真の中の目立った色(特徴的な色・特徴色)を抽出する方法についてまとめました。

Vibrant.jsとは

用意されている関数に画像のオブジェクトを渡すと、目立った色の情報を出力するJavaScriptのライブラリ。
ここでの目立った色とは、「画像を見た時に印象の強い色」を指します。
非常に少ないコード量で特徴的な色の抽出を実現できます。

公式ページ
https://jariz.github.io/vibrant.js

ファイル構成

公式ページからVibrant.min.jsをダウンロードします。

構成は以下の通り。今回はJavaScriptをHTMLファイルに直書きします。

project_directory
  - index.html
  - Vibrant.min.js

実装

公式ページを参考に、HTMLとJavaScriptを書きます。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vibrantjs form</title>
</head>
<body>
    <input type="file" id="target">
    <p id="color-text">この文が目立った色に変わります</p>
</body>
<script src="./Vibrant.min.js"></script>
<script>
    document.getElementById("target").addEventListener("change", function() {
        var file = this.files[0];
        var blobUrl = window.URL.createObjectURL(file); // Blob URLの作成
        document.body.innerHTML += '<a href="' + blobUrl + '" target="_blank"><img src="' + blobUrl + '"></a>' ;

        var img = document.createElement('img');
        img.setAttribute('src', blobUrl);
        img.addEventListener('load', function() {
            // 写真が読み込まれたら、Vibrantを呼び出す
            var vibrant = new Vibrant(img);
            var swatches = vibrant.swatches()
            for (var swatch in swatches) {
                if (swatches.hasOwnProperty(swatch) && swatches[swatch]) {
                    console.log(swatch, swatches[swatch].getHex());
                }
            }

            // 目立った色のカラーコードを取得
            var colorCode = swatches.Vibrant.getHex();
            document.getElementById("color-text").style.color = colorCode;
        });
    });
</script>

以下がポイントです。

  • new Vibrant([画像オブジェクト])で目立った色の抽出を行ったVibrantオブジェクトを作成
  • Vibrantオブジェクトのswatches()を呼び出すと、目立った色情報をまとめて取得できる
    • swatches.Vibrantで目立った色
    • swatches.LightVibrantで目立ったうちの明るい方の色
    • swatches.DarkVibrantで目立ったうちの暗い色を取得できる
  • getHex()でカラーコード(##0000など)が手に入る

動作確認

使用する画像

20181107011434.png

入力画面

20181107011526.png

画像をフォームに入れると

20181107011621.png
このように、目立った色である青が抽出され、文字色を変えることができました。

コンソール

それぞれの暗さに対する目立った色のカラーコードが表示されています。
20181107011722.png

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

Reactive Programmingの基本

はじめに

JavaScriptを触るなかで関数型リアクティブプログラミング(FRP)について知り、面白そうと調べましたが考え方のベースとなる部分を理解することに苦労したので記録として残します。

What is Reactive?

アプリケーションは多くのモジュールによって構成されています。多くの場合、そのモジュール達はデータによって繋がっています。あるモジュールの値が更新されると、他のモジュールの値も更新されるようなシーンです。
Untitled Diagram.jpg

例としてAmazonのようなオンラインショッピングサービスを提供するアプリケーションを挙げ、アプリケーション内の『カート』を担当するモジュールと、『請求書』を担当するモジュールの2つの関係に注目します。例えばユーザーがカートに商品を追加したとき、請求書は商品の金額に応じて自動で更新されます。つまり図の矢印はデータの流れを表していて、カートのデータが更新されると請求書のデータも更新されることを表現しています。このとき請求書はプログラム的にどのように更新されるのか、Cartモジュール側がInvoiceモジュールの値を更新しているのか、Invoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているのか考えてみます。Reactiveなプログラミングの場合、後者がそれと言えます。

Reactive ProgrammingとはInvoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているようなプログラム

つまり外部のモジュールの変更を監視し、自身のデータを更新します。前者とは違い、モジュールの値の更新を他のモジュールに依存していません。ちなみに前者のようなプログラミングをAndré Staltz(cycle.jsを作ったエンジニア)はモジュールの外部のデータに対する受動的な姿勢からPassive Programmingと名付けています。次に何故このリアクティブプログラミングが近年注目されているのかを示していきます。

Why Reactive Programming?

下の図のように6つのモジュールから構成されるアプリケーションがあるとします。
Untitled Diagram (3).jpg
このアプリケーションの挙動を理解しようとしたとき、各モジュールのコードを読んでいくことになると思います。その中でCのモジュールを読んでいるとします。先ほどのPassive Programmingの場合、Cのモジュール内で扱っているデータがどこから来ているのかが分かりません。つまりCのモジュールを見ただけではCの挙動を理解できないのです。Aのモジュールを見ないと理解できません。加えてAのようなモジュールがどこに存在するか探す作業もあります。
Reactive Programmingの場合、CのモジュールはAのモジュールを監視していて、Aが変更されるたびにCのデータは更新されます。従ってCを見るだけで、Cの挙動が理解できるわけです。つまり分かりやすいというメリットがあります。これ以外にも先ほどのアマゾンの例と似ていますが検索候補(search suggestion)のようにinputの値が更新されるたびにサーバーにアクセスすることなくreactiveに検索候補を挙げてくれるプログラムを実現できるというメリットもあります。(次のエクセルの例も参考にしてみて下さい。)
ただここで疑問になるのが、他のモジュールのイベントを監視し、反応するリアクティブプログラミングはどうやって実装されるのか、です。

What is Reactive Programming?

モジュール自身で他のモジュールのデータの変更に対応しようとしても、他のモジュールのデータをフェッチするような関数を作成し他のモジュール側で実行してもReactiveとは言えません。Reactiveなプログラミングを実装するのは結構大変なのです。では、どうやって実装するのか、どういうものがReactive Programmingと言えるのか。まずはリアクティブプログラミングの具体的なイメージから入りましょう。
Reactive Programmingを用いたアプリケーションとして有名なものにエクセルがあります。エクセルをイメージすると仕組みが理解できると思います。
Untitled Diagram (4).jpg
例えば上の図を見たとき赤丸の部分が何を意味しているのか『-2』という値だけでは分かりません。エクセルではセルをクリックすると、どこのデータをフェッチしてきているのか一目で分かります。
Untitled Diagram (5).jpg
このように、どのセルからデータをフェッチしているのか一目で分かります。また『B3』のセルのデータを変更すると自動で赤丸部分の値が更新されます。見れば分かりますが、赤丸部分のデータの更新は『B3』『B9』のセルに依存しておらず、そのため赤丸部分の内容を見るだけで挙動が理解できます。このセルたちをモジュールに置き換えたものが関数型リアクティブプログラミング(FRP)であり、これをコードに置き換えることで実装できます。それでは実際にコードで実装していきましょう。

How to Reactive Programming?

今回はrxjsというライブラリを用いてリアクティブプログラミングを実装していきます。rxjsはコードを分かりやすいものとするために様々な役割を用語として定義しています。rxjsを用いてリアクティブプログラミングを実装するのには理解が必要な用語がいくつかあります。

Untitled Diagram (11).jpg

observable

いつ更新されるか分からないデータを含め全てのデータをストリーム(データの流れ)とし、そのデータのストリーム自体をラップするものをRxjsではobservableと呼ばれます。エクセルのセルのようにデータをラップするものです。例えばDBのデータをラップすることで、DBが変化するごとにリロードせずとも対応できます。ただし関数が呼び出されない限り動かないように、observableもsubscribeされない限り何も返しません。また複数の返り値を返すことができます。

subscribe

observable内にあるデータが更新されたとき、observerに対して知らせる役割を持つ。Youtubeのsubscribe機能が分かりやすいと思います。ユーザーは自分の好きなYouTuberに対してsubscribeすることでYouTuber(observable)の動画投稿の動き(データストリーム)を監視(listen)することができます。よく購読と訳されるのは、恐らくobservableをsubscribeすることで初めて購読(中身のデータを扱えることが)できるためだと思います。
observableをsubscribeするとsubscriptionオブジェクトを返し、これをunsubscribeしない限りobserverはobservableからデータを受け取り続けます。

pipe

実際にobsevableのイベントに対してreactする部分。operatorと呼ばれる、データに対し予め決められた操作のできる関数を用いてデータを操作(manipulate)します。

observer

新しいデータをobservableから受け取ったときに、それに対して処理等を行うのがobserverオブジェクトです。observableからのデータのconsumer。
3種類のコールバックを用意することができます。observableからの通知のようなものです。

  • nextメソッドは新しいデータをobservableが受け取るたびに実行されるメソッド。
  • errorメソッドはobservableがエラーを投げたときに実行されるメソッド。
  • completeメソッドがobservableの更新が完了したときに実行されるメソッド。

エクセルのセルに対するデータの入力、更新に終わりがないようにobservableが決して完了しないケースもあります。つまりcompleteメソッドが必要ないことがあります。

subject

observableと似ているが、多くのobserverに対して変更をマルチキャストすることができます。つまり多くのobserverを持つことができるとも言え、Reactで言えばReduxと概念は似ていると思います。

rxjsを使うメリット

operatorの数が多い
処理のフローが分かりやすい
データを自由に加工できる

Example

App.js
import React, { useState, useEffect } from 'react';
import { from  } from 'rxjs';
import { map, filter, mergeMap, delay } from 'rxjs/operators';

// observableを作成
let numberObservable = from([1, 2, 3, 4, 5]);

// operatorでobservableからのデータを操作
let squareNumbers = numberObservable.pipe(
    filter(val => val > 2),
    mergeMap(val => from([val]).pipe(
        delay(1000 * val)
    )),
    map(val => val * val)
);

// observableフックの作成。
// unsubscribeしなければデータストリームが止まらない限り実行され続ける
const useObservable = (observable, setter) => {
    useEffect(() => {
        let subscription = observable.subscribe(result => {
            setter(result);
        });

        return () => subscription.unsubscribe();
    }, [observable, setter]) 
}

const App = () => {
    const [currentNumber, setCurrentNumber] = useState(0);

    useObservable(squareNumbers, setCurrentNumber);

    return (
        <div className="app">
            Current number is: {currentNumber}
        </div>
    );
}

export default App;

おわりに

誤字、誤った説明があった場合、お手数ですがコメント等下さい。

参考

https://www.youtube.com/watch?v=KOjC3RhwKU4&t
https://www.youtube.com/watch?v=49dMGC1hM1o&t
https://www.youtube.com/watch?v=uQ1zhJHclvs&t
https://www.youtube.com/watch?v=eloMMybBVN0
https://www.youtube.com/watch?v=Tux1nhBPl_w
https://www.youtube.com/watch?v=GCPORlQDFHI
https://www.youtube.com/watch?v=Urv82SGIu_0&t

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

Reactive Programming with JavaScript

はじめに

JavaScriptを触るなかで関数型リアクティブプログラミング(FRP)について知り、面白そうと調べましたが考え方のベースとなる部分を理解することに苦労したので記録として残します。

What is Reactive?

アプリケーションは多くのモジュールによって構成されています。多くの場合、そのモジュール達はデータによって繋がっています。あるモジュールの値が更新されると、他のモジュールの値も更新されるようなシーンです。
Untitled Diagram.jpg

例としてAmazonのようなオンラインショッピングサービスを提供するアプリケーションを挙げ、アプリケーション内の『カート』を担当するモジュールと、『請求書』を担当するモジュールの2つの関係に注目します。例えばユーザーがカートに商品を追加したとき、請求書は商品の金額に応じて自動で更新されます。つまり図の矢印はデータの流れを表していて、カートのデータが更新されると請求書のデータも更新されることを表現しています。このとき請求書はプログラム的にどのように更新されるのか、Cartモジュール側がInvoiceモジュールの値を更新しているのか、Invoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているのか考えてみます。Reactiveなプログラミングの場合、後者がそれと言えます。

Reactive ProgrammingとはInvoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているようなプログラム

つまり外部のモジュールの変更を監視し、自身のデータを更新します。前者とは違い、モジュールの値の更新を他のモジュールに依存していません。ちなみに前者のようなプログラミングをAndré Staltz(cycle.jsを作ったエンジニア)はモジュールの外部のデータに対する受動的な姿勢からPassive Programmingと名付けています。次に何故このリアクティブプログラミングが近年注目されているのかを示していきます。

Why Reactive Programming?

下の図のように6つのモジュールから構成されるアプリケーションがあるとします。
Untitled Diagram (3).jpg
このアプリケーションの挙動を理解しようとしたとき、各モジュールのコードを読んでいくことになると思います。その中でCのモジュールを読んでいるとします。先ほどのPassive Programmingの場合、Cのモジュール内で扱っているデータがどこから来ているのかが分かりません。つまりCのモジュールを見ただけではCの挙動を理解できないのです。Aのモジュールを見ないと理解できません。加えてAのようなモジュールがどこに存在するか探す作業もあります。
Reactive Programmingの場合、CのモジュールはAのモジュールを監視していて、Aが変更されるたびにCのデータは更新されます。従ってCを見るだけで、Cの挙動が理解できるわけです。つまり分かりやすいというメリットがあります。これ以外にも先ほどのアマゾンの例と似ていますが検索候補(search suggestion)のようにinputの値が更新されるたびにサーバーにアクセスすることなくreactiveに検索候補を挙げてくれるプログラムを実現できるというメリットもあります。(次のエクセルの例も参考にしてみて下さい。)
ただここで疑問になるのが、他のモジュールのイベントを監視し、反応するリアクティブプログラミングはどうやって実装されるのか、です。

What is Reactive Programming?

モジュール自身で他のモジュールのデータの変更に対応しようとしても、他のモジュールのデータをフェッチするような関数を作成し他のモジュール側で実行してもReactiveとは言えません。Reactiveなプログラミングを実装するのは結構大変なのです。では、どうやって実装するのか、どういうものがReactive Programmingと言えるのか。まずはリアクティブプログラミングの具体的なイメージから入りましょう。
Reactive Programmingを用いたアプリケーションとして有名なものにエクセルがあります。エクセルをイメージすると仕組みが理解できると思います。
Untitled Diagram (4).jpg
例えば上の図を見たとき赤丸の部分が何を意味しているのか『-2』という値だけでは分かりません。エクセルではセルをクリックすると、どこのデータをフェッチしてきているのか一目で分かります。
Untitled Diagram (5).jpg
このように、どのセルからデータをフェッチしているのか一目で分かります。また『B3』のセルのデータを変更すると自動で赤丸部分の値が更新されます。見れば分かりますが、赤丸部分のデータの更新は『B3』『B9』のセルに依存しておらず、そのため赤丸部分の内容を見るだけで挙動が理解できます。このセルたちをモジュールに置き換えたものが関数型リアクティブプログラミング(FRP)であり、これをコードに置き換えることで実装できます。それでは実際にコードで実装していきましょう。

How to Reactive Programming?

今回はrxjsというライブラリを用いてリアクティブプログラミングを実装していきます。rxjsはコードを分かりやすいものとするために様々な役割を用語として定義しています。rxjsを用いてリアクティブプログラミングを実装するのには理解が必要な用語がいくつかあります。

Untitled Diagram (11).jpg

observable

いつ更新されるか分からないデータを含め全てのデータをストリーム(データの流れ)とし、そのデータのストリーム自体をラップするものをRxjsではobservableと呼ばれます。エクセルのセルのようにデータをラップするものです。例えばDBのデータをラップすることで、DBが変化するごとにリロードせずとも対応できます。ただし関数が呼び出されない限り動かないように、observableもsubscribeされない限り何も返しません。また複数の返り値を返すことができます。

subscribe

observable内にあるデータが更新されたとき、observerに対して知らせる役割を持つ。Youtubeのsubscribe機能が分かりやすいと思います。ユーザーは自分の好きなYouTuberに対してsubscribeすることでYouTuber(observable)の動画投稿の動き(データストリーム)を監視(listen)することができます。よく購読と訳されるのは、恐らくobservableをsubscribeすることで初めて購読(中身のデータを扱えることが)できるためだと思います。
observableをsubscribeするとsubscriptionオブジェクトを返し、これをunsubscribeしない限りobserverはobservableからデータを受け取り続けます。

pipe

実際にobsevableのイベントに対してreactする部分。operatorと呼ばれる、データに対し予め決められた操作のできる関数を用いてデータを操作(manipulate)します。

observer

新しいデータをobservableから受け取ったときに、それに対して処理等を行うのがobserverオブジェクトです。observableからのデータのconsumer。
3種類のコールバックを用意することができます。observableからの通知のようなものです。

  • nextメソッドは新しいデータをobservableが受け取るたびに実行されるメソッド。
  • errorメソッドはobservableがエラーを投げたときに実行されるメソッド。
  • completeメソッドがobservableの更新が完了したときに実行されるメソッド。

エクセルのセルに対するデータの入力、更新に終わりがないようにobservableが決して完了しないケースもあります。つまりcompleteメソッドが必要ないことがあります。

subject

observableと似ているが、多くのobserverに対して変更をマルチキャストすることができます。つまり多くのobserverを持つことができるとも言え、Reactで言えばReduxと概念は似ていると思います。

rxjsを使うメリット

operatorの数が多い
処理のフローが分かりやすい
データを自由に加工できる

Example

App.js
import React, { useState, useEffect } from 'react';
import { from  } from 'rxjs';
import { map, filter, mergeMap, delay } from 'rxjs/operators';

// observableを作成
let numberObservable = from([1, 2, 3, 4, 5]);

// operatorでobservableからのデータを操作
let squareNumbers = numberObservable.pipe(
    filter(val => val > 2),
    mergeMap(val => from([val]).pipe(
        delay(1000 * val)
    )),
    map(val => val * val)
);

// observableフックの作成。
// unsubscribeしなければデータストリームが止まらない限り実行され続ける
const useObservable = (observable, setter) => {
    useEffect(() => {
        let subscription = observable.subscribe(result => {
            setter(result);
        });

        return () => subscription.unsubscribe();
    }, [observable, setter]) 
}

const App = () => {
    const [currentNumber, setCurrentNumber] = useState(0);

    useObservable(squareNumbers, setCurrentNumber);

    return (
        <div className="app">
            Current number is: {currentNumber}
        </div>
    );
}

export default App;

おわりに

誤字、誤った説明があった場合、お手数ですがコメント等下さい。

参考

https://www.youtube.com/watch?v=KOjC3RhwKU4&t
https://www.youtube.com/watch?v=49dMGC1hM1o&t
https://www.youtube.com/watch?v=uQ1zhJHclvs&t
https://www.youtube.com/watch?v=eloMMybBVN0
https://www.youtube.com/watch?v=Tux1nhBPl_w
https://www.youtube.com/watch?v=GCPORlQDFHI
https://www.youtube.com/watch?v=Urv82SGIu_0&t

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

Deno+HTMLで手軽にGUIアプリを作る

はじめに

carolという、DenoとHTMLを使ってGUIアプリを作るためのモジュールを作成しました。
この記事では、その紹介も兼ねてcarolを使ったGUIアプリの作成方法について解説します。

前提条件

以下の環境で動作確認しています。

  • Deno v1.0.3
  • Google Chrome v83.0.4103.61

carolはUIを提供するために、ローカルにインストールされたGoogle Chromeを使用します。
そのため、事前にChromeをインストールしておく必要があります。

Hello World!

まず、以下のようなスクリプトを用意します。

mod.ts
import { launch } from "https://deno.land/x/carol@v0.0.6/mod.ts";
import { dirname, join } from "https://deno.land/std@0.54.0/path/mod.ts";

const app = await launch({
  title: "Hello Deno!",
  width: 480,
  height: 320,
});

// Chromeが終了したらDenoのプロセスも終了させる
app.onExit().then(() => Deno.exit(0));

// Chrome側に関数を公開する
await app.exposeFunction("greet", (name: string) => `Hello, ${name}!`);

const folder = join(dirname(new URL(import.meta.url).pathname), "public");
app.serveFolder(folder); // "./public"ディレクトリ配下からファイルを配信する
await app.load("index.html"); // "./public/index.html"を読み込む

次に、上記のmod.tsと同一ディレクトリにpublicディレクトリを作成し、その中にChromeから読み込むHTMLとJSファイルを用意します。

public/index.html
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <h2>Hello, Deno!</h2>
    <script src="/index.js" type="module">
    </script>
  </body>
</html>
public/index.js
(async () => {
  window.alert(await window.greet("Deno"));
})();

この時点で以下のようなディレクトリ構造になっている想定です。

/
├── mod.ts
└── public
    ├── index.html
    └── index.js

それでは、mod.tsを実行してみます。

$ deno run --allow-env --allow-net --allow-read --allow-write --allow-run ./mod.ts

以下のように表示されれば成功です。

Screenshot.png

解説

先程のmod.tsの内容について解説します。

launch()

launch関数を実行すると、Chromeが起動します。
width及びheightプロパティでWindowの横幅と縦幅を指定できます。

import { launch } from "https://deno.land/x/carol@v0.0.6/mod.ts";

const app = await launch({
  title: "Hello Deno!",
  width: 480,
  height: 320,
});

また、argsプロパティを指定することで、Chromeを起動する際のコマンドライン引数をカスタマイズできます。

例) Chromeをヘッドレスモードで起動する
const app = await launch({ args: ["--headless"] });

app.onExit()

// Chromeが終了したらDenoのプロセスも終了させる
app.onExit().then(() => Deno.exit(0));

app.onExitPromiseを返却します。
このPromiseはChromeが終了した際にresolveされます。
上記コードでは、Chromeの終了に合わせてDenoのプロセスも終了させています。

app.exposeFunction(name, fn)

// Chrome側に関数を公開する
await app.exposeFunction("greet", (name: string) => `Hello, ${name}!`);

app.exposeFunction()を使うと、DenoからChromeへ関数を公開できます。
第1引数はChrome側から参照する際の関数名、第2引数で公開する関数の処理を記述します。

公開した関数はwindowオブジェクト経由で使用することができます。

例えば、上記コードの場合、windowオブジェクトにgreet関数が追加されます。

// 以下はブラウザ上で実行されるJavaScriptコードの想定
(async () => {
  const result = await window.greet("Deno"); // Promiseが解決されると、関数の戻り値が取得できます。
  window.alert(result); // Hello, Deno!
})();

app.load()

ChromeにHTMLをロードさせる際に使います。

例えば、以下の例では、app.serveFolder()と組み合わせて、public/index.htmlをChromeに読み込むように指示しています。

const folder = join(dirname(new URL(import.meta.url).pathname), "public");
app.serveFolder(folder); // "./public"ディレクトリ配下からファイルを配信する
await app.load("index.html"); // "./public/index.html"を読み込む

また、以下のようにURLを直接指定することも可能です。

await app.load("https://www.google.co.jp/"); // ChromeにGoogleのトップページを読み込ませる

終わりに

DenoとHTMLを使ってGUIアプリを作る方法について解説しました。

興味があれば、是非使ってみてください。

また、Denoには他にもdeno_webviewというGUIライブラリがあるので、そちらもおすすめです。

補足

  • carolの名前の由来はcarlolorcaのアナグラムです。
  • carolのライセンスはMITライセンスです。
  • carlolorcaのソースをベースにしている箇所があり、そちらについては個別にライセンス・コピーライトが適用されます。
    • carloのライセンスはApache v2です(2020/05/31現在)
    • lorcaのライセンスはMITです(2020/05/31現在)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【enchant.js】画像を拡大したらピンぼけしてたから直した

趣味でプログラミングをしているいちいち@ichiichi_1115 です。
enchant.jsで画像の拡大をしてたらぼやけてしまったので備忘録&その先に躓く人がいないようにまとめてみました。

imageSmoothingEnabled をFalseに

imageSmoothingEnabled をFalseにすると、補完処理をしなくなるという話を聞いたのでやってみました。

enchant.js(整形版)4460行目あたりに、_dctxといういかにもコンテキストそうな変数があったのでそれに対してやってみたが効果なし...

というか、すでにimageSmoothingEnabledがFalseにされていました(以下コード)。

enchant.js
    _setImageSmoothingEnable: function() {
        this._dctx.imageSmoothingEnabled =
        this._dctx.msImageSmoothingEnabled =
        this._dctx.mozImageSmoothingEnabled =
        this._dctx.webkitImageSmoothingEnabled = false;
    }

JavaScriptの書き方で、a = b = falseと書くと、aもbもFalseになりますが、その記法です。

とりあえず、これでは治りませんでした。

リファレンスを読む

imageSmoothingEnabledのリファレンス を読んでたら、こんなものを発見しました。

このプロパティは、たとえばキャンバスをスケーリングするような、ピクセルアートをテーマにしたゲームに役立ちます。 既定のリサイズアルゴリズムは、ぼやかし効果をかけることで、美しいピクセルを崩してしまいます。このような場合、このプロパティを false に設定します。image-rendering プロパティも確認してください。

image-rendering プロパティも確認してくださいらしいです。

これは、画像を拡大縮小するときのアルゴリズムを指定するものらしいです。

image-rendering

詳しくはリファレンスに書いてありますが、これを pixelated という値にすると、ピクセル画で表示されます。

読解力と知識の無さでその他の値については読み解けませんでしたスマヌ...

まとめ

以下をコピーして、styleタグやcssファイルに貼り付けてください。

style.css
    canvas{
        image-rendering: pixelated;
    }

これで、画像を拡大してもぼやけさせずに表示することができました!!!

以上。

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

時間の設定

時間の設定方法

❶現在日時を初期化して代入

let goal = new Date();

❷未来(ゴールの日時を設定)

goal.setHours(23);
goal.setMinites(59);
goal.setsecond1s(59);

:point_up:現在年月日、23時、59分、59分でセット完了(今日の最終日でセットされる)

:warning:

const goal = new Date(2025,4,3)

2025年4月3日を設定

❷functionを設定

function countdown(due){

❷現在日時に初期化

const now = new Date();

❸パラメータdueのミリ秒から、定数nowのミリ秒を引いて、定数restに代入

const rest = due.getTime()- now.getTime();

ミリ秒で算出される

❹定義する

   const sec = Math.floor(rest/1000)%60;
    const min = Math.floor(rest/1000/60)%60;
    const hours = Math.floor(rest/1000/60/60)%24;
    const days = Math.floor(rest/1000/60/60/24);

❺配列準備する

const count = [days,hours, min,sec];
return count;

呼び出し元にリターンする

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

JavaScriptのスコープについて

スコープとは

簡単に言うと変数がコードの中のどこから参照できるかの有効範囲。

JavaScriptのスコープは下記の二つに分類できる。
- グローバルスコープ:コード全体から参照できる
- ローカルスコープ:定義された関数の中でのみ参照できる

またグローバルスコープを持つ変数のことをグローバル変数ローカルスコープを持つ変数のことをローカル変数といいます。

関数スコープ

まず関数スコープについてみてみます。
下記のようにグローバル変数はコードのどこからでも参照できます。
ローカル変数は、変数を定義した関数の中でしか参照できないため、関数の外から参照しようとするとエラーになります。

const globalScope = 'グローバルスコープ';

function getValue() {
  const localScope = 'ローカルスコープ';

  console.log(globalScope); //グローバルスコープ
  console.log(localScope); //ローカルスコープ
}

getValue();
console.log(globalScope); //グローバルスコープ
console.log(localScope); //not defined

仮引数のスコープ

仮引数とは呼び出し元から関数に渡された値を受け取るための変数です。
下記の関数ではvalueが仮引数になります。

function decrementValue(value) {・・・}

基本型

まずは基本型について見てみます。
①でグローバル変数valueに10が代入されます。
②で関数decrementValueが呼び出されますが、その内部で使用されている仮引数valueはローカル変数と見なされるので、これをいくら操作してもグローバル変数valueに影響を与えることはありません。
なのでグローバル変数のvalueが書き換えられることはなく、③ではもともとの値である10が返されます。

let value = 10 // ①

function decrementValue(value) {
  value--;
  return value
}

console.log(decrementValue(100)); // ② 結果:99
console.log(value); // ③ 結果:10

参照型

次に仮引数に渡される値が参照型の場合を見てみます。
参照型とは値そのものではなく、値を格納したメモリ上の場所だけを格納している型です。

下記の例だと、①で定義されたグローバル変数valuesと、②で定義された仮引数は変数としては別物ですが、③でグローバル変数valuesの値が仮引数valuesに渡された時点で、結果的に実際に参照しているメモリ上の場所が等しくなります。

したがって、deleteItem関数の中で配列を操作した場合、その結果はグルーバル変数valuesにも反映されます。

let values = [1, 2, 3, 4, 5]; // ①

function deleteItem(values) { // ②
  values.pop();
  return values;
}

console.log(deleteItem(values)); // ③ 結果:[1, 2, 3, 4]
console.log(values); // ④ 結果:[1, 2, 3, 4]

ブロックスコープ

{}で囲んだ範囲をブロックといいスコープを作成します。
ブロックによるスコープのことをブロックスコープと呼びます。
ブロック内で宣言された変数は、スコープ内でのみ参照でき、スコープの外側からは参照できません。

下記の例を見てみると、ブロック内で変数valuesが宣言されています。
①でブロック外から、ブロック内の変数を参照していますが、ReferenceErrorとなっていまします。

{
  const value = 10;
  console.log(value);
}

console.log(value); // ① 結果:ReferenceError

if文やwhile文などもブロックスコープを作成します。
単独のブロックと同じく、ブロックの中で宣言した変数は外から参照できません。

if (true) {
    const value = 10;
    console.log(value); // 結果:10
}
console.log(value); // 結果:ReferenceError

for文は、ループごとに新しいブロックスコープを作成します。
下記のコードでは、ループごとにconstでelement変数を定義していますが、エラーなく定義できています。
これは、ループごとに別々のブロックスコープが作成され、変数の宣言もそれぞれ別々のスコープで行われるからです。

const values = [1, 2, 3, 4, 5];

for (const element of values) {
    // forのブロックスコープの中でのみ`element`を参照できる
    console.log(element);
}

console.log(element); // 結果:ReferenceError

switch分は、条件分岐全体として1つのブロックです。
caseはブロックではないため、case句の単位に変数let宣言した場合はエラーとなります。

switch(x) {
  case 0:
    let value = 'x:0';
  case 1:
    let value = 'x:1'; // 変数名の重複
}

参考:JavaScript Primer - 迷わないための入門書 #jsprimer
参考:改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで

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

undefined と null の使い分け

JavaScript の undefinednull について、違いを解説している記事はいくつかありますが、具体的な使い分けには触れられないことが多いようです。

そこで、私が意識している 違い・使い分け を表にまとめてみました。 の部分には undefinednull が入ると思ってください。

undefined null
意味 変数・プロパティが未設定 が無効(利用可能な値がない)
変数: variable = … variable を未初期化の状態にする 1 variable が有効な値を持たないことを表す
プロパティ: object.prop = … object.prop の値をクリアする 1 2 object.prop が有効な値を持たないことを表す
関数の返り値: return … 3 関数から値を返さずに戻る 関数の結果として、無効な値を返す
関数の引数: func(…) 3 引数の省略を表す 引数として、無効な値を渡す 4

これが絶対というわけではありませんが、迷った時に参考にしてみてはいかがでしょうか。


  1. 「未初期化」であることが重要な場合以外は null を使うべき 

  2. プロパティ自体を削除するには、delete object.prop を使う 

  3. undefined の場合は、 の部分を空に(省略)する 

  4. 引数が null という値を持つ点で、undefined とは異なる 

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

C言語で書いたHelloWorld(+α)をJavaScriptから動かしてみた【WebAssembly】

ブラウザ上でC言語のコードを実行するって、一体全体どういうことだろう?
前職でCを扱っていたこともあって、WebAssemblyは気になる技術のひとつでした。
この記事では、HelloWorldに加えて、ちょっとした実験の結果をまとめてみました。


WebAssemblyとは

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine.
(公式より引用)

  • ブラウザ上で動くバイナリコードのフォーマット (名前の通り、ウェブ用のアセンブリ言語)
  • C/C++、Rustなどからコンパイル可能
  • 略称はWASM(ワズム)
  • jsのコードからWASMを呼び出せる。逆もできる。
  • Chrome, FIrefox, Safari, Edgeなどで使用可能
  • つまり、上記言語で書いたコードをブラウザ上で動かすことができるようになる
  • 速くて実行環境を選ばない
  • 主な用途は画像処理やゲームなど
  • ファイルやネットワーク、メモリへのアクセスは仕様策定中らしい(要出典)


おしながき(やったこと)

  • HelloWorldを動かしてみる
  • メモリリークするコードを動かすとどうなる?
  • メモリを使いまくるとどうなる?
  • ランタイムエラーになるコードを動かすとどうなる?


HelloWorldを動かしてみる

参考: https://laboradian.com/tried-webassembly/
(MDNと同じ手順だが、補足説明付きでより分かりやすい)

 
Emscripten(コンパイラ)をインストール

# 今回インストールしたバージョン
$ emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.39.16
clang version 11.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 3774bcf9f84520a8c35bf765d9a528040d68a14b)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
shared:INFO: (Emscripten: Running sanity checks)

 ↓

hello.cを作る

hello.c
#include <stdio.h>

int main(int argc, char *argv[]) {
  printf("Hello World\n");
  return 0;
}

 ↓

コンパイル

# コンパイル前
$ ls
hello.c

# コンパイル
$ emcc hello.c -s WASM=1 -o hello.html

# wasm, js, htmlが生成された
$ ls -l
total 496
-rw-r--r--  1 arene  staff      95  5 19 21:55 hello.c
-rw-r--r--  1 arene  staff  102675  5 21 21:50 hello.html
-rw-r--r--  1 arene  staff  115917  5 21 21:50 hello.js    # 2600行(!)
-rw-r--r--  1 arene  staff   21727  5 21 21:50 hello.wasm

コンパイル後に生成されたjsファイルはなんと2600行。
大変な技術だということが伺えます。
なお、当たり前ですがCとしてコンパイルできないコードは、WASMにもコンパイルできませんでした。

 ↓
 
chrome://flags/ で Experimental WebAssemblyを有効にする

 ↓
 
ローカルPCにWebサーバを立てる

# Emscriptenは簡易的なWebサーバまで用意してくれている
$ emrun --no_browser --port 8080 .
Web server root directory: /Users/arene/temporary/wasm
Now listening at http://0.0.0.0:8080/


 
ブラウザでhello.htmlを開く

1.png
※ ロゴとかターミナル風の画面は、コンパイラが生成したものです
 (もちろん、オプション次第でwasm + jsだけの出力もできます)

 
なお、HelloWorldをコンパイルした時に自動生成されたコードはこんな感じでした。
(しっかり解析できていない&大量にあるため、雰囲気をつかみやすいところだけ抜粋しました)

hello.html
<!-- ロゴとかターミナル風な画面とか余計なものは割愛 -->
<script async type="text/javascript" src="hello.js"></script>
hello.js
// 実際のコードは2600行あります
// 概要を掴む意味で、それっぽく抜き出しました
// 関数の定義順も、頭から読めるように変えています(本当は`run();`がファイル末尾にある)

var wasmBinaryFile = 'hello.wasm';
if (!isDataURI(wasmBinaryFile)) {
  wasmBinaryFile = locateFile(wasmBinaryFile);
}

run(); // ファイル最下にあったこいつが全ての起点

function run(args) {
  // めっちゃいろいろ省略
  callMain(args)
}
function callMain(args) {
  // めっちゃいろいろ省略
  var entryFunction = Module['_main']; // Module['xxx']が大量にあって、main関数含めいろんなものが突っ込まれている
  var ret = entryFunction(argc, argv);
  exit(ret, true);
}

// こんな感じでCの関数をModule["asm"]に紐づけている
var asm = createWasm();
Module["asm"] = asm;
var _main = Module["_main"] = function() {
  assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
  assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
  return Module["asm"]["main"].apply(null, arguments)
}; 
var _malloc = Module["_malloc"] = function() {
  assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
  assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
  return Module["asm"]["malloc"].apply(null, arguments)
}; 
var _free = Module["_free"] = function() {
  assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. waMBit for main() to be called)');
  assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
  return Module["asm"]["free"].apply(null, arguments)
};


メモリリークするコードを動かすとどうなる?

1sごとに100KBメモリリークするコードを用意

memoryLeak.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void memoryLeak(void) {
  int shouldLoop = 1;
  int loopCount = 0;
  while(shouldLoop) {
    loopCount++;
    if (loopCount > 10) {
      shouldLoop = 0;
    }
    char* p = malloc(1024 * 100); // 動的にメモリを確保。解放しないため、ループ毎にメモリリークする。
    printf("memory leak: leak 100KB!\n");
    usleep(1000 * 1000); // 1000msec
  }
  return;
}

int main(int argc, char *argv[]) {
  printf("memory leak: start\n");
  memoryLeak();
  printf("memory leak: end\n");
  return 0;
}

 ↓
 
コンパイル後ブラウザで実行し、devtoolでパフォーマンスを計測

1.png

JS Heapが増えておらず、フロント側でのメモリリークは検知されませんでした。
...どういうことだろう?
(proposalの一覧にGCがあるから、GCが働いたというわけではなさそう)


メモリを使いまくるとどうなる?

前項でメモリリークを確認できなかったのはリーク幅が小さくて分かりにくかっただけなのでは? と思いリーク幅をひろげてみました。
100ms毎に1MB、計100MBリークするコードに書き換え、同じように検証したところ以下のエラーに遭遇。

ブラウザ、WASM、OS、どのレイヤの制限かは不明ですが、使えるメモリには限りがあるみたいです。
(当然っちゃ当然ですが)

console.log
 Cannot enlarge memory arrays to size 17534976 bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 

 memoryLeak.html:1246 Cannot enlarge memory arrays to size 17534976 bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 

 memoryLeak.html:1246 exception thrown: RuntimeError: abort(Cannot enlarge memory arrays to size 17534976 bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ) at Error
     at jsStackTrace (http://0.0.0.0:8080/memoryLeak.js:1978:17)
     at stackTrace (http://0.0.0.0:8080/memoryLeak.js:1995:16)
     at abort (http://0.0.0.0:8080/memoryLeak.js:1735:44)
     at abortOnCannotGrowMemory (http://0.0.0.0:8080/memoryLeak.js:2018:7)
     at _emscripten_resize_heap (http://0.0.0.0:8080/memoryLeak.js:2021:7)
     at wasm-function[13]:0x237a
     at wasm-function[11]:0xd36
     at wasm-function[8]:0x337
     at wasm-function[9]:0x407
     at Module._main (http://0.0.0.0:8080/memoryLeak.js:2212:32),RuntimeError: abort(Cannot enlarge memory arrays to size 17534976 bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ) at Error
     at jsStackTrace (http://0.0.0.0:8080/memoryLeak.js:1978:17)
     at stackTrace (http://0.0.0.0:8080/memoryLeak.js:1995:16)
     at abort (http://0.0.0.0:8080/memoryLeak.js:1735:44)
     at abortOnCannotGrowMemory (http://0.0.0.0:8080/memoryLeak.js:2018:7)
     at _emscripten_resize_heap (http://0.0.0.0:8080/memoryLeak.js:2021:7)
     at wasm-function[13]:0x237a
     at wasm-function[11]:0xd36
     at wasm-function[8]:0x337
     at wasm-function[9]:0x407
     at Module._main (http://0.0.0.0:8080/memoryLeak.js:2212:32)
     at abort (http://0.0.0.0:8080/memoryLeak.js:1741:9)
     at abortOnCannotGrowMemory (http://0.0.0.0:8080/memoryLeak.js:2018:7)
     at _emscripten_resize_heap (http://0.0.0.0:8080/memoryLeak.js:2021:7)
     at wasm-function[13]:0x237a
     at wasm-function[11]:0xd36
     at wasm-function[8]:0x337
     at wasm-function[9]:0x407
     at Module._main (http://0.0.0.0:8080/memoryLeak.js:2212:32)
     at callMain (http://0.0.0.0:8080/memoryLeak.js:2500:15)
     at doRun (http://0.0.0.0:8080/memoryLeak.js:2562:23)

 memoryLeak.js:1741 Uncaught RuntimeError: abort(Cannot enlarge memory arrays to size 17534976 bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ) at Error
     at jsStackTrace (http://0.0.0.0:8080/memoryLeak.js:1978:17)
     at stackTrace (http://0.0.0.0:8080/memoryLeak.js:1995:16)
     at abort (http://0.0.0.0:8080/memoryLeak.js:1735:44)
     at abortOnCannotGrowMemory (http://0.0.0.0:8080/memoryLeak.js:2018:7)
     at _emscripten_resize_heap (http://0.0.0.0:8080/memoryLeak.js:2021:7)
     at wasm-function[13]:0x237a
     at wasm-function[11]:0xd36
     at wasm-function[8]:0x337
     at wasm-function[9]:0x407
     at Module._main (http://0.0.0.0:8080/memoryLeak.js:2212:32)
     at abort (http://0.0.0.0:8080/memoryLeak.js:1741:9)
     at abortOnCannotGrowMemory (http://0.0.0.0:8080/memoryLeak.js:2018:7)
     at _emscripten_resize_heap (http://0.0.0.0:8080/memoryLeak.js:2021:7)
     at wasm-function[13]:0x237a
     at wasm-function[11]:0xd36
     at wasm-function[8]:0x337
     at wasm-function[9]:0x407
     at Module._main (http://0.0.0.0:8080/memoryLeak.js:2212:32)
     at callMain (http://0.0.0.0:8080/memoryLeak.js:2500:15)
     at doRun (http://0.0.0.0:8080/memoryLeak.js:2562:23)


ランタイムエラーで落ちるコードを動かすとどうなる?

最後に、ランタイムエラーになるコードを動かしてみました。
結果は、当たり前ですが、ブラウザ上でもちゃんとランタイムエラーになりました。
ただし、ランタイムエラーとなった行以降のコードも実行されました。
(普通にCとして実行した場合はエラー箇所で落ちるため、微妙に違う。結構怖い挙動。)

 

segfault.c
#include <stdio.h>

int main(void) {
  int i;
  char str[4];
  char* p;

  printf("Before Segmentation Fault\n");
  for(i = 0; i < 16; i++) {
    str[i] = 'a';
    *p = str[i];
  }
  printf("After Segmentation Fault\n");
  return 0;
}
# 普通にC言語としてコンパイル&実行するとエラー箇所で落ちる
$ gcc segfault.c
$ ./a.out
Before Segmentation Fault
[1]    22777 segmentation fault  ./a.out

 
ブラウザから実行するとエラー箇所の後も処理が継続
1.png

エラー時のコンソールログ

console.log
 segfault.html:1246 Runtime error: The application has corrupted its heap memory area (address zero)!
 segfault.js:1741 Uncaught RuntimeError: abort(Runtime error: The application has corrupted its heap memory area (address zero)!) at Error
     at jsStackTrace (http://0.0.0.0:8080/segfault.js:1978:17)
     at stackTrace (http://0.0.0.0:8080/segfault.js:1995:16)
     at abort (http://0.0.0.0:8080/segfault.js:1735:44)
     at checkStackCookie (http://0.0.0.0:8080/segfault.js:1446:46)
     at postRun (http://0.0.0.0:8080/segfault.js:1538:3)
     at doRun (http://0.0.0.0:8080/segfault.js:2545:5)
     at http://0.0.0.0:8080/segfault.js:2554:7
     at abort (http://0.0.0.0:8080/segfault.js:1741:9)
     at checkStackCookie (http://0.0.0.0:8080/segfault.js:1446:46)
     at postRun (http://0.0.0.0:8080/segfault.js:1538:3)
     at doRun (http://0.0.0.0:8080/segfault.js:2545:5)
     at http://0.0.0.0:8080/segfault.js:2554:7






最後に

標準入力、ファイルI/O、スレッド、セマフォ、共有メモリ、シグナル、Cからjsの関数を呼ぶ、複数の関数を定義する、ファイル分割、再帰呼び出し、速度比較...などなど。
他にも気になる部分はありますが、ひとまずはここで実験を終えます。

ゆくゆくはブラウザ上で全てが完結するようになりそうで、夢がありますね。

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

webpack はじめ方

この内容について

この内容は、私が運営しているサイトに、より見やすく掲載しているので、よければそちらもご活用ください。
【webpack】 導入手順 | コレワカ

今回紹介する内容には、Sassのバンドルも含めていますので、Sassを使いたい人にもおすすめです。

webpackとは

オープンソースのJavaScriptモジュールバンドルのこと。
CSSやJavaScriptなどの複数のファイルを1つのファイルにまとめることが可能。

webpackのメリット

多くのCSSやJavaScriptなどのファイルを1ページで読み込むとリクエスト数が増加してしまうため、
ページの表示速度が遅くなるなどのデメリットがあります。
それをwebpackを使って、1つのファイルにすることでリクエスト数が減り、ページ表示速度が速くなるなど
のメリットを得られます。
同時に最終的に1つにすれば良いので、開発時はファイルを分割して管理することも可能なので、開発効率の
アップにもつながります。

はじめ方

プロジェクトを作成する

好きな名前でフォルダを作成します。

ターミナル
mkdir dir

webpack1.png

フォルダ配下に移動

作成したフォルダ配下に移動します。

ターミナル
cd dir

webpack2.png

npm初期化

npm管理下にする初期化処理のコマンドを実行します。

ターミナル
npm init -y

webpack3.png

webpackインストール

webpackをインストールするコマンドを実行します。
今回はSassも使えるような構成です。
Sassを使わない場合は、sass-loader以降は記述しなくて良いです。

ターミナル
npm i -D webpack webpack-cli sass-loader node-sass style-loader css-loader

webpack4.png

configファイル作成

webpack.config.jsという名前でファイルを作成します。

ターミナル
touch webpack.config.js

webpack5.png

configファイルの中身の記述

webpack.config.jsの中身を記述します。
modeは'production'と'development'があり、
'production'は本番環境用のバンドル(よりコンパクトな記述)で、
'development'は開発用のバンドル(より見やすい記述)で使い分けできます。(今回は'development')
entryに、バンドルするエントリーポイントとなるJavaScriptファイルを指定します。
outputに、バンドルされたファイルを出力する場所を指定します。
moduleの部分で詳細な設定ができ、今回はSassファイル(SCSSファイル)のバンドルもできるようにしています。

webpack.config.js
const path = require('path');
module.exports = {
  mode: 'development',
  entry: './src/js/entry.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'src')
  },
  module: {
    rules: [
      {
        test: /.scss$/,
        use: [
          "style-loader",
          "css-loader",
          "sass-loader"
        ]
      }
    ]
  }
};

webpack6.png

srcフォルダの作成

srcという名前でフォルダを作成します。

ターミナル
mkdir src

webpack7.png

src配下に移動

作成したsrcフォルダ配下に移動します。

ターミナル
cd src

webpack8.png

バンドルファイル作成

バンドルして出力されるファイルを作成します。

ターミナル
touch bundle.js

webpack9.png

js・stylesファイル作成

js・stylesという名前でフォルダを作成します。
jsフォルダ配下にjsファイルを置いて、stylesフォルダ配下にSassファイルを置きます。

ターミナル
mkdir js styles

webpack10.png

js配下に移動

作成したjsフォルダ配下に移動します。

ターミナル
cd js

webpack11.png

バンドルの元となるファイルを作成

entry.jsという名前でファイルを作成します。

ターミナル
touch entry.js

webpack12.png

バンドルの元となるファイルの中身の記述

entry.jsの中身を記述します。

entry.js
// 〇〇の部分にバンドルしたいjsファイル名
import "./〇〇.js";
// 〇〇の部分にバンドルしたいSassファイル名
import "../styles/〇〇.scss";

webpack13.png

package.jsonの書き換え

package.jsonファイルを開き、一部分を書き換えます。

package.json
"scripts": {
  "test": "echo "Error: no test specified" && exit 1",
  "build": "webpack"
}

webpack14.png

プロジェクトフォルダ配下に移動

プロジェクトフォルダ配下に移動します。

ターミナル
cd ../..

webpack15.png

バンドルするコマンド実行

npm run buildのコマンドでwebpackコマンドを実行させます。

ターミナル
npm run build

webpack16.png

バンドルされたファイルを記述

バンドルされたjsファイル(bundle.js)を読み込みます。

HTML
<script src="./src/bundle.js"><script>

webpack17.png

最終的なファイル構成

- index.html
- node_mudules
- package.json
- src
    - bundle.js
    - js
        - entry.js
        - 〇〇.js
    - styles
        - 〇〇.scss
- webpack.config.js

webpack18.png

まとめ

今回紹介した手順通りにやるだけで、webpackを使えるようになると思いますので、どんどん利用していきましょう。
webpackはTwitterなどでも利用されている技術で、フロントエンドエンジニアには必須だろうと個人的に思いますので、
この記事を活用しながら、是非使ってみてください(^^)

それから、webpackは、CSSやJavaScriptだけでなく、画像やFontawesomeなどのアイコンもバンドルすることが
できます。その手順に関しては、別記事にして紹介したいと思いますので、是非今後も僕の記事を読んでいただければと
思います(^^)

この記事が良いと思った方は、LGTMをしていただければ嬉しいです!
フォローも是非お願い致します(^^)

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

Ruby on RailsへのjQueryの導入準備

環境・前提条件

macOS Catalina バージョン10.15.4
ruby 2.5.1p57 (2018-03-29 revision 63029)
Rails 5.2.4.3

jQuery導入準備

Gemfileを確認し、

gem "jquery-rails"

と記載する。

application.jsに、 

//= require jquery 

と記載する。※注意点として、"//= require_tree ."より上に記載する。

//= require jquery

//= require_tree .

実際にjQueryが使えるのか、確認する。
任意の名前のjsファイルを作成する。(同名のcoffeeファイルがあれば削除すること。)

$(function(){
 console.log('OK');
});

これでページを更新し、コンソールに'OK'と表示されれば準備OK!

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

[JQuery] ファイルからフォーム選択プレビュー表示

はじめに

ファイルから画像を選択するし、submitする前に自分の選択した画像を表示できるようにする。

イメージとしては以下のようなものです。
画面収録 2020-05-31 13.21.05.mov.gif

前提として

・jQueryを設定できるようにしてある。
・Railsのバージョン5.2を使用
・Hamlを使用

ビューファイルを設定しよう

edit.html.haml
.editmain_right
  .editmain_right_form
    .editmain_right_form_sub
      %span.icon
      %span.icontext  プロフィール画像をアップロード
    .editmain_right_form_main
      .editmain_right_form_main_ajax
      .editmain_right_form_main_file
        = f.file_field :image, class: "image_form"
       %span.texpic  
       写真アップロード

jQueryを設定

edit.js
$(document).on('turbolinks:load', function() {
$(function(){
  //画像ファイルプレビュー表示のイベント追加 fileを選択時に発火するイベントを登録
  $('form').on('change', 'input[type="file"]', function(e) {
    var file = e.target.files[0],
        reader = new FileReader(),
        $subsubimageboxs = $(".editmain_right_form_sub");
        t = this;

    // 画像ファイル以外の場合は何もしない
    if(file.type.indexOf("image") < 0){
      return false;
    }

    // ファイル読み込みが完了した際のイベント登録
    reader.onload = (function(file) {
      return function(e) {
        //既存のプレビューを削除
        $subsubimageboxs.empty();
        // .prevewの領域の中にロードした画像を表示するimageタグを追加
        $subsubimageboxs.append($('<img>').attr({
                  src: e.target.result,
                  width: "150px",
                  class: "editmain_right_form_sub",
                  title: file.name
              }));
      };
    })(file);

    reader.readAsDataURL(file);
  });
});
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebRTCのブラウザ対応状況の調べ方

こんにちは、最近業務でWebRTCを勉強し始めたものです。

WebRTC使うぞ!となった時にまず気になるのが自分たちが対応したいブラウザでサポートされているのかという話なのですが、WebRTCはそれが単独のAPIで構成されているわけではなく、またコーデックなども関わってくるので、自信を持って大丈夫と言えるようにまとめてみました。

WebRTCのAPI

まず WebRTC と一口に言っても window.webrtc みたいなオブジェクトが生えているわけではなく、ブラウザを介したユーザ間のリアルタイムコミュニケーションを実現するためのAPIの総称が WebRTC です。

それを実現するためには大きく二つあって

  • 通信に使用するメディア(カメラやマイク)へのアクセス
    • navigator.mediaDevices.getUserMedia() を使って MediaStreams を取得します。
  • P2Pでのコネクションの確立

(その他には Audio/Video 以外を通信したい場合は RTCDataChannel が必要だったり、レコーディングや画面共有などの機能を作りたい場合はnavigator.mediaDevices.getDisplayMedia()が必要です)

なので navigator.mediaDevices.getUserMedia() と RTCPeerConnection が実装されているブラウザであればとりあえず実現はできそうです。

navigator.mediaDevices.getUserMedia() の対応状況

主要なところで言うと iOS 10 以下とIEを切ることができれば大丈夫そうですね。

スクリーンショット 2020-05-31 11.56.41.png

https://caniuse.com/#feat=stream

RTCPeerConnection の対応状況

こちらも同様です。

スクリーンショット 2020-05-31 11.58.14.png

https://caniuse.com/#feat=stream

コーデックについて

普段Webだけやっている身としては馴染みのない概念ですが、使用するコーデックもブラウザによって対応状況が違うため気をつける必要があります。

MDNに詳しく纏まっているのでこちらをご一読いただけるといいと思いますが、ここでもざっくり解説をします。
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs

コーデックとは

記事の本筋から逸れるので詳細は省きますが、ざっくり言うと動画データと音声データを通信する際の圧縮/復元を行うプログラム(アルゴリズム?)をコーデックと呼びます。

我々Webエンジニアも gzip したり brotli したりすると思いますが、アレの動画/音声版ですね。
それで「brotli、IEだと解凍できないから使えません?」みたいなのがコーデックにもある訳です。

WebRTC で対応しているコーデック

まずブラウザ関係なくWebRTCのRFCを見てみると次のコーデックのサポートが必須となっています。

各ブラウザベンダーの良心を信じればこれらのコーデックを使用していればとりあえずは動きそうです。

と思いたかったのですが、どうやら Safari ではVP8は iOS 12.1 からの対応みたいです。
WebRTC コーデックの現状に関する考察 - ボクココ
Safari 12.1 で VP8 と Unified Plan が入る - V - Medium

まだまだ記事執筆時点では iOS 12.0 以下のシェアも馬鹿にならないのでコーデックを決める際には注意が必要です。またVP9などのコーデックを使用したい場合には個別に調査を頑張りましょう。

こちらの記事が現状整理分かりやすくされていて良かったです。
WebRTC の未来 · GitHub

参考

Getting started with WebRTC <- これ読むとWebRTCがどう動いているかの流れが大体掴めます。
Codecs used by WebRTC - Web media technologies | MDN
Can I use... Support tables for HTML5, CSS3, etc
WebRTC の未来 · GitHub

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

作って理解JavaScript:JOKE開発記 その3

今回のスコープ

開発記その2で予告したように演算機能を実装します。具体的には

  • 算術演算
  • 代入演算子
  • インクリメント/デクリメント。ただし後置のみ1

これだけだと簡単にできてしまうので比較演算・論理演算も実装しようかなと思いましたが、短絡評価のためには条件分岐を実装しないといけないのでステップ4以降で作ることにしました。
というわけで今回のテストプログラムは以下の2つとなります。

step/step0003_01.js
// いわゆる算術演算。優先順位とか結合規則も確認
console.log(1 + 2);
console.log(-1 - 2 - 3);
console.log(1 + 2 * 3);
console.log((1 + 2) * 3);
console.log(1.5 / 2);
console.log(5 % 3);
step/step0003_02.js
// インクリメントと代入演算子
let a = 0;
console.log(a++);
console.log(a);

a += 2;
console.log(a);

ステップ3時点の(以下で説明していく)コードは以下にあります。
https://github.com/junjis0203/joke/tree/step0003

演算機能の仕様と実装

数値解析

ステップ2時点では数値リテラルをサポートしていなかったのでまずはそこからです。
整数だけでいいやと思うものの、JavaScriptでは5 / 22ではなく2.5になるので演算で出るものをリテラルで書けないのは駄目だろうと浮動小数点数もサポート…、というかよく思うとJavaScriptには浮動小数点数しかありませんでした。

仕様に沿って実装、と言いつついくつか手抜きしています。

  • 16進数表記等はサポートしない(単に当面使うつもりがないのでコード量を減らすため)
  • .512.のように小数点の前や後に数値がないものも未サポート(自分があまりそう書かないため)

指数表記は書いた後で「別に要らないか」と思いましたが、実装してしまったので残してあります(笑)
また、先にリンクした仕様だと「3inみたいなのは即エラーにして、3とinという2つのトークンにしてはいけない」と書かれていますが、ここについても今のところ「ちゃんと(見つけたら即刻)」エラーにはしていません。

算術演算の実装

バックトラックの導入

BNFの段階で演算子の優先順位は適切なものになっているので淡々と実装すればOK。
と思っていたらステップ2の代入(以下のBNF)を構文解析する際に

AssignmentExpression :
    ConditionalExpression
    LeftHandSideExpression = AssignmentExpression

手抜きしたつけが早速回ってきましたw

ステップ2のparser.js抜粋
function AssignmentExpression(scanner) {
    // currently, ConditionalExpression equals to LeftHandSideExpression
    // this code must be modified when support various operator
    let node = LeftHandSideExpression(scanner);

厄介(手抜きした理由)なのは途中にある「=」によりConditionalExpressionなのか、(ConditionalExpressionの一部である)LeftHandSideExpression なのかが変わる点です。
これに対応するためにバックトラックを実装しました。つまり、

  1. Scannerの状態を保存(カプセル化していますが実体は現在のポインタを返しているだけです)
  2. まずConditionalExpressionとして解析
  3. 「=」がなければOK
  4. 「=」があったらScannerの状態をリストアしてLeftHandSideExpressionとして解析

演算子の結合規則と再帰下降パーサ

足し算(と引き算)に対応するBNFは以下の通りです。

AdditiveExpression :
   AdditiveExpression + MultiplicativeExpression
   AdditiveExpression - MultiplicativeExpression

これを「素直に」関数で書くともちろん無限再帰になってしまい死にます。
というわけでループの形に書き換える必要があります。

parser.js抜粋
function AdditiveExpression(scanner) {
    let node = MultiplicativeExpression(scanner);
    while (checkCharToken(scanner.token, '+') || checkCharToken(scanner.token, '-')) {
        const operator = scanner.token.char;
        scanner.next();
        const right = MultiplicativeExpression(scanner);
        node = {
            type: Node.BINARY_OPERATOR,
            operator,
            left: node,
            right,
            srcInfo: node.srcInfo
        };
    }
    return node;
}

なお、

AdditiveExpression :
    MultiplicativeExpression + AdditiveExpression
    MultiplicativeExpression - AdditiveExpression

とすれば無限再帰にはならない!解決!とはなりません。
こうしてしまうと引き算が右結合になってしまいます。具体的には1 - 2 - 31 - (2 - 3)となってしまい答えが2になってしまいます。

というかステップ1以前に四則演算パーサ作って構文解析処理について理解を深めていたときに上記のボケを経験していたので今回は引っかかりませんでした(笑)

カッコの処理

掛け算よりも足し算が先に計算されるようにというカッコですが仕様の該当箇所を見てみると地味にめんどくさい書き方がされています。

CoverParenthesizedExpressionAndArrowParameterList :
    ( Expression )
    ( )
    ( ... BindingIdentifier )
    ( Expression , ... BindingIdentifier )

ただしその下に補足(Supplemental Syntax)があって、

When processing the production

    PrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList

the interpretation of CoverParenthesizedExpressionAndArrowParameterList is refined using the following grammar:

    ParenthesizedExpression :
        ( Expression )

「production」が何のことを言ってるのかは気になりますがとりあえずParenthesizedExpressionということで処理をするようにしました。
アロー関数は前処理とかされるのですかね(アロー関数の仕様はまだ見ていない)

インクリメントの処理

構文解析

インクリメント仕様の中の以下の記述、

It is an early Reference Error if IsValidSimpleAssignmentTarget of LeftHandSideExpression is false.

ステップ2でも出てきましたが素のLeftHandSideExpressionは関数呼び出し等も含むために別途「代入操作ができるものか」がIsValidSimpleAssignmentTarget として定義されています。ちゃんとやるにはノードに付加情報として設定(して必要に応じて伝播)すべきでしょうがひとまず「ノードタイプが変数参照(IDENTIFIER_REFERENCE)」の場合はOKとしました2

実行コードへの「展開」

普通のCPUならインクリメント/デクリメントは専用の命令が用意されています3
それを踏まえてJOKEでは以下のようにしています。いやネイティブCPUのコードを吐けるようにしようとか考えてはいませんが。

  • 構文解析の段階(ノード出力)では「インクリメント」という情報を残す
  • 今からプログラムを実行させるVMはインクリメント命令がないので、実行コード生成(Assembler)で「インクリメントを実装する」命令列を作る4

初めに書いたようにインクリメントは後置しかサポートしていませんが、後置の「式の結果は足す前の値」に対応するための「実装命令列」は意外とめんどくさいですね。ある意味性能求めてない「興味に従って書いてみている」ことで得られた知見な気がします。

なお、代入演算子の方は構文解析時点で書き換えており一貫性がないなーと思うのですが、こちらは代入演算子用にノードタイプを増やしたくはなかったのでいいかなと思ってます。

言語仕様以外の改造

言語仕様の追加が少ないからというわけだったりわけじゃなかったりといろいろ今後の開発に役立ちそうな機能を実装しました。

REPL

ステップ2まではテストコードを少し書き換えて動作(不適切な記述が正しくエラーになるのかも含む)確認をしていたのですが、演算が出てくるといろいろなパターンを都度書き換えて実行するのもめんどうになってきたのでREPL(nodeやらpythonやらでソースファイルを指定しないと出てくる対話環境のあれ)を実装しました。
ただし、普通REPLというと入力した式の結果を表示するということを行いますが、そのためにはVMでのコード実行時に最後の一文だけはpopしない(POP命令を入れない)のような「REPL用の変更」を入れる必要があります(CPythonはそこら辺切り替えてたはず)。主目的は構文改正結果とかのダンプを見ることなのでREPL用の改造は入れませんでした(表示のためには自分でconsole.logを書く必要があります)

REPLのための入力処理はNode.jsのReadlineモジュールのexampleほぼそのままです。
あと、初めは「同じ名前の変数が2つ定義できる不具合」があり、簡単REPLだからいいかなとも思ったのですが、グローバルスコープの有効範囲を変える等して直しました。

-eオプション

というわけでREPLを作ったのですが、今度は「毎回コード片を入力するのがめんどくさい」ということになりました5
そう、顧客が本当に欲しかったのは-eオプション(コマンドラインでコード片を指定できる機能)だったのですw

まじめに「-eが複数指定された場合」とかを対応すると手間がかかりますが、「簡単実装」であれば変更箇所も少なく実装が行えました。

タイプ等の定数化

前々から気にはしていたのですが、トークンタイプ等が文字列なので、まあスペルミスが起こる危険があるわけです(あった)
というわけでIDEで補完が効くように定数として定義するようにしました。

ただ、

import * as Token from './token.js';

と書くと「エクスポートされている名前をプロパティとして持つオブジェクト」が作られる、つまり、Token.NO_SUCH_TYPEと書いてもその時点で「そのような名前はない」エラーは起こらずundefinedが返されてしまうようでした。まあ補完性能は上がったのでいいかなと思います6

プリプロセッサライク関数内関数

上の「タイプ等の定数化」と合わせて「ソースコードの行番号を実行コードまで受け渡す」処理も入れたのですが(実行時エラーのときにソース行を示すため)、その際、「Nodeオブジェクトのプロパティとして入れてあるソース情報をInstructionオブジェクトにコピーする処理」は大量のコードコピペが発生するのでどうにかできないかな、見出しのようにプリプロセッサ的なことできないかな、JavaScriptでは無理か、と思ったのですが、限定された状況なら関数内関数を使って実現できました。

assembler.js抜粋
function assembleNode(node, insns) {
    // shorten name
    const N = Node;
    const I = Instruction;

    // define function like preprocessor to reduce redundant code
    function makeInstruction(command, args) {
        insns.push({command, ...args, srcInfo: node.srcInfo});
    }

    switch (node.type) {
    case N.IDENTIFIER_REFERENCE:
        makeInstruction(I.PUSH, {operand: node.identifier});
        makeInstruction(I.LOOKUP);
        break;

ステップ2の実装と見比べるとpushを毎回書いていたのもなくなり(隠蔽され)いい感じです。

ステップ2でのassembler.js対応部分
function assembleNode(node, insns) {
    switch (node.type) {
    case 'IDENTIFIER_REFERENCE':
        insns.push({command: 'PUSH', operand: node.identifier})
        insns.push({command: 'LOOKUP'});
        break;

実行時エラー

さてというわけで実行コードにまでソースコードの情報が渡せたのでそれを使う話です。
例えば指定された変数があるか探すlookupObjectですがこの関数に「この関数自体の処理に必要ではない」ソース情報は渡したくないわけです。

というわけで実行コードを取り出して一つずつ実行する(関数を呼び出す)箇所でcatchしてソース情報を付け加えることにしました。

vm.js抜粋
function vmMain(insns, stack, scopes) {
    let ptr = 0;
    while (ptr != insns.length) {
        const insn = insns[ptr++];
        try {
            executeInsn(insn, stack, scopes);
        } catch (e) {
            // add srcInfo to message
            e.message += `: ${insn.srcInfo}`;
            throw e;
        }
    }
}

例外捕まえてメッセージ書き換えていいのかというのは気になるのですが動くからOK(笑)
なお、動作確認直後に「よく思うとエラー(例外)の起きた行情報ってのはスタックトレースで出るよな」と気づいたのですが、スタックトレースはES仕様にはないのでともかく当面「ソースのどこでエラーが起きているかわかればよし(現場猫略)」としました。

エラーの区別

開発記その2にも書いていた「処理系エラー」と「動かしているプログラムのエラー」が区別できない問題、(遠い将来にセルフホスティングすることも見据えて)あまり奇抜な区別方法は導入したくなかったので開発記その2に書いた通りにエラーメッセージに[JOKE]と入れるだけにしました。

それだけだと芸がなさすぎるので例外をキャッチした側で「プログラムのエラー」と「処理系のエラー」を区別して出力するようにしました。

joke.js(ルートの方)抜粋
function printError(e) {
    if (e.message.startsWith('[JOKE]')) {
        // program's bug. show only message
        console.error(`${e.name}: ${e.message}`);
    } else {
        // JOKE's bug. show stack trace
        console.error(e);
    }
}

function runScript(sourceFile, data) {
    try {
        const joke = new JokeEngine(debug);
        joke.run(sourceFile, data);
    } catch (e) {
        printError(e);
    }
}

今後の予定

以上、ステップ3の演算機能、と各種開発を便利にするための機能について説明してきました。言語仕様以外で結構改造しましたがエンバグしていないかはテストがちゃんと通るかで確認しました。テスト書くの大事ですね。7

さてともかく演算機能ができたので次はようやく関数定義と呼び出しです。RubyやPythonのコード読んだ経験も含めて「こんな感じにやればいいかな」という考えはあるのですがそれ通りにいくかはまだわかりません。


  1. 前置が難しいわけではないですが私があまり使わないので実装する動機がないのです。 

  2. ステップ1でconsole.logのためにプロパティ参照を先行して実装したのでそちらも書いてはありますが多分ちゃんと動かないと思います。 

  3. と書いてて変数がレジスタに対応してない&RISCなCPUだと読み込み、演算、書き込みがいるなぁと思ったりもしますが。 

  4. オレオレVMなので命令セットにインクリメントを入れればいいだけですが、今回はこっちでやってみたという話になります。 

  5. node等のようにREPLを一度終了しても履歴がたどれるようにするのはこれまた実装するのがめんどくさいので。 

  6. *でインポートするのではなく一つずつ名前を書けばいいわけですがそうするとタイプを増やすごとに書き換える箇所が増えて「メンテナンス性が悪い」ことになってしまいます。 

  7. なおテストを書いていないところが変なことになっている可能性はある。 

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

[JavaScript]複数の子要素を削除する。

複数の要素を削除したい!

以下のように実行をクリックするとfizzbuzzが表示されるコードを書いた。
しかし、このままだと2回目以降の結果が前回の結果の下に表示される。
クリック毎に前回の結果を消去して、新しい結果を表示するようにしたい。

remove()を使おうと思ったが、どうやらremove()は一つの要素しか消去できない。
なので、他の方法を調べてみた。

結論から言うと、
子要素がなくなるまで一つずつループ処理で削除する!!
にたどり着きました。

HTML
<html> 
  <body> 
    <main>
      <form action="#" id="form">
        <input id='calNum' type='button' value='実行' >       
      </form><br>
      <div>[結果]</div>
      <p id='pFizzBuzz'></p>
    </main>

    <script>
      'use strict'

      const btn = document.getElementById('calNum');
      btn.addEventListener('click',startFizzBuzz,false);

      function startFizzBuzz(){
        for(let i = 1; i < 100; i++){
          if(i % 15 === 0){                
            const p = document.createElement('p');
            const pText = document.createTextNode('FizzBuzz' + i);
            pFizzBuzz.appendChild(p).appendChild(pText);
          }else if(i % 3 === 0){                
            const p = document.createElement('p');
            const pText = document.createTextNode('Buzz' + i);
            pFizzBuzz.appendChild(p).appendChild(pText);      
          }else if(i % 5 === 0){                
           const p = document.createElement('p');
           const pText = document.createTextNode('Fizz' + i);
           pFizzBuzz.appendChild(p).appendChild(pText);  
         }
        };
      };  
    </script> 
  </body> 
</html>

innerHTMLで要素の中身に空を代入する。

とても便利なinnerHTML
空を代入することで1行で指定した要素の子要素を全て消してくれます。

構文
const content = element.innerHTML;
element.innerHTML = htmlString;

この場合は

JacaScript
pFizzBuzz.innerHTML = '';

を関数startFizzBuzzのすぐ下に書くことで、作られた要素pを全て消した上で新しい結果が表示されます。

しかしinnerHTMLは外部からの入力を受けつけてしまうため、セキュリティー上よろしくないとか既存の要素を破壊するとか、ボリュームがあるときに処理が遅いとか言われているので、他の方法も探してみた。

removeChildで複数の子要素を消す。

removeChildというメソッドもある。

構文
let oldChild = node.removeChild(child);
または
node.removeChild(child);

解説
・childはDOMから取り除きたい子要素
・nodeはchildの親要素

しかしremoveChild()も要素を一つしか削除することができない。
そこで、firstChildと組み合わせて最後の子要素が削除されるまでループ処理をする。

firstChild()

構文
let childNode = node.firstChild;

解説
childNode:nodeの最初の子要素。なければnullを返す。

ということで、この二つを組み合わせて
親要素(pFizzBuzz)の最初の子要素(firstChild)がなくなるまで、removeChildで削除し続ける。

書いてみるとこんな感じ。

JavaScript
const parent = document.getElementById('pFizzBuzz');
 while(parent.firstChild){
   parent.removeChild(parent.firstChild);
 }

実際にコードに追加してみると。

HTML
<html> 
  <body> 
    <main>
      <form action="#" id="form">
        <input id='calNum' type='button' value='実行' >       
      </form><br>
      <div>[結果]</div>
      <p id='pFizzBuzz'></p>
    </main>

    <script>
      'use strict'

      const btn = document.getElementById('calNum');
      btn.addEventListener('click',startFizzBuzz,false);

      function startFizzBuzz(){
         //~~~~~~~~~~~~~~追加しました~~~~~~~~~~~~~~~~
        const parent = document.getElementById('pFizzBuzz');
          while(parent.firstChild){
          parent.removeChild(parent.firstChild);
          };
        //~~~~~~~~~~~~~~~追加しました~~~~~~~~~~~~~~~~
        for(let i = 1; i < 100; i++){
          if(i % 15 === 0){                
            const p = document.createElement('p');
            const pText = document.createTextNode('FizzBuzz' + i);
            pFizzBuzz.appendChild(p).appendChild(pText);
          }else if(i % 3 === 0){                
            const p = document.createElement('p');
            const pText = document.createTextNode('Buzz' + i);
            pFizzBuzz.appendChild(p).appendChild(pText);      
          }else if(i % 5 === 0){                
           const p = document.createElement('p');
           const pText = document.createTextNode('Fizz' + i);
           pFizzBuzz.appendChild(p).appendChild(pText);  
         }
        };
      };  
    </script> 
  </body> 
</html>

まとめ

複数の子要素を一度に削除したい場合
手取り早いのはinnerHTML
でもあまり推奨されていないようなので、その場合は以下の通り。

JavaScript
while(parent.firstChild){
 parent.removeChild(parent.firstChild)
}

まとめて削除が無理なら
一個ずつループ処理で消す!!

参考サイト

element.innerHTML |MDN
Node.removeChild |MDN
Node.firstChild |MDN

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

変数をコピーした時の値 vs 参照

まだまだ完璧に理解しているとは言えないのですが、
フォルダの例えですっきりしたのでシェアします。

値渡しと参照渡し

ひとつの変数から他の変数に中身をコピーしたい場合、
ざっくり言うとStringとObjectで挙動が変わってしまう。

  • string ... コピー元が変わらない
  • object ... コピー元も書きかわってしまう

Stringの場合

Stringはプリミティブ型。メソッドを持たないデータ型。

let name1 = 'tom';
let name2 = 'tom';
console.log(name1, name2): // tom tom

name1 = 'bob'; //ここでname1を書き換える
console.log(name1, name2): // bob tom

name2 = name1; //name1(今はbob)をname2つに入れる。name1のコピーを作ってname2という名前にする
console.log(name1, name2): // bob bob

stringは中にvalueしかないから、copyしたら中身もそのままコピーする

name2 = name1

name1のbobをcopyしてname2にpasteしている感じ

コピー元のname1は変わらない。

フォルダを複製してリネームしたと考えよう

フォルダーのコピペをしていると考えると自分的には理解しやすかった。
name1というフォルダを複製してname2という名前を付けた。
name2の中身を変えてもname1フォルダには影響は受けない。

Objectの場合

stringと違ってメソッドを持つデータ型ということ。

const person1 = {
  first: 'tarou'
  last: 'yamada'
};
const person2 = {
  first: 'hanako'
  last: 'satou'
};
const person3 = person1; // person1をコピーしてperson3としたい
person3.first = 'Yuko'; // person3のfirstだけYukoにしよう。姉妹だし
console.log(person3.first); // Yuko ふむふむ
console.log(person1.first); // Yuko !!!なんでや

const person3 = person1;
このあとperson3.first = 'Yuko';
person3だけを変えたつもりなのに、コピー元のperson1の内容も変わってしまう。

この場合はStringのときと違って、person1の中身をコピペしてるんじゃない。
参照を作ってるだけ。

フォルダのショートカット(エイリアス)を作ったと考えよう

ようはperson1のショートカットを作った。そのショートカットの名前をperson3に変えた。
person3の中身を修正したら、person3とperson1はリンクで繋がっているので
ショートカット元のperson1も変わってしまう。

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

Uncaught TypeError: Cannot read property 'hoge' of undefined

'〇〇' of Undefined

よく出くわすエラーなのですが、〇〇がundefinedで〇〇がおかしい!と私は早とちりしがちなので、
このエラーがどういう意味なのかということを残しておこうと思います。

マリオで見てみよう

こういうobjectがあるとします。

    const mario = {
      job: 'plumbing',
      relationship: {
        brother: 'Luige'
      }
    };

mario.hobbyだと、そんなプロパティはないので、undefinedになります。
image.png
ここまでは想像がつきますね。

cannot read property 'fire' of undefined

で、どんなふうに呼び出すとこのエラーがでるかというと、
mario.ability.fireとしたときです。
image.png

fire of undefinedなのでundefinedになっているfireではなく、
undefinedの中のfireというプロパティが読めないということですね。

marioの中のabilityというプロパティがなく、そこがundefinedになっているので、
undefinedの中のfireプロパティが読めないのです。

const mario = {
      job: 'plumbing',
      relationship: {
        brother: 'Luige'
      }
      undefined: {
        fire: ?
      }
    };

コードが複雑になってくると混乱してきますが、
こんなふうにシンプルに考えてみましょう。(と自分に言い聞かせている)

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

React-Grid-LayoutでサクッとDrag & Dropできるコンポーネントを作る

React-Grid-Layoutを使用して、Drag & Dropで移動でき、サイズの変更も可能なコンポーネントの実装を試してみます。
完成形は以下のようになります。

react-grid-test.gif

React-Grid-Layoutって?

React-Grid-Layout(以下RGL)はReact用のグリッドレイアウトです(まんま)。
上の画像のように、D&Dでコンポーネントの移動ができるようになります。
また、RGLのインストールにはreact-resizableというパッケージも含まれており、コンポーネントのサイズの変更も可能になります。

多分GitHubのREADME見れば実装するのも簡単だと思いますが、
React初めて一週間くらいなので、練習がてらまとめておきたいと思います。

GitHub

https://github.com/STRML/react-grid-layout

インストール

まずはパッケージをインストールします。

npm install react-grid-layout

インストール後、以下のCSSのインポートをどこかに追加しましょう。
今回はindex.jsでインポートしました。

import '../node_modules/react-grid-layout/css/styles.css';
import '../node_modules/react-grid-layout/css/styles.css';

実装

あとはコーディングしていくだけです。
今回は子コンポーネントを一つだけ用意して、propsだけ変えて何個か呼び出すような感じにしました。
先にコードを載せておきます。
React超初心者なので書き方間違ってるところとかあったらスミマセン。

(親)App.js
import React from 'react';
import GridLayout from 'react-grid-layout';
import SampleComponent from './SampleComponent';

class App extends React.Component {

  constructor(){
    super();
    this.state = {
      cols : 12,
      rowHeight : 30,
      width :window.parent.screen.width
    }

    this.layouts = [
      {i: 'a', x: 0, y: 0, w: 2, h: 4},
      {i: 'b', x: 2, y: 0, w: 4, h: 4},
      {i: 'c', x: 6, y: 0, w: 2, h: 8},
      {i: 'd', x: 0, y: 4, w: 5, h: 5},
      {i: 'e', x: 0, y: 9, w: 5, h: 2},
      {i: 'f', x: 6, y: 11, w: 2, h: 2},
    ];
  }

  render(){
      return (
      <GridLayout className="layout" layout={this.layouts} cols={this.state.cols} rowHeight={this.state.rowHeight} width={this.state.width}>
        <div key="a" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="A" /></div>
        <div key="b" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="B" /></div>
        <div key="c" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="C" /></div>
        <div key="d" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="D" /></div>
        <div key="e" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="E" /></div>
        <div key="f" style={{border:"solid", backgroundColor:"#EEEEEE", textAlign:"center"}}><SampleComponent componentName="F" /></div>
      </GridLayout>
    );
  }
}

export default App;
(子)SampleComponent.js
import React from 'react';
import './App.css';

class SampleComponent extends React.Component{

    render(){
        console.log(this.props);
        return (
            <div>
                <h3>Component {this.props.componentName}</h3>
            </div>
        );
    }

}

export default SampleComponent;

親のGridLayout要素で囲んだ部分がグリッドレイアウトになります。
layout属性にあらかじめ子コンポーネントの位置を指定しておくようにします。
これでなんとなく機能を試すことができます。

しかしこれ、ページ表示時やリフレッシュ時にコンポーネントを移動させようとすると、
全コンポーネントが一瞬だけ浮き上がったような挙動をするんですよね…
自分のやり方がどこか間違ってるのかなんなのか…

ちょっとここは後で調べておかないとですね。

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

JavaScriptHack Hackフォルダ オブジェクト志向編

JavaScript Hackとは(進捗率 40%) オブジェクト志向編

前提

弱いなりにサバイバルしていくためのもの。きちんと学ぶことも必要だが
英語のようにお客さんに単語を並べて乗り切られ、残念な結果は避けたい。

概要

なぜJavaScriptをやるか?
→ 今でも使ってるところは使っている。
→ 裏ニーズとしておしゃれなフレームワークを学ぶためにもやっておいたほうがいい。

コンテンツ

オブジェクト指向を学ぶ。
シラバスを作成する。

オブジェクト指向を学ぶ。

Progate JavaScript Ⅳ
https://prog-8.com/languages/es6

シラバスを作成する。

・オブジェクト
・クラス
・継承

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

JavaScriptHack Hackフォルダ 

JavaScript Hackとは

前提

弱いなりにサバイバルしていくためのもの。きちんと学ぶことも必要だが
英語のようにお客さんに単語を並べて乗り切られ、残念な結果は避けたい。
経験としてDomとFormを押さえればいけるのではないかという見込みで
Hackを行っていく。オブジェクト指向は今回はさわりだけしか取り上げないことにする。

概要

なぜJavaScriptをやるか?
→ 今でも使ってるところは使っている。
→ 裏ニーズとしておしゃれなフレームワークを学ぶためにもやっておいたほうがいい。

JavaScriptHack Hackフォルダ オブジェクト指向編

コンテンツ

オブジェクト指向を学ぶ。
シラバスを作成する。

オブジェクト指向を学ぶ。

Progate JavaScript Ⅳ
https://prog-8.com/languages/es6

シラバスを作成する。

・オブジェクト
・クラス
・継承

JavaScriptHack Hackフォルダ Dom編

コンテンツ

Domを学ぶ。
ゲームを作成する。
シラバスを作成する。

DOMを学ぶ。

詳解JavaScript DOM編 (ドットインストール) 有料会員

ゲームを作成する。

【旧版】JavaScriptでスロットマシンを作ろう (ドットインストール) 有料会員

シラバスを作成する。

querySelector()
getElementById()
querySelectorAll()
addEventListener()
className
classList.add()
classList.remove()
classList.contains()
classList.toggle()
createElement()
appendChild()

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

JavaScriptHack Hackフォルダ オブジェクト指向編/Dom編

JavaScript Hackとは

前提

弱いなりにサバイバルしていくためのもの。きちんと学ぶことも必要だが
英語のようにお客さんに単語を並べて乗り切られ、残念な結果は避けたい。
経験としてDomとFormを押さえればいけるのではないかという見込みで
Hackを行っていく。オブジェクト指向は今回はさわりだけしか取り上げないことにする。

概要

なぜJavaScriptをやるか?
→ 今でも使ってるところは使っている。
→ 裏ニーズとしておしゃれなフレームワークを学ぶためにもやっておいたほうがいい。

JavaScriptHack Hackフォルダ オブジェクト指向編

コンテンツ

オブジェクト指向を学ぶ。
シラバスを作成する。

オブジェクト指向を学ぶ。

Progate JavaScript Ⅳ
https://prog-8.com/languages/es6

シラバスを作成する。

・オブジェクト
・クラス
・継承

JavaScriptHack Hackフォルダ Dom編

コンテンツ

Domを学ぶ。
ゲームを作成する。
シラバスを作成する。

DOMを学ぶ。

詳解JavaScript DOM編 (ドットインストール) 有料会員
https://dotinstall.com/lessons/basic_javascript_dom_v2

ゲームを作成する。

JavaScriptでスロットマシンを作ろう (ドットインストール) 有料会員
https://dotinstall.com/lessons/slot_js_v6
上記が難しい人のために旧を紹介しておきます。
https://dotinstall.com/lessons/slot_js_v3 有料会員
https://dotinstall.com/lessons/slot_js 有料会員

シラバスを作成する。

querySelector()
getElementById()
querySelectorAll()
addEventListener()
className
classList.add()
classList.remove()
classList.contains()
classList.toggle()
createElement()
appendChild()

関連ページ
JavaScriptHack Hackフォルダ Form編
https://qiita.com/gina/items/3aa0174eda82532d68ea
Developerツールについて
https://developers.google.com/web/tools/chrome-devtools/javascript

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