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

jqueryメモ オブジェクトにプロパティを追加・削除する方法 

オブジェクトにプロパティを追加する方法

jquery.js
let data = {} //空のオブジェクトを定義
data['name'] = taro ;  //これでdataは{name:'taro'}になった

オブジェクトのプロパティを削除する方法

jquery.js
let data = {name:'太郎',age : 20}
delete data['age']; //data中のageを削除
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OPTICSをJavaScriptで実装した

はじめに

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

今回はOPTICSの実装について解説します。

デモはこちらから。(TaskをClusteringにして、ModelのOPTICSを選択)
実際のコードはoptics.jsにあります。

なお、可視化部分については一切触れません。

概説

OPTICSについては、Wikipediaの擬似コードをそのままコードに起こしただけとなります。

ただし、内部で優先度付きキューを使用する必要があるため、その実装も行っています。

優先度付きキュー

優先度付きキューの実装はシンプルで、以下の関数を用意します。

  • push
    データを追加する。引数は値と優先度の二つ。
  • move
    データの優先度を変更する。
  • shift
    優先度の高いデータを取得する。

注意すべき点としては、優先度に距離を直接設定しているため、「優先度の値が小さい方が優先度が高い」ということです。

内部実装は、値と優先度のペアの配列を保持するようにしています。

コード

優先度付きキュー

class PriorityQueue {
    constructor(arr) {
        this._value = arr || []
    }

    get length() {
        return this._value.length;
    }

    _sort() {
        this._value.sort((a, b) => a[1] - b[1])
    }

    push(value, priority) {
        this._value.push([value, priority])
        this._sort()
    }

    move(value, priority) {
        for (let i = 0; i < this.length; i++) {
            if (this._value[i][0] === value) {
                this._value[i][1] = priority;
                this._sort()
                return
            }
        }
        this.push(value, priority)
    }

    shift() {
        const [value, priority] = this._value.shift();
        return value;
    }
}

OPTICS

class OPTICS {
    // https://en.wikipedia.org/wiki/OPTICS_algorithm
    constructor(eps = Infinity, minPts = 5, metric = 'euclid') {
        this._eps = eps;
        this._minPts = minPts;

        this._metric = metric
        switch (this._metric) {
        case 'euclid':
            this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0));
            break
        case 'manhattan':
            this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
            break
        case 'chebyshev':
            this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
            break;
        }
    }

    fit(datas) {
        const n = datas.length;
        const d = Array(n);
        for (let i = 0; i < n; d[i++] = Array(n));
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < i; j++) {
                const v = this._d(datas[i], datas[j]);
                d[i][j] = d[j][i] = v;
            }
        }
        const getNeighbors = (i) => {
            const neighbors = [];
            for (let k = 0; k < n; k++) {
                if (d[i][k] < this._eps) neighbors.push(k);
            }
            return neighbors
        }
        const coreDist = (i) => {
            const neighbors = getNeighbors(i).map(k => d[i][k]);
            if (neighbors.length <= this._minPts) return null;
            neighbors.sort((a, b) => a - b);
            return neighbors[this._minPts];
        }
        const reachabilityDist = (o, i) => {
            const cd = coreDist(i);
            if (cd === null) return cd;
            return Math.max(cd, d[i][o])
        }

        const processed = Array(n).fill(false);
        const rd = Array(n).fill(null);
        const update = (n, p, seeds) => {
            const cd = coreDist(p);
            if (cd === null) return
            for (const o of n) {
                if (processed[o]) continue
                const nrd = Math.max(cd, d[p][o]);
                if (rd[o] === null) {
                    rd[o] = nrd;
                    seeds.push(o, nrd);
                } else if (nrd < rd[o]) {
                    rd[o] = nrd;
                    seeds.move(o, nrd)
                }
            }
        }

        this._core_distance = [];
        for (let p = 0; p < n; p++) {
            if (processed[p]) continue;
            const neighbors = getNeighbors(p);
            processed[p] = true;
            const cd = coreDist(p)
            this._core_distance.push([p, cd])
            if (cd !== null) {
                const seeds = new PriorityQueue()
                update(neighbors, p, seeds)
                while (seeds.length > 0) {
                    const q = seeds.shift();
                    const nd = getNeighbors(q);
                    processed[q] = true
                    const cdq = coreDist(q)
                    this._core_distance.push([q, cdq])
                    if (cdq !== null) {
                        update(nd, q, seeds);
                    }
                }
            }
        }
    }

    predict(threshold = 0.1) {
        let c = 0;
        const n = this._core_distance.length
        const clusters = Array(n)
        for (let i = 0; i < n; i++) {
            const [k, d] = this._core_distance[i]
            clusters[k] = d > threshold ? ++c : c;
        }
        return clusters
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-navigationで画面遷移をする【ReactNative+expo】

はじめに

初めましてTutuと申します。
最近react-nativeの勉強を始めました。
その中でreact-navigation(5系)を使う機会があったので備忘録として残しておきたいと思います。

準備

react-navitaionをインストール
npm install @react-navigation/native

react-navigation-stackをインストール
npm install @react-navigation/stack

依存ライブラリをインストール(expoを使用)
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

環境

react-navigation-native : 5.8.9
react-navigation-stack : 5.12.6

実装

遷移の設定

CreateStackNavigatorで遷移先登録のための初期化処理をします。(おまじないみたいなものなので気にしないで大丈夫です。)

初期化
const Stack = createStackNavigator();

ここで遷移先の登録と初期条件などを設定します。(今回はfunctionComponentで書いています)
例えばinitialRouteNameに設定した名前が最初の画面になったり、ヘッダーの情報をデフォルトで設定しておくことができます。

※詳細は公式をご覧ください。

画面登録
const RootStack = () => {
    return (
        <Stack.Navigator
            initialRouteName="Home" //最初の画面
            screenOptions={{
                headerStyle: {
                    backgroundColor: "#265366",
                },
                headerTintColor: "#fff",
                headerTitleStyle: {
                    fontWeight: "bold",
                },
            }}>
            <Stack.Screen name="Home" component={MemoListScreen} />
            <Stack.Screen name="MemoDetail" component={MemoDetailScreen} />
        </Stack.Navigator>
    );
};

最後にNavigationContainerでラップし、App.jsに登録することで設定したコンポーネントでreact-navigationを使うことができるようにします。

遷移画面の管理
export default function App() {
    return (
        <NavigationContainer>
            <RootStack />
        </NavigationContainer>
    );
}
App.js(全体)
import React from "react";
import {NavigationContainer} from "@react-navigation/native";
import {createStackNavigator} from "@react-navigation/stack";
import MemoDetailScreen from "./src/screens/MemoDetailScreen";
import MemoListScreen from "./src/screens/MemoListScreen";

//createStackNavigatorを初期化
const Stack = createStackNavigator();

//遷移画面の登録
const RootStack = () => {
    return (
        <Stack.Navigator
            initialRouteName="Home" //最初の画面
            screenOptions={{
                headerStyle: {
                    backgroundColor: "#265366",
                },
                headerTintColor: "#fff",
                headerTitleStyle: {
                    fontWeight: "bold",
                },
            }}>
            <Stack.Screen name="Home" component={MemoListScreen} />
            <Stack.Screen name="MemoDetail" component={MemoDetailScreen} />
        </Stack.Navigator>
    );
};

//NavigationContainerで囲む必要があるためfunctionalコンポーネントを使用
export default function App() {
    return (
        <NavigationContainer>
            <RootStack />
        </NavigationContainer>
    );
}

遷移方法

遷移させたい処理に以下を記述することで遷移が可能になります。
(今回はボタンを押した際に遷移する処理としました。)

data.navigation.navigate("MemoDetail"); 
遷移方法
import React from "react";
import {StyleSheet, View, Text} from "react-native";
import MemoList from "../components/MemoList";
import CircleButton from "../elements/CircleButton";

const styles = StyleSheet.create({
    container: {
        flex: 1,
        width: "100%",
    },
});

const MemoListScreen = (data) => (
    <View style={styles.container}>
    //別のコンポーネントにnavigationの情報を渡している(今回は関係なし)
        <MemoList navigation={data.navigation}/> 
        <CircleButton
            name="plus"
            onPress={() => {
                data.navigation.navigate("MemoDetail"); //遷移先
            }}
        />
    </View>
);

export default MemoListScreen;



最後に

今回react-navigationを実装するにあたり5系かつfunction Component(Const)を利用した情報が少なかったため、備忘録として残しておきました。
また、初学者のため間違っている等ありましたらご指摘いただけるとありがたいです。

参考文献

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

WebGL入門(概念の話)

はじめに

WebGL、できるようになるとかっこいいですが、なかなか理解しづらいですよね。
私もまだ初心者ですが、最初の入門としてこのような記事があればよかったなあと思いながら
備忘録も兼ねて概念の記事を書いてみることにしました。
ソースコードは出てきません。

WebGL

WebGLって何?

WebGLはブラウザからJavaScriptを使用してGPUに命令を出し、結果をCanvasエレメントに描画することができます。
GPUはGraphics Processing Unitの略で、画面上にグラフィックを描画する処理を行うユニットです。
GPUの持つ強い処理能力をブラウザで活用することで、ブラウザで3DCGのような高負荷のグラフィックを表示させちゃおうというのがWebGLです。

GLSL

GLSLは"OpenGL Shader Language"の略で、シェーダと呼ばれたりします。
名前にもOpenGLとあるように、OpenGL上で使われている言語です。
OpenGLとは、

クロノス・グループ が策定している、グラフィックスハードウェア向けの2次元/3次元コンピュータグラフィックスライブラリである。

というもので、
WebGLはこのモバイル版であるOpenGL ESから派生しているためGLSLを使用することができます。
WebGLでは、GLSLを使用して絵を描いていくことになります。
(GLSLはGPUの上で動くプログラムという認識で大丈夫だと思います。)

どうやって絵を描いていくの?

WebGLには
「丸を描いてください」「四角を描いてください」などの命令は存在しません。
基本的にできることは

  1. 頂点をどのように配置するのか
  2. どのように色を塗るのか

です。たとえば四角を描きたい場合、

  1. 4つの頂点を打つ
  2. 頂点で囲まれた中を塗りつぶす。

円を描きたい場合は

  1. 円の形に点を並べる
  2. 囲まれた中を塗りつぶす

といった具合です。打った点の数が少なければカクカクした円になりますね。

「頂点をどのように配置するのか」はVertex Shader、
「どのように色を塗るのか」はFragment Shaderで行います。
ちなみに、必ずVertex Shader -> Fragment Shaderの順で実行されます。

Vertex Shader

Vertex Shaderは頂点シェーダとも呼ばれます。
このシェーダーでは頂点の位置を動かす作業を行います。
「位置を動かす」のが仕事ということで、頂点の基本的な情報(言うなれば元データ)はJavaScriptで生成します。

(例)
三角形を作りたい!しかも回転させたいかも!

JavaScript: 点が3つ必要だな、点の座標はコレとコレとコレだな

JavaScript: 座標データ送信!

VertexShader: 座標データうけとった!え、三角形を回転させたい?じゃあこっちで座標動かして回転させとくで!

という感じです。
たとえば上記の三角形を回転する処理をVertex Shaderで行う場合、
三角形には3つの頂点が存在し、それぞれの頂点の位置を移動させる必要があります。
つまり頂点の移動処理を3回行う=頂点の個数分VertexShaderの処理が発生します。

Fragment Shader

Fragment Shaderはピクセルシェーダとも言われます。
このシェーダーでは受け取った頂点によって作られた図形を
どのように塗るのか決定します。

CGは最終的には必ず画面に表示されます。
画面にはピクセルが存在し、そのピクセル1つ1つに色がつくことで描画されるわけです。
このピクセル1つ1つに対し、何色になるべきかを計算するのがFragment Shaderの仕事です。
つまり描画範囲に存在するピクセルの個数回実行されるため、
描画範囲が広大であったり、画面が高画質であったりすると実行回数が増える=負荷が増える
ことになります。

流れをざっくりと図で

ざっくりですがこんな感じです。
本当はブレンディングなど、もっといろいろな処理が存在します。
webGL_pipeline.png

なにやら壁がありますね、壁については次の項で解説します。

CPUとGPUの壁

CPUとGPUはユニットがそもそも違います。
メモリ空間も別のものになるため、相互でシームレスなやりとりをすることはできません。
GPUで計算を行うためには
CPU側で値をGPUで扱える形式に変換し、値を送り込む処理が必要になります。
処理はざっくりとこんな感じ。
webgl_compile.png

その1: shaderの作成

GLSLを一生懸命に記述しても、それはCPU上ではただの文字列です。
GPUに送ることで初めて動作します。
したがってまずはWebGL APIを使用しGPU上で使用できる形にコンパイルする必要があります。

その2: programの作成

vertex shader, fragment shaderがそれぞれコンパイルできたら、次はその二つをつなげてprogramを作成します。
このprogramがGPUで動作するプログラムになると捉えてよいと思います。
このプログラムは1度作ればOKなので、最初の初期化として生成します。

その3: データの変換

頂点座標を入れた配列など、JavaScript上で取り扱っていた頂点毎のデータは、
GPUで使用できるように変換する必要があります。
これをVertex Buffer Object、略してVBOと言います。
頂点毎のデータとは座標のような、頂点ごとに異なる必要があるデータのことです。
例えば頂点毎に違う座標、違う色にしたい場合などは座標のVBO, 色のVBOというように、
属性毎にVBOを作成します。

その4: ロケーションの取得

あとは描画に必要なデータをprogram(GPUで動作するプログラム)へ送ってしまうだけです。
しかし、C言語などでいうポインタのような、送り込む先を指定しなければなりません。
これをロケーションと言います。

「vertex shaderのこの変数に座標の値を入れたいのかい?
じゃあこの変数の在り処(アドレス)を教えるね、その場所にデータを入れればここにちゃんとデータが入るよ」

というイメージです。

ちなみに図中のAttribute変数は
頂点毎に異なる変数のこと(頂点座標や色の指定など)

Uniform変数は
どの頂点、どのピクセルでも等しいグローバルな値(時間、マウス座標など)のことです。

その5: shaderへ値の送信

送り込む先のロケーション(アドレス)を教えてもらったので、これでようやく送信ができます。
送信が完了したら描画命令を発行しましょう。GPUでの処理が行われます。

番外編: ループ処理

もしアニメーションなどを行う際にJavaScript側のデータを更新するときは

  1. JavaScriptの元データ書き換え
  2. VBOの再生成
  3. programへデータ送信

を毎度やる必要があります。

pure webGLの処理

これらの処理を一生懸命に記述していく必要があります。
詳しい処理内容はDoxasさんのwgldをご覧ください。
ものすごく詳しく描いてあります。
WebGL 開発支援サイト wgld.org

おわりに

WebGLは初期セットアップを行うだけでも理解が難しく、大変苦しみました。
概念的なところを、とてもざっくりとしか書いていませんが、
私はこのような大きい流れから把握して少しずつ理解ができました。
もし同じような方がいれば、少しでも参考になればと思っています。

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

Vue.Jsのv-forディレクティブはforEachと考えればしっくりくる!

この記事について

JavaScriptもろくに書けないにも関わらずいきなり、Vueをバリバリ使った案件にぶち込まれて、はや半年。最初に理解に苦しんだのが"v-for"。
今ではv-forにはかなりお世話になっているが、最初のうちはもの凄く苦しんだ。
何せfor文すら書けなかったのだから無理もない。

そもそもv-for とは??

まずVue.jsを触りはじめたときは、ディレクティブすら意味が分からなかった。
よくもまあそんな状態で初めたものである。

簡単にいうとディレティブとは、Html側でJavaScriptっぽいことができるように、
Vue.Jsで用意されている便利な部品のこと だと私は思っている。
Vueにはいろんなディレクティブが用意されている。それらを駆使することによって、
ScriptからHtmlへのアクセス(逆もしかり)が非常に簡単になるというのがVue.Jsの
本質かと思う。

それではv-for は何かというと
公式サイトによると

v-for で配列に要素をマッピングする
配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。v-for ディレクティブは item in items の形式で特別な構文を要求し、items はソースデータの配列で、item は配列要素がその上で反復されているエイリアスです:

半年前のど素人の私なら、完結に書かれすぎてさっぱり分からなかった。
今見返すと実に完結に書かれてある。(笑)
当然、初学者にはわかるはずもない(笑)私だけだったりして

私なりの見解だが、ある配列の値を表示するために、v-forというVueが用意した
便利な部品を使って、item in itemsみたいな書き方でその値を取得して、itemで反復して配列を呼び出すことができるというのがv-forだ。
自分で書いていてもよくわからなくなってきたので、JavaScriptのforEachを考えればしっくりくるのではないか?

試しにforEachを使って配列の値をconsole.logで表示させるコードを下記に示したい。
items という配列をitemという引数を使って、一つずつ描画していることがわかる。
Vue.jsではv-forを使うことで、いちいちforEachなんて書かなくて済むのだ。

JavaScript
const items = ['item1', 'item2', 'item3']
const copyItemsArray = []
items.forEach((item) => {
  copyItemsArray.push(item)
 console.log(item)
})
//結果は
//item1
//item2
//item3
//と表示される。

もう少しforEachにお付き合いいただきたい。
今度は連想配列で考えてみる。

JavaScript
const objectItems = [
  {name:"太郎",age:22},
  {name:"治郎",age:20},
  {name:"三郎",age:18}
]
const objectArray = []
objectItems.forEach((item)=>{
  objectArray.push(item)
  console.log(`名前:${item.name}/年齢:${item.age}`)
})
//名前:太郎/年齢:22
//名前:治郎/年齢:20
//名前:三郎/年齢:18

forEachを知って、かなりしっくりきた。
というかv-forもforEachもかわらんやん(笑)

さて、ここで、v-for を使用して描画とやらをやってみる。
CSSは苦手なので、Vuetifyを使用している。
それと普段のプロジェクトで、TypeScriptを触っている都合上
私が一番慣れている書き方で書かせていただいた。

Vue.js
<template>
    <div>
        <ul>
            <li v-for="(brother,i) in brothers" :key="i">名前{{brother.name}}/年齢:{{brother.age}}</li>
        </ul>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

interface Person {
    name:string
    age:number
}
@Component({})
export default class extends Vue{
    brothers:Person[] = [
        {name:"太郎",age:22},
        {name:"治郎",age:20},
        {name:"三郎",age:18}
    ]
}
</script>

img1.png

↑こんな感じで描画される。
v-forを使用するときは、注意点としてkey要素というものをつけなければならない。
大抵の場合はオブジェクトの中にidなんかがあってそれをkey要素として返してあげるのだが、とくにid指定がないときなんかはindexで返してあげるのがベター。
その場合は、v-forのitemの2番目の引数としてindexを取ることができるので、
(item,i)のように書いてあげる。
このv-for のkeyの動作については下記のサイトに非常に詳しく載っているのでぜひ参考にしてみてほしい。
参考: 【徹底解説】これを見ればわかるvue.jsのv-forのkeyの動作

さて、せっかくUI frameworkとしてVuetify.jsを使用しているのだから、
何かコンポーネントを使用して描画してみよう。
今回はv-simple-tableというコンポーネントを使用。
その名の通り、めっちゃシンプルなコンポーネント。

Vue.js
<template>
    <div>
        <ul>
            <li v-for="(brother,i) in brothers" :key="i">名前{{brother.name}}/年齢:{{brother.age}}</li>
        </ul>
        <v-simple-table>
            <template v-slot:default>
                <thead>
                    <tr>
                        <th>名前</th>
                        <th>年齢</th>
                    </tr>
                </thead>
                <tbody>
                    <tr
                        v-for="(brother,i) in brothers" :key="i">
                        <td>{{brother.name}}</td>
                        <td>{{brother.age}}</td>    
                    </tr>
                </tbody>
            </template>
        </v-simple-table>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

interface Person {
    name:string
    age:number
}
@Component({})
export default class extends Vue{
    brothers:Person[] = [
        {name:"太郎",age:22},
        {name:"治郎",age:20},
        {name:"三郎",age:18}
    ]
}
</script>

img2.png

↑こんな感じで描画される。

v-forなんて簡単

Vue.Jsなんて所詮はJavaScriptのフレームワーク(笑)って言ってみたかったのだが、
半年前の自分にいってやりたい。
まだまだひよっこエンジニアの私だが、最初は公式ページを見てもちんぷんかんぷん。
何度辞めたいと思ったことか、、、、、、

Vue.Jsは慣れれば非常に便利なツールだと思っている。
なんなら私なんて、innerHTML とか書けない(笑)
恥ずかしくて言えないが、、、、、
それはいいか悪いのか別として、非常に楽なツールである。
最初、Vueの勉強を始めたときは、”学習コストが低いのがVue。だから、初心者向きなんだ”って書いていたサイトを見て、俺は全く理解できていないから、もう辞めた方がいいのか、、、なんて本気で悩んだこともある。

勉強する順序が悪かっただけだった。
これからもVue.Jsを使ってガンガン開発に挑戦してみようと思っている。

最後まで読んでくださった方ありがとうございました。

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

【vue.js】Vuexを学ぶ

Vuexの概念が何となく理解出来たので、ついでに使い方もまとめておく。
自社の開発においても大量のコンポーネントを扱わなければならない中でVuexが必須となっているので、勉強し直した。

Vuex(ビューエックス)とは何か?

Vuex とは何か? | Vuex

ググったら一番最初に出てくるこれを見てもイマイチ分からない人に向け、
vue.jsの基本は一応分かっている前提で説明すると、
スクリーンショット 2020-11-14 19.07.13.png
沢山のコンポーネントがあるそれなりの規模のプロジェクトがあったとする。
さて、この図でComponentAのデータをComponentCやComponentEに渡すにはどうしたらいいだろうか。

親コンポーネントから子コンポーネントへ、
また子コンポーネントから親コンポーネントへのデータのやりとりが出来るのはわかると思う。でも、孫から子へ、それを親に渡し、さらにそれを子から孫へ、みたいなやりとりは手間である。($emitして$emitしてpropsしてpropsして見たいにやるのはスマートではない。)
スクリーンショット 2020-11-14 19.29.39.png
そこでVuexを使うと、グローバル変数のような感じでデータを扱うことが出来るようになると言う感じ(概念)。と言われれば分かり易いのではないだろうか。これで深いコンポーネント同士でのやりとりが出来るようになる。

導入

Vuexのパッケージをインストールする。
vue cliでプロジェクト作成時にセットでインストール済みである場合は必要ない。

npm install vuex

vuex用のファイルを新たに設ける。
ファイル名は何でも良いのだが、store.jsとするのが一般的。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex); // vuexをvue全体で使用する宣言

