20201011のJavaScriptに関する記事は29件です。

LINEBotを使って海外現地金額から日本円を返してみた

目的

仕事で海外とやりとりすることが多くなってきました。
海外に行ったときに現地通貨が日本円ではいくらなのかをすぐに調べることができるツールがあれば便利だと思って今回、自分で作ってみました。

実現方法

入力

入力についてはLINEを使用しました。

使用したAPI

Foreign exchange rates API
https://exchangeratesapi.io/

ロジック

LINEに入力する文字の形式は固定にしました。
 入力として受け付ける文字
  (国名):(現地金額)は日本でいくら?
 例
  中国:1000は日本でいくら?

LINEで入力した内容をもとにAPIを利用して日本円に換算しています。
1.LINEで入力した国名をもとに通貨名を取得します。
  連想配列でkey:国名、value:通貨名として紐づけを持たせています。
2.1.で取得した通貨名からレートを求め、日本円を取得します。
  今回使用したAPIについては1ユーロあたりのレートが返却される仕様となっていました。
  そのため、まず、入力した現地金額を何ユーロか求めて、1ユーロあたりの日本円をかけて
  求めています。

コード

'use strict';

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル(自分のPC)でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル(秘匿情報)です。
const config = {
    channelSecret: '作成したBotのチャネルシークレット',
    channelAccessToken: '作成したBotのチャネルアクセストークン'
};

// 国名と通貨の紐づけをしています。
const currency = new Array();
currency["カナダ"] = "CAD:ドル";
currency["香港"] = "HKD:ドル";
currency["アイスランド"] = "ISK:クローナ";
currency["フィリピン"] = "PHP:ペソ";
currency["デンマーク"] = "DKK:クローネ";
currency["ハンガリー"] = "HUF:フォリント";
currency["チェコ"] = "CZK:コルナ";
currency["オーストラリア"] = "AUD:ドル";
currency["ルーマニア"] = "RON:レウ";
currency["スウェーデン"] = "SEK:クローネ";
currency["インドネシア"] = "IDR:ルピア";
currency["インド"] = "INR:ルピー";
currency["ブラジル"] = "BRL:レアル";
currency["ロシア"] = "RUB:ルーブル";
currency["クロアチア"] = "HRK:クナ";
currency["日本"] = "JPY:円";
currency["タイ"] = "THB:バーツ";
currency["スイス"] = "CHF:フラン";
currency["シンガポール"] = "SGD:ドル";
currency["ポーランド"] = "PLN:ズウォティ";
currency["ブルガリア"] = "BGN:レフ";
currency["トルコ"] = "TRY:リラ";
currency["中国"] = "CNY:人民元";
currency["ノルウェー"] = "NOK:クローネ";
currency["ニュージーランド"] = "NZD:ドル";
currency["南アフリカ共和国"] = "ZAR:ランド";
currency["アメリカ"] = "USD:ドル";
currency["メキシコ"] = "MXN:ポンド";
currency["イスラエル"] = "ILS:新シケル";
currency["イギリス"] = "GBP:ポンド";
currency["韓国"] = "KRW:ウォン";
currency["マレーシア"] = "MYR:リンギット";


// ########################################
//  APIでデータを取得する部分
// ########################################

// APIを呼び出し日本円を求める
const rateFunction = async (event) => {
    const userText = event.message.text;
    // ユーザーメッセージが「日の出」か「日の入り」かどうか
    if (userText.indexOf('日本でいくら?') === -1) {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「(国名):(金額)は日本でいくら?」と話しかけてね'
        });
    } else {
        // 「リプライ」を使って先に返事しておきます
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '調べています……'
        });

        let pushText = '';
        try {
            const country_amt = userText.substring(0, userText.indexOf(''));
            // LINEに入力した国名
            const country = country_amt.substring(0, userText.indexOf(':'));
            // LINEに入力した金額
            const amt = country_amt.substring(userText.indexOf(':')+1, userText.length);
            // axiosでレート取得のAPIを叩きます(少し時間がかかる・ブロッキングする)
            const res = await axios.get('https://api.exchangeratesapi.io/latest');

            // LINEに入力した国名から通貨を取得する
            const country_tmp = currency[`${country}`];
            // 通貨(英語名)
            const country_currency = country_tmp.substring(0, country_tmp.indexOf(':'));
            // 通貨(日本語名)
            const country_currency_jpn = country_tmp.substring(country_tmp.indexOf(':')+1, country_tmp.length);

            // LINEに入力した国名の1ユーロあたりのレート
            const country_rates = res.data.rates[`${country_currency}`];
            // 1ユーロあたりの日本円
            const jpy_rates = res.data.rates.JPY;
            // LINEに入力した金額の日本円
            const jpy_yen = Math.round((amt / country_rates) * jpy_rates);

            pushText = `日本円で約${jpy_yen}円です!`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
    }
};


// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // 関数を実行します
    return rateFunction(event);
}


// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
    // Webhookの中身を確認用にターミナルに表示します
    console.log(req.body.events);

    // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
    if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
        res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
        console.log('検証イベントを受信しました!'); // ターミナルに表示します
        return; // これより下は実行されません
    }

    // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
    // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// (環境によってはローカルネットワーク内にも公開されます)
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

結果

LINEに
 中国:1000は日本でいくら?
と入力することで
 日本円で約15807円です!
と返信をもらうことができました。

image.png

考察

目的である現地通貨が日本円ではいくらなのかを調べるツールを作ることができました。
ただ、下記の課題があると思っているので、改善していければと思ってます。

1.国名と通貨名の紐づけを連想配列にしているが、もう少しスマートな書き方を検討する。
2.連想配列に存在しない国名がきたときの制御ができていない。
3.入力として受け付ける文字が(国名):(現地金額)は日本でいくら?と指定しづらい。
  下記のように対話形式で実現させる。
  国名を入力してね!
  →中国
  →何元?
  →1000
  →1000元は日本円で約15807円です!

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

k-means法とそれに関連したモデルのJavaScriptによる実装

はじめに

色々な機械学習処理をブラウザ上で試せるサイトを作った」中で実装したモデルの解説の一回目です。

k-means法に関連した以下のモデルの実装について解説します。

  • k-means
  • k-means++
  • k-medois
  • Neural Gas

デモはこちらから。(TaskをClusteringにして、ModelのK-MeansまたはNeural Gasを選択)
実際のコードはkmeans.js、Neural Gasはneural_gas.jsにあります。

数学的な話は分かりやすく説明する自信が無いため、ほとんど行いません。
また、可視化部分については一切触れません。

概説