export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

あとはmain.jsで読み込むだけ。

main.js
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store' // store.jsをインポート

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  store, // store: store,
  components: { App },
  template: '<App/>',
});

stateを使ってみる

stateとは?

文字通りstoreの状態をプロパティーとして返すと言う位置付けになろうか。前述の通り、ここで定義した内容はグローバルに取得する事が出来るようになる。

store.js
export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

ComponentAで表示する変数を

ComponentA.vue
<template>
  <p>{{ number }}</p>
</template>

<script>
  export default {
    computed: {
      number() {
        // $storeとする事でどこからでも呼べるようになる
        return this.$store.state.number;
      }
    }
  }
</script>

ComponentEから操作出来る、みたいな事が出来る。

ComponentE.vue
<template>
  <b-button @click="increment">+1</b-button>
</template>

<script>
  export default {
    methods: {
      increment(){
        // $storeとする事でどこからでも呼べるようになる
        this.$store.state.number++;
      },
    },
  }
</script>

gettersを使ってみる

gettersとは?

storeの状態を算出したい時にgettersを定義する事で、算出プロパティーとして使う事が出来る感じ。

store.js
export default new Vuex.Store({ // インスタンスをmain.jsで読み込めるようにする
  state: { // 状態を全体で使用出来るようにする
    number: 0
  },
  getters: {
    counter: state => state.number++, 
  }
})

こんな感じでVuexの中でcomputedのような事が出来る。

ComponentA.vue
<template>
  <p>{{ count }}</p>
</template>

<script>
  export default {
    computed: {
      count() {
        return this.$store.getters.counter; // $store.gettersで呼ぶ
      }
    }
  }
</script>

まぁこの程度であればgettersを使う意味はないのだけど、
よりコンポーネントが複雑化してきた時に真価を発揮するものなのでそこはご容赦頂きたい。

複数のgettersを扱う時にもっと便利に使う事が出来るのが、mapGettersである。

store.js
export default new Vuex.Store({ 
  state: { 
    number: 2
  },
  // 複数定義しているものがあるとする
  getters: {
    doubleCount: state => state.number * 2,
    tripleCount: state => state.number * 3
  }
})

こう書くことも出来るが、

ComponentA.vue
<script>
  export default {
    computed: {
      doubleCount() {
        return this.$store.getters.doubleCount;
      },
      tripleCount() {
        return this.$store.getters.tripleCount;
      },
    }
  }
</script>

mapGettersを用いると一気に整理される。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // 配列
    computed: mapGetters(["doubleCount", "tripleCount"]),
  }
</script>

配列ではなく、オブジェクトでも良い。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // オブジェクトでも良い
    computed: mapGetters({
      doubleCount: "doubleCount",
      tripleCount: "tripleCount"
    }),
  }
</script>

必要なgettersだけを取ってくる事が出来るので是非使いこなしたい。
ちなみにこのままだと、computedにmapGetters以外に記述する事が出来ないので、普段使いでは

ComponentA.vue
<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    // スプレッド演算子(object spread operator)を使って組み込む
    ...mapGetters([
      'doubleCount',
      'tripleCount',
    ])
  }
}
</script>

という感じで使うっぽい。

mutationsを使ってみる

mutationsとは?

stateを使う事でコンポーネント間のデータのやりとりは簡単にはなるけれど、
何でもかんでも自由にやりとりをさせ過ぎると、逆に分かりづらくなったり、追跡、管理が煩雑になる。そこで使用するのがmutationsという事のようだ。

store.js
export default new Vuex.Store({
  state: {
    number: 2
  },
  mutations: {
    increment(state, str) { // 第一引数にstateをとり、実際の変更を記述する
      state.number += str;
    }
  }
})

呼び出す側ではstore.commitでmutationsを指定する。

ComponentA.vue
<script>
methods: {
  increment(){
    this.$store.commit('increment', 2);
  },
},
<script>

stateをあっちこっちで変更する事は思わぬバグを発生させる可能性があるから、
mutationsで扱いましょうという感じだろうか。

また、mutationsにもmapMutationsというヘルパーが存在する。

ComponentA.vue
<script>
import { mapMutations } from "vuex"; // mapMutationsをインポートする

  export default {
    methods: {
      // mapGettersと同じようにスプレット演算子で書ける
      ...mapMutations(["increment","decrement"]),
    },
  }
</script>

ちなみにmutationsは同期的でなければならないというルールがある。
では非同期を扱うにはどうしたらいいのか。

actionを使ってみる

actionとは?

actionはmutationsと異なり、任意の非同期処理を含む事が出来る。
なので、setTimeoutで一定時間後に特定の処理を行うという記述も可能となっている。

store.js
export default new Vuex.Store({ 
  state: { 
    count: 2
  },
  actions: {
    increment({ commit },number) {
      commit('increment', number);
    }
  }
})

store内のactionsでcommitし、コンポーネントでdispatchする。

ComponentA.vue
<script>
methods: {
  increment() {
    this.$store.dispatch('increment', 2); 
  }
},
</script>

非同期でもOK

store.js
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

最後はmapActionsヘルパー、これまで出てきたmapと同じ感覚で使用出来る。

ComponentA.vue
<script>
import { mapActions } from "vuex";

  export default {
    methods: {
      // スプレット演算子で書ける
      ...mapActions(["increment","decrement"]),
    }
</script>

まとめると・・・

・stateでvuexの状態を管理
・gettersでstateの変更を算出
・mutationsでstateの状態を変更、commitで呼び出される
・actionで同期、非同期なデータの処理、必要に応じてcommitする

これらの基本に基づいて、導入するプロジェクト毎にどうVuexを使っていくかを取捨選択していく形になるという事が分かった。

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

nodeList.forEachを使用してJavaScriptの同一処理をスマートに書く

はじめに

※ JavaScriptが書ける人には不要な記事です。

ウェブサイトにおいてスライダーやモーダル、その他タブ等を実装するとき、そのページ上に複数存在する場合の記述方法の解説です。

今回はひとつのページ内で、スライダー(Swiper)を複数設置するシチュエーションがあったとします。

ググるとよく出てくるやり方

まったく同じスライダーがページ内に3個あったとします。ググるとよく出てくる情報が以下のようなやり方です。

<div class="js-swiper-1"></div>
<div class="js-swiper-2"></div>
<div class="js-swiper-3"></div>
const swiper1 = new Swiper('.js-swiper-1', {});
const swiper2 = new Swiper('.js-swiper-2', {});
const swiper3 = new Swiper('.js-swiper-3', {});

スライダーが100個あったら100個同じことを書くのでしょうか?いえ、無理です・・・。

NodeList.forEach を使用する方法

結論から書きます。

<div class="js-swiper"></div>
<div class="js-swiper"></div>
<div class="js-swiper"></div>
const sliders = document.querySelectorAll('.js-swiper');

sliders.forEach((slider) => {
  const swiper = new Swiper(slider, {});
});

解説

今回のものを実装するのに必要な知識は以下です。

STEP1: Document.querySelectorAll() でNodeListを作る

まずは対象の要素をDocument.querySelectorAll()を使用してNodeListを作成します。

const sliders = document.querySelectorAll('.js-swiper');

これにより、変数slidersにはNodeListが入ります。

STEP2: NodeList.forEach()をまわす

NodeListにはNodeList.forEach()というメソッドがあります。
要はNodeListに入っている要素の数だけ順番に処理が走ると言うことです。

そして、NodeList.forEach()のコールバック関数内で、各スライダーの処理を走らせます。

sliders.forEach((slider) => {});

// アロー関数ではなく従来の関数で書くと...
sliders.forEach(function(slider) {});

STEP3: コールバック関数内に処理を書く

コールバック関数内で、スライダーの処理を定義します。

sliders.forEach((slider) => {
  const swiper = new Swiper(slider, {});
});

DONE

これでスライダーの数が何個あったとしても、記述を追加する必要はありません。
もちろんコールバック関数の中身は自由に書けるので、例えば対象がアコーディオンだったとしても問題無く実装できます。

アコーディオンの実装例

<dl class="faq">
  <div class="faq__item js-accordion">
    <dt class="faq__question js-accordion-toggle"></dt>
    <dd class="faq__answer js-accordion-content"></dd>
  </div>
  <div class="faq__item js-accordion">
    <dt class="faq__question js-accordion-toggle"></dt>
    <dd class="faq__answer js-accordion-content is-hidden"></dd>
  </div>
  <div class="faq__item js-accordion">
    <dt class="faq__question js-accordion-toggle"></dt>
    <dd class="faq__answer js-accordion-content is-hidden"></dd>
  </div>
</dl>
const accordions = document.querySelectorAll('.js-accordion');

accordions.forEach((accordion) => {
  const button = accordion.querySelector('.js-accordion-toggle');
  const content = accordion.querySelector('.js-accordion-content');

  button.addEventListener('click', () => {
    content.classList.toggle('is-hidden');
  });
});

注意

本例でのJavaScriptはIE11では動作しないので、別途対応が必要です。

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

アツマールでコメントの個人特定をする方法

筆者メモ

この記事は、もともとNoteで公開していた内容と完全に同じ内容です。
元の記事:https://note.com/hadhad/n/nad8548c4943d

もともと100円で売ってたんですが、誰も買わなかったので無料にしたところ、
twitterでプチバズったのにNoteのフォロワーがまったく増えなかったので、
もうQiitaに自己転載して、もっといろんな人が見れるようにしました。

記事に記載したプラグインやソースコードはWTFPLライセンスで配布します。勝手にして、どうぞ。

はじめに

RPGアツマールのコメント機能は、ニコニコ動画と違って投稿したIDが公開されていない。
そのため、コメントした人全員が184コメントをしているような扱いになっている。
また、ゲーム側でコメントのNG機能も用意されてはいるが、ID指定ではなく「キーワード」での指定となっている。

その為に、RPGアツマールでは、ゲームをコメントで荒らされた場合、
ユーザー単位でNGしたいのに、できないという仕様になっている。

Twitter等で「アツマール コメント NG」とかで検索してみると、
ユーザーID特定機能が欲しいという人もチラホラ存在する。

だが僕は思う。
運営を頼る以前に、自分で作れるハズだ
と。

かくゆう自分も、この問題に直面していた。
気に入らないコメントは削除すればよいのだが、
コメントの削除は運営の人力で申請制のため時間がかかるし、
消せたとしても粘着質なユーザーは
すぐに同じようなコメントを書き込むからだ。

そこで僕は思った。
「コメントした人のIDを特定して、
 指定IDの人をBANできるプラグインを作ったら面白そうだ」

と。

結果、僕はこのプラグインの開発に成功した。

どうやったのかを次に記す。

コメントを個人特定する仕組み

予め断っておくが、この方法では、
既に書き込まれている過去のコメントからIDを特定することはできない。

今回の方法はあくまで、
「仕組みを取り入れた瞬間からのコメントが特定できる」
という話だ。

この仕組みを実現するために僕は、
「コメントを投稿する瞬間」に細工をすることにした。

用意するアツマールのAPIは次の通り。
コメントAPI
シグナルAPI

コメントAPIには、
「コメントを行った瞬間にコメント内容を取得できる」
関数が用意されている。

そしてシグナルAPIには
「100バイト以内の文章をユーザー情報付きで送信できる」
関数が用意されている。
またグローバルシグナルとして送信された情報は、
1000件までサーバーに記録される。

ここまで見て、察しが良い人はもう分かっただろう。

つまり、
1.コメントを行った瞬間に
2.ユーザー情報とコメントの内容をグローバルシグナルとして送信し
3.確認したいときにグローバルシグナルの中身を覗く

という順序を辿れば、コメントの個人特定が可能というわけだ。

さて、仕組みを理解できたところで、
さっそくこれをツクールMVプラグインにしてみよう!

コメントのユーザー情報を記録するプラグイン

このプラグインは、ツクールMVに適用するだけで効果を発揮する。
ソースコードは次の通り・・・

/*:
* @plugindesc 投稿されたコメントをグローバルシグナルに送信
* @author Had2Apps
* @help
* コメントが投稿されると同時に、グローバルシグナルに内容が送信されます。
* これにより、誰がどのコメントを行ったかを特定することができます。
*
* グローバルシグナルの一覧をコンソールに表示したい場合は、スクリプトで
* window.RPGAtsumaru.signal.getGlobalSignals().then(console.log)
* を呼び出してください。
*/
(function () {
 if (window.RPGAtsumaru === undefined) {
   console.warn("APIが読み込めません");
   return;
 }
 function shorten(text) {
   var chars = text.split("");
   var current = 0;
   var temp = "";
   for (i = 0; i < chars.length; i++) {
     var escapedChar = escape(chars[i]);
     current = escapedChar.length < 4 ? current + 1 : current + 2;
     if (current > 100) return temp;
     temp += text.charAt(i);
   }
   return text;
 }
 function sendCommentLog(res) {
   var comment = res.comment;
   var log = shorten(comment);
   window.RPGAtsumaru.experimental.signal.sendSignalToGlobal(log).then(() => {
     console.log("OK");
   });
 }
 window.RPGAtsumaru.comment.posted.subscribe(sendCommentLog);
})();

解説しよう。

ここで使用されているAPI関数は
RPGAtsumaru.comment.posted.subscribe
RPGAtsumaru.experimental.signal.sendSignalToGlobal
の2つだ。

※今は experimental. なしでも動くらしい。動かなかった場合は外してみたらいいんじゃないかな。

RPGAtsumaru.comment.posted.subscribe は、
引数にコールバック関数を渡してやることで、ユーザーがコメントを投稿した瞬間に、コメント内容を引数に渡した状態でコールバック関数が実行されるようになっている。
このプラグインでは、後者のAPI関数を使った処理をコールバック関数として指定した。

RPGAtsumaru.experimental.signal.sendSignalToGlobal は、
引数に100バイト以内の文字列を渡してやることで、その文字列とユーザー情報をRPGアツマールのサーバーに送信してくれる。
shorten 関数では、取得されたコメントが100バイトに収まるように調整している。

ちなみに、送信されたグローバルシグナルを確認したい場合は、
スクリプトで次の処理を書き込めばいい。

if (window.RPGAtsumaru !== undefined) {
  window.RPGAtsumaru.signal.getGlobalSignals().then(console.log)
}

この処理が実行されることで、
ブラウザのコンソール画面にグローバルシグナルの一覧が表示される。
これを見ればOKだ。

応用編:ユーザーIDのNG機能を実現する

ここまで読んだ人なら、ユーザーIDのNG機能を実装するのも、
そう難しくないはずだ。

ようは、上述のプラグインで取得したIDが一致するユーザーが
ゲームをプレイできないように
細工すればいいのだ。

本記事では、実際にどのようにコードを書けばいいかは掲載しないが、
実現するためのヒントを以下に載せておこうと思う。

まず、NG機能を実装するのに使えそうな追加のAPIは次の通りだ。
ユーザー情報取得API
コメントAPI:シーン切り替え

まずユーザー情報取得APIは、
今プレイしているユーザーのIDなどを取得できるAPIだ。
これを使用することで、グローバルシグナルをいちいち送信しなくても
ユーザーIDを取得することができる。
なので、プラグインで取得した荒らしのユーザーID
このAPIの情報を照らし合わせて、処理を変えるようにすればいいのだ。

次にコメントのシーン切り替えAPIは、
コメント投稿先のシーン名を強制的に変更できるAPIだ。
これを応用することで、荒らしユーザーだけ
荒らし専用のシーンに強制的に投稿させる
仕組みを作り、
他のユーザーからその荒らしのコメントを見れなくすることができる。

つまり処理の流れとしては、こうなればいいわけだ。
1.プラグインから荒らしのユーザーIDを予め指定しておく
2.ユーザー情報取得APIでプレイヤーのIDを調べ、荒らしのIDと照合
3.一致する場合、コメントのシーンを荒らし専用シーンに強制設定
4.ゲームをプレイ不能にする

先ほども書いた通り、
この記事ではそのプラグインをどのようにして
作るのかに関しては書かない。

もしプラグインを書いたことがない場合は、
勉強がてら作ってみることをおススメする。

これにて以上!

さいごに

(この部分はもともとのNote記事にはなかったことです)

この方法を使えば、荒らしコメントを書く輩のリストを作ることが可能なので、
荒らしユーザーリストを作者のみんなで共有すれば、
実質「NG共有機能」みたいなことができるはず。

特定のユーザーのプレイを禁止するようにできれば、荒らし撲滅できると思う。
わりと過激な考え方だけど。

ちなみにこの技術は、すでに個人的にも試験運用していて、
悪質コメントをした人のハンドルネームとアカウント名を特定し、その人の投稿した動画や、10年前のMMORPG掲示板の黒歴史な書き込みを特定することに成功した。

今は、その人が次に来た時に「特別なイベント」を開く罠を仕掛けて、様子を見てるところだったりする。
そのゲーム: https://game.nicovideo.jp/atsumaru/games/gm14221

この記事で書いた技術は、あくまで良いことに使ってほしいです。
悪用しようと思えばできちゃうと思うんだよね。
絶対に、人の迷惑になるようなことには使わないこと!

そして自分がターゲットになっても困らないように、身に振り方には気を付けよう!

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

TECH CAMP (エンジニア転職)6週目の学習内容の振り返り

火曜日から本格的に最終課題の学習にはいったのですが、火曜日から日曜日までで6割ぐらいは進めた(?)と思います。この調子ですすめて22日までは必要な作業すべてを終わらせたいですね。それが終われば自分のポートフォリオを進めれる、、、ぐへへへ(変人感)
復習に入っていきたいところですがその前に、最終課題とはなんなのか、どんなことをやるのか少し話していきたいと思います。最終課題ではメルカリのようなフリマアプリの作成を通してサーバーサイドの実装を行っていきます。フロントサイドに関してはすでに用意されているのでやらなくても大丈夫です。この最終課題と今までの学習内容の違いはなにかというと、目標を達成(機能実装)するための手順を自分で考えないといけないところです。今までのカリキュラムだと、テキストに沿って決められたことをこなしていけばよかったのですが、最終課題においては機能実装を行うためにどのような工程が必要か自分で道筋を立てながら学習していくことになります。
例えば、ユーザー登録機能を作るならカラムの洗い出しとDB作成、必要なアクションの定義などなどがあります。最終課題以前だとカリキュラムに書かれたそれらの項目をこなしていくことで実装できるのですが、今回だと実装のための過程が明示されていないのでプロセスを考えないといけないという難しさがあります。私は今の所は大丈夫ですが、終盤にどうなるかが心配です。(笑)
ではでは、今週の学習内容をざざっと振り返ろうかなと思います。

11/10~11/15までの進捗状況
・herokuへのデプロイ
・Basic認証の導入
・テーブル read.meの作成
・ユーザー管理(登録 ログイン ログアウト)機能の実装
・商品を出品機能の実装
・トップページに商品を表示する機能の実装

herokuへのデプロイについて
これと認証に関してはやることがそう多くないですね
heroku内にアプリケーション作成をして、mysqlを作成、アプリケーションの情報をgitからプッシュしてマイグレーションを実行することでデプロイが可能となります!

Basic認証について
Basic認証はauthenticate_or_request_with_http_basicメソッドを用いることでidとpasswordを要求することができます。ここで注意すべき点は、環境変数にidとpasswordを入れることです。コントローラーにそのまま設定してたらどちらもgithubで漏洩してしまうので。

テーブル設計 Read.meの作成
それぞれのテーブルに必要なカラムを洗い出します。難しいことはないですが、read.meのバー(|や-)を揃えるのがかなり面倒です

ユーザー管理機能の実装
deviseの導入と正規表現の導入がかなり苦戦しました。
passwordだと半角英数字混合6文字以上というのがあったり、半角カタカナ限定だったりと、、、あとちゃんと機能しているか確認のためテストコードを書くのですが、100行以上かくはめになりました。がんばったなぁ()

商品を出品機能の実装
ここでは商品の状態や発送元、送料の負担などをプルダウン機能を用いて実装していくのですが、結構調べるのに時間がかかってしまいました。商品の値段に下限と上限があり、そのバリデーションをどうかけるのか全くわからなかったですね。自分のググり力が上がる食べ物があったらそれを主食にしたいです。

トップページに商品を表示する機能の実装
トップページに登録された商品が新しいものから上に表示されるように実装を行いました。
横並びにするのに試行錯誤して2時間ほどかかってしまいました。

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

現役保育士+駆け出しエンジニア

初めまして。現在保育士をしていて、WEB系エンジニアに転職を考えているNOZOMIです。

学習を始めて1ヶ月が経とうとしています。
現在の私の学習状況と取り組みについてまとめておこうと思います。

ドットインストール

完了済み

  • 実践!アプリ紹介ページを作ろう (全16回)
  • 詳解HTML 基礎文法編 (全22回)
  • はじめてのRuby (全9回)
  • JavaScriptでモーダルウィンドウを作ろう (全8回)
  • JavaScriptでタブメニューを作ろう (全10回)
  • はじめてのPython (全9回)
  • はじめてのJavaScript (全11回)
  • 【試験運用中】はじめてのPHP [macOS版] (全12回)
  • 【試験運用中】Dockerを導入しよう [macOS版] (全2回)
  • はじめてのCSS (全15回)
  • はじめてのHTML (全14回)

進行中

  • 詳解CSS 基礎文法編 (全33回)
  • CSSでチャット風のUIを作ってみよう (全7回)
  • 詳解HTML フォーム部品編 (全8回)
  • Ruby入門 (全26回)
  • 詳解JavaScript 基礎文法編 (全26回)
  • 詳解PHP 基礎文法編 (全34回)

かなりのスロースターターと自負しております・・・。徐々にエンジン全開にして行けるように頑張ります!!

雑食系サロン入会

YOUTUBEがきっかけで勝又さんの雑食系サロンに入会しました。かなりレベルの高い方がたくさんいらっしゃって、ポートフォリオも参考にさせていただいています。オフ会やもくもく会等参加したいと考えています。

MENTA

雑食系サロンでのYOUTUBEライブで、未経験者はメンターをつけてコードレビューをしてもらうべきとのお話を伺い、MENTAに登録し、chilldrainさんに5000円でポートフォリオ作りのロードマップを作成していただいています。

企業探し

保育士の経験を活かして働いて行けるような、保育系システムの自社開発系企業に絞りました。


  • エクシオジャパン

  • 日本ソフト開発株式会社

  • キッズコネクト株式会社

来年の4月入社を目指して、頑張ります!!

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

配列変数 と 連想配列 初学者が初学者向けにまとめた

どうも。
今まで掲示板等々を学習がてら作成してきましたが、5ヶ月というスパンで焦り走りしてきた事で
「フワッとした理解」で「〇〇機能は〇〇で実装できるだろう」と何となく分かるものの、Google先生の力がなければこれ一つ自分で実装できない僕が、
一つ一つの細かな理解が疎かであることから、
これから基礎の基礎を深く理解するためにアウトプットの場としてQiitaに投稿していきます。

自身でGoogle先生を活用して数十サイトや書籍を参考に理解を深めながら、
1、、実際にコードを書いて
2、、ノートに自分の言葉でいつかの自分が見ても伝わるようにまとめていく
3、、最後にQiitaに他の方が見ても分かるよう伝えていく

※間違い部分、誤解している箇所が出てくる可能性もあります。

[配列とは]

複数(プロパティ)のデータをまとめて整理して1つの大きな箱にいれるイメージ。
333.png
出力結果:アイス

呼び方まとめ

・青box1つ1つのまとまりをプロパティと呼ぶ。

・ここでは$box全ての部分を【配列】と呼ぶ。

・自動的に割り当てられる添字のことを【キー】と呼ぶ。

・アイス、チョコアイス、カレー味アイスの部分を【値】と呼ぶ。

問題

ee.png

正解はスクロールしたらすぐ出てきますので、ここから下にスクロールせず解答してみること。

正解:2.3.4.1

※プロパティを要素、配列をインデックスとも呼ぶ。

問題2

Hamburger 配列変数に
テリヤキバーガー/ベーコンレタスバーガー/ホットケーキ
上記3つのハンバーガー[]を上記の順番に格納すること
最後にテリヤキバーガーを出力結果に表示してください

先ずは自身でやること。
答えは以下のリンクを押すと出てきます。
https://paiza.io/projects/pik0u6YBKOv40ZuMP-pG6g

次に、$Hamburger配列変数からテリヤキバーガーとホットケーキを改行して出力結果に表示してください。
https://paiza.io/projects/pik0u6YBKOv40ZuMP-pG6g

改行の仕方は複数通りあるため上記URLの方法以外でも改行して出力出来ていればOK!!

ちなみに、上記URLの通りに文字列の中に変数を取り入れる場合は""(ダブルクォーテーション)です。
ダブルクォーテーションとシングルクォーテーションの違いは以下URLが非常に分かりやすいです!
https://qiita.com/bitcoinjpnnet/items/64458299eaeefbacab44

配列に格納されている値を全て出力結果に表示するには?

foreach文を使います。

【foreach文とは】
(リスト)や(配列)に格納されたデータ[要素とも値とも呼ぶ]に対して記述[プログラム]された処理を
繰り返し実行する力を持っているループ文です。

$box = ['アイス','チョコアイス','カレー味アイス'];
foreach($box as $value) {
    echo $value;
}
出力結果:アイスチョコアイスカレー味アイス

格納されたデータが全て出力されると繰り返し終了します。

また、添字[キー]も出力したい場合には

foreach ($box as $key=>$value){
echo "$key=>$value \n";
}

出力結果:
0=>アイス
1=>チョコアイス
2=>カレー味アイス

おさらいです。
文字列の中で変数展開したい場合はダブルクォーテーションで囲む。
\nは改行を意味します。
Macユーザーの方は半角[optionキー+¥]。

$valueの部分は任意名です。

問題3

Hamburger配列変数に格納されてる値を全て出力結果に表示してください。

答え
https://paiza.io/projects/pik0u6YBKOv40ZuMP-pG6g

Hamburger配列変数に格納されている値とキーを全て出力結果に表示してください。
条件
改行を行うこと。
カンマ/コンマをいれること

表示結果例
キー=>値、
キー=>値、
キー=>値、

答え
https://paiza.io/projects/pik0u6YBKOv40ZuMP-pG6g

但し、やり方は複数通りある。

これである程度配列についての理解が出来たのではないでしょうか?
配列を初めて聞いた方は少しまだ理解できていないかもしれません。
Google先生に「配列 とは」で検索すると結構出てきます。

それでは次に連想配列について理解を深めていきたいと思います。

連想配列とは

任意の文字列をキーに割り当てる事が可能。
・文字列(クォーテーションで囲むもの)
・整数

連想配列の良いところ

・キーが任意文字列のため値と同じ意味を持たせた文字列にすればパッと見て分かりやすい!
例: Apple =>りんご car => 車

・1つの小箱(要素ともいうプロパティともいう)にキーと値で2つの意味を持たせれる。
例: 顧客の連絡先と名前を管理したい場合
例: '〇〇会社小林様' => xxx-xxxx-xxxx(これは電話番号です)

実際に試して覚えよう!

連想配列のキーに割り当てられるが注意すべき事

・true false ・・・ キーに割り当てれるが、true = 1 false = 0に自動変換される
・小数点       ・・・ キーに割り当てれるが、自動的に切り捨て整数に自動変換される


実際に試して覚えよう!
https://paiza.io/projects/pik0u6YBKOv40ZuMP-pG6g

連想配列もつまりは配列の仲間なので配列を理解出来れば連想配列も理解できると思います。

次回、配列を複数の出力方法で出力をし、ループ処理等を併せて理解する記事を書きます。

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

[Rails][ jQuery]フォームの入力ならびに選択が完了するまで送信ボタンを押せないようにする。

はじめに

今回は、jQueryを使って、フォームの入力ならびに選択が完了するまで送信ボタンが押せないように設定していきます。

完成イメージ

submitButtonJs.gif

記事を書いた目的

情報の共有ならびに、自身の備忘録として執筆する。

導入した目的

誤送信を防ぎ、ユーザビリティを向上させるため。

環境

MacOS 10.15.7
ruby 2.6.5
Ruby on Rails 6.0.0

前提条件

  • jQueryが導入済みであること。
  • 画像の複数枚投稿機能を実装している。

記事執筆者の状況

テーブル

Untitled Diagram-ページ2 (1).png

Userテーブルが「投稿者」、Postテーブルが「投稿」、Imageテーブルが「投稿画像」、Prefectureテーブルが「都道府県データ」、Categoryテーブルが「投稿カテゴリー」となります。

PrefectureテーブルとCategoryテーブルはseedデータを活用しております。

コントローラー

新規投稿機能はposts_controller.rbが担当しております。

posts_controller.rb
class PostsController < ApplicationController
  def new
    @post = Post.new
    @post.build_spot
    @post.images.build()
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to root_path, notice: "投稿が完了しました"
    else
      flash.now[:alert] = "必須項目を入力してください"
      @post.images.build()
      render :new
    end
  end
  ...下記一部記述を省略
  .
  .
  .
  private
  def post_params
    params.require(:post).permit(:title, :content, :prefecture_id, :category_id, images_attributes: [:id, :image, :_destroy]).merge(user_id: current_user.id)
  end

それでは作業していきましょう。

①-1 new.html.erbを作成する

まずはフォームをhtmlで作成していきます。

new.html.erb
<%= form_with(model: @post, local: true, multipart: true) do |form| %>
  <ul class='formSpace'>
    <li class="prefecture">
      <label class="labelName" for="Prefecture">Prefecture:</label>
      <%= form.collection_select :prefecture_id, Prefecture.all, :id, :name, {include_blank: '選択してください'}, {class: "prefecture__input", id: 'input01'} %>
    </li>
    <li class="category">
      <label class="labelname" for="category">Category:</label>
      <%= form.collection_select :category_id, Category.all, :id, :name, {include_blank: '選択してください'}, {class: "category__input", id: 'input02'} %>
    </li>
    <li class="title">
      <label class="labelName" for="titleSpace">Title:</label>
      <%= form.text_field :title, class: 'title__input', id: "input03", placeholder: "タイトルを入力してください" %>
    </lil
    <li class='newImage'>
      <label class="labelName" for="imageSpace">Photo:</label>
      <div class="prevContent">
      </div>
      <div class="labelContent">
        <label class="labelBox" for="post_images_attributes_0_image">
          <div class="labelBox__text-visible">
            クリックしてファイルをアップロード(最大5枚)
          </div>
        </label>
      </div>
      <div class="hiddenContent">
        <%= form.fields_for :images do |i| %>
          <%= i.file_field :image, class: "hiddenField", id: "post_images_attributes_0_image", name: "post[images_attributes][0][image]", type: "file" %>
          <%= i.file_field :image, class: "hiddenField", id: "post_images_attributes_1_image", name: "post[images_attributes][1][image]", type: "file" %>
          <%= i.file_field :image, class: "hiddenField", id: "post_images_attributes_2_image", name: "post[images_attributes][2][image]", type: "file" %>
          <%= i.file_field :image, class: "hiddenField", id: "post_images_attributes_3_image", name: "post[images_attributes][3][image]", type: "file" %>
          <%= i.file_field :image, class: "hiddenField", id: "post_images_attributes_4_image", name: "post[images_attributes][4][image]", type: "file" %>
        <% end %>
      </div>
    </li>
    <li class='content'>
      <label class="labelName" for="contentSpace">Content:</label>
      <%= form.text_area :content, class: 'content__input', id: "input05", placeholder: "コメントを入力してください" %>
    </li>
  </ul>
  <div class='send'>
    <%# <%= form.submit "送信中", class: 'send__btn', id: 'sending', value: "投稿する" %>
    <input type='submit' id='sending' class='send__btn' value='投稿する'>
  </div>
<% end %>

続いてscssを記述します。

new.scss
.formSpace {
  height: auto;
}

.labelName {
  color: #000000;
}
// 都道府県================================================================

.prefecture {
  height: auto;
  width: auto;
  margin-top: 1vh;
  font-size: 1.5vh;
  line-height: 1.5;
  color: #fff;
  &__input {
    width: auto;
    border: 1px solid #ccc;
    background-color: #fff;
    border-radius: 5px;
    text-align: center;
    color: #000000;
  }
}

// カテゴリー==============================================
.category {
  height: auto;
  width: auto;
  margin-top: 1vh;
  font-size: 1.5vh;
  line-height: 1.5;
  color: #fff;
  &__input {
    width: auto;
    border: 1px solid #ccc;
    background-color: #fff;
    border-radius: 5px;
    color: #000000;
  }
}

//Title===================================================================
.title {
  height: auto;
  width: auto;
  margin-top: 1vh;
  font-size: 1.5vh;
  line-height: 1.5;
  color: #fff;
  &__input {
    width: 30vw;
    border-radius: 5px;
    border: 1px solid #ccc;
    background-color: #fff;
    color: #000000;
    margin-left: 25px;
  }
}

//Image======================================================================

.newImage {
  display: block;
  margin: 16px auto 0;
  display: flex;
  flex-wrap: wrap;
  cursor: pointer;
}

.imageLabelName {
  color: #fff;
  margin-right: 25px;
}

.prevContent {
  display: flex;
}

.previewBox {
  height: 162px;
  width: 112px;
  margin: 0 15px 10px 0;
}

.upperBox {
  height: 112px;
  width: 100%;
  img {
    width: 112px;
    height: 112px;
  }
}

.lowerBox {
  display: flex;
  text-align: center;
}

.deleteBox {
  color: #1e90ff;
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: #f5f5f5;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}

.imageDeleteBtn {
  background-color: #f5f5f5;
  line-height: 4vh;
  height: 4vh;
  width: 60px;
}

.imageDeleteBtn:hover {
  color: rgba($color: #1e90ff, $alpha: 0.7);
}

//投稿クリックエリアのCSS
.labelContent {
  margin-bottom: 10px;
  width: 620px;
  .labelBox {
    display: block;
    border: 1px dashed #ccc;
    position: relative;
    background: #f5f5f5;
    width: 100%;
    height: 162px;
    cursor: pointer;
    &__text-visible {
      position: absolute;
      top: 50%;
      left: 16px;
      right: 16px;
      text-align: center;
      font-size: 14px;
      line-height: 1.5;
      font-weight: bold;
      -webkit-transform: translate(0, -50%);
      transform: translate(0, -50%);
      pointer-events: none;
      white-space: pre-wrap;
      word-wrap: break-word;
    }
  }
}
//file_fieldのcss
.hiddenContent {
  .hiddenField {
    display: none;
  }
  .hidden-checkbox {
    display: none;
  }
}

//コメント====================================================================

.content {
  display: flex;
  height: auto;
  width: auto;
  margin-top: 5px;
  line-height: 1.5;
  font-size: 1.5vh;
  &__input {
    height: 15vh;
    width: 40vw;
    border-radius: 5px;
    color: #000000;
    border: 1px solid #ccc;
    background-color: #fff;
    margin-left: 0px;
    padding: 1vh;
  }
}


//SENDボタン=========================================================================
.send {
  display: flex;
  justify-content: center;
  &__btn {
    height: 5vh;
    width: 25vw;
    margin: 50px 0;
    border-radius: 20px;
    background-color: #87cefa;
    border: none;
    box-shadow: 0 0 8px gray;
    color: #ffffff;
    line-height: 1.5;
    font-size: 2vh;
    font-weight: bold;
    -webkit-transition: all 0.3s ease;
    -moz-transition: all 0.3s ease;
    -o-transition: all 0.3s ease;
    transition: all 0.3s ease;
  }
  :hover {
    background-color: #00bfff;
  }
  .send__btn[disabled] {
    background-color: #ddd;
    cursor: not-allowed;
  }
}

ここまで行うと次のような形になると思います。
newpageform1.png

ポイント

今回、JavaScriptにてフォームが入力または選択されているかどうかを状態管理するために

<%= form.collection_select :prefecture_id, Prefecture.all, :id, :name, {include_blank: '選択してください'}, {class: "prefecture__input", id: 'input01'} %>

という風に、id: 'input01'という形でidを指定しています。

このように、各フォームにidを指定していくのですが、idは同名のものを使い回しすることが不可のため、今回は
id='input02'id='input03'・・・という形でidを順番に振っています。
(クラス名を統一して、使い回すのもありですが、今回は省略します。)

SCSSについては、ボタン(class:send__btn)が有効・無効の状態別でボタンの色を変更する記述をしております。ボタンの状態管理は後述するdistabledという値を使って管理します。
無効の場合にはdistabledという値が要素に付与されることになるので、scssの方で

.send__btn[disabled] {
  background-color: #ddd;
  cursor: not-allowed;
}

という記述をして、ボタンが無効状態の場合は色を灰色にして、カーソルを無効にしています。

①-2 submit.jsに処理を記述

あとはjsファイルにフォームの入力・選択がされているかどうかを判定し、送信ボタンの有効・無効を切り替える処理を記述していきます。

今回はsubmit.jsというファイルに記述していきます。

submit.js
// フォームを入力・選択するまで送信ボタンが押せないようにする=============================================
$(function() {
  //最初に送信ボタンを無効にする
  $('#sending').prop("disabled", true);

  //idに「input」と設定している入力欄の操作時
  $("[id^= input],#post_images_attributes_0_image").change(function () {
      //入力欄が空かどうか判定を定義するために、sendという変数を使ってフォームの中身の状態管理を行う。
      let send = true;
      //id=input~と指定している入力欄をひとつずつチェック&画像(インデックス番号が0番の画像)をチェックする
      $("[id^= input],#post_images_attributes_0_image").each(function(index) {
        //フォームの中身(値)を順番に確認し、もしフォームの値が空の時はsend = false とする
        if ($("[id^= input],#post_images_attributes_0_image").eq(index).val() === "") {
          send = false;
        }
      });
      //フォームが全て埋まっていたら(send = trueの場合)
      if (send) {
          //送信ボタンを有効にする
          $('#sending').prop("disabled", false);
      }
      // フォームが一つでも空だったら(send = falseの場合)
      else {
          //送信ボタンを無効にする
          $('#sending').prop("disabled", true);
      }
  });
});

ポイント

最初に送信ボタン

<input type='submit' id='sending' class='send__btn' value='投稿する'>

に対して、prop(disabled, false)として、ボタンを無効化しています。

propメソッドは指定した属性に値を設定する役割を持っています。
distabledとは指定したHTML要素を無効化できる属性のことです。
propメソッドと組み合わせて使うことで
prop( ‘disabled’, true)」・・・要素を無効化
prop( ‘disabled’, false)」・・・要素を有効化
というふうに使用することができます。

参照: 
propメソッド・・・http://js.studio-kingdom.com/jquery/attributes/prop
distabled・・・https://persol-tech-s.co.jp/hatalabo/it_engineer/463.html#disabled

次に、

$("[id^= input],#post_images_attributes_0_image").change(function ()

と記述しています。

[id^= input]#post_images_attributes_0_imageの値が変化した時に、イベントが発火する」という記述になります。

注目いただきたいのは
javascript
[id^= input]

の部分です。
こちらはjQueryの属性を使った指定方法を採用しています。
指定方法には大まかに分けて4つあります。

  • 前方一致
  • 後方一致
  • 部分一致
  • 否定

「前方一致」は「属性 ^= 属性名」のように「^」を追加するだけで、属性名の先頭部分の文字列が一致するすべての要素を取得することができます。

今回の場合、[id^= input]とすることで、id="input01", id="input02,・・・ id="input05の要素、つまりid名にinputと命名されている要素を全て取得することができます。

なお、jQueryの属性を使った指定方法についてはこちらの記事を参考にさせていただきました。前方一致指定意外にも知りたい方はご覧いただければと思います。

続いて、

let send = true;

については、入力欄が空かどうか判定するために、sendという変数を用いてフォームの状態管理を行うために記述しています。trueの場合は、フォームが全て埋まっている状態を表します。

$("[id^= input],#post_images_attributes_0_image").each(function(index) {
  //フォームの中身(値)を順番に確認し、もしフォームの値が空の時はsend = false とする
  if ($("[id^= input],#post_images_attributes_0_image").eq(index).val() === "") {
    send = false;
  }
});

については、eachメソッドを使って、idにinputと命名している要素を、要素の個数分に応じて取り出します。
取り出す際、eachメソッドの引数にコールバック関数を定義する必要があるので、「function(index)」と指定します。こうすることでindex番号を取得することができ、取り出した要素にそれぞれindex番号を振り分けます。

今回の場合、イメージとしては

0 : id="input01"の要素
1 : id="input02"の要素
2 : id="input03"の要素
3 : id="input04"の要素
4 : id="input05"の要素

このような形になるかと思います。

加えて、#post_images_attributes_0_imageも対象のオブジェクトに加えております。正直なところ、
複数枚画像投稿する際、上手いidの設定、指定ができなくて、ここに加えております。
(他に上手い方法があれば教えていただけると助かります!)

eachメソッドでインデックス番号と一緒に取り出したあと、

if ($("[id^= input],#post_images_attributes_0_image").eq(index).val() === "") {
  send = false;
}

の処理に移ります。

ここでは、取り出した要素1つ1つのフォームの中身が空なのかを検証しています。
1つ1つ検証するにあたり、eqメソッドを使用しています。
eqメソッドとは現在マッチしている要素をインデックス番号でフィルタリングします。(eqメソッド参照サイト)

idにinputと命名されている要素は、eachメソッドでインデックス番号が0〜4が振られているので、順番にeqメソッドの引数にインデックス番号が入るイメージです。(例: eq(0),eq(1)...eq(4) )

フォームの値を取得するのはvalメソッドを使用します(valメソッド参照サイト

〜〜 === ""とは、「〜〜は空である」という意味を表します。

1つ1つ取り出した要素を検証して、1つでもフォームの値が空の要素があれば、send = falseを返します。

最後の

//フォームが全て埋まっていたら(send = trueの場合)
if (send) {
  //送信ボタンを有効にする
  $('#sending').prop("disabled", false);
}
// フォームが一つでも空だったら(send = falseの場合)
else {
  //送信ボタンを無効にする
  $('#sending').prop("disabled", true);
}

の部分については、
if (send)(if send ==trueという意味)の場合つまりフォームが全て埋まっている場合は、$('#sending').prop("disabled", false);というふうに、送信ボタンを有効にして、押せる状態にしています。

else(send == false)の場合つまりフォームが1つでも空だった場合は、$('#sending').prop("disabled", true);という形で、送信ボタンを無効にして、押せない状態にします。

①-3 完成

以上で完成です。

submitButtonJs2.gif

最後に

初学者のためまだまだ理解不足な部分も多く、今回の実装については正直、改善の予知が多くあると思いますので、もっと良い実装の方法等がありましたらご教示いただけますと幸いです。
また、この記事をご覧いただきましたら、LGTMもいただけるとすごく嬉しいです。何卒よろしくお願い致します。

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

Javascript基礎知識#7(関数)

はじめに

この記事はJavascriptが苦手な私ために書いている自分用のメモです。

※内容について、誤った知識やもっとわかりやすい表現等ございましたら、ご指摘いただけますと幸いです。

目次

  1. 関数 (function) とは
  2. 関数 (function) の書き方
  3. 関数(function) の例
  4. 関数式とは
  5. 関数式の例
  6. アロー関数

1. 関数 (function) とは

複数の処理をまとめたもの。プログラムを何度も使い回しができるかたちにしたものです。
コード量が少なくなって、見通しが良くなる!!

2. 関数 (function) の書き方

関数の一番シンプルな基本のかたちです。

function 関数名( 引数 ) {
  処理する内容
}

関数名(引数);//関数を呼び出す

関数名は変数名を付けるときとルールは大体同じ。
最初に動詞を持ってくることが多い。
例)・add○○
  ・is○○

・引数とは
関数に与える情報で、関数を呼び出すときに一緒に値を渡すことで、関数の中でその値を使うことができる。

3. 関数(function) の例

変数だけのプログラムで書くと、、、

1
const price = 6000; 
if(price > 5000) {
  console.log("高い");
} else if(price > 3000) {
  console.log("普通");
} else {
  console.log("安い");
}
結果1
>> 高い
2
const price = 4000; 
if(price > 5000) {
  console.log("高い");
} else if(price > 3000) {
  console.log("普通");
} else {
  console.log("安い");
}
結果2
>> 普通
3
const price = 1000; 
if(price > 5000) {
  console.log("高い");
} else if(price > 3000) {
  console.log("普通");
} else {
  console.log("安い");
}
結果3
>> 安い

constの値を変えるたびに
同じプログラムをたくさん書かなければならない。

だけど関数を使うと、、、(関数宣言)

function judge(price) {
    if(price > 5000) {
        console.log("高い");
      } else if(price > 3000) {
        console.log("普通");
      } else {
        console.log("安い");
      }
  }
呼び出す
judge(6000);
>> 高い

judge(4000);
>> 普通

judge(1000);
>> 安い

これだけでいい!!すっきりー

4. 関数式とは

さっきの関数は関数宣言と言い、巻き上げに注意しなきゃいけないよー!!
で、関数式は、関数に定数や変数に代入することができる
関数式は、「無名関数」とすることができ、関数名をつけなくてもいい!!

関数式で書く場合は、、、

const 定数 = function(引数) {
    処理する内容
}

定数(引数);//関数式を呼び出す

関数名を省略できる!!(無名関数)

5. 関数式の例

const judge = function(price) {
    if(price > 5000) {
        console.log("高い");
      } else if(price > 3000) {
        console.log("普通");
      } else {
        console.log("安い");
      }
  }
呼び出す
judge(6000);
>> 高い

関数名が定数に変わった感じ。

6. アロー関数

その名の通り「アロー (arrow) (矢) =>」 を使った関数のこと。
関数式のfunction()部分をアローに変えれる。
アロー関数を使うと、コードがシンプルになり読みやすくなる

const judge = (price) => {
    if(price > 5000) {
        console.log("高い");
      } else if(price > 3000) {
        console.log("普通");
      } else {
        console.log("安い");
      }
  }
呼び出す
judge(6000);
>> 高い

こんな感じ。

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

Gulp実行エラー[ReferenceError: primordials is not defined]を解決した方法

gulpを実行した際に下記エラーに遭遇した。

ReferenceError: primordials is not defined

調べてみると理由は、「node.jsとgulpのバージョンの組み合わせがよくないから実行できないよ」ということ。

改善方法は以下の2つが考えられる。

1.gulpのバージョンを上げる(gulp3=>gulp4)
2.node.jsのバージョンを下げる

1の場合、gulpファイルの書き換えが必要になるが、今後はgulp4が主流になると思うので、1の方法で対応しました。

まずは、グローバルインストールされているgulp v3を削除して、gulp v4をグローバルインストールします。

$ npm rm -g gulp
$ npm install -g gulp-cli

次にローカルインストールされているgulp v3を削除して、gulp v4をローカルインストールします。

$ npm uninstall --save-dev gulp
$ npm install --save-dev gulp

インストールが完了したのでpackage.jsonのgulpを4.0.2に書き換えます。

package.json
{
  "name": "backbone_sample02",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "backbone": "^1.3.3",
    "jquery": "^3.2.1",
    "underscore": "^1.8.3"
  },
  "devDependencies": {
    "browser-sync": "^2.18.13",
    "browserify": "^14.5.0",
    "gulp": "^4.0.2",
    "vinyl-source-stream": "^1.1.0"
  }
}

書き換えたら下記のコマンドを実行して、変更を反映させます。

$ npm i

これでgulpのバージョンアップは完了です。

次にgulpfile.jsを編集します。

これが既存のgulpfile.jsの中身です。

gulpfile.js
//plug-in
var gulp = require('gulp');
var browserify = require('browserify');
var browserSync = require('browser-sync').create();
var source = require('vinyl-source-stream');


// gulpタスクの作成
gulp.task('build', function(){
  browserify({
    entries: ['src/app.js'] // ビルド元のファイルを指定
  }).bundle()
    .pipe(source('bundle.js')) // 出力ファイル名を指定
    .pipe(gulp.dest('dist/')); // 出力ディレクトリを指定
});
gulp.task('browser-sync', function() {
  browserSync.init({
    server: {
      baseDir: "./", // 対象ディレクトリ
      index: "index.html" //indexファイル名
    }
  });
});
gulp.task('bs-reload', function () {
  browserSync.reload();
});

// Gulpを使ったファイルの監視
gulp.task('default', ['build', 'browser-sync'], function(){
  gulp.watch('./src/*.js', ['build']);
  gulp.watch("./*.html", ['bs-reload']);
  gulp.watch("./dist/*.+(js|css)", ['bs-reload']);
});

上記の中身をgulp4仕様に書き換えました。

gulpfile.js
//plug-in
var gulp = require('gulp');
var browserify = require('browserify');
var browserSync = require('browser-sync').create();
var source = require('vinyl-source-stream');


// gulpタスクの作成
gulp.task('build', function(){
  browserify({
    entries: ['src/app.js'] // ビルド元のファイルを指定
  }).bundle()
    .pipe(source('bundle.js')) // 出力ファイル名を指定
    .pipe(gulp.dest('dist/')); // 出力ディレクトリを指定
});
gulp.task('browser-sync', function() {
  browserSync.init({
    server: {
      baseDir: "./", // 対象ディレクトリ
      index: "index.html" //indexファイル名
    }
  });
});
gulp.task('bs-reload', function () {
  browserSync.reload();
});

gulp.task('default', gulp.series( gulp.parallel('build', 'browser-sync'), function(){
  gulp.watch('./src/*.js', gulp.task('build'));
  gulp.watch("./*.html", gulp.task('bs-reload'));
  gulp.watch("./dist/*.+(js|css)", gulp.task('bs-reload'));
}));

主な変更点は、gulp.watchの記述方法を変更しました。

これで大丈夫かなと思いgulpを実行したら下記のエラーが発生しました。

[18:08:52] 'build' errored after 736 ms
[18:08:52] Error: Received a non-Vinyl object in `dest()`
[18:08:52] 'default' errored after 740 ms

buildとdefaultでエラーが発生している。

調べると、同様のエラーがvinyl-source-streamのバージョンをアップデートすれば直ったという記事を発見し、package.jsonのvinyl-source-streamを2.00に書き換えた。

package.json
{
  "name": "backbone_sample02",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "backbone": "^1.3.3",
    "jquery": "^3.2.1",
    "underscore": "^1.8.3"
  },
  "devDependencies": {
    "browser-sync": "^2.18.13",
    "browserify": "^14.5.0",
    "gulp": "^4.0.2",
    "vinyl-source-stream": "^2.0.0"
  }
}

下記コマンドを実行

$ npm i

これで無事gulpを実行できました。

しかし、gulpfileの書き方やバージョンによる影響についてはもっと勉強が必要だと感じました。

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

【JavaScript】非同期処理と複数非同期処理の実装方法まとめ

TL; DR

非同期処理について、たくさんのいいリソースや記事等がありますので、本記事は単刀直入に実装例から見てみましょう。
非同期処理の基礎概念に興味がある方はこちらの記事をご参考ください。
今回はXMLHttpRequest, Fetch API, Async/Awaitについて、それぞれの実装例と複数の非同期処理の方法を紹介します。(複数の非同期処理向けのPromise Allも記述しています。)
また、HTTPリクエストをシミュレートするためにJSONPlaceholderのエンドポイントを使っています。

XMLHttpRequest (XHR)

現在はよりモダンなFetch APIがあるため、XMLHttpRequestを若干非推奨としていますが、歴史的な理由により、例え既存のスクリプトをサポートする必要がある場合等、そのままXMLHttpRequestが使われている場合もあります。

リクエストをするためには、4 つのステップが必要です。
1. XMLHttpRequestを作成します
2. リクエストを初期化します
3. リクエストを送ります
4. (イベントをリッスン)レスポンスに対する処理します

非同期のリクエスト

基本構文

const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('load', () => {
  if (request.status !== 200) console.log('error');
  const data = JSON.parse(request.responseText);
  console.log(data);
});

loadイベント以外はreadystatechangeイベントも使えます。
Ready Stateの値について、以下の表を参照してください。

状態 説明
0 UNSENT open()まだ呼ばれていない。
1 OPENED open()が呼び出し済み。
2 HEADERS_RECEIVED レスポンスヘッダを受け取った。
3 LOADING レスポンスはロード中。
4 DONE リクエスト完了。

ここで、一つ注意点があります。
URLに誤りがある場合、リクエストはそのまま実行されます。つまり、リクエスト完了したらステータス 4 になります。なので、request.status200かどうかも確認なければならないです。

const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('readystatechange', () => {
  if (request.readyState !== 4) return;
  if (request.status !== 200) console.log('error');
  // request.readyState === 4 && request.status === 200 場合
  const data = JSON.parse(request.responseText);
  console.log(data);
});

これで、リクエストができましたが、もし他のURLにリクエストしたい場合、コードが重複される可能性があります。重複したコードを避けるため、関数に入れてリファクタリングします。

関数でリファクタリング

const getUsers = (url, callback) => {
  const request = new XMLHttpRequest();
  request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) callback('error', undefined);
      const data = JSON.parse(request.responseText);
      callback(undefined, data);
  });
  request.open('GET', url);
  request.send();
};