ほとんどの処理は同一であり、モデルによって処理の変わる部分は
- 新規のセントロイドの追加(add
- セントロイドの位置の移動(move
の二つのみです。

なので、これらの処理をコンストラクタで注入するようにします。
デザインパターンでいうところの、Dependency Injectionです。

まずは注入される側の共通処理を実装するクラスを示し、次に、モデル別の処理を行うクラスを示します。

共通処理

KMeansModelで、全てのモデルで共通の処理を実装します。

コンストラクタでモデル別の処理を行うクラスのインスタンスを受け取ります。
fitで学習処理を、predictで推論処理を行います。共に、二次元配列を受け取ります。
学習処理fitはセントロイドの移動を一回だけ行うようにし、移動距離を返却することで終了判定ができるようにしています。

class KMeansModel {
    constructor(method = null) {
        this._centroids = [];
        this._method = method || new KMeans();
    }

    get centroids() {
        return this._centroids;
    }

    get size() {
        return this._centroids.length;
    }

    set method(m) {
        this._method = m;
    }

    _distance(a, b) {
        let v = 0
        for (let i = a.length - 1; i >= 0; i--) {
            v += (a[i] - b[i]) ** 2
        }
        return Math.sqrt(v)
    }

    add(datas) {
        const cpoint = this._method.add(this._centroids, datas);
        this._centroids.push(cpoint);
        return cpoint;
    }

    clear() {
        this._centroids = [];
    }

    predict(datas) {
        if (this._centroids.length === 0) {
            return;
        }
        return datas.map(value => {
            return argmin(this._centroids, v => this._distance(value, v));
        });
    }

    fit(datas) {
        if (this._centroids.length === 0 || datas.length === 0) {
            return 0;
        }
        const oldCentroids = this._centroids;
        this._centroids = this._method.move(this, this._centroids, datas);
        const d = oldCentroids.reduce((s, c, i) => s + this._distance(c, this._centroids[i]), 0);
        return d;
    }
}

method.addの引数には、現在のセントロイドの配列と、分類対象データの配列を渡します。
method.moveの引数には、自分自身と現在のセントロイドの配列、分類対象データの配列を渡します。なおセントロイドの配列は第一引数から取ることができるので、渡さない方がいいかもしれません。

以降は、コンストラクタの引数methodに渡されるインスタンスのクラスの解説です。

k-means

k-means法では、新規のセントロイドはランダムに選択します。ただし、完全に一致するデータを取ると困るので、既存のセントロイドに近すぎる場合は選択しなおしています。

データ数に対してセントロイドの数が上回る場合は無限ループに陥る可能性がありますが、その制御は呼び出し元で行っています。
また、全てのデータ同士の距離が小さすぎる場合にも無限ループに陥りますが、ここでは発生しないものとして無視しています。

class KMeans {
    add(centroids, datas) {
        centroids = centroids.map(c => new DataVector(c));
        while (true) {
            const p = new DataVector(datas[randint(0, datas.length - 1)]);
            if (Math.min.apply(null, centroids.map(c => p.distance(c))) > 1.0e-8) {
                return p.value;
            }
        }
    }

なお、DataVectorは配列に対してベクトル演算を実行できるようにするクラスで、distance関数によって他のDataVectorインスタンスとのユークリッド距離を計算しています。

セントロイドの移動先は、自身が最も近いデータ群の重心となります。
一度データの分類結果を取得(predict)して、各セントロイドに属するデータの重心を計算しています。

    _mean(d) {
        const n = d.length
        const t = d[0].length
        const m = Array(t).fill(0);
        for (let i = 0; i < n; i++) {
            for (let k = 0; k < t; k++) {
                m[k] += d[i][k]
            }
        }
        return m.map(v => v / n);
    }

    move(model, centroids, datas) {
        let pred = model.predict(datas);
        return centroids.map((c, k) => {
            let catpoints = datas.filter((v, i) => pred[i] === k);
            return this._mean(catpoints)
        });
    }
}

k-means++

k-means++法は、k-means法と比較して新規のセントロイドの選択方法が変わるだけなので、KMeansクラスを継承したクラスを作ります。

新規のセントロイドの選択は、各データと最近傍セントロイドとの距離によって確率的に決めます。
それら距離の累積値を累積分布関数と見立て、それに対する逆関数法を用いて選択しています。なお実装上、$[0,1]$に正規化していません。

export class KMeanspp extends KMeans {
    add(centroids, datas) {
        if (centroids.length == 0) {
            return datas[randint(0, datas.length - 1)]
        }
        centroids = centroids.map(c => new DataVector(c));
        const d = datas.map(d => new DataVector(d)).map(p => Math.min.apply(null, centroids.map(c => p.distance(c))) ** 2);
        const s = d.reduce((acc, v) => acc + v, 0);
        let r = Math.random() * s;
        for (var i = 0; i < d.length; i++) {
            if (r < d[i]) {
                return datas[i];
            }
            r -= d[i];
        }
    }
}

k-medois

k-medois法は結局はセントロイドの移動先が変わるだけなので、こちらもKMeansクラスを継承したクラスを作ります。
セントロイドの移動先は、そのクラスに属するデータの中で、他のデータとの距離の総和が最も小さいデータになります。

class KMedoids extends KMeans {
    move(model, centroids, datas) {
        let pred = model.predict(datas);
        return centroids.map((c, k) => {
            let catpoints = datas.filter((v, i) => pred[i] === k).map(v => new DataVector(v));
            if (catpoints.length > 0) {
                let i = argmin(catpoints, cp => {
                    return catpoints.map(cq => cq.distance(cp)).reduce((acc, d) => acc + d, 0);
                });
                return catpoints[i].value;
            } else {
                return c;
            }
        });
    }
}

なおargminは最小の値の位置を返す関数です。第一引数で配列を、第二引数で比較する値を返す関数を渡します。

Neural Gas

数学的にはSelf-organization mapやNeural Networkから解釈するようですが、ここでの実装はk-means法をベースに作成しました。
Wikipediaでの$w$がセントロイドに該当します。

新規のセントロイドの追加はKMeansと同様としていますが、セントロイドの移動先計算が大きく異なります。
やはりこれもKMeansクラスを継承したクラスとします。

class NeuralGas extends KMeans {
    // https://en.wikipedia.org/wiki/Neural_gas
    constructor() {
        this._l = 1;
        this._eps = 1;
        this._epoch = 0;
        this._sample_rate = 0.8;
    }

    move(model, centroids, datas) {
        const x = datas.filter(v => Math.random() < this._sample_rate).map(v => new DataVector(v));
        this._epoch++;
        const cvec = centroids.map(c => new DataVector(c));
        const distances = x.map(v => {
            let ds = cvec.map((c, i) => [i, v.distance(c)])
            ds.sort((a, b) => a[1] - b[1]);
            ds = ds.map((d, k) => [d[0], d[1], k])
            ds.sort((a, b) => a[0] - b[0]);
            return ds;
        })
        return cvec.map((c, n) => {
            const updates = distances.map((v, i) => x[i].sub(c).mult(this._eps * Math.exp(-v[n][2] / this._l)))
            const update = updates.slice(1).reduce((acc, v) => acc.add(v), updates[0]).div(updates.length);
            return c.add(update).value;
        });
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript ミリ秒を秒、分、時間、日にちに変換する方法

まず、定数nowにDate()オブジェクトをインスタンス化(初期化)したものを代入。
now.getTimeメソッドで求められるのは、1970年1月1日0時0分から現在までの時間をミリ秒(1/1000秒)で表した数字。

const now=new Date();
const time=now.getTime();

まずは、秒を求める。

const sec = Math.floor(time/1000)%60;

もし、now.getTime()で得られた数値が1655555555000(ミリ秒)だとしたら、
定数time/1000で秒数になる。
60(1分=60秒)で割った余りがまだ1分に満たない秒数つまり、秒となる。

次に、分を求める。

const min=Math.floor(time/1000/60)%60;

Math.floor(分数)を60分(1時間)で割った余り。つまり、まだ1時間に満たない分数。

次に、時間を求める。

const hours=Math.floor(time/1000/60/60)%24;

Math.floor(時間)を24時間で割った余り。つまり、まだ24時間(1日)に満たない時間。

const days=Math.floor(time/1000/60/60/24);

Math.floor(日数)。つまり、24時間で割った数。

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

JavaScriptの分からない部分まとめてみたら最強だった件

どうも、三町哲平です。

Ruby on RailsでWebアプリを開発中なのですが、どんなプログラミング言語やフレームワークを使っていてもJavaScriptが絡んできます。

正直な話、HTMLやCSSは分からない部分はその都度調べていけば、よっぽど手の込んだアプリケーションではない限りは素人でもそれなりのクオリティに仕上げれるという感覚があるのですが、JavaScriptが予想以上の難敵なんですよね。

しかも調べていくうちにどうも、フロントエンドだけではなくバックエンドでも使えるらしいじゃないですか...てことは、JavaScriptが最強なのでは...!?という疑問から色々とJavaScriptに調べてみましたので、少しばかりお付き合い下さい...。

では、どうぞ!

JavaScript

一番身近なのは、Webサイトを表示するブラウザ上で動くプログラム(クライアントサイド・スクリプト)です。無くてもWebサイトは見ることはできますが、文章や写真をそのまま読むだけで、いろいろな操作ができません。これだと、とても不便ですね。
そこで、JavaScriptというプログラミング言語を動かすことで、ブラウザ上で画像を拡大表示して見やすくしたり、入力フォームを設置してメッセージを送付できます。

引用元:JavaScript初心者でもすぐわかる!DOMとは何か?

DOM

JavaScriptでhtmlの要素を操作するための仕組みのことだ。
JavaScriptを扱っていく上で、絶対に知らないといけない仕組みのひとつだろう。
このページではDOMの仕組みと使い方について初心者の方でもわかるように解説した。

引用元:JavaScript初心者でもすぐわかる!DOMとは何か?

Ajax

「Asynchronous JavaScript + XML」の略
Asynchronousとは、非同時性の、非同期の
つまり、「JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと。」

引用元:初心者目線でAjaxの説明 - Qiita

ライブラリ

jQuery

JavaScriptでできることを、より簡単な記法で実現できように設計されたJavaScriptライブラリです。2006年にリリースされ、JavaScriptライブラリのデファクトスタンダードであると言われています。
jQueryのおかげで、プログラミングの初心者でも、フロントエンド開発に参加しやすくなりました。jQueryは初心者にやさしい、とても有用なJavaScriptライブラリです。

引用元:今さら聞けない!jQueryとは【初心者向け】 | TechAcademyマガジン

React.js

Reactとは一言で言うとUIを作るためのJavaScript用ライブラリです。Facebookが開発元でFacebook、Yahoo、ATOM、Airbnbなど、名だたる有名な企業で採用されています。

引用元:【Reactとは?】その特徴から将来性まで徹底解説! | Geekly Media

Riot.js

React.jsのようなJavascriptの軽量UIライブラリです。
カスタムタグにHTML、JS、CSSなどを記述して、それらを組み合わせてページを作成する事が出来ます。

引用元:Riot.jsの使い方について - Qiita

JavaScriptフレームワーク

Bootstrap

スマートフォンなどのモバイル端末にも対応し、レスポンシブデザインを採用した HTML, CSS, JavaScript フレームワークです。

引用元:とほほのBootstrap 4入門 - はじめに - とほほのWWW入門

Vue.js

クライアントサイドで使われるJavaScriptのフレームワークです。
クライアントサイドとは、Webサーバーにアクセスして得られた結果をブラウザで表示する部分のことです。
それに対し、サーバサイドの言語は、ブラウザに結果を渡すためにサーバ内の処理を行う部分のことです。
サーバサイドはユーザーからは見えません。
Vue.jsはクライアントサイド、つまり画面に表示される要素をリッチに表現するためのフレームワークです。
日本だと、LINEやnote、Rettyなど様々なサービスでVue.jsが使われています。

引用元:jQuery代わりになる?JavaScriptのフレームワーク!Vue.jsってどんなことができるの?

Angular

Googleによって開発されているJavaScriptフレームワークです。非常に人気があり、WEBアプリケーションの開発では、Reactなどと並び、最も使われているJavaScriptフレームワークの一つです。

引用元:【初心者必見】Angularとは?いまさら聞けない基礎を学ぼう | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

Backbone.js

JavaScriptの基本的な学習を一通り学んだあとで最初に触れるフレームワークとしては最適です。
特殊な概念やアーキテクチャーなどを新しく勉強する必要はなく、基本的な構文やオブジェクトの知識があれば誰でも手軽に利用できます。ただし、日本語の情報が少なく公式サイトを見てもどこから学習すればいいのか困る人も少なくありません。

引用元:JavaScript初心者でも理解!Backbone.jsの基本チュートリアル | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

サーバーサイドライブラリ

Node.js

サーバーサイドのJavaScript実行環境です。
Node.jsはJavaScriptを使用して処理しています。したがって、Node.jsを操作する上で使用する言語はJavaScriptです。
ただし、フロントエンドのJavaScriptではなく、サーバーサイドのJavaScriptです。

引用元:リアルタイム通信で活用!「Node.js」とは【初心者向け】 | TechAcademyマガジン

Webpack

Node.jsでサーバーサイドで動かすモジュールバンドラーツールになります。
Node.jsでモジュールという単語を聞くと、「npmやbowerとかと何が違うの?」みたいな印象を持たれる方もいるかもしれませんが、基本的にはそれらとはまた違った役割をもっています。
npmやbowerはJSライブラリのバージョン管理などの目的としてよく利用されますが、WebpackはJSファイルのコーディングの部分で開発者の手助けをしてくれるのです。

引用元:Webpackってどんなもの? - Qiita

Webpacker

Railsのgemの1つです。
webpackは色々な設定ができる分、学習コストがやや高いのが難点ですがwebpackerはwebpackの中身を良く知らなくても、良い感じにwebpackを動かしてくれる優れものです。
webpackerはWebアプリケーションで一般的に良く使われるメジャーな設定を、標準で実装してくれるwebpackのラッパーです。

引用元:webpack学習の基本のき |

Vanilla JS

単にJavaScriptのことです。ネイティブのJavaScript。
Pure JSと言ったりもします。
Vanilla JSは、JavaScriptだけでいろいろできるのにフレームワークありきで考える風潮を皮肉ったジョークフレームワークです。

引用元:Vanilla JSはジョークフレームワーク。普通のJSのこと。 - Qiita

JavaScriptパッケージマネージャー

npm

npmの正式名称は、Node Package Managerです。
npmの正式名称からも解るように、Node.jsのパッケージ(Package )を管理する(Manager)ツールです。

引用元:便利なパッケージ管理ツール!npmとは【初心者向け】 | TechAcademyマガジン

Yarn

yarn.png

  • JavaScriptのパッケージマネージャ
  • 2016年にFaceBookが公開した
  • npmと互換性がある = 同じpackage.jsonが使える

引用元:yarnとは - Qiita

Bower

bower.png

Twitter社が作ったフロントエンド用のパッケージマネージャで、必要なライブラリを簡単にインストールすることができます。

引用元:Bower(フロントエンド用パッケージマネージャー)の導入方法と使い方 | 株式会社LIG

gulp

Node.jsをベースとしたタスクランナーの一つです。
タスクランナーとはWebサイト構築に必要な処理をタスクとして自動化してくれるプログラムで、作業の効率化に使われています。
引用元:【たった5分で完了】「gulp」の導入方法と使用例を紹介します。

GRUNT

grunt.png

Node.js上で動作するオープンソースのタスクランナーです。主にWeb開発に関わるタスクを自動化するために使われています。それぞれのタスクはプラグインとして開発されており、「ファイルの圧縮/minify(ミニファイ)」「コンパイル」「画像圧縮」などを自動化できます。設定ファイルを記述することで、どのようなプロジェクトにも柔軟に対応可能です。
引用元:GruntでWeb開発を便利に!タスクランナーで手作業を自動化する | tracpath:Works

Popper.js

マウスを載せた際などに表示されるポップオーバー。 ユーザの好きなタイミングで(多くは困った時に)表示してくれるので便利なツールです。
Popper.jsはJavaScript製のオープンソース・ソフトウェア(MIT License)です。

引用元:Popper.js - 便利なポップオーバーライブラリ MOONGIFT

Alternative JavaScript

AltJS とは、JavaScript の代わりとなる 言語の総称です。
その言語で書いたものを JavaScript に変換して使用します。
なぜ、そんな面倒くさいことをするのか、JavaScript がある定常以上の規模となると、下記の理由から実装・保守の効率が非常に悪くなります。
* 型の定義がないので、意図しない値が入ることがある。
* null safety でないので、意図しない null や undefined が入ることがある。
* オブジェクト指向言語だが、インターフェースやクラス定義がなく、プロパティ名を間違っていても実行時までエラーにならず、エラーになっても原因の解析に時間がかかることが多い。
* 型やインターフェース、クラス定義がないので、エディタによる入力補完があまり受けられない。

引用元:TypeScript の概要 - Qiita

TypeScript

マイクロソフトが開発したオープンソース言語で、一言で言うと、「型定義できるJavaScript」。
他の Alt JavaScript と比べて後発ながら人気が高く、Google の6番目の社内標準言語としても採用されました。
AltJS としては、Coffee Script が牽引してきましたが、最近では TypeScript に取って代わられた感じがあります。

引用元:TypeScript の概要 - Qiita

CoffeeScript

→JavaScriptのコードを生成するためのコンパクトなrubyライクなスクリプト言語
→安全で、高性能なJavaScriptのコードを自動的に作成できる!
→Javascriptと比べて2分の1から3分の1の量で記述可
→自動でコンパイルする手段は幾つもあるので手間は増えない
→jQueyとの組み合わせ可能

引用元:CoffeeScriptとはなんぞや? - Qiita

Opal

OpalはRubyのコードをJavaScriptに変換するコンパイラです。Opalを使うと、Rubyのプログラムをブラウザ上で動かすことができます。

引用元:Opalとは(2018年版) - Qiita

さいごに

JavaScriptの歴史を学ぼう

上記みたいなJavaScriptに関連した用語の概要を掴んだ上で、下記リンクのページで歴史を見てみるととても読みやすい記事なので、時間がある方はぜひ!

Ruby on Railで開発したい方

Railsを使う上で、Yarn、jQuery、Bootstrapなどの利用方法の基礎的な部分が分かりやすく書かれています。オススメです。

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

【JavaScript】分からない部分まとめてみたら最強だった件

どうも、三町哲平です。

Ruby on RailsでWebアプリを開発中なのですが、どんなプログラミング言語やフレームワークを使っていてもJavaScriptが絡んできます。

正直な話、HTMLやCSSは分からない部分はその都度調べていけば、よっぽど手の込んだアプリケーションではない限りは素人でもそれなりのクオリティに仕上げれるという感覚があるのですが、JavaScriptが予想以上の難敵なんですよね。

しかも調べていくうちにどうも、フロントエンドだけではなくバックエンドでも使えるらしいじゃないですか...てことは、JavaScriptが最強なのでは...!?という疑問から色々とJavaScriptに調べてみましたので、少しばかりお付き合い下さい...。

では、どうぞ!

JavaScript

一番身近なのは、Webサイトを表示するブラウザ上で動くプログラム(クライアントサイド・スクリプト)です。無くてもWebサイトは見ることはできますが、文章や写真をそのまま読むだけで、いろいろな操作ができません。これだと、とても不便ですね。
そこで、JavaScriptというプログラミング言語を動かすことで、ブラウザ上で画像を拡大表示して見やすくしたり、入力フォームを設置してメッセージを送付できます。

引用元:JavaScript初心者でもすぐわかる!DOMとは何か?

DOM

JavaScriptでhtmlの要素を操作するための仕組みのことだ。
JavaScriptを扱っていく上で、絶対に知らないといけない仕組みのひとつだろう。
このページではDOMの仕組みと使い方について初心者の方でもわかるように解説した。

引用元:JavaScript初心者でもすぐわかる!DOMとは何か?

Ajax

「Asynchronous JavaScript + XML」の略
Asynchronousとは、非同時性の、非同期の
つまり、「JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと。」

引用元:初心者目線でAjaxの説明 - Qiita

ライブラリ

jQuery

JavaScriptでできることを、より簡単な記法で実現できように設計されたJavaScriptライブラリです。2006年にリリースされ、JavaScriptライブラリのデファクトスタンダードであると言われています。
jQueryのおかげで、プログラミングの初心者でも、フロントエンド開発に参加しやすくなりました。jQueryは初心者にやさしい、とても有用なJavaScriptライブラリです。

引用元:今さら聞けない!jQueryとは【初心者向け】 | TechAcademyマガジン

React.js

Reactとは一言で言うとUIを作るためのJavaScript用ライブラリです。Facebookが開発元でFacebook、Yahoo、ATOM、Airbnbなど、名だたる有名な企業で採用されています。

引用元:【Reactとは?】その特徴から将来性まで徹底解説! | Geekly Media

Riot.js

React.jsのようなJavascriptの軽量UIライブラリです。
カスタムタグにHTML、JS、CSSなどを記述して、それらを組み合わせてページを作成する事が出来ます。

引用元:Riot.jsの使い方について - Qiita

JavaScriptフレームワーク

Bootstrap

スマートフォンなどのモバイル端末にも対応し、レスポンシブデザインを採用した HTML, CSS, JavaScript フレームワークです。

引用元:とほほのBootstrap 4入門 - はじめに - とほほのWWW入門

Vue.js

クライアントサイドで使われるJavaScriptのフレームワークです。
クライアントサイドとは、Webサーバーにアクセスして得られた結果をブラウザで表示する部分のことです。
それに対し、サーバサイドの言語は、ブラウザに結果を渡すためにサーバ内の処理を行う部分のことです。
サーバサイドはユーザーからは見えません。
Vue.jsはクライアントサイド、つまり画面に表示される要素をリッチに表現するためのフレームワークです。
日本だと、LINEやnote、Rettyなど様々なサービスでVue.jsが使われています。

引用元:jQuery代わりになる?JavaScriptのフレームワーク!Vue.jsってどんなことができるの?

Angular

Googleによって開発されているJavaScriptフレームワークです。非常に人気があり、WEBアプリケーションの開発では、Reactなどと並び、最も使われているJavaScriptフレームワークの一つです。

引用元:【初心者必見】Angularとは?いまさら聞けない基礎を学ぼう | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

Backbone.js

JavaScriptの基本的な学習を一通り学んだあとで最初に触れるフレームワークとしては最適です。
特殊な概念やアーキテクチャーなどを新しく勉強する必要はなく、基本的な構文やオブジェクトの知識があれば誰でも手軽に利用できます。ただし、日本語の情報が少なく公式サイトを見てもどこから学習すればいいのか困る人も少なくありません。

引用元:JavaScript初心者でも理解!Backbone.jsの基本チュートリアル | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

サーバーサイドライブラリ

Node.js

サーバーサイドのJavaScript実行環境です。
Node.jsはJavaScriptを使用して処理しています。したがって、Node.jsを操作する上で使用する言語はJavaScriptです。
ただし、フロントエンドのJavaScriptではなく、サーバーサイドのJavaScriptです。

引用元:リアルタイム通信で活用!「Node.js」とは【初心者向け】 | TechAcademyマガジン

Webpack

Node.jsでサーバーサイドで動かすモジュールバンドラーツールになります。
Node.jsでモジュールという単語を聞くと、「npmやbowerとかと何が違うの?」みたいな印象を持たれる方もいるかもしれませんが、基本的にはそれらとはまた違った役割をもっています。
npmやbowerはJSライブラリのバージョン管理などの目的としてよく利用されますが、WebpackはJSファイルのコーディングの部分で開発者の手助けをしてくれるのです。

引用元:Webpackってどんなもの? - Qiita

Webpacker

Railsのgemの1つです。
webpackは色々な設定ができる分、学習コストがやや高いのが難点ですがwebpackerはwebpackの中身を良く知らなくても、良い感じにwebpackを動かしてくれる優れものです。
webpackerはWebアプリケーションで一般的に良く使われるメジャーな設定を、標準で実装してくれるwebpackのラッパーです。

引用元:webpack学習の基本のき |

Vanilla JS

単にJavaScriptのことです。ネイティブのJavaScript。
Pure JSと言ったりもします。
Vanilla JSは、JavaScriptだけでいろいろできるのにフレームワークありきで考える風潮を皮肉ったジョークフレームワークです。

引用元:Vanilla JSはジョークフレームワーク。普通のJSのこと。 - Qiita

JavaScriptパッケージマネージャー

npm

npmの正式名称は、Node Package Managerです。
npmの正式名称からも解るように、Node.jsのパッケージ(Package )を管理する(Manager)ツールです。

引用元:便利なパッケージ管理ツール!npmとは【初心者向け】 | TechAcademyマガジン

Yarn

yarn.png

  • JavaScriptのパッケージマネージャ
  • 2016年にFaceBookが公開した
  • npmと互換性がある = 同じpackage.jsonが使える

引用元:yarnとは - Qiita

Bower

bower.png

Twitter社が作ったフロントエンド用のパッケージマネージャで、必要なライブラリを簡単にインストールすることができます。

引用元:Bower(フロントエンド用パッケージマネージャー)の導入方法と使い方 | 株式会社LIG

gulp

Node.jsをベースとしたタスクランナーの一つです。
タスクランナーとはWebサイト構築に必要な処理をタスクとして自動化してくれるプログラムで、作業の効率化に使われています。
引用元:【たった5分で完了】「gulp」の導入方法と使用例を紹介します。

GRUNT

grunt.png

Node.js上で動作するオープンソースのタスクランナーです。主にWeb開発に関わるタスクを自動化するために使われています。それぞれのタスクはプラグインとして開発されており、「ファイルの圧縮/minify(ミニファイ)」「コンパイル」「画像圧縮」などを自動化できます。設定ファイルを記述することで、どのようなプロジェクトにも柔軟に対応可能です。
引用元:GruntでWeb開発を便利に!タスクランナーで手作業を自動化する | tracpath:Works

Popper.js

マウスを載せた際などに表示されるポップオーバー。 ユーザの好きなタイミングで(多くは困った時に)表示してくれるので便利なツールです。
Popper.jsはJavaScript製のオープンソース・ソフトウェア(MIT License)です。

引用元:Popper.js - 便利なポップオーバーライブラリ MOONGIFT

Alternative JavaScript

AltJS とは、JavaScript の代わりとなる 言語の総称です。
その言語で書いたものを JavaScript に変換して使用します。
なぜ、そんな面倒くさいことをするのか、JavaScript がある定常以上の規模となると、下記の理由から実装・保守の効率が非常に悪くなります。
* 型の定義がないので、意図しない値が入ることがある。
* null safety でないので、意図しない null や undefined が入ることがある。
* オブジェクト指向言語だが、インターフェースやクラス定義がなく、プロパティ名を間違っていても実行時までエラーにならず、エラーになっても原因の解析に時間がかかることが多い。
* 型やインターフェース、クラス定義がないので、エディタによる入力補完があまり受けられない。

引用元:TypeScript の概要 - Qiita

TypeScript

マイクロソフトが開発したオープンソース言語で、一言で言うと、「型定義できるJavaScript」。
他の Alt JavaScript と比べて後発ながら人気が高く、Google の6番目の社内標準言語としても採用されました。
AltJS としては、Coffee Script が牽引してきましたが、最近では TypeScript に取って代わられた感じがあります。

引用元:TypeScript の概要 - Qiita

CoffeeScript

→JavaScriptのコードを生成するためのコンパクトなrubyライクなスクリプト言語
→安全で、高性能なJavaScriptのコードを自動的に作成できる!
→Javascriptと比べて2分の1から3分の1の量で記述可
→自動でコンパイルする手段は幾つもあるので手間は増えない
→jQueyとの組み合わせ可能

引用元:CoffeeScriptとはなんぞや? - Qiita

Opal

OpalはRubyのコードをJavaScriptに変換するコンパイラです。Opalを使うと、Rubyのプログラムをブラウザ上で動かすことができます。

引用元:Opalとは(2018年版) - Qiita

さいごに

JavaScriptの歴史を学ぼう

上記みたいなJavaScriptに関連した用語の概要を掴んだ上で、下記リンクのページで歴史を見てみるととても読みやすい記事なので、時間がある方はぜひ!

Ruby on Railで開発したい方

Railsを使う上で、Yarn、jQuery、Bootstrapなどの利用方法の基礎的な部分が分かりやすく書かれています。オススメです。

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

Reactの関数コンポーネントでMapbox GL JSを表示するデモ

はじめに

Vueが好きです(告白)
でもReactの方が流行ってるらしいのでキャッチアップしておかなきゃならんという事でcreate-react-appしてみました
とりあえずMapbox GL JSを表示したかったのですが、関数コンポーネントでちゃんと動く例がネット探してもなかったので備忘録兼ねて記事化しておきます

関数コンポーネント?

今まで使ってこなかったのであんまりよくわかってないですがクラスより関数の方が良いらしいです(関数型がトレンドという事でしょう)
ネット上で見つかるReact+Mapbox GL JSの例はほとんどクラスコンポーネントだったので、じゃあよりよいという関数コンポーネントでやってみようとしましたが、hooksがどうとか謎用語連発でちょっと困りました

https://sparkgeo.com/blog/build-a-react-mapboxgl-component-with-hooks/
参考サイト(でもこれをコピペしても動きません)

コード

App.tsx
import React, { CSSProperties, useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

const styles: CSSProperties = {
    width: '100vw',
    height: 'calc(100vh - 80px)',
    position: 'absolute',
};

const App = () => {
    const [map, setMap] = useState(null);
    const mapContainer = useRef(null);

    useEffect(() => {
        const initializeMap = ({
            setMap,
            mapContainer,
        }: {
            setMap: any;
            mapContainer: any;
        }) => {
            const map = new mapboxgl.Map({
                container: mapContainer.current,
                style: 'YOUR_MAPBOX_STYLE_URL',
                center: [140, 44.0],
                zoom: 5,
            });

            map.on('load', () => {
                setMap(map);
                map.resize();
            });
        };

        if (!map) initializeMap({ setMap, mapContainer });
    }, [map]);

    return <div ref={mapContainer} style={styles} />;
};

export default App;

メモ

リファレンス読んでないしほぼ語れる事がないのでほとんど推測

  • useState()によりコンポーネント内の変数を1箇所で管理する(っぽい、たぶんgetterとsetterを提供してる)
  • useRef()でクラス内のhtml的参照を変数で管理する(っぽい)
  • CSSProperties、オブジェクト的にCSS書けるの強そう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『りあクト!Firebaseで始めるサーバーレスReact開発』を読んで、為になったことまとめ 1-1基本環境を作る

はじめに

りあクト!Firebaseで始めるサーバーレスReact開発を読んで、個人的に為になったTIPSを学習記録としてまとめます。

Typescriptの設定でコンパイル高速化

Typescriptの設定ファイルtsconfig.jsonにincrementalを有効にするオプションを記述すると再コンパイルの時間を短縮することができ、ホットリロードにかかる時間も短くなる

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "baseUrl": "src",
    "incremental": true //←追記
  },
  "include": [
    "src"
  ],
  "exclude": ["node_modules","build","scripts","functions"]
}

TypeScriptの型定義ファイル自動インストール

パッケージのtypesyncを使用すればpackage.jsonの中身を調べて、必要な型ファイルがなければ自動でdevDevpendenciesに追加してくれる。

$ npm install -g typesync
$ typesync

コミット時に自動でLintチェック

huskylint-stagedというパッケージを使うことで、コミット実行前にLintチェックすることができる。
huskyはgitに用意されているhooks機能と同様のタイミングに処理を登録することができ、コミット前だけでなくpush実行前など、ここに定義されているほとんどのhooksを使用できる。
lint-stagedはgitでステージングされたファイルのみにLintチェックすることができる。

設定はpackage.jsonに記述する。

  "scripts": {
    
    
    "precommit": "lint-staged"
  }
  
  
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "git add"
    ],
    "src/**/*.{css,jsx,tsx}": [
      "stylelint --fix",
      "git add"
    ],
    "functions/src/**/*.{js,ts}": [
      "cd functions/ && eslint --fix",
      "git add"
    ]
  },

参考

りあクト!Firebaseで始めるサーバーレスReact開発(https://booth.pm/ja/items/1572683)
https://typescript-jp.gitbook.io/deep-dive/intro-2/husky
https://qiita.com/khlizard/items/dfe1ec9d82c0ed5da7c6
https://github.com/typicode/husky/tree/master
https://kic-yuuki.hatenablog.com/entry/2019/05/27/090515
https://github.com/okonet/lint-staged
https://kray.jp/blog/expound-git-add/

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

【Vue.js】Vue.jsのdataと、forEachを使う時の注意点

Vue.jsで使うthis.dataと、forEach文を活用するときは、注意が必要という記事になります。

そもそもforEach文とは

// 配列を繰り返す
offices.forEach(function(office){
  console.log(office)
})

// 繰り返す配列を、thisで取得する
offices.forEach(function(office){
  console.log(this)
}, offices)

第一引数にはコールバック関数を、第二引数には繰り返す配列を定義することで、thisとして取得できる。

結論:forEachのthisと、Vue.jsのthisは別物

forEach文内でのthisは、繰り返す配列のことを指してしまうので、直接格納することができない

// エラーになる
offices.forEach(function(office){
  this.array.push(office)
})

配列の要素を、this.dataの配列に格納したい

以下のような、Vueインスタンスのデータがあるとする。

data () {
  return {
    holidays: [],
  }
}

そして、以下のレスポンスデータを取得して、Vueインスタンスのデータに格納したい。

[
    {
        "id": 1,
        "name": "owada"
        "regular_holidays": [
            {
                "id": 1,
                "holiday": "mon"
            },
            {
                "id": 2,
                "holiday": "tue"
            },
            ・・・省略
        ],
    },
    ・・・省略
]

配列の中のオブジェクトの中の配列の要素を、格納したい時

つまり、"regular_holidays"の要素群をthis.dataに格納する

this.holidays = res.data.regular_holidays.map((day) => day.holiday);

配列の中のオブジェクトの中の配列の中のオブジェクトの値を、格納したい時

つまり、"regular_holidays"の要素のオブジェクトキーである"holiday"this.dataに格納する

const holidays = [];

this.offices.forEach(function (office) {
  office.regular_holidays.forEach(function (day) {
    holidays.push(day.holiday);
  });
});

this.holidays = holidays;

forEach内で、this.dataを取得できないので、うまく工夫する必要がある。

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

【Vue.js】Vue.jsで、forEachを使う時の注意点

Vue.jsで使うthis.dataと、forEach文を活用するときは、注意が必要という記事になります。

そもそもforEach文とは

// 配列を繰り返す
offices.forEach(function(office){
  console.log(office)
})

// 繰り返す配列を、thisで取得する
offices.forEach(function(office){
  console.log(this)
}, offices)

第一引数にはコールバック関数を、第二引数には繰り返す配列を定義することで、thisとして取得できる。

結論:forEachのthisと、Vue.jsのthisは別物

forEach文内でのthisは、繰り返す配列のことを指してしまうので、直接格納することができない

// エラーになる
offices.forEach(function(office){
  this.array.push(office)
})

配列の要素を、this.dataの配列に格納したい

以下のような、Vueインスタンスのデータがあるとする。

data () {
  return {
    holidays: [],
  }
}

そして、以下のレスポンスデータを取得して、Vueインスタンスのデータに格納したい。

[
    {
        "id": 1,
        "name": "owada"
        "regular_holidays": [
            {
                "id": 1,
                "holiday": "mon"
            },
            {
                "id": 2,
                "holiday": "tue"
            },
            ・・・省略
        ],
    },
    ・・・省略
]

配列の中のオブジェクトの中の配列の要素を、格納したい時

つまり、"regular_holidays"の要素群をthis.dataに格納する

this.holidays = res.data.regular_holidays.map((day) => day.holiday);

配列の中のオブジェクトの中の配列の中のオブジェクトの値を、格納したい時

つまり、"regular_holidays"の要素のオブジェクトキーである"holiday"this.dataに格納する

const holidays = [];

this.offices.forEach(function (office) {
  office.regular_holidays.forEach(function (day) {
    holidays.push(day.holiday);
  });
});

this.holidays = holidays;

forEach内で、this.dataを取得できないので、うまく工夫する必要がある。

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

【IE限定】COMからJavaScriptを呼び出す

前記事の続きです
COM<-->JavaScript のやりとりができれば、いろいろなことができそうです。

前提

【IE限定】JavaScriptからCOMを呼び出すの続きです。

この記事は、上の記事を読んでいることを前提に書いています。
上の記事を読んでいない人は、この記事を読む前に、上の記事をお読みください。
(でないと、この記事で書いていることがサッパリわからないかもしれません。。)

実装

COM側の実装

IDLファイルを記述

IDLファイルには、以下を追加します。

  • JavaScript関数を設定するプロパティ
  • JavaScript関数を呼び出すメソッド
IDLファイル
// ATLProject1.idl : ATLProject1 の IDL ソース
//

// このファイルは、タイプ ライブラリ ([!output SAFE_IDL_NAME].tlb) およびマーシャリング コードを
// タイプ ライブラリ (ATLProject1.tlb) とマーシャリング コードを生成します。

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(d0b8ec50-7953-4607-86c6-6f4b2499db6f),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IATLSimpleObject : IDispatch
{
    // ★★★★★★★★★★ ここから ★★★★★★★★★★
    [propput, id(1)] HRESULT JsFunc([in] VARIANT newVal);
    [id(2)] HRESULT CallJsFunc();
    // ★★★★★★★★★★ ここまで ★★★★★★★★★★
};
[
    uuid(8cb35385-6c0f-4d8c-aef3-864ff7ec2143),
    version(1.0),
]
library ATLProject1Lib
{
    importlib("stdole2.tlb");
    [
        uuid(da1a207a-5427-49b2-b2fb-08b9f5fef902)
    ]
    coclass ATLSimpleObject
    {
        [default] interface IATLSimpleObject;
    };
};

import "shobjidl.idl";

ヘッダーファイルを記述

ヘッダーファイルにもプロパティ、メソッドを追加します。

ヘッダーファイル
// ATLSimpleObject.h : CATLSimpleObject の宣言

#pragma once
#include "resource.h"       // メイン シンボル



#include "ATLProject1_i.h"



#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "DCOM の完全サポートを含んでいない Windows Mobile プラットフォームのような Windows CE プラットフォームでは、単一スレッド COM オブジェクトは正しくサポートされていません。ATL が単一スレッド COM オブジェクトの作成をサポートすること、およびその単一スレッド COM オブジェクトの実装の使用を許可することを強制するには、_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA を定義してください。ご使用の rgs ファイルのスレッド モデルは 'Free' に設定されており、DCOM Windows CE 以外のプラットフォームでサポートされる唯一のスレッド モデルと設定されていました。"
#endif

using namespace ATL;


// CATLSimpleObject

class ATL_NO_VTABLE CATLSimpleObject :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CATLSimpleObject, &CLSID_ATLSimpleObject>,
    public IDispatchImpl<IATLSimpleObject, &IID_IATLSimpleObject, &LIBID_ATLProject1Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
    CATLSimpleObject()
    {
    }

DECLARE_REGISTRY_RESOURCEID(106)


BEGIN_COM_MAP(CATLSimpleObject)
    COM_INTERFACE_ENTRY(IATLSimpleObject)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()



    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }
// ★★★★★★★★★★ ここから ★★★★★★★★★★
private:
    CComPtr<IDispatch> mJsFunc;

public:

    STDMETHOD(put_JsFunc)(VARIANT newVal);
    STDMETHOD(CallJsFunc)();
};
// ★★★★★★★★★★ ここまで ★★★★★★★★★★