getUsers('https://jsonplaceholder.typicode.com/users', (err, data) => {
  if (err) console.log(err);
  console.log(data);
});

ひとつ注意すべき点として、このコードで複数の非同期処理を実行する時にコールバック地獄になるかもしれませんので、以下のようなPromiseを使って、複数の非同期処理のためリファクタリングします。

Promiseでリファクタリング

const getRequest = (url) => {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) reject('error');
      const data = JSON.parse(request.responseText);
      resolve(data);
    });
    request.open('GET', url);
    request.send();
  });
};

getRequest('https://jsonplaceholder.typicode.com/users')
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

複数の非同期のリクエスト

const getRequest = (url) => {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) reject('error');
      const data = JSON.parse(request.responseText);
      resolve(data);
    });
    request.open('GET', url);
    request.send();
  });
};

getRequest('https://jsonplaceholder.typicode.com/users')
  .then((data) => {
    console.log('Request 1: Users', data);
    return getRequest('https://jsonplaceholder.typicode.com/posts');
  })
  .then((data) => {
    console.log('Request 2: Posts', data);
    return getRequest('https://jsonplaceholder.typicode.com/comments');
  })
  .then((data) => console.log('Request 3: Comments', data))
  .catch((err) => console.log(err));

FetchAPI

Fetch API は XMLHttpRequestよりもっと柔軟な操作が可能です。

非同期のリクエスト

fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

複数の非同期のリクエスト

複数のリクエストと言ったら、多分直感的に以下のようなネストする事がなりますが、ネストが深くなると構造が把握しづらくなります。
以下の例は三つのリクエストをネストしたサンプルコードです。

悪い例:

fetch('https://jsonplaceholder.typicode.com/users')
  .then((userResponse) => userResponse.json())
  .then((userData) => {
    console.log(userData);
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then((postResponse) => postResponse.json())
      .then((postData) => {
        console.log(postData);
        fetch('https://jsonplaceholder.typicode.com/comments')
          .then((commentResponse) => commentResponse.json())
          .then((data) => console.log(data))
          .catch((commentData) => console.log(commentData));
      })
      .catch((postError) => console.log(postError));
  })
  .catch((userError) => console.log(userError));

ネストを避けるため、2回目と3回目のfetch(url)前にreturnをすると、以下のことができます。
1. fetch(url)にいるコードブロックはPromiseとして返しますので、.thenメソッドチェーンを使用できます。
2. 一つのcatchで全てリクエストのエラーをキャッチできます。

fetch('https://jsonplaceholder.typicode.com/users')
  .then((userResponse) => userResponse.json())
  .then((userData) => {
    console.log(userData);
    return fetch('https://jsonplaceholder.typicode.com/posts');
  })
  .then((postResponse) => postResponse.json())
  .then((postData) => {
    console.log(postData);
    return fetch('https://jsonplaceholder.typicode.com/comments');
  })
  .then((commentResponse) => commentResponse.json())
  .then((commentData) => console.log(commentData))
  .catch((err) => console.log(err));

Async/Await + Fetch APIを使えば、もっと簡潔なコードを書けます。

Async/Await + Fetch API

非同期のリクエスト

const getRequest = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  if (response.status !== 200) throw new Error('Failed to fetch');
  const data = await response.json();
  return data;
};

getRequest()
  .then((data) => console.log(data))
  .catch((err) => console.log(err.message));

非同期関数の返す値はまだPromiseですので、.thenを使わなと戻ったデータを使えないです。

複数の非同期のリクエスト

const getRequest = async () => {
  const checkStatus = (res) => {
    if (res.status !== 200) throw new Error('Failed to fetch');
  };

  const userResponse = await fetch('https://jsonplaceholder.typicode.com/users');
  checkStatus(userResponse);
  const userData = await userResponse.json();
  const postResponse = await fetch('https://jsonplaceholder.typicode.com/posts');
  checkStatus(postResponse);
  const postData = await postResponse.json();
  const commentResponse = await fetch('https://jsonplaceholder.typicode.com/comments');
  checkStatus(commentResponse);
  const commentData = await commentResponse.json();
  return { userData, postData, commentData };
};

getRequest()
  .then((data) => console.log(data))
  .catch((err) => console.log(err.message));

Async/Await + Fetch APIを使うと、コードの可読性を高められます。

Promise All

Promise.all()メソッドは複数の非同期のリクエストをすることができます、そしてまとめて単一の Promise を返します。

const getRequest = async (url) => {
  const response = await fetch(url);
  if (response.status !== 200) throw new Error('Failed to fetch');
  return response.json();
};

const userPromise = getRequest('https://jsonplaceholder.typicode.com/users');
const postPromise = getRequest('https://jsonplaceholder.typicode.com/posts');
const commentPromise = getRequest('https://jsonplaceholder.typicode.com/comments');

Promise.all([userPromise, postPromise, commentPromise])
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

参考文献

MDN - Ready State

ここまでお読みいただきありがとうございました。
日本語まだ勉強中ですので、文法が多少おかしいと思います。
もし文法ミスや誤字がありましたら、編集リクエストでご意見いただければと思います。

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

【JavaScript】非同期処理を実装例まとめ (XMLHttpRequest, FetchAPI, Async/Await, Promise All)

TL; DR

非同期処理について、たくさんのいいリソースや記事等がありますので、本記事は単刀直入に実装例から見てみましょう。
非同期処理の基礎概念に興味がある方はこちらの記事をご参考ください。
今回はXMLHttpRequest, Fetch API, Async/Awaitについて、それぞれの実装例と複数の非同期処理の方法を紹介します。(複数の非同期処理向けのPromise Allも記述しています。)
また、HTTPリクエストをシミュレートするためにJSONPlaceholderのエンドポイントを使っています。

XMLHttpRequest (XHR)

現在はよりモダンなFetch APIがあるため、XMLHttpRequestを若干非推奨としていますが、歴史的な理由により、例え既存のスクリプトをサポートする必要がある場合等、そのままXMLHttpRequestが使われている場合もあります。

リクエストをするためには、4 つのステップが必要です。
1. XMLHttpRequestを作成します
2. リクエストを初期化します
3. リクエストを送ります
4. (イベントをリッスン)レスポンスに対する処理します

非同期のリクエスト

基本構文

const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('load', () => {
  if (request.status !== 200) console.log('error');
  const data = JSON.parse(request.responseText);
  console.log(data);
});

loadイベント以外はreadystatechangeイベントも使えます。
Ready Stateの値について、以下の表を参照してください。

状態 説明
0 UNSENT open()まだ呼ばれていない。
1 OPENED open()が呼び出し済み。
2 HEADERS_RECEIVED レスポンスヘッダを受け取った。
3 LOADING レスポンスはロード中。
4 DONE リクエスト完了。

ここで、一つ注意点があります。
URLに誤りがある場合、リクエストはそのまま実行されます。つまり、リクエスト完了したらステータス 4 になります。なので、request.status200かどうかも確認なければならないです。

const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('readystatechange', () => {
  if (request.readyState !== 4) return;
  if (request.status !== 200) console.log('error');
  // request.readyState === 4 && request.status === 200 場合
  const data = JSON.parse(request.responseText);
  console.log(data);
});

これで、リクエストができましたが、もし他のURLにリクエストしたい場合、コードが重複される可能性があります。重複したコードを避けるため、関数に入れてリファクタリングします。

関数でリファクタリング

const getUsers = (url, callback) => {
  const request = new XMLHttpRequest();
  request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) callback('error', undefined);
      const data = JSON.parse(request.responseText);
      callback(undefined, data);
  });
  request.open('GET', url);
  request.send();
};

getUsers('https://jsonplaceholder.typicode.com/users', (err, data) => {
  if (err) console.log(err);
  console.log(data);
});

ひとつ注意すべき点として、このコードで複数の非同期処理を実行する時にコールバック地獄になるかもしれませんので、以下のようなPromiseを使って、複数の非同期処理のためリファクタリングします。

Promiseでリファクタリング

const getRequest = (url) => {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) reject('error');
      const data = JSON.parse(request.responseText);
      resolve(data);
    });
    request.open('GET', url);
    request.send();
  });
};

getRequest('https://jsonplaceholder.typicode.com/users')
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

複数の非同期のリクエスト

const getRequest = (url) => {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.addEventListener('readystatechange', () => {
      if (request.readyState !== 4) return;
      if (request.status !== 200) reject('error');
      const data = JSON.parse(request.responseText);
      resolve(data);
    });
    request.open('GET', url);
    request.send();
  });
};

getRequest('https://jsonplaceholder.typicode.com/users')
  .then((data) => {
    console.log('Request 1: Users', data);
    return getRequest('https://jsonplaceholder.typicode.com/posts');
  })
  .then((data) => {
    console.log('Request 2: Posts', data);
    return getRequest('https://jsonplaceholder.typicode.com/comments');
  })
  .then((data) => console.log('Request 3: Comments', data))
  .catch((err) => console.log(err));

FetchAPI

Fetch API は XMLHttpRequestよりもっと柔軟な操作が可能です。

非同期のリクエスト

fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

複数の非同期のリクエスト

複数のリクエストと言ったら、多分直感的に以下のようなネストする事がなりますが、ネストが深くなると構造が把握しづらくなります。
以下の例は三つのリクエストをネストしたサンプルコードです。

悪い例:

fetch('https://jsonplaceholder.typicode.com/users')
  .then((userResponse) => userResponse.json())
  .then((userData) => {
    console.log(userData);
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then((postResponse) => postResponse.json())
      .then((postData) => {
        console.log(postData);
        fetch('https://jsonplaceholder.typicode.com/comments')
          .then((commentResponse) => commentResponse.json())
          .then((data) => console.log(data))
          .catch((commentData) => console.log(commentData));
      })
      .catch((postError) => console.log(postError));
  })
  .catch((userError) => console.log(userError));

ネストを避けるため、2回目と3回目のfetch(url)前にreturnをすると、以下のことができます。
1. fetch(url)にいるコードブロックはPromiseとして返しますので、.thenメソッドチェーンを使用できます。
2. 一つのcatchで全てリクエストのエラーをキャッチできます。

fetch('https://jsonplaceholder.typicode.com/users')
  .then((userResponse) => userResponse.json())
  .then((userData) => {
    console.log(userData);
    return fetch('https://jsonplaceholder.typicode.com/posts');
  })
  .then((postResponse) => postResponse.json())
  .then((postData) => {
    console.log(postData);
    return fetch('https://jsonplaceholder.typicode.com/comments');
  })
  .then((commentResponse) => commentResponse.json())
  .then((commentData) => console.log(commentData))
  .catch((err) => console.log(err));

Async/Await + Fetch APIを使えば、もっと簡潔なコードを書けます。

Async/Await + Fetch API

非同期のリクエスト

const getRequest = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  if (response.status !== 200) throw new Error('Failed to fetch');
  const data = await response.json();
  return data;
};

getRequest()
  .then((data) => console.log(data))
  .catch((err) => console.log(err.message));

非同期関数の返す値はまだPromiseですので、.thenを使わなと戻ったデータを使えないです。

複数の非同期のリクエスト

const getRequest = async () => {
  const checkStatus = (res) => {
    if (res.status !== 200) throw new Error('Failed to fetch');
  };

  const userResponse = await fetch('https://jsonplaceholder.typicode.com/users');
  checkStatus(userResponse);
  const userData = await userResponse.json();
  const postResponse = await fetch('https://jsonplaceholder.typicode.com/posts');
  checkStatus(postResponse);
  const postData = await postResponse.json();
  const commentResponse = await fetch('https://jsonplaceholder.typicode.com/comments');
  checkStatus(commentResponse);
  const commentData = await commentResponse.json();
  return { userData, postData, commentData };
};

getRequest()
  .then((data) => console.log(data))
  .catch((err) => console.log(err.message));

Async/Await + Fetch APIを使うと、コードの可読性を高められます。

Promise All

Promise.all()メソッドは複数の非同期のリクエストをすることができます、そしてまとめて単一の Promise を返します。

const getRequest = async (url) => {
  const response = await fetch(url);
  if (response.status !== 200) throw new Error('Failed to fetch');
  return response.json();
};

const userPromise = getRequest('https://jsonplaceholder.typicode.com/users');
const postPromise = getRequest('https://jsonplaceholder.typicode.com/posts');
const commentPromise = getRequest('https://jsonplaceholder.typicode.com/comments');

Promise.all([userPromise, postPromise, commentPromise])
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

参考文献

MDN - Ready State

ここまでお読みいただきありがとうございました。
日本語まだ勉強中ですので、文法が多少おかしいと思います。
もし文法ミスや誤字がありましたら、編集リクエストでご意見いただければと思います。

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

JSXGraph/MathJaxの使い方:関数グラフのプロットと数式のタイプセッティングを行うJavaScriptライブラリ

ブラウザ上で関数グラフをプロットするJavaScriptライブラリJSXGraphと、数式をタイプセッティングしてくれるJavaScriptライブラリMathJaxの使い方のメモです。今回作ったサンプルの画面はこれです。
JSXGraph-demo.gif
DEMO: https://codepen.io/kaz_hashimoto/pen/vYKvyVr

このDEMOでは、メニューで曲線の種類を選ぶとグラフと関数の数式を表示します。うち1つのグラフは、関数のパラメータをスライダーで調整できます。グラフが更新されると、下のカラーチャートにも反映されます。10個のセルの背景色は、ベースの色rgb(128,255,0)に対して、それぞれアルファチャネルにy = f(x) (x = n/10, n = 1〜10)の値を指定して得られた色です。

動作環境 (記事執筆時点)

  • JSXGraph v1.1.0
  • MathJax v3.1.2
  • jQuery 3.5.1
  • 動作確認ブラウザ: デスクトップ版 Chrome 86, Firefox 82, Safari 14, Opera 72
  • macOS 10.14 Mojave

JSXGraphとMathJaxの使い方

設定

JSXGraphとMathJaxライブラリはCDNから利用できます。HTMLファイルに追加する行は以下のとおり。

JSXGraph

html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsxgraph/1.1.0/jsxgraph.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsxgraph/1.1.0/jsxgraphcore.min.js"></script>

MathJax

html
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>

埋め込み先のdivを設置

グラフと数式を埋め込むために空のdivを2つ用意します。JSXGraphのAPIはグラフの描画領域を作成するのにHTML要素のidを必要とするため、グラフの出力先のdivにはid="plot"を付けました。

html
<div class="graph-wrap">
  <!-- グラフの出力先(JSXGraph用) -->
  <div id="plot" class="graph"></div>
  <!-- 数式の出力先(MathJax用) -->
  <div class="math"></div>
</div>

グラフの出力先のdiv.plot要素については、CSSであらかじめ領域を確保しておきます。このサンプルでは幅・高さ共に260pxに設定しました。

css
.graph-wrap {
  width: 260px;
}
.graph {
  width: 100%;
  height: 260px;
}

JSXGraphの使い方

Boardの作成

まず、プロット領域となるバウンディングボックスをJXG.JSXGraphのメソッドinitBoardを呼び出して作成します。第1引数は、プロット領域となるHTML要素のidです。領域のサイズは、左上、右下頂点の座標の成分を配列にしてboundingboxに指定します。

javascript
let board = JXG.JSXGraph.initBoard('plot', {
  boundingbox: [ -0.1, 1.1, 1.1, -0.1],  // 領域の座標[左、上、右、下]
  axis: true,  // 軸を表示する
  showNavigation: false,  // ナビゲーションボタンを表示しない
  showCopyright: false    // コピーライト文字列を表示しない
});

軸タイトルの追加

軸を表すタイトルを付けましょう。JSXGraph APIには軸タイトルを指定する機能が用意されてないようなので、Boardcreateメソッドを呼び出し、ジオメトリック要素textとしてboardに文字列を配置しました。

javascript
const text_css = 'font-family: "Times New Roman", Times, "serif"; font-style: italic';
board.create('text', [1.05, 0.08, 't'],
       { fontSize: 16, cssStyle: text_css });
board.create('text', [0.05, 1.05, 'y'],
       { fontSize: 16, cssStyle: text_css });

テキストの位置と内容は、create()の第2引数に配列で [ x座標, y座標, 文字列 ] の形式で指定します。テキストのスタイルは第3引数に指定するのですが、fontSize以外のスタイルはcssStyle項目の値として、CSSのルールセットを渡します。
参考)Wiki: Texts and Transformations

関数グラフのプロット

関数のグラフを描くには、Boardcreateメソッドの第1引数にfunctiongraphを指定して呼び出します。下記のコードは、関数bezier(t)(0 ≤ t ≤ 1)の曲線のグラフを描画します。

javascript
function bezier(t) {
  return t * t * (3 - 2 * t);
}

let graph = board.create('functiongraph', [bezier, 0, 1]);

スライダーの作成

グラフにスライダーを設置することができます。スライダーを作成するには、Boardcreateメソッドを第1引数にsliderを指定して呼び出します。下記のコードは、ラベルpを持つスライダーを目盛の開始位置の座標(0.2, 0.4) 、終了位置(0.8, 0.4)に配置し、値のレンジを1〜4、初期値を2に設定します。

javascript
let slider = board.create('slider', [[0.2, 0.4], [0.8, 0.4], [1, 2, 4] ], {name: 'p'});

スライダーの現在の値はValueメソッドで読み出します。dragイベントのハンドラを登録することにより、ハンドルを動かしている時のスライダーの値を取得できます。

javascript
slider.on('drag', function(e) {
  console.log('p=' + this.Value());
});

パラメータを含む関数のグラフ

次に、パラメータpを含む関数f(t, p)の曲線を描く方法です。pの値をスライダーで動かしながら曲線の形状の変化をグラフに反映させることができます。下記のコードは、パラメータpを含む関数parameterized(t)(0 ≤ t ≤ 1)の曲線1について、pの現在値をスライダーから読み取ってグラフを描画します。

javascript
function parameterized(t) {
  const p = slider.Value();
  const tp = t**p;
  return tp / (tp + (1 - t)**p);
}

graph = board.create('functiongraph', [parameterized, 0, 1]);

グラフの要素の消去

メニューでグラフを切り替えた時やスライダーでパラメータpの値を動かした時、グラフの内容を更新する前に、現在表示されている曲線などを消去する必要があります。でないと、前の曲線が画面に残ったままの状態で新たな曲線が追記されてしまいます。グラフの要素を消すには、BoardremoveObjectメソッドを呼び出します。

javascript
function clearGraph() {
  if (graph) { // 今表示されている曲線があれば消す
    board.removeObject(graph);
    graph = null;
  }
  if (slider) { // スライダーが表示されていれば消す
    board.removeObject(slider);
    slider = null;
  }
}

これでグラフが描けました! 次は数式を表示してみましょう。

MathJaxの使い方

MathJaxを使って数式を描画するには、出力先divのinnerHTMLに、LaTeXコマンドで記述した数式の文字列をセットし、MathJax.typeset()関数を呼び出します。下記のコードは、変数mathに設定したディスプレイ数式モードのLaTex形式文字列(\$\$...\$\$)をdiv.math要素のコンテントに書き込んだ後、MathJax.typeset()関数を使って数式を描画させます。タイプセットの更新前に現在の状態をクリアするため、最初にdiv.math要素に対してMathJax.typesetClear()関数を呼んでいます。

javascript
MathJax.typesetClear([$('.math').get(0)]);
const math = '$$f_{p}(t)=\\frac{t^p}{t^p+(1-t)^p}$$';
$('.math').html(math);
MathJax.typeset();

参考) Typesetting and Converting Mathematics

LaTexタイプセッティングの表示確認には、こちらのLive Demoが便利です。
https://www.mathjax.org/#demo