OBJECT_ENTRY_AUTO(__uuidof(ATLSimpleObject), CATLSimpleObject)

実装ファイルを記述

実装ファイルにもプロパティ、メソッドを追加します。

実装ファイル
// ATLSimpleObject.cpp : CATLSimpleObject の実装

#include "pch.h"
#include "ATLSimpleObject.h"


// CATLSimpleObject


// ★★★★★★★★★★ ここから ★★★★★★★★★★
STDMETHODIMP CATLSimpleObject::CallJsFunc()
{
    if (mJsFunc)
    {
        DISPPARAMS params = { 0, 0, 0, 0 };
        mJsFunc->Invoke(0,
            IID_NULL,
            LOCALE_USER_DEFAULT,
            DISPATCH_METHOD,
            &params, NULL, NULL, NULL);
    }
    return S_OK;
}

STDMETHODIMP CATLSimpleObject::put_JsFunc(VARIANT newVal)
{
    if (newVal.vt == VT_DISPATCH)
    {
        mJsFunc = newVal.pdispVal;
    }
    return S_OK;
}
// ★★★★★★★★★★ ここまで ★★★★★★★★★★

JavaScript側の実装

JavaScript側の実装です。
COM側にJavaScript関数を設定し、呼び出します。

HTMLファイル
<!doctype html>