注)上記コード例で、「\\frac」のようにLaTexコマンドの前のバックスラッシュをエスケープしていますが、Live Demoのテキストエリアにはバックスラッシュを1個にして入力します。


  1. この関数の数式は、stackexchange記事 Ease-in-out function より引用 

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

Webページ表示高速化改善、始めます!

背景

  • 先週からWebページ高速化を勉強しはじめて、なんとか最初の改善リリースを終えました。
    • TOPページとLPをリニューアルしたのですが、表示速度が極端に低くなった(PageSpeed Insights で5/100点!)ため、急いでWebページ高速化に取り組まざるをえなくなった。最終的には数日程度で50点近くまで上げることができた。
  • とにかく急いでやったプロジェクトだったんですが、似たような状況に追い込まれた人のために、やったことを残しておきます。

やったこと

1. 「初歩からのPageSpeed Insights」 を読む

とりあえずまず以下の本を読みました。

重いサイトを軽くする、Webページ表示速度の高速化10の基本: 初歩からのPageSpeed Insights (にししふぁくとりー叢書)

  • Amazon Unlimitedに加入してれば無料で読める
  • 少ない分量でまとまっている。
  • 全部読まなくても、自分に関係がありそうな部分だけを拾い読む -> 可能な施策だけ着手 の流れが容易。

まずは、この本の内容と、自分のWebサイトのPageSpeed Insightsの結果を見比べて、効果がありそうなものから着手していきました。基本的な知識は全部この本で得られたので、この記事では、上記の本で足りなかったことを解説していきます。

2. 外部js, youtubeなどの超遅延ロード

超遅延ロード というのは私の造語ですが、 defer などを用いた単なるjsの遅延ロードではなく、「Userが何らかのアクションをしたらロードする」というアクロバティックな遅延ロード手法です。この記事で紹介されてました。

うちのサイトの場合、tweetのjavascriptの埋め込みyoutubeの埋め込み が表示速度の悪化の大部分の原因を占めていたので、 これが最も効果があった施策でした。

以下のコードで、Userの何らかのアクションに引っかけて、<script> タグや <iframe> などのDOMをinsertします。上記記事からコードの大部分をお借りしております。

lazy_load.js
(function(window, document) {
  function insertResource() {

    // twitterの読み込み
    var tw_script = document.createElement('script');
    tw_script.type = 'text/javascript';
    tw_script.defer = true;
    tw_script.src = 'https://platform.twitter.com/widgets.js';
    document.getElementById('js-twitter-load').appendChild(tw_script); // 対象のページに<div id="js-twitter-load"></div> を埋め込んでおく

    // youtubeの読み込み
    var youtube = document.createElement('iframe');
    youtube.className = "youtube__iframe";
    youtube.src = "https://www.youtube.com/embed/xxxxxxxxx";
    youtube.frameborder = 0
    youtube.allow = "autoplay; encrypted-media"
    youtube.allallowfullscreenow = ""
    document.getElementById('js-youtube').appendChild(youtube); // 対象のページに<div id="js-youtube"></div> を埋め込んでおく
  }

  // 遅延読込み
  var lazyLoad = false;
  function onLazyLoad() {
    if (lazyLoad === false) {
      // 複数呼び出し回避 + イベント解除
      lazyLoad = true;
      window.removeEventListener('scroll', onLazyLoad);
      window.removeEventListener('mousemove', onLazyLoad);
      window.removeEventListener('mousedown', onLazyLoad);
      window.removeEventListener('touchstart', onLazyLoad);
      window.removeEventListener('keydown', onLazyLoad);

      insertResource();
    }
  }
  window.addEventListener('scroll', onLazyLoad);
  window.addEventListener('mousemove', onLazyLoad);
  window.addEventListener('mousedown', onLazyLoad);
  window.addEventListener('touchstart', onLazyLoad);
  window.addEventListener('keydown', onLazyLoad);
  window.addEventListener('load', function() {
    // ドキュメント途中までスクロールしている場合(更新時 or ページ内リンク)
    if (window.pageYOffset) {
      onLazyLoad();
    }
  });
})(window, document);

あとは lazy_load.js 自体を通常の遅延読み込みで埋め込みます。

index.html
<script type="text/javascript" src="/js/lazy_load.js" defer="defer" />

3. CSS と WebFont の遅延ロード

defer を使えば簡単にできるjavascriptの遅延ロードと違って、CSSの遅延ロードはちょっと面倒ですよね。
以下の記事で簡単な方法が紹介されていたので、活用しました。

CSSを非同期ロードする最も簡単な方法

media="print" onload="this.media='all'" を追加するだけ。便利。

以下は Uikit のcssを埋め込む時の例です。

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.5.9/dist/css/uikit.min.css" media="print" onload="this.media='all'" />

WebFont(例:GoogleFontsの"Noto Sans JP")でも同様のことができます。
1. Google Fonts から 埋め込み用のタグを取得
2. media=”print” onload="this.media='all'" を追記する

index.html
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet" media=”print” onload="this.media='all'">

ファーストビューの描画に必要なCSSだけを同期的に読み込み、それ以外のCSSやWebFontは全部遅延読み込みにしてしまいます。

4. .webp 形式の画像の導入

上記までの施策で、うちのサイトの点数はだいたい改善されたのですが、残ったのが画像の重さ問題。
PageSpeed Insightsがしつこく勧めてくるので、.webp形式の画像の導入を決めました。
.webp 形式は .jpg や .png より軽いのが利点ですが、対応していないブラウザも多いので、他の形式の画像と併用する必要があります。

<img> タグを使用している場合は、以下の書き方でOK。

index.html
<picture class="thumb">
  <source srcset="/img/top/thumb.webp" type="image/webp">
  <img class="image" src="/img/top/thumb.jpg" loading="lazy" />
</picture>

問題は CSSの background-image などで画像を指定している場合ですね。
modernizr.jsを使用することで、ブラウザの対応状況に合わせてCSSを切り替えることができます。詳しくは以下記事をどうぞ。

WebP(ウェッピー)を導入してみよう

5. Resource Hintsの使用

そこまでやる必要があるのかはよくわからなかったんですが、 Resource Hints なるものを使うことで、必要になるであろうリソースを先にブラウザに伝えることができるようです。
今回の場合、ファーストビューのメインビジュアル画像をCSSのbackground-imageで指定してたので、HTML -> CSS -> 画像 という3要素のリクエストチェーンになってしまうので、読み込み開始を早めるために使用しました。

index.html
<link rel="preload" href="/img/top/mainvisual.webp" as="image">

これにより、リクエストチェーンを HTML -> 画像 に短縮できます。
体感でしかないけど、ファーストビューの表示がちょっとだけ早くなったような気はする。

結果

これだけやったところ、5/100点から50/100点にまでは上がったんですが、まだ Largest Contentful Paint(LCP) の点数が辛くてそれ以上にはならないようです。なんでなんや・・・。
もうちょっと勉強したらまた続きを書こうと思います。

参考

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

Webページ表示速度高速化、始めます!

背景

  • 先週からWebページ高速化を勉強しはじめて、なんとか最初の改善リリースを終えました。
    • TOPページとLPをリニューアルしたのですが、表示速度が極端に低くなった(PageSpeed Insights で5/100点!)ため、急いでWebページ高速化に取り組まざるをえなくなった。最終的には数日程度で50点近くまで上げることができた。
  • とにかく急いでやったプロジェクトだったんですが、似たような状況に追い込まれた人のために、やったことを残しておきます。

やったこと

1. 「初歩からのPageSpeed Insights」 を読む

とりあえずまず以下の本を読みました。

重いサイトを軽くする、Webページ表示速度の高速化10の基本: 初歩からのPageSpeed Insights (にししふぁくとりー叢書)

  • Amazon Unlimitedに加入してれば無料で読める
  • 少ない分量でまとまっている。
  • 全部読まなくても、自分に関係がありそうな部分だけを拾い読む -> 可能な施策だけ着手 の流れが容易。

まずは、この本の内容と、自分のWebサイトのPageSpeed Insightsの結果を見比べて、効果がありそうなものから着手していきました。基本的な知識は全部この本で得られたので、この記事では、上記の本で足りなかったことを解説していきます。

2. 外部js, youtubeなどの超遅延ロード

超遅延ロード というのは私の造語ですが、 defer などを用いた単なるjsの遅延ロードではなく、「Userが何らかのアクションをしたらロードする」というアクロバティックな遅延ロード手法です。この記事で紹介されてました。

うちのサイトの場合、tweetのjavascriptの埋め込みyoutubeの埋め込み が表示速度の悪化の大部分の原因を占めていたので、 これが最も効果があった施策でした。(あと、tweetとyoutubeはページのかなり下の方のコンテンツだったので、ロードを遅らせる合理的な理由もありました)

以下のコードで、Userの何らかのアクションに引っかけて、<script> タグや <iframe> などのDOMをinsertします。上記記事からコードの大部分をお借りしております。

lazy_load.js
(function(window, document) {
  function insertResource() {

    // twitterの読み込み
    var tw_script = document.createElement('script');
    tw_script.type = 'text/javascript';
    tw_script.defer = true;
    tw_script.src = 'https://platform.twitter.com/widgets.js';
    document.getElementById('js-twitter-load').appendChild(tw_script); // 対象のページに<div id="js-twitter-load"></div> を埋め込んでおく

    // youtubeの読み込み
    var youtube = document.createElement('iframe');
    youtube.className = "youtube__iframe";
    youtube.src = "https://www.youtube.com/embed/xxxxxxxxx";
    youtube.frameborder = 0
    youtube.allow = "autoplay; encrypted-media"
    youtube.allallowfullscreenow = ""
    document.getElementById('js-youtube').appendChild(youtube); // 対象のページに<div id="js-youtube"></div> を埋め込んでおく
  }

  // 遅延読込み
  var lazyLoad = false;
  function onLazyLoad() {
    if (lazyLoad === false) {
      // 複数呼び出し回避 + イベント解除
      lazyLoad = true;
      window.removeEventListener('scroll', onLazyLoad);
      window.removeEventListener('mousemove', onLazyLoad);
      window.removeEventListener('mousedown', onLazyLoad);
      window.removeEventListener('touchstart', onLazyLoad);
      window.removeEventListener('keydown', onLazyLoad);

      insertResource();
    }
  }
  window.addEventListener('scroll', onLazyLoad);
  window.addEventListener('mousemove', onLazyLoad);
  window.addEventListener('mousedown', onLazyLoad);
  window.addEventListener('touchstart', onLazyLoad);
  window.addEventListener('keydown', onLazyLoad);
  window.addEventListener('load', function() {
    // ドキュメント途中までスクロールしている場合(更新時 or ページ内リンク)
    if (window.pageYOffset) {
      onLazyLoad();
    }
  });
})(window, document);

あとは lazy_load.js 自体を通常の遅延読み込みで埋め込みます。

index.html
<script type="text/javascript" src="/js/lazy_load.js" defer="defer" />

3. CSS と WebFont の遅延ロード

defer を使えば簡単にできるjavascriptの遅延ロードと違って、CSSの遅延ロードはちょっと面倒ですよね。
以下の記事で簡単な方法が紹介されていたので、活用しました。

CSSを非同期ロードする最も簡単な方法

media="print" onload="this.media='all'" を追加するだけ。便利。

以下は Uikit のcssを埋め込む時の例です。

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.5.9/dist/css/uikit.min.css" media="print" onload="this.media='all'" />

WebFont(例:GoogleFontsの"Noto Sans JP")でも同様のことができます。
1. Google Fonts から 埋め込み用のタグを取得
2. media=”print” onload="this.media='all'" を追記する

index.html
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet" media=”print” onload="this.media='all'">

ファーストビューの描画に必要なCSSだけを同期的に読み込み、それ以外のCSSやWebFontは全部遅延読み込みにしてしまいます。

4. .webp 形式の画像の導入

上記までの施策で、うちのサイトの点数はだいたい改善されたのですが、残ったのが画像の重さ問題。
PageSpeed Insightsがしつこく勧めてくるので、.webp形式の画像の導入を決めました。
.webp 形式は .jpg や .png より軽いのが利点ですが、対応していないブラウザも多いので、他の形式の画像と併用する必要があります。

<img> タグを使用している場合は、以下の書き方でOK。

index.html
<picture class="thumb">
  <source srcset="/img/top/thumb.webp" type="image/webp">
  <img class="image" src="/img/top/thumb.jpg" loading="lazy" />
</picture>

問題は CSSの background-image などで画像を指定している場合ですね。
modernizr.jsを使用することで、ブラウザの対応状況に合わせてCSSを切り替えることができます。詳しくは以下記事をどうぞ。

WebP(ウェッピー)を導入してみよう

5. Resource Hintsの使用

そこまでやる必要があるのかはよくわからなかったんですが、 Resource Hints なるものを使うことで、必要になるであろうリソースを先にブラウザに伝えることができるようです。
今回の場合、ファーストビューのメインビジュアル画像をCSSのbackground-imageで指定してたので、HTML -> CSS -> 画像ファイル という3工程のリクエストチェーンになってしまうので、読み込み開始を早めるために使用しました。

index.html
<link rel="preload" href="/img/top/mainvisual.webp" as="image">

これにより、ファーストビューのメインビジュアル画像のリクエストチェーンを HTML -> 画像ファイル の2工程に短縮できます。
体感でしかないけど、ファーストビューの表示がちょっとだけ早くなったような気はする。

結果

これだけやったところ、5/100点から50/100点にまでは上がったんですが、まだ Largest Contentful Paint(LCP) の点数が辛くてそれ以上にはならないようです。なんでなんや・・・。
もうちょっと勉強したらまた続きを書こうと思います。

参考

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

【JavaScript】非同期処理の書き方 Promise編

はじめに

今週と来週の二回に分けてUdemyで学んだJavaScriptの非同期処理の書き方を投稿します。
今週は非同期処理実装時にコールバック地獄を回避できるPromiseについて書きます。

非同期処理とは?

多くのプログラミング言語にはコードの評価の仕方として、同期処理(sync)非同期処理(async)という大きな分類があります。
※並列処理は省略します。

同期処理はコードを順番に処理していき、ひとつの処理が終わるまで次の処理は行いません。

その為、重たい処理が間にあると、そこで大きな待ち時間が生まれ、画面の操作が一切行えないといった悪影響がでます。

非同期処理はコードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を評価します。

つまり、非同期処理では同時に実行している処理が複数あります。

今回、以下のように料理レシピのIDとレシピ作成者をsetTimeOutを使ってコンソールに出力するという実装をします。

Screen Recording 2020-11-15 at 10.23.33.mov.gif

コールバック地獄とは?

非同期処理を実装する際に気をつけたいのが、
複数のデータの読み込みが完了してから読み込んだデータに対して処理をかけたいという場合です。
以下のように処理を書くとします。

function getRecipe(){
                setTimeout(() => {
                    const recipeID = [523, 883, 473, 974];
                    console.log(recipeID)

                    setTimeout((id) => {
                        const recipe = {title: 'Udon', publisher: 'Jacob'};
                        console.log(`${id}:${recipe.title}`);

                        setTimeout(publisher =>
                            const recipe = {title: 'Ramen', publisher: 'Tony'}
                            console.log(recipe)
                        }, 1500, recipe.publisher)
                    } ,1500, recipeID[2]);

                }, 1500);
            }
            getRecipe();

このようにコールバック関数が連続することを”コールバック地獄”と言います。
上記コードは

  • 深いネストにより可読性が低い
  • 処理の追加/削除が大変

と言えるでしょう。

このコールバック地獄を回避できるのがPromiseです。

Promiseを使った書き方

const getRelated = publisher =>{
                return new Promise((resolve, reject) =>{
                    setTimeout(pub =>{
                        const recipe = { title: 'Ramen', publisher: 'Tony' }
                        resolve(`${pub}: ${recipe.title}`);
                    }, 2000, publisher);
                });
            };

            const getIDs = new Promise((resolve, reject) =>{
                setTimeout(() =>{
                    resolve([523, 883, 473, 974]);
                }, 1500);
            });

            const getRecipe = recId =>{
                return new Promise((resolve, reject) =>{
                    setTimeout(ID =>{
                        const recipe = { title: 'Udon', publisher: 'Taro' };
                        resolve(`${ID}: ${recipe.title}`)
                    }, 2000, recId);
                });
            };

            getIDs
            .then(IDs => {
                console.log(IDs);
                return getRecipe(IDs[2]);
            })
            .then(recipe => {
                console.log(recipe);
                return getRelated('Tony');
            })
            .then(recipe => {
                console.log(recipe);
            })
            .catch(e => {
                console.log(e);
            });

結果は、成功と失敗の2つに分かれます。

成功時には引数で渡される関数resolveを、失敗時には引数で渡されるrejectを呼び出すように記述します。
(今回はrejectは省略)

resolve()した値はthen()で受け取れ、reject()した値はcatch()で受け取れます。

次週はpromiseよりも簡潔に書けるように導入されたasync/awaitという機能について書きます。

参考

Udemy: The Complete JavaScript Course 2020: From Zero to Expert! (Lecture No.434)

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

JSエコシステムぶらり探訪(6): AMDとモジュールローダー

第2回から第5回にかけては、Node.jsの伝統的なモジュールシステムであるCommonJSに絞って挙動を説明していました。これらはECMAScript 2015で導入されたES Modulesで置き換えられつつありますが、それ以外にもいくつかのモジュールシステムが提案され、運用されてきました。これらのうちAMD, UMD, SystemJSについて本稿で扱います。

←前 目次

モジュールローダー

モジュールシステムをWebブラウザでも使うための方法として、モジュールバンドラーを前回紹介しました。

モジュールバンドラーが依存関係をビルド時に解決する一方、依存関係をクライアントで実行時に解決する方法 (モジュールローダー) も古くから試みられていました。1 Node.jsは依存関係を実行時に解決するので、ある意味ではこちらのほうが自然な方法に見えます。ここでCommonJSがそのまま使えたら便利そうですが、CommonJSフォーマットをそのまま使う方法は、以下の3つの問題を抱えています。

  • 1ファイル1モジュールの制限
  • 関数ラッパーが必要
  • require の解析が必要

1ファイル1モジュール

CommonJSは1つのファイルに1つのモジュールという対応関係があります。これはサーバー環境であればそれほど問題ではありませんが、クライアント環境では各スクリプトを個別に転送することになります。特にHTTPレイヤが十分に最適化されていなければ大きな通信コストが発生しかねません。

必要に応じて (たとえばパッケージ単位で) 複数のモジュールをひとつのファイルにまとめて転送できたほうが望ましいでしょう。

関数ラッパーが必要

第2回で説明したように、CommonJSのモジュールファイルはグローバル文脈でそのまま実行されるのではなく、モジュール文脈で実行されます。 var/function はグローバルを汚染しませんし、 this はモジュールを指しています。

ブラウザの <script> タグはグローバル文脈で実行される2ので、CommonJSファイルをそのまま <script> に入れることはできません。かわりに、 XMLHttpRequestやfetchで自力でスクリプトファイルを取得し、自力で加工する必要があります。

require の解析が必要

第1回で説明したように、ブラウザ上での通信は非同期で行うのが普通で、同期的な通信APIには様々な問題や制限があります。 require 関数は同期的に動作することが期待されているので、その中で非同期にスクリプトを読みに行くのでは間に合いません。