<html>

<head>
  <meta charset="utf-8">
  <title>ActiveX</title>
</head>
<body>
  <object id="activeXObj" classid="clsid:da1a207a-5427-49b2-b2fb-08b9f5fef902"></object>
  <script>
    var activeXObj = document.getElementById('activeXObj');
    activeXObj.JsFunc = jsFunc;
    activeXObj.CallJsFunc();

    function jsFunc() {
      alert('Call js func');
    }
  </script>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】ハンバーガーメニュー作成(メニュー外のクリック動作)

目標

Xボタンだけでなく、右側の表示領域をクリックしても、
ハンバーガーメニューが閉じるようにします。
ハンバーガーメニュー.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

【Javascript】ハンバーガーメニュー作成(左、上、右から表示)
今回は上記記事の補足になります。

実際のコード

html
<div id="box"></div> <!-- 追加 -->
<nav id="nav">
  <ul>
    <li><a href="#">リンク1</a></li>
    <li><a href="#">リンク2</a></li>
    <li><a href="#">リンク3</a></li>
  </ul>
</nav>
<div id="hamburger">
  <span class="inner_line" id="line1"></span>
  <span class="inner_line" id="line2"></span>
  <span class="inner_line" id="line3"></span>
</div>

<style>
body{
   background-color: rgba(0,0,0,0.2);
 }

#nav{
  position: absolute;
  height: 100vh;
  width: 40%;
  left: -40%;
  top: 0;
  background: #ffffff;
  transition: .7s;
}
#nav ul{
  padding-top: 80px;
}
#nav ul li{
  list-style-type: none;
}
#hamburger {
  display: none;
  position: absolute;
  top: 20px;
  left: 30px;
  width: 50px;
  height: 44px;
  transition: 1s;
}
.inner_line {
  display: block;
  position: absolute;
  left: 0;
  width: 50px;
  height: 3px;
  background-color: #000000;
  transition: 1s;
  border-radius: 4px;
}
#line1 {
  top: 0;
}
#line2 {
  top: 20px;
}
#line3 {
  bottom: 0px;
}

.in{
  transform: translateX(100%);
}
.line_1,.line_2,.line_3{
  background: #000000;
}
.line_1 {
  transform: translateY(20px) rotate(-45deg);
  top: 0;
}
.line_2 {
  opacity: 0;
}
.line_3 {
  transform: translateY(-20px) rotate(45deg);
  bottom: 0;
}

/* ここから*/

#box{
  position: absolute;
  height: 100vh;
  width: 100%;
  left: -100%;
  top: 0;
  background: rgba(0,0,0,0.8);
}
.back{
  transform: translateX(100%);
}

/* ここまで追加*/


@media (max-width: 1200px) {
  #hamburger {
    display: block;
  }
}
</style>

<script>
  function hamburger(){
    document.getElementById('line1').classList.toggle('line_1');
    document.getElementById('line2').classList.toggle('line_2');
    document.getElementById('line3').classList.toggle('line_3');
    document.getElementById('nav').classList.toggle('in');
    document.getElementById('box').classList.toggle('back'); // 追加
  };
  document.getElementById('hamburger').addEventListener('click',function(){
    hamburger();
  });

  // ここから
  document.getElementById('box').addEventListener('click',function(){
    hamburger();
  });
// ここまで追加
</script>

追加項目

追加箇所-html
<div id="box"></div>
追加箇所-css
#box{
  position: absolute;
  height: 100vh;
  width: 100%;
  left: -100%;
  top: 0;
  background: rgba(0,0,0,0.8);
}
.back{
  transform: translateX(100%);
}
追加箇所-js
document.getElementById('box').classList.toggle('back');

document.getElementById('box').addEventListener('click',function(){
  hamburger();
});

まとめ

方法としては前提記事同様、画面外に表示させておき、
classList.toggle('back')でbackクラスを付与して表示させています。
アニメーションもつけることはできますが、
一緒に出てくると少し違和感があったので、アニメーションはつけていません。

もしスクロールアウトしてもハンバーガーメニューを表示させたい場合は、
#nav,#hamburger,#boxposition: absolute;position: fixed;
に変更すれば可能です。

またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork

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

Promiseを理解する(非同期処理, コールバックも)

非同期処理とは

ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと。
そうすることで、時間のかかる処理を並行で処理し、その処理の完了を待たずに次の処理ができる。

JavaScriptはシングルスレッドなため、通常では並列で複数の処理ができない。そのため効率的に処理できるように考えられた仕組み。

コールバック関数とは

別の関数(高階関数)に渡すための関数。

// 例: setTimeout(callback, ms)
setTimeout(function() {
  console.log(5);
}, 1000);