そのため、CommonJSモジュールのソースコードをそのまま使うのであれば、Webpackがそうしているように require(定数) という形の呼び出しをあらかじめ発見して、モジュールコードの実行前に依存先モジュールの取得を済ませておく必要があります。

AMD

こうしたCommonJS Modulesとモジュールローダーの相性問題を解決するために生まれたのがAMD (Asynchronous Module Definition) というモジュールフォーマットです。元はCommonJSプロジェクトのTransport/Cという規格提案としてスタートしたものが、CommonJSプロジェクトから離脱して今に至るようです。

たとえば、以下のようなCommonJS Modulesプログラムを考えます。

multiply.js
exports.multiply = function(x, y) {
  return x * y;
};
square.js
var multiply = require("./multiply");
exports.square = function(x) {
  return multiply.multiply(x, x);
};
main.js
var square = require("./square");
console.log(square.square(42));

これはAMDでは以下のようになります。

multiply.js
define("multiply", [], function() {
  //   ^^^^^^^^^^  ^^-- 依存関係
  //            \------ モジュール名
  return {
    multiply: function(x, y) {
      return x * y;
    },
  };
});
square.js
define("square", ["multiply"], function(multiply) {
  //   ^^^^^^^^  ^^^^^^^^^^^^           ^^^^^^^^- ロードされた依存関係 (順番に渡される)
  //          \             \-------------------- 依存関係
  //           \--------------------------------- モジュール名
  return {
    square: function(x) {
      return multiply.multiply(x, x);
    },
  }
});
main.js
define("main", ["square"], function(square) {
  console.log(square.square(42));
  return {};
});

第一引数(モジュール名)と第二引数(依存関係) は存在しない場合は省略できます。

AMDはCommonJSと違い、「必要な依存関係があらかじめわかっていること」と「必要な依存関係が先に読み込まれている」という制約がつきます。そのため、AMDのモジュールはCommonJSにおける冠頭形モジュールに対応するといえます。

CommonJS Modules互換モード

CommonJS Modulesとの相互運用性のために、他にも以下のモードが定められています。これらを使うとCommonJS Modulesのコードをほぼそのまま関数で包むだけでAMDとして使えるようになります。

square.js
// require, exports, moduleという名前の依存関係を指定した場合は特別に、CommonJS互換のrequire/exports/moduleオブジェクトが与えられるようになる。
// このrequireはあらかじめロードされた依存関係を取り出す処理のみを行う。
define("square", ["require", "exports", "module", "multiply"], function(require, exports, module) {
  var multiply = require("./multiply");
  exports.square = function(x) {
    return multiply.multiply(x, x);
  };
});
square.js
// 依存関係の自動発見を行うモード。
// 関数ソースコード (Function.prototype.toString) から require() 呼び出しがスキャンされる。
// 依存関係が空のときにスキャンが行われるが、全てのAMDローダーがサポートしているわけではない。
// なお、依存関係が空のときは ["require", "exports", "module"] を指定したのと同等の引数が渡ってくることが規定されている。
define("square", function(require, exports, module) {
  var multiply = require("./multiply");
  exports.square = function(x) {
    return multiply.multiply(x, x);
  };
});

モジュールの評価順序

CommonJS Modulesではモジュールの評価は最初に require が呼ばれたタイミングで行われます。

main.js
console.log("Loading dep...");
const dep = require("dep");
console.log("Answer is " + dep.answer);
dep.js
console.log("This is dep");
exports.answer = 42;

これを実行すると以下の順序で出力されます。

Loading dep...
This is dep
Answer is 42

WebpackをはじめとするCommonJSモジュールバンドラーはこのような require の挙動をシミュレートするため、同じ結果が期待できます。

一方、AMDは読み込み済みの依存関係が与えられる形式で、 require 関数も読み込み済みの依存関係を返す処理しか行いません。そのため、必要になってからモジュールの評価を行うということは (require.ensure を使わない限り) できません。CommonJS互換機能を使った場合でも、実際の挙動はAMD特有のものに揃えられます。

main.js
define("main", function(require, exports, module) {
  console.log("Loading dep...");
  var dep = require("./dep");
  console.log("Answer is " + dep.answer);
});
dep.js
define("dep", [], function() {
  console.log("This is dep");
  return {
    answer: 42,
  };
});
This is dep
Loading dep...
Answer is 42

モジュール名

AMDのモジュール名はCommonJS Modulesと同様にファイルシステムと対応しています。つまり、 foo/bar のようにスラッシュ区切りで指定することで階層性を表すすることができます。

AMDには、 require や依存関係の指定が相対パスであることだけ規定されています。拡張子の省略に関する規定や、 非相対パス (node_modules からの探索) の規定は存在しません。

RequireJS

RequireJSはAMDの主要な実装です。

RequireJSを利用したプロジェクトの構成は色々考えられますが、一番シンプルなのはモジュール名=パスとして対応づける方法です。先ほどの main.js, square.js, multiply.js を含んだ、以下のようなディレクトリ構成を考えます。

|- index.html
|- require.js
|- main.js
|- square.js
\- multiply.js
index.html
<!doctype html>
<html>
  <head>
    <script data-main="main.js" src="require.js"></script>
  </head>
</html>

require.js はRequireJSのWebサイトで配布されているものを配置します。

これを実行すると、ブラウザは直接的には require.js のみをロードし、残りのスクリプトはRequireJSによって依存解決したうえで読み込まれます。

UMD

ここまでで既に以下の3種類のモジュールシステムが登場しています。

  • グローバルモジュール (IIFE + window に直接エクスポート)
  • CommonJS Modules
  • AMD

3種類のモジュールシステムがあるということは、各ライブラリは必要に応じて個別に「グローバルモジュール」「CommonJS」「AMD」をサポートする必要があるということです。3種類の形式でそれぞれ配布してもよいのですが、CommonJSとAMDは構文上はグローバルJSと互換性があるため、自力でモジュール種別を判別するライブラリを作ることができます。これをパターン化し規格化したものがUMD (Universal Module Definition) です。

UMDも要件によって微妙に異なる形態をとることができ、templatesディレクトリにその書き方がまとめられています。たとえばcommonjsStrictは以下のような形式です。

// 全体がIIFEになっているので、デフォルトではグローバルを汚染しないようになっている
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AMDは define.amd オブジェクトを定義するよう規定している
    // AMDの場合はAMDのdefine関数を呼び出す。exportsが必要なのでそれも要求しておく
    define(['exports', 'dependency1', 'dependency2'], factory);
  } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
    //       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CommonJS文脈ではexportsが定義されているのでそれで判定している
    // AMDをサポートするために、依存関係の取得はファクトリ関数の外側で行うようになっている。
    // そのため、CommonJSを要求された場合はここでrequireをすることになる
    factory(exports, require('dependency1'), require('dependency2'));
  } else {
    // AMDでもCommonJSでもない場合はグローバル変数を定義する
    // 依存関係もグローバル変数にあると仮定する
    factory((root.myLibrary = {}), root.dependency1, root.dependency2);
  }
}(typeof self !== 'undefined' ? self : this, function (exports, dependency1, dependency2) {
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ グローバルオブジェクトの検出。IIFE内ではthisが変わってしまうのでここで書く
  //                                                            ^^^^^^^^^^^^^^^^^^^^^^^^ インポートはAMD形式
  // CommonJS形式での定義とエクスポートをここに書く
  exports.something = function() {}
}));

UNPKG

UNPKG はnpmにアップロードされたUMDパッケージを配信するサーバーです。scriptタグやRequireJSを使って直接読み込むために使うことができます。

bower

npmが元々Node.jsで動かすためのJavaScriptプログラムを配布するパッケージマネージャとして始まったのに対して、ブラウザで動かすためのJavaScriptプログラムを配布するパッケージマネージャとして作られたのが bower です。

現在ではNode.js・ブラウザの如何を問わずnpmで管理するほうが主流であり、bowerは自身を非推奨としてnpmへの移行を呼びかけています。

SystemJSとSystem.register

SystemJSはECMAScript Modules (ES Modules; ESM) をサポートしたライブラリベースのモジュールローダーです。Webブラウザ、Node.jsともにES Modulesのネイティブ実装が存在していますが、それらが提供されていないバージョンでもESMの恩恵を受けられることがSystemJSの特徴のようです。

ESMはそれまでのJavaScriptにない構文を使うため、ESMの互換レイヤとしてSystem.registerというモジュールフォーマットが定義されています。これはAMDとよく似ていますが、ES Modulesに特有のセマンティックスに対応しているようです。AMDと同様、複数のモジュールを1ファイルにまとめるトランスポートフォーマットとしての役割も担っているようです。

jspm

jspmは歴史的に2種類に分けられます。

  • 旧jspm (jspm-cli) はbowerのようなパッケージマネージャで、2020年6月に非推奨化されました。
  • 新jspm (jspm.dev) は旧jspmにかわり2020年6月にリリースされたサービスです。これはUNPKGと同様、npmのパッケージの配布サーバーとして提供されていますが、UMDではなくESMをベースにしています。

まとめ

  • モジュールフォーマット
    • AMDはクライアント側でのモジュールローダーを実装するにあたってのCommonJSの問題を解消したモジュールフォーマット。
    • UMDはグローバルモジュール、CommonJSモジュール、AMDのいずれとしても読み込めるように書かれたモジュールと、そのパターン。
    • System.registerはAMDと似ているが、ES Modulesのセマンティックスに沿って設計されている。
  • モジュールローダー
    • RequireJSはAMDの主要なローダー。
    • SystemJSはSystem.registerの主要なローダー。
  • パッケージ配布サーバー
    • UNPKGはUMD形式のためのCDN。
    • jspm.devはES Modules形式のためのCDN。
  • npm互換以外のパッケージマネージャー
    • bower。非推奨。
    • jspm-cli。非推奨。

←前 目次


  1. RequireJSのような動的な解決とWebpackのようなビルド時解決のどちらが良いかには様々な議論があるようです。本稿ではこの比較には深入りしません。 

  2. ES Modulesについては別記事で扱う予定 

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

JavaScriptにおけるfindとfilterの違い

findメソッド

条件を満たす配列の最初の値を返します。

JavaScript
const array = [1, 2, 3, 4, 5];

const found = array.find(item => item > 2);

console.log(found); //3

配列arrayの中で、2より大きい最初の値を返すので、
この例の場合コンソールに出力される値は、「3」となります。

filterメソッド

条件を満たす配列の全ての値を、新たな配列として返します。

JavaScript
const array = [1, 2, 3, 4, 5];

const found = array.filter(item => item > 2);

console.log(found); //[3, 4, 5]

配列arrayの中で、2より大きい全ての値を配列として返すので、
この例の場合コンソールに出力される値は、[3, 4, 5]となります。

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

時刻表記文字列に対し一日の区切り時刻を変更して返す

文字列中の時刻表記部分に対して、引数で指定した時刻を日界時刻(1日の区切り時刻)とした時刻に変更した文字列を返します。
例えば引数として04:00を指定した場合は03:5927:59に、04:00はそのまま04:00として返ります。

スクリプト

String.prototype.boundaryTime = function(boundaryTime) {
    const
        inputTime = String(this),
        inputArray = inputTime.match(/(\d{1,2}):(\d{1,2})(:(\d{1,2}))?/);
    if(!inputArray) return inputTime;
    if(inputArray[4] === undefined) inputArray[4] = 0;

    let secondsFlag = 1;
    if(boundaryTime === undefined) boundaryTime = '00:00';
    const boundaryArray = boundaryTime.match(/^(\d{1,2}):(\d{1,2})(:(\d{1,2}))?$/);
    if(!boundaryArray) return inputTime;
    if(boundaryArray[4] === undefined) {
        boundaryArray[4] = 0;
        secondsFlag = 0;
    }

    const
        inputSeconds = inputArray[1] * 3600 + 
            inputArray[2] * 60 + Number(inputArray[4]),

        boundarySeconds = (boundaryArray[1] * 3600 + 
            boundaryArray[2] * 60 + Number(boundaryArray[4])) % 86400,

        resultSeconds =
            (inputSeconds - boundarySeconds + 86400) % 86400 + boundarySeconds;

    return inputTime.replace(/(\d{1,2}):(\d{1,2})(:(\d{1,2}))?/, 
                ('0' + Math.floor(resultSeconds / 3600) % 48).slice(-2) + ':' +
                ('0' + Math.floor(resultSeconds / 60) % 60).slice(-2) +
                (secondsFlag ?
                   (':' + ('0' + Math.floor(resultSeconds % 60)).slice(-2)) : '')
            );
}

Object.prototype.boundaryTime = function(boundaryTime) {
    const d = new Date(this);
    if(d.toTimeString === undefined) return this;
    if(!/\d\d:\d\d:\d\d/.test(d.toTimeString())) return this;

    const result = String(d).boundaryTime(boundaryTime);
    const tmp = result.match(/(\d\d):\d\d(:\d\d)?/);
    if(tmp[1] > 23) {
        d.setDate(d.getDate() - 1);
        return String(d).replace(/\d\d:\d\d:\d\d/, tmp[0]);
    }
    return result;
}

書式
文字列.boundaryTime(日界時刻)

console.log('00:00'.boundaryTime('02:00')); // 24:00
console.log('01:59'.boundaryTime('02:00')); // 25:59
console.log('02:00'.boundaryTime('02:00')); // 02:00
console.log('05:59'.boundaryTime('06:00')); // 29:59
console.log('06:00'.boundaryTime('06:00')); // 06:00
console.log('05:59'.boundaryTime('30:00')); // 29:59
console.log('06:00'.boundaryTime('30:00')); // 06:00

// 引数の秒の有無は戻り値の秒の有無として反映
console.log('06:00'.boundaryTime('06:00:00')); // 06:00:00

console.log('11:59:59'.boundaryTime('12:00:00')); // 35:59:59
console.log('12:00:00'.boundaryTime('12:00:00')); // 12:00:00
console.log('23:59:59'.boundaryTime('12:00:00')); // 23:59:59
console.log('00:00:00'.boundaryTime('12:00:00')); // 24:00:00

const d = new Date();
console.log(d); // Sun Nov 15 2020 02:40:25 GMT+0900 (日本標準時)
console.log(d.boundaryTime('03:00')); // Sat Nov 14 2020 26:40 GMT+0900 (日本標準時)
console.log(d.boundaryTime('03:00:00')); // Sat Nov 14 2020 26:40:25 GMT+0900 (日本標準時)

console.log(new Date('2020-12-01 00:59:00').boundaryTime('01:00')); // Mon Nov 30 2020 24:59 GMT+0900 (日本標準時)
console.log(new Date('2020-12-01 01:00:00').boundaryTime('01:00')); // Tue Dec 01 2020 01:00 GMT+0900 (日本標準時)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript基礎知識#6(while文)

はじめに

この記事は私の脱コピペを目指すために書いている自分用のメモです。

※内容について、誤った知識やもっとわかりやすい表現等ございましたら、ご指摘いただけますと幸いです。

目次

  1. while文とは
  2. while文の書き方
  3. while文の例

1. while文とは

while 文では「()」の中に記述した条件式が true を返すあいだ、繰り返し処理を行う

2. while文の書き方

while文の基本形は下記のような感じです。

while(条件式) {
  //繰り返す処理を書く
}

3. while文の例

let num = 3;
while(num < 100) {
  console.log(num);
  num = num * 3;
}
console.log('end');
>> 3
>> 9
>> 27
>> 81
>> end

こんな感じ。

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

Javascript基礎知識#5(for文)

はじめに

この記事は私の脱コピペを目指すために書いている自分用のメモです。

※内容について、誤った知識やもっとわかりやすい表現等ございましたら、ご指摘いただけますと幸いです。

目次

  1. 繰り返し処理とは
  2. for文とは
  3. for文の書き方
  4. for文の例
  5. 配列を使った繰り返し処理
  6. continue
  7. break

1. 繰り返し処理とは

同じ処理を何回か繰り返したいときに使う処理。

繰り返し処理を使えば何回も同じコードを書かなくて済む。

2. for文とは

決められた回数だけ処理を繰り返す場合に使用します。
事前にどんな処理を何回繰り返すのかを決めておくのが一般的です。

3. for文の書き方

for文の基本形は下記のような感じです。

for ( カウンタ変数定義と初期化; 繰り返し回数の指定; カウンタ加算幅) {
  // 繰り返す処理を書く
}

for文は、3つの引数を与えてあげるのが一般的です。

1. カウンタ変数定義と初期化
今何回目の処理かをカウントするための変数定義。
i=0で初期化してね。

2. 繰り返し回数の指定
何回処理したいかを指定する。

3. カウンタ加算幅
i++と書くと、カウンタを1つづつ加算するという意味になる。

4. for文の例

for(let i = 0; i < 3; i++) {
  document.write('for文だよ');
}
結果
>> for文だよ
>> for文だよ
>> for文だよ

5. 配列を使った繰り返し処理

配列についての記事はこちら

forを使わないで配列の要素をすべて表示すると

let lists = ['山田', '伊藤', '佐藤', '鈴木'];

console.log ( lists[0] );
console.log ( lists[1] );
console.log ( lists[2] );
console.log ( lists[3] );
結果
1. 山田
2. 伊藤
3. 佐藤
4. 鈴木

console.log()を使って表示していますが、同じ処理を6回も繰り返して面倒臭いです。
保守性も低いです。

for文を使うと、、、

let lists = ['山田', '伊藤', '佐藤', '鈴木'];

for(let i = 0; i < lists.length; i++) {
  console.log( lists[i] );
}
結果
1. 山田
2. 伊藤
3. 佐藤
4. 鈴木

文が短いし、見やすいです。

6.continue: 特定のタイミングだけ処理を実行

continueを使うと、繰り返し処理の最中に特定のタイミングだけ処理を実行させないようにできる

for文を使って奇数だけを表示するよ

for(let i = 0; i > 10; i++) {
  if(i % 2 === 0) continue; //偶数(2で割れるとき)だけ処理しない
  console.log(i);
}
結果
1
3
5
7
9

7.break: 途中で処理を中断する

breakを使うと、繰り返しの途中で処理を中断できる

for(let i = 0; i > 10; i++) {
  if(i === 7) break; //7以降は中断
  console.log(i);
}
結果
1
2
3
4
5
6

こんな感じ。

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

JavaScript 配列やオブジェクトのマージ関数

同じKeyの値を足し合わせたい

こちらの記事をみていました。

【JavaScript】連想配列の配列に対して、同じkeyの値を足し合わせたい - Qiita
https://qiita.com/hirakuma/items/0c4fd425f76bccab858c

const objs = [
    {key1: 100, key2: 200, key3: 300},
    {key1: 100, key2: 150, key3: 100},
    {key1: 100,            key3: 200, key4: 100},
]

sumObjects(objs)
// {key1: 300, key2: 350, key3: 600, key4: 100}

こういうデータ加工ニーズあるよな、と思い、コメント欄で自分なりの実装をしつつ、

何か工夫できないかと考えてみて、こうすればいいかと思い、複数のオブジェクトか配列をマージする関数を書きました。

const merge = (dataArray, func = v => v, target) => {
  if (dataArray.length === 0) {
    return target;
  }
  if (isUndefined(target)) {
    target = new dataArray[0].constructor();
  }
  for (const data of dataArray) {
    for (const [key, value] of Object.entries(data)) {
      target[key] = func(value, target[key], key, data, target);
    }
  };
  return target;
};

マージしたいデータをdataArrayに投入し、マージ方法をfuncに記載して、targetには何か出力結果の元となるものを指定できるものです。

target未指定の場合、dataArrayが配列内配列なら配列を生成、配列内オブジェクトならオブジェクトを生成するようにしています。

実行した結果は次の通り。様々なデータ加工ができます。

const objArray = [
  { key1: 100,  key2: 200,  key3: 300},
  { key1: 100,  key2: 150,  key3: 100},
  { key1: 100,              key3: 200, key4: 100},
];

console.log(merge(objArray));
// 単純なマージ処理
// {key1: 100, key2: 150, key3: 200, key4: 100}

console.log(merge(objArray,
  (v, t) => t + v,
  { key1: 0, key2: 0, key3: 0, key4: 0 },
));
// 初期値を指定した加算関数を指定すると加算したマージ
// {key1: 300, key2: 350, key3: 600, key4: 100}

console.log(merge(objArray,
  (v, t) => {
    return isUndefined(t) ? v : t + v;
  },
));
// 初期値していなくともundefined判定をすると加算マージできる
// {key1: 300, key2: 350, key3: 600, key4: 100}

const average = merge(objArray,
  (v, t) => {
    return isUndefined(t) ? [1, v] : [t[0] + 1, t[1] + v];
  },
);
console.log(average);
// {key1: [3, 300], key2: [2, 350], key3: [3, 600], key4: [1, 100]}
console.log(
  objectFromEntries(Object.entries(average).map(
    ([key, value]) => [key, value[1] / value[0]]
  ))
);
// {key1: 100, key2: 175, key3: 200, key4: 100}
// 平均値を求めるために加算した数と値を保持している

先の記事のコメント欄にも載せました。
https://qiita.com/hirakuma/items/0c4fd425f76bccab858c#comment-568fe8aa1ef32aad00c9

配列に対しても動作する

Object.entriesは、オブジェクトだけではなく配列に対しても有効なので、オブジェクトと配列のどちらも動かす事ができます。

ほとんど同一の実装内容を npmライブラリとして公開している Parts.js に組み込みました。

テストコードを完備していますが、そこでは配列に対して、同様のマージ操作をしています。
https://github.com/standard-software/partsjs/blob/v10.0.0/source/common/_merge.js
https://github.com/standard-software/partsjs/blob/v10.0.0/source/common/common.test.js#L1550

        const testArrayArray = [
          [100, 200, 300],
          [100, 150, 100],
          [100,    , 200, 100],
        ];
        checkEqual(
          [100, 150, 200, 100],
          merge(testArrayArray),
        );
        checkEqual(
          [300, 350, 600, 100],
          merge(
            testArrayArray,
            (v, t) => t + v,
            [0, 0, 0, 0],
          ),
        );
        checkEqual(
          [300, 350, 600, 100],
          merge(
            testArrayArray,
            (v, t) => isUndefined(t) ? v : t + v,
          ),
        );
        checkEqual(
          [[3, 300], [2, 350], [3, 600], [1, 100]],
          merge(
            testArrayArray,
            (v, t) => isUndefined(t) ? [1, v] : [t[0] + 1, t[1] + v],
          ),
        );

他の場面に使えるかどうか

このmerge関数、汎用的、実用的に使えるのかな、と自分でも疑問だったので、こちらの記事をみかけたので課題にして書いてみます。

連想配列(辞書オブジェクト)n個の共通配列を上書きなしで結合マージする - Qiita
https://qiita.com/khsk/items/092fe59baee050e420b4

Javascript:オブジェクトをマージする関数mergeを考えてみた - Qiita
https://qiita.com/ttatsf/items/d5f0067f5bba8ef77433

const {
  merge, isUndefined, isArray,
} = parts;

const hira = {
  'dog': 'いぬ',
  'cat': ['ねこ'],
  'human': 'ひと',
}
const kana = {
  'dog': ['イヌ'],
  'cat': ['ネコ', 'ニャンコ'],
}
const kan = {
  'dog': [],
  'cat': '',
  'human': '',
  'car' : '',
}
// このような出力ができたらよい。
// [object Object] {
//   car: ["車"],
//   cat: ["ねこ", "ネコ", "ニャンコ", "猫"],
//   dog: ["いぬ", "イヌ"],
//   human: ["ひと", "人"]
// }

const result = merge([hira, kana, kan], (value, targetValue, key) => {
  // 1
  if (!isArray(value)) {
    value = [value];
  }

  // 2
  if (isUndefined(targetValue)) {
    return value
  }

  // 3
  for (const v of value) {
    targetValue.push(v);  
  }
  return targetValue;
});

console.log(result);
// [object Object] {
//   car: ["車"],
//   cat: ["ねこ", "ネコ", "ニャンコ", "猫"],
//   dog: ["いぬ", "イヌ"],
//   human: ["ひと", "人"]
// }
// 出力できました。

ここで動作確認しています。
https://jsbin.com/vojajepuho/edit?html,js,console

うまくうごきました。

オブジェクトのプロパティをループして関数を呼び出してきてmergeのfuncが次の引数で呼び出されます。

  • value: 見ているオブジェクトのプロパティ値
  • targetValue: 結果として出力されるオブジェクトのプロパティ値
  • key: プロパティ名(キー)

上記のコードで、[// 1] では、
元データが配列や配列じゃないものがあるのでそれを判定し配列にして
[// 2] では
mergeの第三引数として指定されるtargetオブジェクト(未指定なら空オブジェクト生成する)のプロパティの値がtargetValueとして手に入るので、その値を見てundefinedなら、そのまま見ているオブジェクトのプロパティ値を返し
[// 3] では、targetオブジェクトのプロパティ値はすでに配列と確定しているので、そちらに見ているオブジェクトのプロパティを配列として加算する。

日本語だと説明しにくいですが、こういうコードでうまく動くようになります。

短く書いてみた。

mergeにわたす関数を短く書いてみました。

でも、読みにくいから、良い子は真似しちゃだめ。という感じです。

const result = merge([hira, kana, kan], (value, target, key) => {
  value = [value].flat();
  return !target ? value : [...target, ...value];
});

配列かどうか判定して配列化するのではなく、一度配列にしてflatにするというやり方と、三項演算子。また、配列へのpush はスプレッド構文で行えます。

さらに短くこんな書き方もできます。これでも同じ結果を返します。

const result = merge([hira, kana, kan], 
  (value, target, key) => !target ? [value].flat() : [...target, ...value]
);

こんな風に短くかけますが、現実の実務では大量のプログラムを素早く読み解いて新しい機能や修正する機能を開発しなければいけないので、素早く読めるコードがすごく大事。

なので、一つのコードブロックを読み解くのに、3秒かかるのか、1秒で済むのか、という差が開発効率に直結してきます。

プロのプログラマとしてコードを作る場合には、読みやすいコードを書いて置いたほうがいい。(そして人より3倍早く仕事する。)私はそんなふうに思っているので、短く書かずに、わざと長くわかりやすく書いたりします。

なので、参考程度です。

終わりに

merge関数というぼちぼち実用なものを作ることができました。

オブジェクトや配列を値の加工をしながらマージするときに使えると思います。

便利そうに思えたら、ダウンロードして使ってくださると嬉しいです。

@standard-software/parts - npm
https://www.npmjs.com/package/@standard-software/parts

こちらで動作確認もできるようになっています。

https://npm.runkit.com/%40standard-software%2Fparts
https://jsbin.com/johazazutu/edit?html,js,console

const { merge } = parts;

こんなふうに書くと使えます。よろしくです。

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

JavaScript基本文法【関数】

はじめに

現在、JavaScriptの独学をしています。

復習を兼ねて、今回は関数についてまとめます。

私と同じようにJavaScript初心者の方の参考になればと思います。

関数とは

関数とは、タスクや値計算を実行する文の集まりのことです。

JavaScriptでは、関数オブジェクトとも呼ばれ、オブジェクトの一種です。

そのため、他の値と同じように変数へ代入したり、関数の引数として渡すことが可能です。

以下のように定義します。

function 関数名 (仮引数) {
    // 色々処理・・・
    return 関数の返り値;
}

この定義方法以外にも、関数式で定義することもできます。

関数式

関数式とは、関数を値として代入している式のことです。

// 関数式
const 関数名 = function() {
    // 色々処理・・・
    return 関数の返り値;
};

関数式では右辺の関数名を省略できます。

このような名前を持たない関数を匿名関数とも言います。

関数式には上記のfunctionキーワードを使った方法以外に、Arrow Functionと呼ばれる書き方もあります。

// Arrow Functionを使った関数定義
const 関数名 = () => {
    // 色々処理・・・
    return 関数の返す値;
};

コールバック関数

JavaScriptでは、その場で作った匿名関数を関数の引数として渡すことができます。

引数として渡される関数のことをコールバック関数と呼びます。

また、コールバック関数を引数として使う関数やメソッドのことを高階関数と呼びます。

function 高階関数(コールバック関数) {
    コールバック関数();
}

メソッド

オブジェクトのプロパティである関数をメソッドと呼びます。

JavaScriptにおいて、関数とメソッドの機能的な違いはありません。

const obj = {
    method1: function() {
        // `function`キーワードでのメソッド
    },
    method2: () => {
        // Arrow Functionでのメソッド
    }
};

また、ES2015からは、メソッドとしてプロパティを定義するための短縮した書き方が追加されています。

オブジェクトリテラルの中で メソッド名(){ /メソッドの処理/ } と書くことができます。

const obj = {
    method() {
        return "this is method";
    }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】Intersection Observer APIを使ってtargetを表示遅延させたい

Intersection ObserverAPIについて

Intersection ObserverAPIは、「画面上のある要素が、ブラウザ上の表示されている領域(viewport)に対してどの位置にあるかを監視して教えてくれる」という機能を持っている交差監視APIです。(IE11対応ならPolyfill必要)

言い換えると、ユーザーがブラウザ上のページをスクロールした時、特定の要素=targetがブラウザの表示領域に交わったかどうか(表示領域=viewportに入った時と出た時)を検知します。

引用: HACKS Intersection Observer comes to Firefox

Googlebotが認識できることからSEO的に良さそうだし、scrollイベントのように絶えず要素の位置を確認することを避けられるのでパフォーマンス的にも良さそうです。

Intersection Observer APIを使う

IntersectionObserverオブジェクトを作成し、交差を監視したい要素をobserveします。

基本形
// IntersectionObserverオブジェクトを作成
// 交差時に実行するコールバック関数を渡す
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => console.log(entry));
});

// 監視したい要素をobserve
observer.observe(document.querySelector('.target'));

デフォルトでは、監視したい要素が画面内に入ってきたときに、または完全に下側に出て行ったときにコールバック関数が呼ばれます。

IntersectionObserverEntry

コールバック関数の第一引数には、監視したいtargetの数だけのIntersectionObserverEntryオブジェクトが入ります。

options

2個目の引数として渡されたoptions オブジェクトは、observerのcallbackが呼び出される状況を制御します。
optionsを指定するには、コンストラクタの第二引数にオブジェクトを渡します。root、rootMargin、thresholdの3つがあります。

// オプションを指定
const options = {
  root: document.querySelector('.root'),
  rootMargin: '100px',
  threshold: [0, 0.5, 1.0]
}

// オプションとともにIntersectionObserverオブジェクトを作成
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => console.log(entry))
}, options);

// 監視したい要素をobserve
observer.observe(document.querySelector('.target'));

root: ルート(交差判定のベース)となる要素を指定。指定なし、またはNULLの場合デフォルトではviewportが使用される。rootを明示的に指定することで、viewport以外との交差判定を実現できる。

rootMargin: はrootからのマージンを指定。マージンを指定することで、rootと交差する前に発火させることができる。指定方法はCSSのmarginとほぼ同じで、単位はpxか%(0の場合でも単位が必要)。デフォルトは’0px 0px 0px 0px’。

threshold: 単一の数値もしくは数値の配列でコールバック関数が呼ばれるタイミングを指定、デフォルトでは0。0はほんの少しでもrootに入ってきたときのことを示し、1だと完全にrootに入ってきたときのことを示す。

サンプル作成①

targetであるtargetBoxがviewportに入って100px入ったところでふわっと表示させ、出るところでふわっと消すサンプルです。(cssアニメーションで動きをつけた)

構成

javascript
import "./styles.css";
//コールバック関数の第一引数にはIntersectionObserverEntryオブジェクトが入る
const callback = (entries) => {
  entries.forEach((entry) => {
  console.log({ entry });
//isIntersectingプロパティは交差しているかどうかのbool値
//viewportに交差し、入ったときにisIntersecting===true、出たときにfalse
    if (entry.isIntersecting) {
      entry.target.classList.add("is-showTargetBox");
      entry.target.textContent = true;
    } else {
      entry.target.classList.remove("is-showTargetBox");
      entry.target.textContent = false;
    }
  });
};

//Intersection observer のオプションを作成
const option = {
  rootMargin: "-100px"
};
//Intersection observer を作成
//ターゲットが IntersectionObserver に指定された閾値を満たす度にコールバックが呼び出される
const io = new IntersectionObserver(callback, option);
//監視したい複数要素をターゲットにした
const targets = document.querySelectorAll(".targetBox");
console.log({ targets });
targets.forEach((target) => {
//監視したい対象をobserve
  io.observe(target);
});

consoleで確認するとこんな感じ

html
<!DOCTYPE html>
<html>
  <head>
    <title>io EX</title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div class="height300 bgPink">
      <div class="targetBox">1個目</div>
    </div>
    <div class="height300 bgGray">
      <div class="targetBox">2個目</div>
    </div>
    <div class="height300 bgPink">
      <div class="targetBox">3個目</div>
    </div>
    <div class="height300 bgGray">
      <div class="targetBox">4個目</div>
    </div>
    <div class="height300 bgPink">
      <div class="targetBox">5個目</div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>
css
body {
  font-family: sans-serif;
}
.height300 {
  height: 300px;
  display: flex;
}
.bgPink {
  background-color: pink;
}
.bgGray {
  background-color: lightgray;
}
.targetBox {
  height: 100px;
  width: 100px;
  background-color: white;
  border: 3px solid yellow;
  margin: auto;
  opacity: 0;
  transition: opacity 0.5s ease-in;
}
.is-showTargetBox {
  opacity: 1;
  transition: opacity 0.5s ease-in;
}

動きはこんな感じ。

サンプル作成②

画像を遅延させるためのコードをメモ

const $lazyLoad = document.querySelectorAll(".lazyload");
const io = new IntersectionObserver(callback, { rootMargin: "-100px" });

$lazyLoad.forEach((element) => {
  io.observe(element);
});

function callback(entries) {
  entries.forEach((entry) => {
//intersectionRatioは交差している量を0.0〜1.0の範囲で示す
    if (entry.intersectionRatio > 0) {
      const imgElement = entry.target;
      imgElement.src = imgElement.dataset.src;

      imgElement.addEventListener("load", () => {
        imgElement.classList.add("lazyloaded");
      });
      io.observe(entry.target);
    }
  });
}

表示遅延させたいimg要素に対してlazyloadクラスを指定し、Intersection Observerで監視。
viewportにtarget要素が交差したら、data-src属性に指定している画像をロード、完了した時点でlazyloadedクラスをaddします?

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

Javascript基礎知識#4(比較演算子と論理演算子)

はじめに

この記事は私の脱コピペを目指すために書いている自分用のメモです。

※内容について、誤った知識やもっとわかりやすい表現等ございましたら、ご指摘いただけますと幸いです。

目次

  1. 比較演算子とは
  2. 比較演算子一覧
  3. 比較演算子の基本的な使い方
  4. 論理演算子とは
  5. 論理演算子一覧
  6. 論理演算子の基本的な使い方

1. 比較演算子とは

if文やfor文・while文などのループ処理の条件式に使われることが多い、
2つの値を比較することが目的の演算子です

2. 比較演算子一覧

比較演算子 意味 結果
> 左辺が右辺よりも大きい 2>1 true
>= 左辺が右辺より大きいか同じ 2>=2 true
< 左辺より右辺が大きい 2<4 true
<= 左辺より右辺が大きいか同じ 2<=4 true
=== 左辺と右辺の値と型が同じ 2===2 true
!== 左辺と右辺が異なる 2!==2 true

比較演算子は、2つの値を比較して正しいかどうかを「True / False」で返してくれるのが特徴です。

3. 基本的な使い方

if(1 === 1) {
  console.log('あってるよ!')
}
結果
あってるよ!

こんな感じ。

4. 論理演算子とは

『~かつ~だった場合』や『~または~だった場合』といったような感じです。

5. 論理演算子一覧

論理演算子 意味
a && b aとbどちらもtrueであればtrue
a||b aかbどちらかtrueであればtrue
!a aがtrueであればfalse、aがfalseであればtrue

6. 論理演算子の基本的な使い方

1
if(true && true) {
  console.log('実行したよ');
} else {
  console.log('実行してないよ')
}
結果1
>> 実行したよ
2
if(true && false) {
  console.log('実行したよ');
} else {
  console.log('実行してないよ')
}
結果2
>> 実行してないよ
3
if(true || true) {
  console.log('実行したよ');
} else {
  console.log('実行してないよ')
}
結果3
>> 実行したよ
4
if(true || false) {
  console.log('実行したよ');
} else {
  console.log('実行してないよ')
}
結果4
>> 実行したよ

って感じです。

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

【JavaScript】要素に指定したCSSスタイルを取得したい!

プログラミング勉強日記

2020年11月15日
ドットインストールでJavaScriptを学習中にふと、『CSSのスタイルはどうやって取得するのか?』と気になりました。
自分的には、ややこしかったので今後間違えないように記録していきます。

取得したいこと

  • CSSファイルに記述したbackgroundプロパティの値を取得したいと思います。

スクリーンショット 2020-11-15 8.37.08.jpg

index.html
  <div class="box" id="target"></div>
style.css
.box {
    width: 100px;
    height: 100px;
    background-color: rgb(204,204,204);
}

完成コード

まず最終的に取得できたコードが以下になります。

script.js
'use strict';
let $target = document.getElementById('target');
console.log(window.getComputedStyle($target)['background']);

完成コードの解説

1.id属性が'target'の要素を取得。

2.window.getComputedStyle($target)[background]で取得した要素のCSSStyleDeclarationオブジェクトのbackgroundプロパティの値を返す。

つまづいた箇所

ドットインストールで、boxクラスの背景色を変える際に、

script.js
$target.style.background = 'pink';

と記述していたので、背景色を取得する時も、

script.js
console.log($target.style.background);

でいけるかと思ったのですが、コンソール見てみると何も取得できていませんでした。

もし、上記の書き方で取得する際は、

index.html
  <div class="box" id="target" style='background: pink;'></div>

要素にstyle属性を直接指定するもしくは、

script.js
$target.style.background = 'pink';
console.log($target.style.background);

jsファイルで先に背景色を変える指定を行い、取得する必要があると分かりました。

参考資料

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