setTimeout(() => { // アロー関数ver.
  console.log(5);
}, 1000);
NG
// 1秒毎に数値を表示してカウントダウンしたいが、このコードでは1秒後にすべて実行される
setTimeout(() => console.log(5), 1000);
setTimeout(() => console.log(4), 1000);
setTimeout(() => console.log(3), 1000);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(1), 1000);
setTimeout(() => console.log(0), 1000);
コールバック地獄
setTimeout(() => {
    console.log(5);
    setTimeout(() => {
        console.log(4);
        setTimeout(() => {
            console.log(3);
            setTimeout(() => {
                console.log(2);
                setTimeout(() => {
                    console.log(1);
                    setTimeout(() => {
                        console.log(0);
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);

Promiseとは

JavaScriptにおいて、非同期処理の操作が完了したときに結果を返すもの。
(後で返す という「約束」)
コールバック地獄を避けるために考えられた仕組み。

ステータス

Promiseはステータス(状態)を持つ。

  • pending (保留中)
  • fulfilled (達成された)
  • rejected (却下された)

resolve, reject

Promiseの引数に設定するコールバック関数。

  • 処理が問題なく完了すればresolveが実行される。 (ステータスはfulfilled)
  • 問題があればrejectが実行される。 (ステータスはrejected)

then(), catch()メソッド

次に処理したいことを登録しておく。

  • then() : resolve()された場合の値を取得
  • catch() : reject()された場合の値を取得
getImage(file) // 画像ファイルの読み込みをする
  .then(image => compressImage(image)) // 画像を読み込んだら、圧縮する
  .then(cImage => saveImage(cImage)) // 圧縮が完了したら、その画像をDBに保存する
  .then(result => console.log(result)) // 保存した結果を出力する
  .catch(err => {throw new Error(err)})

このプログラムのどこでエラーが発生したとしても、エラーをキャッチする。
then()はfulfilled以外の場合は全てスルーして次の処理に渡すため。
エラーの場合、ステータスはrejectedになっているので、全てのthen()はこれをスルーする。最後に残ったcatch()だけがこれを掴むことができる。

Async と Await

コールスタックとキュー

同期処理、非同期処理ともに、まずはコールスタックに入る。
ただし、非同期処理のコールバックは、キューに入る。
コールスタックの処理が完了した後、キューに処理が残っている場合はそれをコールスタックに移し、処理が行われる。
そのため、非同期処理は同期処理より遅く処理される。= コードの上から順に処理されない。

Async

asyncが記述された関数は同期関数から非同期関数に変化し、常にPromiseを返す。

コード中にPromiseを返さないreturnがある場合、JavaScriptは自動的にその値を解決(resolve)されたPromiseにラップする。
つまり、次の全てのコードは同じ振る舞いをする。

1
async function func() {
    return 1;
}
2
function func() {
    return Promise.resolve(1);
}
3
function func() {
    return new Promise((resolve, reject) => {
        resolve(1);
    });
}
実行
func().then((num) => {
    console.log(num); // 1~3ともに、結果は1
});

Await

asyncの中では、awaitを指定した行はPromiseが解決されるのを待つ。= 上から順に処理される。(asyncは必須)
つまり、非同期処理を同期処理っぽく動かせる。

Promise.all()

並行で(非同期で)処理するほうが効率がよい場面では、awaitで待たせてはいけない。
Promise.all()を使うことで、複数の非同期処理を並列で処理し、全て完了させた後にコールバックできる。(Promise.all()を待たせる(awaitする))

bad
async function showNewData() {
  const allData = await fetchAllData();
  const oldData = await fetchOldData(); // allDataに待たされる
  showData(allData, oldData);
}
good
async function showNewData() {
  const [allData, oldData] = await Promise.all([fetchData(), fetchOldData()]);
  showData(allData, oldData); // allData, oldDataともに完了すると実行される
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10/10 学習メモ

●デイトラ3日目 jQuery基礎

【scriptタグでjQueryのライブラリー本体を読み込む】
 src="https://code.jquery.com/jquery-3.4.1.js"
 ※必ずjQuery本体を読み込んだ後にjQueryで書いたファイルを読み込む

【基本の型】
 $(function() {
  //この中にプログラムを書いていく
 });
 ※自動的にイベント監視をしてくれるのでロードにより実行される

【書き方】
 $("主語:セレクタ、操作の対象").動詞:行う処理("補語:メソッドによって追加の条件式や引数を渡せたりする"); が基本

 例)
 $(function() {
  $('h2').text('デイトラ');  //h2の 文字を変える デイトラに
 }); */

 $(function() {
  $('a').hide(); //aを 隠す 主語と動詞で完結する場合は補語は不要
 });

【セレクタの指定の仕方】
 ■タグ名で指定する $("h2")
 ■クラス名で指定する $(".lead")
 ■ID名で指定する $("#js-for-web")

【代表的なメソッド】
 ■.text()テキストを取得する
 ■.text('変更したいテキスト')テキストを''内の文字列に変更する
 ■.html()HTMLを取得する
 ■.html('変更したいHTML')HTMLを''内のHTMLに変更する
 ■.click() 対象要素をクリックする
 ■.prepend('要素')要素の先頭にHTMLを挿入する
  例:$(#lists).prepend('< li >先頭に追加するリスト< /li >')
 ■.append('要素')要素の最後にHTMLを挿入する
  例:$(#lists).append('< li >最後に追加するリスト< /li >')
 ■.remove()要素を削除する
 ■.attr('属性')指定した属性の値を取得する
  例:$(a#special-link).attr('href')
 ■.attr('属性', '値')指定した属性の値を変更する
  例:$(a#special-link).attr('href', 'https://xxxx.com')
 ■.removeAttr(属性名)指定した属性を削除する
 ■.addClass(class属性値)class属性を追加する
  例:$(#button).addClass('active')
 ■.removeClass(class属性値)class属性を削除する
  例:$(#button).removeClass('active')
 ■.css(プロパティ名)指定したCSSプロパティの値を取得する
  例:$(#logo).css('color')
 ■.css(プロパティ名,値)指定したCSSプロパティの値を設定する
  例:$(#logo).css('color','red')
 ■val()フォームの入力値(value属性の値)を取得する
  例:$(input#name).val()
 ■val("入力値")フォームの入力値(value属性の値)を設定・上書きする
  例:$(input#name).val('tofuri')

【指定要素の子要素からセレクタに当てはまる要素を取得 find()メソッドとchildren()メソッド】
 ■.find('セレクタ') 対象要素の子孫要素から、セレクタに該当するものを取得する
  var texts = $('#fruits').find('p');
  console.log(texts[0]); //fruits要素下のすべての要素の中から1番目のpタグを取得

 ※findは子孫要素全体から探してくるので、孫要素に当たる要素も所得できる

 ■.children('セレクタ') 対象要素の直属の子要素から、セレクタに該当するものを取得
  var texts = $('#fruits').children('p');
  console.log(texts[0]); //fruits要素の直属の子要素の中から1番目のpタグを取得

 ※childrenは直属の子要素からしか探せないので、孫要素以下に該当するタグがあっても返り値は空になる。

【イベント処理】
 ■イベントの構文は、$('セレクタ').イベント名(function(){ });という構文で書く。
  例えば、クリックされたときに何か処理を追加したい場合は
  $('セレクタ').click(function(){
   //ここに処理を書く
  });
 
  スクロールしたときに何か処理を追加したい場合は
  $('セレクタ').scroll(function(){
   //ここに処理を書く
  });

【イベント処理 click編】
 ■jsで書くと
  document.getElementById("button").addEventListener('click', function()
  document.getElementById('service-title').innerText = 'デイトラ'

 ■jQuery 圧倒的にシンプル!
  $('#button').click(function(){
   $('#service-title').text('デイトラ');
  });

【イベント処理 hover編】
 hoverイベントはfunctionをコンマ区切りで2個続けて書くことができ、最初に「ホバーされたときの処理」、2つ目に「ホバーが外れたときの処理」を書く。

 $('#language-wrapper').hover(
  function() {
   //ここにホバーされたときの処理を書く
  },
  function() {
   //ここにホバーが外れたときの処理を書く
  }
 );

【アニメーション】
 色々な機能は https://api.jquery.com/category/effects/ にある。

 ■スライドのように開閉して表示
  $('#fruits').hover(
   function(){
    $('#apple').slideUp();
   },
   function(){
    $('#apple').slideDown();
   }
  );

 ■フワッと浮き上がる表示
  $('#service-title').hover(
   function(){
    $('#register').fadeIn();
   },
   function(){
    $('#register').fadeOut();
   }
  );

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

【jQuery】【ancestry】を使用した、多階層ツリーメニューの実装

経緯

現在絶賛作成中のポートフォリオで、フリマアプリのラクマのような多階層ツリーメニューを実装したいと思い作成。振り返ってみればさほど難しくはなかったけれども、色々躓いたので記載しておこうと思います。

環境

ruby 2.6.5
Rails 6.0.3.2
haml使用

完成図

moving image
親のカテゴリー(第1階層)をクリックすると、子のカテゴリー(第2階層)のサブメニューが現れるという仕組み。

手順

1、ancestryの導入
2、HTMLで多階層の形を実装
3、jQuery(JavaScript)で、第1階層のカテゴリーをクリックすると、第2階層のサブメニューが表示されるイベントを記載する

※CSSは今回は記載しません、ごめんなさい

ancestryの導入

余談ですが、ancestryを導入しなくても、HTMLに記載すれば多階層ツリーメニューの実装自体は出来ます。只、第2階層のサブメニューのカテゴリをクリックして、そのカテゴリの商品一覧を表示させたいとなるとancestryを導入は必須になります。

余計な余談になりましたが、早速ancestryを導入していきましょう!

Gemfile.
#追記(一番下が好ましい)
gem 'ancestry'

記載が終了したら、
ターミナルで% bundle install して % rails s忘れずに行いましょう。


続いてcategoryモデルの作成です。

ターミナル.
$ rails g model category

この時点で、マイグレーションファイルは作成されていますので、
マイグレーションファイルを編集しましょう。

2020xxxxxxxx_create_categories.rb
class CreateCategories < ActiveRecord::Migration[6.0]
  def change
    create_table :categories do |t|
      t.string :name, null: false #これを追記

      t.timestamps
    end
  end
end

ターミナルでrails db:migrate

さらに、categoryモデルにancestryのカラムを追加したいのでターミナルで以下のコマンドを入力

ターミナル.
% rails g migration AddAncestryToCategory ancestry:string:index

ターミナルでrails db:migrate

続いて、seedにデータ作成をします。
詳しくはこちらの記事を見てみてください。
分かりやすく書いてくれている記事なので、親カテゴリー、子カテゴリーなどの意味が理解出来ると思います。
【フリマアプリ】ancestryを用いたseedデーター作成について

seeds.rb にデータを記載していくんですが、今回は僕が作ったものを貼り付けておきますので参考にして見てください。と言ってもメチャクチャ使い回しなんですけど・・・・
大事なのは、name:xxxxxのxxxxxの部分なので、ここの名前を実装したいものと、照らし合わせて変更してください。

db/seed.rb
# パーツ
lady = Category.create(name: "パーツ")
lady_1 = lady.children.create(name: "パーツすべて")
lady_2 = lady.children.create(name: "マフラー")
lady_3 = lady.children.create(name: "エンジン/冷却装置")
lady_4 = lady.children.create(name: "ホイール")
lady_5 = lady.children.create(name: "タイヤ")
lady_6 = lady.children.create(name: "ハンドル")
lady_7 = lady.children.create(name: "ブレーキ")
lady_8 = lady.children.create(name: "外装")
lady_9 = lady.children.create(name: "駆動系")
lady_10 = lady.children.create(name: "電装形")
lady_11 = lady.children.create(name: "その他")

# アクセサリ
men = Category.create(name: "アクセサリ")
men_1 = men.children.create(name: "アクセサリすべて")
men_2 = men.children.create(name: "バイクカバー")
men_3 = men.children.create(name: "ステッカー/デカール")
men_4 = men.children.create(name: "タンクバック")
men_5 = men.children.create(name: "サドルバッグ/サイドバック")
men_6 = men.children.create(name: "シートバック")
men_7 = men.children.create(name: "ツーリングネット/ロープ")
men_8 = men.children.create(name: "鍵/ロック")
men_9 = men.children.create(name: "エアバック")
men_10 = men.children.create(name: "ETC")
men_11 = men.children.create(name: "ナビ")
men_12 = men.children.create(name: "オーディオ")
men_13 = men.children.create(name: "その他")

# バイクウェア
baby_kids = Category.create(name: "バイクウェア")
baby_kids_1 = baby_kids.children.create(name: "バイクウェアすべて")
baby_kids_2 = baby_kids.children.create(name: "ヘルメット/シールド")
baby_kids_3 = baby_kids.children.create(name: "ゴーグル")
baby_kids_4 = baby_kids.children.create(name: "グローブ")
baby_kids_5 = baby_kids.children.create(name: "プロテクター")
baby_kids_6 = baby_kids.children.create(name: "ジャケット")
baby_kids_7 = baby_kids.children.create(name: "革ツナギ")
baby_kids_8 = baby_kids.children.create(name: "雨具")
baby_kids_9 = baby_kids.children.create(name: "靴")
baby_kids_10 = baby_kids.children.create(name: "バック")
baby_kids_11 = baby_kids.children.create(name: "パンツ")
baby_kids_12 = baby_kids.children.create(name: "シャツ")
baby_kids_13 = baby_kids.children.create(name: "フェイスマスク/ネックウォーマー")
baby_kids_14 = baby_kids.children.create(name: "その他")

# メンテナンス
interior_residence_accessory = Category.create(name: "メンテナンス")
interior_residence_accessory_1 = interior_residence_accessory.children.create(name: "メンテナンスすべて")
interior_residence_accessory_2 = interior_residence_accessory.children.create(name: "バッテリー")
interior_residence_accessory_3 = interior_residence_accessory.children.create(name: "オイル")
interior_residence_accessory_4 = interior_residence_accessory.children.create(name: "フィルター")
interior_residence_accessory_5 = interior_residence_accessory.children.create(name: "スタンド")
interior_residence_accessory_6 = interior_residence_accessory.children.create(name: "工具")
interior_residence_accessory_7 = interior_residence_accessory.children.create(name: "その他")

# その他
book_music_game = Category.create(name: "その他")
book_music_game_1 = book_music_game.children.create(name: "すべて")
book_music_game_2 = book_music_game.children.create(name: "ファッション")
book_music_game_3 = book_music_game.children.create(name: "グッズ")
book_music_game_4 = book_music_game.children.create(name: "カタログ")
book_music_game_5 = book_music_game.children.create(name: "マニュアル")
book_music_game_6 = book_music_game.children.create(name: "本/雑誌")
book_music_game_7 = book_music_game.children.create(name: "その他")       

ターミナルで % rails db:seed でこのデータを反映させます。

Sequel pro(ホットケーキがアイコンのもの)をインストールしている方は、それにデータがちゃんと反映されているか確認して見て下さい。
ちゃんと反映されていたら下記の画像のようになります。
ファイル名
ancestry カラムのデータ中の NULL の所が親カテゴリーです
コントローラーの入力の所で必要な知識になるので覚えて置いてください。

ちなみに、上記の記事を参考にして貰えば分かりますが、
lady = Category.create(name: "パーツ") ←この形の記載が親のカテゴリー(第1階層)
baby_kids_1 = baby_kids.children.create(name: "バイクウェアすべて") ←この形の記載が子カテゴリー(第2階層)の記載になります。

もし入力するカラム名などに不備があった場合は以下のコマンドを入力してseed.rbを修正しましょう。
% rails db:migrate:reset
修正してもう一度rails db:seed
% rails db:seed

最後に、categoryモデルで ancestryを使用できるようにモデルに下記を記載します。

category.rb
has_ancestry

以上で、ancestryの導入は終了です!




HTMLで多階層の形を実装

続いては、HTMLで多階層の形を実装していきます。
下記のものが、コードになります。

index.html.haml
.categories
  .categories--contents
    .categories--title カテゴリから探す
    %ul.category__parent
      - @parents.each do |parent|
        .category-box{id: "parent_category#{parent.id}"}
          %p.parent_category{id: "parent#{parent.id}"}
            = "#{parent.name}"
          %i.fas.fa-angle-down
        %ul.category__child{id: "parent_list#{parent.id}"}
          - parent.children.each do |child|
            .category-box{id: "parent_category#{child.id}"}
              = link_to "#{child.name}", "#", class: "parent_category",id: "child#{child.id}"
              %i.fas.fa-angle-right


続いて、コントローラーの記載です。

xxxxx_controller.rb
def index
  @parents = Category.where(ancestry: nil)
end

先ずは、HTMLでも使っているので、コントローラーの変数の説明から

これは、Categoryテーブルの ancestryカラムの nilのデータ、
要は上記のSequel proの画像を見て貰えればと思いますが、NULLと書かれたデータを
取り出して、parentsという変数に代入しています。
つまり、親カテゴリーのデータを取り出しているという事ですね。

続いてHTMLの説明ですが、
- @parents.each do |parent|
コントローラーで作った変数parents と eachメソッドを使って、
親カテゴリー(第1階層)のデータを抽出します。

- parent.children.each do |child|
これも同様に eachメソッドを使って、
子カテゴリー(第2階層)のデータを抽出します(理屈よりもこの形で覚えた方が早いと思います)

いくつかあるid:"xxxxx"の部分は、jQueryのイベントの際に、またCSSでのコーティングの際に必要になるので、わざわざidを振っています。

このままでは、子カテゴリー(第2階層)が見えたままなので、display: noneで消しましょう。

xxxxx.css
.category__child{
  display: none;
}

これで、HTMLで多階層の形を実装は終了です!

最後に、jQueryの実装です。



jQuery(JavaScript)で、第1階層のカテゴリーをクリックすると、第2階層のサブメニューが表示されるイベントを記載する

それでは、最後にイベントを発火させるjQueryの記載をしましょう。
下記のものが、コードになります。

$(function(){
  $('#parent_category1').click(function(){
    $("#parent_list1").slideToggle('1000');
  });
  $('#parent_category13').click( function(){
    $("#parent_list13").slideToggle('1000');
  });
  $('#parent_category27').click(function(){
    $("#parent_list27").slideToggle('1000');
  });
  $('#parent_category42').click(function(){
    $("#parent_list42").slideToggle('1000');
  });
  $('#parent_category50').click(function(){
    $("#parent_list50").slideToggle('1000');
  });
});

ここで注意なのですが、コード自体はそれ程難しいものではないと思うのですが、
parent_category"1" parent_list"1" などの数字の部分に注意が必要です。

上記の seed.rb をそのままコピペした方は特に問題ないと思いますが、
自分で作成したという方は、恐らくここの数字が変わってくると思いますので、
大変恐縮ですが、ご自身で検証画面を見て、この数字の部分を確認してみてください。

コードの説明ですが、とても簡単です。
parent_category1(親カテゴリー)の階層をクリックしたら、parent_list1(子カテゴリー)が
表示されるというシンプルなものになっています。

この実装にはslideToggleメソッドを使用しています。
このメソッドは、指定された要素が表示されている時はslideUpで非表示にし、非表示になっている時はslideDownで表示するというメソッドになります。ちなみに、格好の中の数字〔slideToggle('1000')〕は、スライドのスピードです。

このメソッドについて詳しく知りたい方はこちらの記事を参照ください。
slideToggle()メソッドについて




これで、全ての実装が完了です!
実装出来なかったなどの不備が有ればご連絡ください!

ありがとうございました。

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

【初心者向け】郵便番号で住所検索をする外部APIを叩く

はじめに

実務でいつ使用してもいいように、ローカル環境で外部提供のAPIを叩く練習をしていきます。
あと、長いこと使用していないAjaxの復習もかねて。

今回は会員登録時に住所入力をサポートする、最近のデファクトスタンダードな仕様を目指してフォームの作成をしていきます。

使用API

郵便番号検索API
http://zipcloud.ibsnet.co.jp/doc/api

※画面下部にAPI利用規約がありますので、使用する場合は一読するようにお願いします。

〜以下引用(2020年10月11日)〜
郵便番号検索APIは、日本郵便が公開している郵便番号データを検索する機能をRESTで提供しています。
現在使用しているデータは、「2020年9月30日更新分の全国一括データ(加工済バージョン)」です。

環境+使用ライブラリなど

macOS Catalina 10.15.7
MAMP(Apache + MySQL)
PHP 7.3.11
jQuery(Ajax通信に使う)
Bootstrap(別に必要ではない)

今回のゴールとする仕様

  • 半角数字で7ケタの郵便番号を入力する。
  • 「住所検索」ボタンを押す。
  • 入力した郵便番号を使って外部APIを叩く
  • レスポンスで受け取った情報をもとに、住所がフォームに自動入力される。
  • クリアボタンを押すとフォーム全てがリセットされる(サービスにおいては必要なさそう)

完成画面

image.png

作成コード

sample.php
<?php

  session_start();  // 今回の説明には不要

?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>郵便番号検索API</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  <script src="postal_api.js"></script>
</head>
<body>
  <div class="container col-6">
    <h1 style="text-align: center; margin-top: 2.4rem; margin-bottom: 1.6rem">入力フォーム</h1>
    <div>
      <form method="post" action="#" style="width: fit-content; margin: 0 auto;">
        郵便番号<br />
        <input type="text" name="zip_code" style="width:100px" id="zip_code">
        <input type="button" value="住所検索" id="search_address_btn">
        <input type="button" value="クリア" id="search_clear_btn">
        <br />
        都道府県<br />
        <input type="text" name="address1" style="width:500px" id="address1"><br />
        市区町村<br />
        <input type="text" name="address2" style="width:500px" id="address2"><br />
        その他<br />
        <input type="text" name="address3" style="width:500px" id="address3"><br />
        建物名など<br />
        <input type="text" name="address4" style="width:500px"><br />
        <br />
        <div class="submit_button_right" style="text-align: right;">
          <input type="submit"><br />
        </div>
      </form>
    </div>
  </div>

</body>
</html>

見た目を少しででも整えるために
Bootstrap関係のタグが多いですが、以下のタグだけでも動作確認済みです。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> //ajax通信用
<script src="postal_api.js"></script> //jsファイルの読み込み


読み込んでるjsのスクリプトはこんな感じです。

postal_api.js
$(document).ready(function(){

    // 住所検索ボタンを押すと外部apiを叩く処理が走る。
    $('#search_address_btn').click(function() {
        $.getJSON('http://zipcloud.ibsnet.co.jp/api/search?callback=?',
            {
            zipcode: $('#zip_code').val()
            }
        )
        .done(function(data) {
            if (!data.results) {
                alert('該当の住所がありません');
            } else {
                let result = data.results[0];
                $('#address1').val(result.address1);
                $('#address2').val(result.address2);
                $('#address3').val(result.address3);
            }
        }).fail(function(){
            alert('入力値を確認してください。');
        })
    })

    // クリアボタンを押すと、フォームの中身がリセットされる。
    $('#search_clear_btn').click(function(){
        $('#zip_code').val('');
        $('#address1').val('');
        $('#address2').val('');
        $('#address3').val('');
    })
})

記事を書いてて、ふと
"クリアボタンとかいらなくね?"
と、思ったのは私だけでしょうか。また検索すればいいだけですよね。
なんなら、間違って押してしまったユーザーにとってはストレスでしかありません。

今回は、簡易的なフォームを作って動作を確認するためだけに実装しましたが、
実際のサービスでフォームの内容を考える場合はきちんとそのあたりも設計した方がいいですね。

あと、このままのコードで動作を実行すると
Chrome検証ツールのConsoleに、サードパーティーやCSRFによるリークに関する注意が issueとして表示されます。
今回は個人的な動作確認なので、割愛。

データ取得に関する説明

APIの提供元でサンプルの取得データが確認できます。
image.png

一部見切れてしまっていますが、"message","results","status"という要素があります。
今回使いたいのはresultsの中身です。

            } else {
                let result = data.results[0];
                $('#address1').val(result.address1);
                $('#address2').val(result.address2);
                $('#address3').val(result.address3);
            }

配列は[0]から始まるので、一旦、中身をresultという変数に置き換えます。
そして、レスポンスサンプルで確認できるように、address1〜address3をそれぞれフォームのvalueに入れてあげればいいわけです。

注意事項

今回の例は配列の最初の要素を住所候補としてブラウザに返していますが、
場所によっては、同じ郵便番号でも住所が3パターン存在したりします。

例:0790177
image.png

こういった場合は押した回数で分岐させるような構造がいいんでしょうか。
Amazonで試してみたら7ケタ目を入力した直後に住所検索の処理が走るので、上記の例だと上美唄町しか出てきませんでした。AmazonのAPIでは候補が一つだけなんですかね。

実装中に詰まった部分

  • 久しぶりにAjax触ったので、そもそも最初は流れが思い出せなかった。
  • JSON形式でデータを受け取るにあたって、同じドメイン上のAPIしか叩いたことがなかったので、クロスドメインでAPIを叩く処理を実装する部分に時間がかかった。(記述はシンプルなので、検索して情報を見つけてからは大したことなかった)

最後に

これで住所入力の手間が省けるフォーム部分が完成したので、実際使用する場合には他の項目と一緒にPOSTでphpスクリプトに送ってやればいいわけですね。

今回、①外部APIを叩く ②Ajaxの復習 という目的が無事達成できたので良かったです。
今後もこれぐらい軽めに取り掛かれる実装をプライベートでもガンガンやっていきたいと思います。

ここまで読んでいただきありがとうございました。

参考

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

ESLint 7.11.0

v7.10.0 | 次 (2020-10-24 JST)

ESLint 7.11.0 がリリースされました。機能追加は行われていませんが、ESLint の semver ポリシー (指摘が増えるかもしれないバグ修正はマイナーバージョンで行う) に従ってマイナーバージョンが上がっています。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

特になし

? 新しいルール

特になし

? オプションが追加されたルール

特になし

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

ESLint 7.10.0

v7.9.0 | 次 v7.11.0

ESLint 7.10.0 がリリースされました。小さな機能追加とバグ修正が行われました。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

特になし

? 新しいルール

特になし

? オプションが追加されたルール

no-inline-comments ignorePattern

? #13029

正規表現で許可するインラインコメントを指定できるようになりました。

/*eslint no-inline-comments: [error, { ignorePattern: "^\\s*webpackChunkName:.+$" }]*/

//✓ GOOD
import(/* webpackChunkName: "my-chunk-name" */ './locale/en');

Open Online Demo

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

ESLint 7.9.0

v7.8.0 | 次 v7.10.0

ESLint 7.9.0 がリリースされました。機能追加は行われていませんが、ESLint の semver ポリシー (指摘が増えるかもしれないバグ修正はマイナーバージョンで行う) に従ってマイナーバージョンが上がっています。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

特になし

? 新しいルール

特になし

? オプションが追加されたルール

特になし

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

ESLint 7.8.0

v7.7.0 | 次 v7.9.0

ESLint 7.8.0 がリリースされました。小さな機能追加とバグ修正が行われました。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

env.es2021 が追加されました

? #13603

.eslintrc.js
module.exports = {
    env: {
        es2021: true,
    },
}

2つの新しいグローバル変数 WeakRefFinalizationRegistry を定義します。

論理演算の複合代入演算子をサポートしました

? #13609, #13612, #13618

a1 &&= b1
a2 ||= b2
a3 ??= b3

これらの演算子の正規化は a1 = a1 && b1 ではなく a1 && (a1 = b1) です。つまり、短絡する場合は代入演算が発生しません。

これらの演算子には短絡動作 (short-circuit) があるため、今までは複合代入演算子をサポートしていませんでした。最近追加された ?? 演算子によって需要が出たため、短絡動作があっても複合代入演算子をサポートすることになりました。

この新しい構文を使用する場合、parserOptions.ecmaVersion オプションを 2021 に指定してください。

.eslintrc.js
module.exports = {
    parserOptions: {
        ecmaVersion: 2021,
    },
}

数値リテラルの桁区切りをサポートしました

? #13609, #13574, #13581

const binary = 0b0000_1010_0101_1111
const octal = 0o012_666_755
const hex = 0xDEAD_BEAF
const decimal = 1_000_000_000

桁数の多い数値をアンダーバーで区切って読みやすくできます。

この新しい構文を使用する場合、parserOptions.ecmaVersion オプションを 2021 に指定してください。

.eslintrc.js
module.exports = {
    parserOptions: {
        ecmaVersion: 2021,
    },
}

? 新しいルール

特になし

? オプションが追加されたルール

no-magic-numbers ignoreDefaultValues

? #12611

デフォルト値にマジックナンバーを許可するオプションが追加されました。

/*eslint no-magic-numbers: [error, { ignoreDefaultValues: true }]*/

//✓ GOOD
const { PI = 3.14159265359 } = constants

Open Online Demo

id-length exceptionPatterns

? #13576

正規表現で許可する名前を指定するオプションが追加されました。

/*eslint id-length: [error, { min: 3, exceptionPatterns: ["^(e|pi)$"] }]*/

//✓ GOOD
const e = 2.71828182846
const pi = 3.14159265359

//✘ BAD
const f = () => {}

Open Online Demo

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

ESLint 7.7.0

v7.6.0 | 次 v7.8.0

ESLint 7.7.0 がリリースされました。小さな機能追加とバグ修正が行われました。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

特になし

? 新しいルール

特になし

? オプションが追加されたルール

no-underscore-dangle allowFunctionParams

? #13545

仮引数のアンダーバーで始まる名前を許可するためのオプションが追加されました。

これまで、このルールは仮引数を見逃していたため、破壊的変更を避けるためにデフォルト値は true になっています。次のメジャーバージョンで false になる予定です。

/*eslint no-underscore-dangle: [error, { allowFunctionParams: true }]*/

//✓ GOOD
function f(_a) {}

Open Online Demo

/*eslint no-underscore-dangle: [error, { allowFunctionParams: false }]*/

//✘ BAD
function f(_a) {}

Open Online Demo

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

ESLint 7.6.0

v7.5.0 | 次 v7.7.0

ESLint 7.6.0 がリリースされました。機能追加は行われていませんが、ESLint の semver ポリシー (指摘が増えるかもしれないバグ修正はマイナーバージョンで行う) に従ってマイナーバージョンが上がっています。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)


[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。


✨ 本体への機能追加

特になし

? 新しいルール

特になし

? オプションが追加されたルール

特になし

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

javascriptで簡単CSV解析

関数1つでCSVを二次元配列に変換します。

  • ES3対応。
  • 区切り文字を変えればタブ区切りテキストでもOK。
  • " で囲ってあれば、改行や区切り文字が含まれていてもOK。
  • " を含めるならば "" を入れる。
  • バグがあったら教えてください。
// CSVをパース
function parseCsv(csvStr, delimiter) {
    var rowRegex = /(?:(?:"[^"]*")*[^\r\n"]*)+/g,
        colRegex = new RegExp('(?:(?:"[^"]*")*[^' + delimiter + '"]*)+', 'g'),
        rows = [],
        row, cells, cell, rowMaches, colMaches;
    //行を切り出す
    while ((rowMaches = rowRegex.exec(csvStr)) !== null) {
        if (rowMaches[0] !== '') {
            cells = [];
            row = rowMaches[0];
            //セルを切り出す
            while ((colMaches = colRegex.exec(row)) !== null) {
                cell = colMaches[0].replace(/^\s+|\s+$/g, '');
                if (cell.charAt(0) == '"' && cell.charAt(cell.length - 1) == '"') {
                    cell = cell.slice(1, -1);
                }
                cell = cell.replace(/""/g, '"');
                cells.push(cell);
                colRegex.lastIndex++; //一歩前へ!
            }
            rows.push(cells);
        }
        rowRegex.lastIndex++; //一歩前へ!
    }
    return rows;
}

// カンマ区切り
var str1 = "連番,氏名,氏名(カタカナ),性別,電話番号,生年月日\n" +
    "1,小平勝美,コダイラ カツミ,女,\"0993298298\",1987/07/12\n" +
    "2,沢井慶太,サワイ ケイタ,男,\"0881257342\",1986/10/25\n" +
    "3,神崎勝男,カンザキ カツオ,男,\"087946508\",1985/09/30\n" +
    "4,島津源治,シマヅ ゲンジ,男,\"0173059125\",1971/06/06\n" +
    "5,岩谷由姫,イワタニ ユキ,女,\"0970852069\",1999/09/07";

var data1 = parseCsv(str1, ",");
console.log(JSON.stringify(data1, null, '  '));

// タブ区切り
var str2 = "連番\t氏名\t氏名(カタカナ)\t性別\t電話番号\t生年月日\n" +
    "6\t合田郁美\tアイダ イクミ\t\t\"025775007\"\t1989/06/17\n" +
    "7\t梅原侑歩\tウメハラ ユウホ\t\t\"089975035\"\t1995/08/22\n" +
    "8\t吉野有香\tヨシノ ユカ\t\t\"0244902616\"\t1975/09/02\n" +
    "9\t松野時雄\tマツノ トキオ\t\t\"0982714767\"\t1978/06/15\n" +
    "10\t依田永遠\tイダ トワ\t\t\"0522081957\"\t1996/10/14";

var data2 = parseCsv(str2, "\t");
console.log(JSON.stringify(data2, null, '  '));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで円周率の有効桁数をユーザーに指定してもらう方法

<main>
<div class="container">
<section>
    <p>これが有効桁数<span id="digit"></span>円周率<span id='pi'></span>です</p>

</section>
</div><!-- /.container -->
</main>
<footer>
<div class="container">
<p>JavaScript Samples</p>
</div><!-- /.container -->
</footer>
<script>
'use strict';
const digits=parseInt(window.prompt('有効桁数を数字で入力してね'));
const DIGIT=digits-1;
 function point(num,digits){
     const mover =10**digits;
     return Math.floor(num*mover)/mover;

 }
document.getElementById('digit').textContent=digits;
document.getElementById('pi').textContent=point(Math.PI,DIGIT)

</script>
</body>
</html>

参考文献:JavaScript超入門書P174

(説明)
まず、window.prompt('')でユーザーに有効桁数を入力してもらう。
そのデータが文字列(string)なので、parseInt()メソッドで数値、整数(integer)に変換する。
その値を、定数digitsに代入。
円周率(Math.PI)の指定した小数点以下を切り捨てる関数を作る。function point(num,digits)
しかし、3.14159265358979を10*digitsした後に小数点以下を切り捨てても、もともと小数点の前(一の桁)にある3も有効桁数に入るので、このままだとユーザーが入力した桁数より1桁多くなる
(ex:ユーザーの有効桁数「3」
3.141592x10
3=3141.592
この数字の小数点以下を切り捨てると3141
これをまた元の数字に戻すと
3141/10
*3=3.141
つまり有効数字4桁になってしまう。)
よって、私は
const DIGIT=digits-1;
document.getElementById('pi').textContent=point(Math.PI,DIGIT)
この二つのコードを工夫して、出力をユーザーが求める有効桁数にした。
関数を呼び出すときの引数にpoint(Math.PI,DIGIT)をして、円周率とユーザーが入力した有効桁数−1で帳尻を合わせた。

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

Vue.jsでお手軽carousel

carouselって何?

carousel(カルーセル)はスライドショーの様に画像が自動でスライドしたり、手動でスライドさせたりとWebサイトにおいてメジャーな動きのある機能ですね。
デモサイト
こちらの機能をVue.jsで簡単にできるのでやっていきましょう!

※Vue CLIで環境構築が済んでいる前提で進めていきますまだの方はこちらを参考に。
Vue.js を vue-cli を使ってシンプルにはじめてみる

インストール

npm install vue-carousel
インストール後、グローバル、使用コンポーネントで記述して使える様にしていきます。
今回はプロフィールコンポーネントで使用したかったのでpurofile.vueに書いていきます。

main.js
import Vue from 'vue';
import VueCarousel from 'vue-carousel';

Vue.use(VueCarousel);
profile.vue
import { Carousel, Slide } from 'vue-carousel';

export default {
  省略
  components: {
    Carousel,
    Slide
  }
  省略
};

実際に使ってみる

カルーセル表示したいところにオプションと一緒に書いていきます。
今回私はスライドに表示する写真数、スライドの自動、スライドが始まるまでのタイムアウト時間、スライドのスピードのみデフォルトから変えています。
その他のオプションやデフォルトはこちらこちらから確認できます。(英語)
今回は使用しませんでしたが、他にもボタン表示で手動スライドできたりするみたいです。

profile.vue
<template>
  <div>
    <h1 class="content-title">Profile</h1>
    <div class="Profile">
      <div class="profile-content">
        <!-- オプションはこのように書く -->
        <carousel :per-page="1" :autoplay="true" :loop="true" :autoplayTimeout="4000" :speed="1000">
          <slide>
            <img src="../image/profile.jpg">
          </slide>
          <slide>
            <img src="../image/profile2.jpg">
          </slide>
        </carousel>
      </div>
    </div>
  </div>
</template>

各オプションの数値をお好みで変更してください。

終わり

BootstrapでもcarouselやってみたりしましたがVueの方が簡単かつカスタムしやすいので個人的には好きです!!

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

Typescript ジェネリクス

Typescriptでは予め型を決めておく必要がありますが、ジェネリクスを使うと使用直前まで型を定義しなくて良くなります。
型が違う同じような機能をいくつも定義しなくて良くなるので、ソースコードの再利用性や保守性が増します。

以下では、ジェネリクスを使はない場合はstringとnumberの両方のクラスを用意しないといけないですが、ジェネリクスを使う場合は1つのクラスだけで機能提供できていることがわかります。

ジェネリクスを使わない場合
class useString {
  constructor(public arrays: string[]) {}
  print(value: string): void {
    for(const array of this.arrays) {
      if(array === value) {
        console.log(`Yes you have ${value}`)
      }
    }
  }
}

class useNumber {
  constructor(public arrays: number[]) {}
  print(value: number): void {
    for(const array of this.arrays) {
      if(array === value) {
        console.log(`Yes you have ${value}`)
      }
    }
  }
}

new useString(['a', 'b', 'c']).print('b')
new useNumber([1, 2, 3]).print(2)
ジェネリクスを使う場合
class useAnyting<T> {
  constructor(public arrays: T[]) {}
  print(value: T): void {
    for(const array of this.arrays) {
      if(array === value) {
        console.log(`Yes you have ${value}`)
      }
    }
  }
}

new useAnyting<string>(['a', 'b', 'c']).print('b')
new useAnyting<number>([1, 2, 3]).print(2)

必ず存在するかわからないメソッドを使う場合

ジェネリクスで使うことで、型ごとに同じような機能を作る必要がない例は見ていただいたと思いますが、もし未来に定義する型にメソッドがあるかどうかわからない例だとエラーになってしまいます。

class Student {
  print(): void {
    console.log('This is Student')
  }
}

class Teacher {
  print(): void {
    console.log('This is Teacher')
  }
}

interface People {
  print(): void
}

function print<T>(arr: T[]) {
  for(let i = 0; i < arr.length; i++) {
    // arrが必ずprintメソッドを持つ訳ではないので、エラーになる
    console.log(arr[i].print) 
  }
}

print([new Student(), new Teacher])

ジェネリクスの引数をprintメソッドを持つインターフェイスで継承すれば、引数に渡る値はインターフェイスを満たす必要があり、必ずprintメソッドを持つことが確約されるためエラーにはなりません。

class Student {
  print(): void {
    console.log('This is Student')
  }
}

class Teacher {
  print(): void {
    console.log('This is Teacher')
  }
}

interface People {
  print(): void
}

// Peopleインターフェイスを継承する
// 引数にprintメソッドを持っている値が渡される限りエラーにならない
function print<T extends People>(arr: T[]) {
  for(let i = 0; i < arr.length; i++) {
    console.log(arr[i].print)
  }
}

print([new Student(), new Teacher])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

express.jsアプリをAWS CloudFormationでデプロイする

CloudFormationを使用してexpressアプリをlambda functionとしてデプロイ、API gatewayを通して公開する

express.jsでアプリを作る

まず、ふつうにアプリを書く。ファイル名はapp.jsとする

// app.js
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3000;

app.use(cors());

app.get('/', (req, res) => {
  console.log(req.method, req.url);
  res.send({ status: true });
});

app.listen(port, () => {
  console.log(`listening on http://localhost:${port}`);
});

GETで何かが返ってくるごくごく簡単なアプリ

aws-serverless-expressの導入

middlewareを使うのでpackageを導入

$ npm install --save aws-serverless-express

サンプルのファイルを拝借

$ git clone https://github.com/awslabs/aws-serverless-express.git ase
$ cp -r ase/example/scripts .
$ cp ase/example/api-gateway-event.json .
$ cp ase/example/cloudformation.yaml .
$ cp ase/example/lambda.js .
$ cp ase/example/simple-proxy-api.yaml .
$ rm -rf ase

サンプルのpackage.jsonからスクリプトなどを拝借

package.json
  "scripts": {
    "start": "node app.local.js",
    "config": "node ./scripts/configure.js",
    "deconfig": "node ./scripts/deconfigure.js",
    "local": "node scripts/local",
    "invoke-lambda": "aws lambda invoke --function-name $npm_package_config_functionName --region $npm_package_config_region --payload file://api-gateway-event.json lambda-invoke-response.json && cat lambda-invoke-response.json",
    "create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region",
    "delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region",
    "package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region",
    "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region",
    "package-deploy": "npm run package && npm run deploy",
    "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region",
    "setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || npm run create-bucket) && npm run package-deploy"
  },
  "config": {
    "s3BucketName": "YOUR_UNIQUE_BUCKET_NAME",
    "region": "YOUR_AWS_REGION",
    "cloudFormationStackName": "YOUR_STACK_NAME",
    "functionName": "YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME",
    "accountId": "YOUR_ACCOUNT_ID",
    "profile": "YOUR_PROFILE"
  }

configの修正

  • cloudFormationStackNameはわかりやすい名前
  • profileはaws-cliの使いたいprofile

これ以外のconfig項目は設定スクリプトで変更する

package.json
    "cloudFormationStackName": "YOUR_STACK_NAME",
    "profile": "YOUR_PROFILE"

nodejsのランタイムが古いので修正

cloudformation.yaml#L60
-     Runtime: nodejs8.10
+     Runtime: nodejs12.x

APIのtitleをわかりやすいものに変更

simple-proxy-api.yaml#L4
- title: AwsServerlessExpressApi
+ title: <YourApiTitle>

設定変更スクリプトの実行

package.jsonに設定を書き換えるスクリプトが用意されているので実行

$ npm run config -- --account-id="<accountId>" --bucket-name="<bucketName>" --region="<region>"
  • accountId→AWSのアカウントID
  • bucketName→S3のバケットの名前(わかりやすい名前をつける)
  • region→ap-northeast-1とか

--function-nameオプションもあるが、指定するとかえってハマるので私は指定しなかった

アプリの修正

app.jsの修正

  • aws-serverless-expressのmiddlewareをexpress appに食わせる
  • app.listenは別ファイルに移動するため削除
  • appをmoduleとしてexportしておく
app.js
+ const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');
- const port = 3000;
  ...
+ app.use(awsServerlessExpressMiddleware.eventContext());
  ...
- app.listen(port, () => {
-   console.log('Listening on port ' + port + ' ...');
- });
  ...
+ module.exports = app;

app.local.jsを追加(ローカル実行用)

修正したapp.jsのmoduleをimportしてlisten実行部分をもってくる

app.local.js
const app = require('./app');
const port = 3000;

app.listen(port, () => {
  console.log(`listening on http://localhost:${port}`);
});

デプロイ実行

$ npm run setup

問題なければ CloudFormation にスタックが追加される。スタックの詳細の「出力」タブから Lambda function、API gateway、エンドポイントにアクセスできる

必要ならば設定情報を破棄

$ npm run deconfig

その他

  • デプロイに失敗した場合はAWS Console の CloudFormation > スタック > イベント を見てエラー内容などを確認する
  • エラーで生成に失敗したスタックは都度削除したほうがよさそう

参考

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

RxJS公式Overview要約(備忘録)

僕もJavascript触り出したの最近で非同期処理という言葉を認識して勉強し出したのはこれがほぼ初めてなので勉強がてら書いていきます。間違っていることがあれば指摘してくれると嬉しいです。
原典はここです。https://rxjs-dev.firebaseapp.com/guide/overview

そもそも非同期処理って?

同期的な処理とは上から順に実行される処理です。反対に非同期処理とはクリックなどのイベントが起こるまで実行されない処理です。document.addEventListner("click",()=>{console.log("クリックされたよ!")})}などが非同期処理の典型例です。

RxJSを使うと何が嬉しい?

非同期処理の流れがわかりやすく書ける。RxJSを用いないとコールバック地獄と呼ばれるIf文が複雑に絡み合った大変読みづらいプログラムになってしまう。

イントロダクションを行う上でのローカル環境構築

クリックとかのブラウザに関するイベントのために一応説明しておく。ただし、1回しか使わないので飛ばしてもらっても問題ないです。他はVSCodeのデバッガーで動きます。
あとあとAngularで使いたいと思って勉強してるのでAngular上で実行していく。(Angularのインストールは各々してください。)
もちろん、Angularを用いずに環境構築してくれても全く問題ない。
ターミナル上でng new rxjs-practiceでプロジェクトを作成し、ng g c rxjsで作成したコンポーネントの中で実行するのが手軽だろう。以下がその例である。

app.component.html
<app-rxjs></app-rxjs>
rxjs.component.ts
import { Component, OnInit } from '@angular/core';
import { fromEvent } from 'rxjs';
import { throttleTime, map, scan } from 'rxjs/operators';

@Component({
  selector: 'app-rxjs',
  templateUrl: './rxjs.component.html',
  styleUrls: ['./rxjs.component.css']
})
export class RxjsComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
    fromEvent(document, 'click')
    .pipe(
      throttleTime(1000),
      scan(count => count + 1, 0)
    )
    .subscribe(count => console.log(`Clicked ${count} times`));
  }

}

これだけ書いてng serve --openと実行すればangularを触ったことがない人でもRxJSをローカルで動かすことができるようになるだろう。

全く同じ動作をする公式イントロダクションのなかのコードはこちらである。

import { fromEvent } from 'rxjs';
import { throttleTime, map, scan } from 'rxjs/operators';

fromEvent(document, 'click')
  .pipe(
    throttleTime(1000),
    map(event => event.clientX),
    scan((count, clientX) => count + clientX, 0)
  )
  .subscribe(count => console.log(count));

ngOnInitのなかにコードを書けばコンポーネント生成時にその中のコードが実行される。今回はRxJSをローカルで動かすことを目的としているのでこれ以上はAngularについては触れない。サンプルコードを適宜importngOnInitに振り分けてください。

PullとPush

大まかに言えば
- Pull : 呼び出し元が呼び出すタイミングを決める。呼び出された側はいつ呼び出されるかわからない。(例:Generator)
- Push : 呼び出された側がいつ呼び出されるか決める。呼びだし元はいつ呼び出すかわからない。(例:Promiss)

RxJSはPushに属する。話をわかりやすくするために例を示す。

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
  }, 1000);
});

console.log('just before subscribe');
observable.subscribe({
  next(x) { console.log('got value ' + x); },
  error(err) { console.error('something wrong occurred: ' + err); },
  complete() { console.log('done'); }
});
console.log('just after subscribe');

出力は

console
just before subscribe
got value 1
got value 2
got value 3
just after subscribe
got value 4
done

となる。subscribeによってobservableが呼び出される(以下では購読されると言う)とただちに1,2,3の値が購読者に渡され関数next(x)の引数として実行される。しかし、その後、呼び出され側(以下ではObservableと呼ぶ)が1000msタイムアウトしているのでその間next(x)は実行されず、そのあとのconsole.log('just after subscribe');が実行される。1000ms後にnext(x)xとして4が渡される。ここでObservableは終了し購読者のcomplete() { console.log('done'); }
});
が実行される。これは値を作る側が処理の実行タイミングを決めているのPushなのである。

Observable

Observableの作成、購読

以下の例では一秒毎にcountの値が1つ大きくなり購読者に渡される。observableを作成する際はnew Observable(subscriber=>{//処理//subscriber.next()})のようにすれば良い。

import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {
  let count = 0
  let id = setInterval(() => {
    count++
    subscriber.next(count)
    }, 1000);
  }
);
observable.subscribe(x => console.log(x));

購読する際は今までもみてきたように

observable.subscribe(x=>f(x));

とすればよい。

また、複数の購読間で状態は共有されない。以下の例を見るとわかりやすいだろう。(以下ではimportは省略する。コードエディター上でエラーが出ると思うので適切に対処してほしい。)

code
const observable = new Observable(function subscribe(subscriber) {
    subscriber.next(1)
    setTimeout(()=>{subscriber.next(2);},2000)
  }
);
observable.subscribe(x=>{console.log(`A: ${x}`)})
observable.subscribe(x=>{console.log(`B: ${x}`)})

このコードの出力は以下である。

A:1
B:1
A:2
B:2

これを見るとたしかに購読が共有されていないことがわかる。observable.subscribenew Observable(function subscribe(subscriber) {...})の中で定義されたsubscriberを呼び出すだけにすぎない。また、これはObservable executionとも呼ばれる。一度の購読につき一度だけ実行されるが、これが渡す通知は以下の3種類である。

  • "Next"通知: 何かしらの値を渡す。
  • "Error"通知 : エラーもしくは例外を渡す。
  • "Complete"通知 : 何も渡さない。

購読の終わらせ方

一度、始めた購読は何かしらの方法で終了させねばずっと継続されるが、購読はそれぞれ独立である。ただし、購読に変数名をつけることで購読を好きなタイミングで終了させることができる。const sub = observable.subscribe(x=>console.log("obserbver: ",x))のように変数名をつけた後でsubscribe.unsubscribe()とすれば良い。observableの定義の中にあるreturnの中にunsubscribe時に実行したい処理を書くことができる。書かなければ購読が中止されるだけであり、以下の例ではこれをかかないと裏でIntervalが動き続ける。

const observable = new Observable(subscriber => {
    let count = 0
    const id = setInterval(() => {
      count++
      subscriber.next(count)
      console.log("timer: ",count)
    }, 1000)
    return () => {
        console.log("unsubscribe")
        setTimeout(()=>{clearInterval(id)},2000)
    }
})
const sub = observable.subscribe(x=>console.log("obserbver: ",x))
setTimeout(() => sub.unsubscribe(), 2000)

出力は

obserbver:  1
timer:  1
unsubscribe
timer:  2
timer:  3

となる。この出力を見てわかるようにunsubscribeされたからと言って内部の処理がすぐに止まるわけではないので、内部でAddEventListenrなどを使っている時はremoveEveentListerreturnのあとに書くことを忘れないようにしてください。

Observer

observerとは単に以下の形をした連想配列である。(いくつかの要素が欠けていても問題ないが、欠けた要素から通知が来ても反応しない。例えば、絶対に失敗しないことがわかっているのならerrorの処理を書かなくても良い。)

const observer = {
  next?: x => f(x),//成功した時の処理
  error?: err => g(err),//エラーや例外が発生した時の処理
  complete?: () => h(),//subscribeが終了した時の処理
};

observerを使うには以下のように

observable.subscribe(observer);

subscribeの中にいれればよい。

Operator

Operatorは以下の2種に分類できる
- Creation Operators : 配列などを元に新しいObservableを作る。
- Pipeable Operators : Observableを元に新しいObservableを作る。

Creation Operators

とりあえず、offromだけ説明しておく
使い方は見たまんまである。

import {of,from} from 'rxjs';
let ofObservable = of(1,2,3)
let fromObservable = from([1,2,3])

Pipeable Oparators

Observableから送られてきた値を加工してに新たなObservableを作る。元のObservableは変化していない。
使い方は以下のようにする。

of(1,2,3).pipe(map(x=>x*x)).subscribe(x=>console.log(x))

コンソールは

1
4
9

である。他にも色々種類があるが、多すぎるので適宜紹介していきたい。
また、Pipeable Operatorはつなげることができる。たとえば、以下のように用いる

of(1,2,3).pipe(map(x=>x*x),fillter(x=>x>3).subscribe(x=>console.log(x))

コンソールは

4
9

である。

Pipeable Operatorの作り方

せっかくなのでオペレータ(pipeable operator)の作り方も説明する。基本的には自作する必要はないだろうが、作り方を知っておくことでオペレータに対する理解が深まるはずだ。オペレータとは何かというとObservableを受け取ってObservableを返す関数を返す関数だ。

見本としてmapオペレータを自作してみる。実際のものも似たような実装になってるんじゃないかなあ(憶測)

import { Observable ,of} from 'rxjs'

function newMap<T,U>(f:(x:T)=>U){
    return (observable:Observable<T>) => new Observable<U>(observer=>{
        const subscription = observable.subscribe({
            next(value){
                const out = f(value)
                observer.next(out)
            },
            error(err){observer.error(err)},
            complete(){observer.complete}
        })
        return ()=>{
            subscription.unsubscribe()
            console.log("unsubscribe")
        }
    })
}
let a = newMap((x:number)=>(x*x))(of(1,2,3)).subscribe((x)=>{console.log(x)})
a.unsubscribe()

返ってくる関数を見てみるとobservableを引数に取り、戻り値となるObservableは購読されるとobservableの購読を開始し渡された値valueを関数f(value)に代入し、その値をObservableの値としてその購読者へと渡す。最後のreturnの中にはunsubscribeメソッドが呼び出された時の処理を書く。今回はobservableへの購読を終了し、尚且つコンソールにunsubscribeと出力されるようにした。
出力は

1
4
9
unsubscribe

となり、実際に動くことがわかる。

Subscription

先ほども見たが

let a = observable.sebscribe(observable)

aのことである。このようにすることでa.unsubscribe()で購読をやめることができる。
しかし、他にも使い道がある。たとえば、一つの購読をやめたとき他の購読も一緒にやめたいなどの処理をするときだ。それには以下のようにaddを用いる。

import { interval } from 'rxjs';

const observable1 = interval(400);
const observable2 = interval(300);

const subscription = observable1.subscribe(x => console.log('first: ' + x));
const childSubscription = observable2.subscribe(x => console.log('second: ' + x));

subscription.add(childSubscription);

setTimeout(() => {
  // Unsubscribes BOTH subscription and childSubscription
  subscription.unsubscribe();
}, 1000);

コンソールは以下の通りである。

second: 0
first: 0
second: 1
first: 1
second: 2

上のコードではsubscription.unsubscription()をした時に同時にchildsubscriptionの購読も終了する。addがあるといことはremoveもある。働きは想像通りである。

Subject

Subjectとは複数の購読間で共有できる特殊なObservableである。
観測者の立場からするとただObservableSubjectのどちらか区別できない。内部実装の話をするとSubjectにたいする購読は値を届ける新しい実行を始めない。通常のAddListnerなどのように購読のリストを保持するだけである。
また、Subjectでは購読側からnextメソッドを使って新しく値を渡すことができる。

const subject = new Subject<number>();

let a = subject
a.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});
let b = subject
b.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

a.next(1);
a.next(2);

この操作コードの出力はどうなるであろうか?答えは

observerA:1
observerB:1
observerA:2
observerB:2

となる。一つの購読からObservableに値を渡すと全体の購読間で値が共有されるのだ。これはSPAでコンポーネント間でイベントやデータを共有したい時に用いることができる。

refCount()あたりの話は最初はイントロとしては盛り込みすぎかなと思うので省きます。
ただ、connect()refCount()あたりのコードをTypeScriptで実行すると、型エラーが発生します。これは何故かと言うとconnect()メソッドを使うためにはConnectableSubject型である必要があり、multicast(subject)を行うとこの型に変換されるはずですが、pipeの中でオペレータを使用しても適切に型推定がされないから起こります。なので、ここでは型アサーションを行うか、pipeを使わずにmulticast(subject)(source)と書く必要があります。

BehaviorSubject

最新の値を持つことができる便利なSubjectです。
値を取得するには以下のように.valueで読み出してください。

    const subject = new BehaviorSubject(0);     
    subject.subscribe({
      next: (v) => console.log(`observerA: ${v}`)
    });
    subject.next(1)
    let b = subject.value
    console.log("value:",b)
    subject.next(2)

出力は

observerA: 0
observerA: 1
value:1
observerA: 2

ReplaySubject

複数の値を保持しておき、新たな購読が始まった際にいままでの値を全て新しい購読に渡します。
どれだけの数の値を保持するか、何ミリ秒前の値まで保持するかということも指定することができます。
使い方は下のコードを見ながら解説していきます。

import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(100, 500 /* windowTime */);

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

let i = 1;
setInterval(() => subject.next(i++), 200);

setTimeout(() => {
  subject.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 1000);

出力

observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerA: 5
observerB: 3
observerB: 4
observerB: 5
observerA: 6
observerB: 6
...

最初のconst subject = new ReplaySubject(100, 500 /* windowTime */);で値を100個までかつ500ms前の値までを保持するとしていします。
その後、200msに一度値がObservableに渡されるのでObserverAは通常通り値を受け取ります。しかし、1000msに到達した時ObserverBの購読が始まります。ここからがただのSubjectと挙動が異なります。500ms前までのSubjectの値を持っているので購読が始まった時点でObserverBは3,4,5の値を渡されて実行します。

Async Subject

イントロとしてはそこまで重要度高くなさそうなのでここでは省きます。

Schedular

処理の時間とか優先順位とかを決められる。
必要になったら調べるくらいで良さそう。

Testing

時間がある時にしっかり読みたい。

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

なぜスクールはRubyを学習させるのか

なぜJavaではなくRubyを推奨するのか

iStock-973074712-490x265.jpg

結論

挫折しにくく比較的理解しやすいから(参考文献豊富)

実際に転職で求められるスキルとは

レバテックでの言語別求人
sub1(1).png
こんな感じでJavaが全体の約35%と高割合を占めていて、PHPと合わせる全体の約50%という結果となっています。
kangaeruhito.png

Rubyの需要・・・・・

ってなる人、分かります。正直Wantedlyや他媒体の求人を見ていてもRubyエンジニアを求めてる企業はかなり少ないです。じゃあなんでスクールは推すのかというと、、
アプリケーションを自力で作りやすいから!!!
これに尽きると思います。Railsを使って便利なgemも使えば簡単に実装できますし、個人スケールで簡単にアプリ作成ができます。

だからこそ企業が見る視点とは

スクール経由の求人企業なら何を学んできたのかを全て知り尽くされています。だからこそ、自発的に学んだ事をアプリに取り入れたり、自己研鑽しているかを重視しているような気がします。(多分)

結論

スクールでは実装に必要最低限の事しか教えてもらえず、あぁ〜もう無理やぁ〜ってなる気持ちも分かりますが、どちらかというとその部分を1番見ているポイントだと思うので最後まで諦めずに自発的に学んで転職・就職を成功させましょう!!!

現場からは以上です!

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

Javascriptのthis参照パターン

はじめに

Javascriptにおけるthisのお作法を備忘録としてまとめました。

thisとは

実行コンテキストによって参照先が変化するキーワードです。
thisは直感的に何を参照するかわかりやすい場合もありますが、お作法を知らないと思わぬところを参照することがあるのでそれらのパターンをまとめてみました。

※実行コンテキストとは、簡単にいうとどこで実行されるかということです。

オブジェクトを参照をするパターン

const pikachu = {
    name: "ピカチュー",
    type: "電気",
    detail: function(){
      console.log(`${this.name}${this.type}タイプのポケモンです。`)
    }
  };
  pikachu.detail();
// ピカチューは電気タイプのポケモンです。

オブジェクトのメソッドでthisが使用される場合には、呼び出し元であるオブジェクトを参照します。今回はpikachuオブジェクトを参照していますね。

※オブジェクトが保有している関数をメソッドと呼びます。

コンストラクター内部で呼び出されるパターン

 function FactoryNum(arg){
  this.value = arg;
  this.increment = () => (
    this.value ++
  )
 };
  const factoryNum =  new FactoryNum(1); 
  console.log(factoryNum.value)
  // 1
  factoryNum.increment();
  console.log(factoryNum.value);
  // 2

このthisはコンストラクタによって生成されたインスタンスを参照します。
※参照しているのは下記のようなオブジェクトです。

FactoryNum {value: 1, increment: ƒ}

上記のようにコンストラクタで作成したオブジェクト自身を指しています。

関数のthis

const checkContent = () => {
    console.log(this)
  }
checkContent();
// Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

上記の通り関数の中でthisを使用するとwindowオブジェクトを参照します。

↓※オブジェクトを参照するthisと関数内のthisの違い

function detail(){
  console.log(`${this.nickName}${this.type}のポケモンです。`);
}

const pikachu = {
  nickName: "ピカチュー",
  type: "電気",
  detail
}
pikachu.detail()
// ピカチューは電気タイプです。
detail();
// undefinedはundefinedタイプです。(windowオブジェクトを参照してます。)
});

pikachuオブジェクトを経由したdetailメソッドの場合は、thisは呼び出し元であるpikachuオブジェクトを参照し、pikachuオブジェクトを経由しない場合には、thisはwindowオブジェクトを参照します。

アロー関数におけるthis

アロー関数にはthisが存在せず、レキシカルスコープ(外部スコープ)を参照します。
オブジェクトを参照するパターンでは、object.method()とするとthisはobjectを参照しますが、アロー関数の場合には、外部のプロパティを参照します。2つのパターンを見てましょう。

const character = {
  name: "Joseph Joestar",
  introduceMyself() {
    const arrow = () => console.log(this.name);
    arrow();
  }
};

character.introduceMyself();
// Joseph Joestar

上記のパターンにおいてthisはアロー関数であるarrowの外部にあるnameプロパティを参照しています。

次のパターンです。

name = "Dio Brando";
// グローバルスコープの変数nameを定義

const introduceMyself = () => {
  console.log(`第3部のラスボスは${this.name}です。`);
}
const charaObj1  = {
  name: 'Giorno Giovanna',
  func: introduceMyself
}
const charaObj2 = {
  name: 'Bruno Bucciarati',
  func: introduceMyself
}
charaObj1.func();
// 第3部のラスボスはDio Brandoです。
charaObj2.func();
// 第3部のラスボスはDio Brandoです。

});

この挙動はアロー関数特有で外部のnameプロパティを参照しに行きますが、呼び出し元のオブジェクトを参照しません。この場合は外部のwindowオブジェクトを参照しております。

(わかりやすい画像を作成して入れる)

イベントハンドラとイベントリスナ内におけるthis

htmlのbuttonタグ用意します。

<button id="eventBtn"></button>

この要素に対してJavascriptでクリックイベントを仕込みます。

const eventBtn = document.getElementById("eventBtn");
  eventBtn.addEventListener("click", function(){
    console.log(this)
    // <button id="eventBtn"></button>
  });
const eventBtn = document.getElementById("eventBtn");
  eventBtn.onclick = function(){
    console.log(this);
// <button id="eventBtn"></button>
  }

コードに若干の違いはありますが、やっていることは同じです。
このパターンのthisはイベントハンドラを設定した要素自身を参照します。
今回では、buttonタグですね。

参照をコピーした場合のthis

オブジェクトで定義されている関数を新しい変数に渡すとthisの参照先が変わります。
実際に見てみましょう。

const pikachu = {
    nickName: "ピカチュー",
    type: "電気",
    detail: function(){
      console.log(`${this.nickName}${this.type}タイプのポケモンです。`)
    }
  };
  pikachu.detail();
// ピカチューは電気タイプのポケモンです。
  const copyDetail =pikachu.detail;
  copyDetail();
// undefinedはundefinedタイプのポケモンです。

では下記のようにするとどうなるでしょうか?

nickName = "ライチュウ";
type = "電気"
// グローバルスコープにnickNameを定義(window.name)
const pikachu = {
    name: "ピカチュー",
    type: "電気",
    detail: function(){
      console.log(`${this.nickName}${this.type}タイプのポケモンです。`)
    }
  };
  pikachu.detail();
// ピカチューは電気タイプのポケモンです。
  const copyDetail =pikachu.detail;
// pikachu.datail関数をcopyDetailへ代入する。
  copyDetail();
// ライチュウは電気タイプのポケモンです。

上記を確認するとthisはwindowオブジェクトを参照していることがわかります。この場合、pikachuオブジェクトを経由せずにdetail関数を実行してます。関数の内部で実行されるthisはグローバルオブジェクトを参照します。
下記画像を確認ください。
スクリーンショット 2020-10-11 16.13.51.png

※こちらはコールバック関数を用いた場合にも同じ挙動をします。
データの参照が変わるとthisの挙動が変わることがありますので、注意が必要です。

参考サイト

現代のJavascript(オブジェクトメソッド"this")
実行環境JSBin

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