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

インクリメンタルサーチで追加済みユーザーが検索されないようにする

目的

チャットアプリのグループメンバー追加ページを以下の仕様で作成する。
- インクリメンタルサーチでメンバー検索をして登録リストに表示
- 登録リストに表示されたメンバーを登録ボタンで追加リストに登録
- 登録されたメンバーは登録リストに表示されない

問題点

登録されたメンバーが登録リストに表示されてしまう。

考え方

検索されたメンバーのIDが追加リストに登録されたメンバーのIDと重複しない場合のみ表示させれば良い。

コード

before.js
users.forEach(function (user) {
  if (user.id) {
    addUser(user);
  }
})
after.js
users.forEach(function (user) {
  let idNum = document.getElementById(user.id);
  if (user.id && !idNum) {
    addUser(user);
  }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueの$routeがどうしてもundefinedになってしまう

反省文

Vueでルーターの設定をしようとしていましたが、なぜか何をやっても設定がVueに反映されません。

下のどちらかのコードには間違いがあります。

a.js
import router from "./router";

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");
b.js
import Router from "./router";

new Vue({
  Router,
  render: h => h(App)
}).$mount("#app");

a.jsの方は動きますが、b.jsの方は動きません。動かそうとするとこんなエラーが出て止まります。

vue.esm.js?efeb:628 [Vue warn]: Error in render: "TypeError: Cannot read property 'meta' of undefined"

found in

---> <App> at src/App.vue
       <Root>

上記の該当箇所はこんなかんじです。

export default {
  computed: {
    layout() {
      console.log(this);
      return this.$route.meta.layout || "default"; // << ココでエラー発生
    }
  }
};

$routeに何も代入されていません。

この様に書き直すと、今までのエラーが嘘の様に動き出しました。

b.js
import Router from "./router";

new Vue({
  router: Router,
  render: h => h(App)
}).$mount("#app");

routerと、オブジェクトのキーをちょいと書き加えてやるだけ。

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

TypeScript 型 メモ

型リテラル

[]

  • 空のTupleを表す
  • Params<T>は関数の引数がないときに引数の型は[]になる

any

  • すべての型の値を代入することができる
  • あらゆるプロパティがあるものとしてアクセスすることができる
  • すなわち型システムとしてこいつは崩壊しているので使用するべきではない
const notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn’t check)

unknown

  • 他の言語で言うany

TypeScript 3.0のunknown型 - タイプセーフなany - Qiita

void

  • 他の言語で言う普通のvoid、関数が何も返さないときの値
  • 一応nullundefinedスーパータイプになれる
let unusable: void = undefined;
unusable = null; // OK if `—strictNullChecks` is not given

never

  • bottom型と呼ばれる値を持たない型
  • 決して処理が終わらないものを表すときに使用する i.e. while(truc)、例外のthrow
  • 集合論における空集合のようなものなので、あらゆる型に代入できるがnever以外を代入できない
const a: never = 1 // compile error
const b: number = never // ok

never型とのunion type

  • neverと他の型のunion typeはneverが消える
number | never == number

型コンストラクタ

Required<T>

  • Tの内nullableな属性の方を全てマストにする

Parameters<T>

  • Tが関数ならばその引数の型を返す
  • そうでないならneverを返す

Exclude<T, U>

  • TがUのサブタイプならneverを返し、そうでないならTを返す
  • union distributionと組み合わせることでunionから不要な物を削除できる
type A = "a" | "b"
type B = "b"

Exclude<A, B>
// => "a"

Pick<T, K extends keyof T>

  • Tからunion typeであるKに含まれるkeyの値だけを取り出す
type A = {a: any, b: any}
Pick<A, "a">
// => {a: any}

Omit<T, K extends keyof any>

  • TからKをのぞいた型を返す

型演算子

keyof T

  • Tのプロパティのキーからなるunion typeを返す
type T = {a: any; b: any}
keyof T
// => "a" | "b"

in

  • mapped typeと呼ばれる機能を実現するためのもの
  • 配列の型の一つ一つを処理して新たな型を返すようなときに使用する
type Readonly<T> = {
    readonly [P in keyof T]: T[P]; // Tのキー全てにreadonly属性をつけた新たな型を定義している
};

TypeScript2.1.4 で導入された keyof キーワードと in キーワード、そして Lookup Types と Mapped Types - 角待ちは対空

extends

型の境界

  • ある型が他の型のサブタイプであることを指定する場合に使用する
const apply<T extends (...args: any[]) => any, A extends any[]>(f: T, args: A) => f(...args)

apply((...args: number[]) => args.length, [1, 2])
// => 2

conditional type

  • ある型が別の型のサブタイプかどうかで型を分岐させる
declare function f<T extends boolean>(x: T): T extends true ? string : number;

// Type is 'string | number'
let x = f(Math.random() < 0.5);

TypeScript: Handbook - Advanced Types

  • 更にinferを使用することで型変数を定義し、それを用いて返す型を定義することができる
// Rを型変数として定義し、条件にあうときはそのRを用いて型を返す
// inferがなければRはそもそも定義されていない型変数なので使用できない
type ReturnType<T> = T extends (args: any[]) => infer R ? R : any;

union typeのサブタイプとextend判定

  • union typeのサブタイプはその候補を減らした型が当てはまる
type T = "a" | "b"
a extends T // => true
b extends T // => false
  • union typeの候補数を増やしても同様

union distribution

  • 上記のunion typeにおけるサブタイプとスーパータイプの関係性を保証するのがunion distributionという仕組み
  • 簡単に言うとunion typeの対するextends判定は分配法則に従って実行される
type A = number[] | string
type ArrayValue<T> = T extends (infer U)[] ? U : undefined
ArrayValue<A> // => number | undefined

ユースケース

スマートキャストするための型判定関数の作成

isを使用する

const isString = (arg: any): arg is string  => typeof arg === "string";

const foo = 'foo' as any // ここではanyとして振る舞いたい

if (isString(foo)) {
  // isStringの結果、arg is string = foo is string = fooはstring型であることが確定している
    foo.length // fooをas anyしたけどfooはstringとして解釈される
}

Advanced Types · TypeScript

nullableをnon nullにキャストする

!(Non-null assertion operator)を使用する

const a: string? = ""
const b: string = a // compile error
const c: string = a! // success
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの論理演算で値に対して「または」を使いたい時のTip

JavaScriptの学習中に、論理演算の課題に取り組んだ際に「または」の表現で悩んだので備忘録も兼ねてまとめます。
具体的には値について「または」を使いたい場合の考え方です。

以下の記事を参考にさせていただきました。
https://wp-p.info/tpl_rep.php?cat=js-application&fl=r6
https://wp-p.info/tpl_rep.php?cat=js-biginner&fl=r19

課題内容

ジャンケンで、自分が出したい手(user_hand)をpromptに入力し、

  1. グー・チョキ・パーが入力された際にはランダムに生成したJavaScript側の手(js_hand)との勝敗を出力
  2. グー・チョキ・パー以外の文字列が入力されたときは、「グー・チョキ・パーのいずれかを入力してください」とアラートを出し、もう一度入力させる。
  3. キャンセルボタンが押されたときは「またチャレンジしてね」を出力

別途、JavaScript側の手のランダム生成はgetJShand、勝敗の判定・出力はjudgeという関数を組んでいます。

困ったこと

上記の条件2を記述する際に、「そういえばまたはの時には||が使えるんだったな」と思い出し以下の様に書いてみました。

var js_hand = getJShand();

//ループ処理の判定用。終わるときはflagを1にしてwhileループから抜ける。
var flag = 0;

while(flag == 0) {
    var user_hand = prompt('ジャンケンの手をグー、チョキ、パーから選んでください。');
    if(user_hand == null) {
        alert("またチャレンジしてね");
        flag = 1;
    }else if(user_hand != "グー" || "チョキ" || "パー") {
        alert("グー・チョキ・パーのいずれかを入力してください。");
    }else{
        var judge = winLose(user_hand, js_hand);
        alert('あなたの選んだ手は' + user_hand + 'です。\nJavaScriptの選んだ手は' + js_hand + 'です。\n結果は' + judge + 'です。');
        flag = 1;
    }
}

・・・ご存知の方は「これはまずい書き方だ」と一瞬で気付かれるかと思いますが、これでは上手くいきませんでした。

具体的には、グー、チョキ、パーが入力されても「グー・チョキ・パーのいずれかを入力してください」とアラートが出て、もう一度入力を求めてきます
つまり、上記の表記では文字を入力した場合はelse ifの箇所で必ずtrueが返ってしまっているということです。

原因

上記の記事をごらんいただければ原因は記載されていますが、こちらでもまとめておきます。
原因は文字列(正確には特定の値)はtrueとして評価されてしまうことです。(上記2つ目の記事参照)
また、上記の1つ目の記事にもある通り、JavaScriptの||は他の言語と違った処理がなされます。
以下引用させていただきます。

つまり、『||』の場合は左側の式の評価が『true』扱いされるのであれば左側の式の評価結果が、左側の式の評価が『false』扱いされるのであれば全体の評価が『true』扱いになろうとも『false』扱いになろうとも常に右側の式の評価結果が返って来ます。

と言うことで、上記の記法では以下の様なことが起こっています。

  • 入力した文字が「グー以外」だった場合
    一番左のuser_hand != "グー"trueになるので、true確定。
  • 入力した文字が「グー」だった場合
    一番左のuser_hand != "グー"false扱いになるので右側の式へ
    文字列"パー"はtrueとして評価されるのでtrue確定

となり、(特定の)値を入力した場合は全て最終的にtrueとなります。

ただ単に解決するのであれば、横着せずに||の左右全てを条件式にしてあげればOKです。

    }else if(user_hand != "グー" || user_hand !="チョキ" || user_hand !="パー") {
        alert("グー・チョキ・パーのいずれかを入力してください。");

それでも横着がしたくて

さて、ようやく本題です。

今回の場合はジャンケンなので3パターンしかなくuser_hand != をそれぞれに書くのもそれほどの手間ではないのですが、より多くのことについて同様の記述をする際にはその手間も膨大になります。
なのでなんとか値について「または」を使う方法はないかと考えたところ、配列を活用する方法を思いつきました。
以下の通りです。

var js_hand = getJShand();
const hands = ["グー", "チョキ", "パー"]; //これに含まれているかどうかで判断
var flag = 0;

while(flag == 0) {
    var user_hand = prompt('ジャンケンの手をグー、チョキ、パーから選んでください。');
    if(user_hand == null) {
        alert("またチャレンジしてね");
        flag = 1;
    }else if(!hands.includes(user_hand)) { //!を頭につけてuser_handがhandsに含まれて「いなければ」に変換
        alert("グー・チョキ・パーのいずれかを入力してください。");
    }else{
        var judge = winLose(user_hand, js_hand);
        alert('あなたの選んだ手は' + user_hand + 'です。\nJavaScriptの選んだ手は' + js_hand + 'です。\n結果は' + judge + 'です。');
        flag = 1;
    }
}

これで上手く行きました。(上記の通り、後の追加に対応するならpromptの中身の'ジャンケンの手をグー、チョキ、パーから選んでください。'のグー、チョキ、パーの部分も配列から取れる様にした方が良いですね。)

あとがき

詳しく調べてはいないので分かりませんが、上記の配列を使う、という方法はjavascriptに限らず他の言語でも使えるかも知れないので、他の言語で論理演算する場合はまず試してみようと思います。
また、横着したい、と言う気持ちが工夫を生み出すと思うので、プログラミングに関してはめんどくさがりの姿勢を今後も貫いていきたいです。

p.s.
自分は前職教育系の仕事をしていたので、数学でも横着な子の方が成長早かったなぁと思い出したりしました。
数学とプログラミングってなんだかんだ必要な考え方が似てるところ多いな、と思います。

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

【備忘録】JavaScriptでfilterに通る値とは ~ nullとかundefinedとか空文字とか~

はじめに

JavaScriptで filter を使用しているときに「あれ?なんでこうなるんだ?」ってつまづいたところを備忘録として記載します。

つまづいたところ

例えばこちらのコード

var fruits = ['', null, undefined, 'mango', 'orange'];
var text = fruits.filter(value => {
    return value;
});
console.log(text);
結果
["mango", "orange"]

空文字 とか null とか undefined をfilterにかけるだけでそれらを抜かした配列ができている。。。。!!
ほぉーなんでだろーと調べました。

説明

調べてみると、MDNサイトにちゃんと説明がのっていました。

Array.prototype.filter()

filter() は、与えられた callback 関数を配列の各要素に対して一度ずつ呼び出し、callback が true と評価される値を返したすべての要素からなる新しい配列を生成します。callback は値が代入されている配列の添字に対してのみ呼び出されます。つまり、すでに削除された添字や、まだ値が代入されていない添字に対しては呼び出されません。callback によるテストに合格しなかった配列要素は単純にスキップされ、新しい配列には含まれないだけです。

つまり、値が代入されてない場合は勝手にスキップしてくれるよってことですね!なるほど~
そこで次なる疑問「値が代入されてない」ってどういうことをいうの??

値が代入されてない定義

上の説明から再度引用すると、

callback が true と評価される値を返したすべての要素からなる新しい配列を生成します。

つまり、callback が false と評価される値を返した場合は配列を生成しない。

さらにつまり、値を条件式に入れたときに ture になるか false になるかってことで判断すればいいと理解しました。

null・undefined・空文字を条件式に入れるとfalseになることを検証

①null

var value = null;
if(value){
 console.log('true!');
} else {
 console.log('false');
}
結果
false

②undefined

var value = null;
if(value){
 console.log('true!');
} else {
 console.log('false');
}
結果
false

③空文字

var value = "";
if(value){
 console.log('true!');
} else {
 console.log('false');
}
結果
false

おまけ:数字の 0

var value = 0;
if(value){
 console.log('true!');
} else {
 console.log('false');
}
結果
false

※数値の 0 もfalseになっちゃうので注意しましょう

まとめ

条件式に関して、たまに (!str) 的なものを見て、ふーん、?、と浅い理解にとどまっていましたが、今回のことでだいぶ理解が深まったと思います。

ご存じの方からするとそんなことも知らないのかって感じかもしれませんが、、、

こういった地道な理解が複雑なコードを理解したりするうえで大事と(勝手に)思っているので、日々勉強で頑張りたいと思います。

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

Reactは怖くない!入門1

はじめに

Reactが怖い、書き方全くわかんない!!って人がReactに抵抗がなくなることを目的としてこの記事書いてます。
めちゃくちゃ噛み砕いてるつもりです。
間違った表現や、もっと説明するべきものがあれば教えてください。
初心者の方は、ガンガン質問してもらえると嬉しいです。

Reactって何

javascriptのライブラリ
UIを部品と考えその部品をたくさん作り、それを組み合わせて画面を作ることができます。

ディレクトリ構造(例)

src/
    components/
        Hoge.js
    index.js
    App.js
public/
    index.html
package.json
webpack.config.js

各ディレクトリの説明

src/

reactのcomponentというか実装はこの中に全部書いていく

public/

htmlファイル、画像ファイルはここに配置する。
webpackでビルドされたファイルの出力先もここになることが多い

package.json

使用するライブラリとか、コマンドの設定をするところ

webpack.config.js

複数のjs, image, cssを1枚のjsファイルにいい感じにまとめてくれるもの。(設定次第で分割とかできるけども)
src/*を読み込んで、public/に出力って設定したりできます

その1枚にまとめられた ファイルをhtmlで読み込むことによってReactが動いてくれる

軽くReactComponentをどうやって表示させてるかについて

一番元となるファイル

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

こいつ ReactDOM.render(<App />, document.getElementById('root'))
AppというReactのcomponentをidがrootのdomに描画してくれる。
Appの内部で変更が起これば再描画してくれる。
基本的にはあまりここをつつくことがないので最初のうちは気にする必要はないかも。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/javascript" src="/js/bundle.js"></script> //ここでwebpackで出力した1枚のjsを読み込む
  </body>
</html>

このhtmlを表示すると<div id="root"></div> の中にReactのcomponentが表示されている

前提知識

今の段階ではなんのことかはわからないかもしれないが、頭の隅に置いておいてください。

stateとprops

State: そのコンポーネントが持っている状態
Props: 親コンポーネントから渡されたプロパティ

例えば、stateがあるreact componentに引数として渡されると、渡された先のcomponentではpropsと言われます。

サンプル
const Parent = () => {
    const [hoge setHoge] = useState(''); //これはstate

    return <Child hoge={state} />
}

const Child = ({ hoge }) => ( //ここのhogeはpropsと呼ばれる
    <div>{hoge}</div>
)

定義されたcomponentの中ではstate、そのstateが下のcomponentに渡るとpropsと呼ばれるようになります。

アロー関数

componentを作る時、関数を定義するときは基本的にallow関数を使います。

下の二つの関数は同じ意味です。

function hoge(params) {
  return params
}

const hoge = params => params

関数の中でいろいろ処理を書かず、returnだけで済まされる場合はreturnを省略できます。
また、引数が一つの時は ()を省略できます。

省略できないパターン
const hoge = (hage1, hage2 ) => {
    const sum = hage1 + hage2

    return sum
}

(まあhage1 + hage2を変数に置く必要ないんですが、一応例として)

あとthisを気にしなくてよくなるとかあるのですが、最初のうちはあまりわかってなくてもいいです。
https://qiita.com/sitilma/items/5c2a4ed03d53896321a6

stateの扱い

基本的に、stateは上位componentだけで定義します。
そして下のcomponentにバケツリレーで渡して使っていきます。
適当にどこでもstateを定義してしまうと意味がわかんなくなっちゃいます。
(例外として下で定義することもありますが今は気にしないでください)

また、stateを変更する場合は特別な関数を通さなければなりません。適当に代入して変更はできません。

//定義
const [state, setState] = useState(''); // state = '';
const [hoge, setHoge] = useState(''); // hoge = '';

//変更する時
setState('page') // state = page;
setHoge('ooooooooo') // hoge =  ooooooooo;

// やっちゃダメな奴
state = 'page';
hoge = 'ooooooooo'

ちゃんとset〇〇のような関数を通して変更しましょう。

実践です

簡単なToDoList作りましょう。
画面収録-2020-03-19-17.22.35.gif

仕様
・textfieldに打った文字がリアルタイムで下に表示される
・登録ボタン押すとそれが保存される
・クリアボタンを押すとリセットされる

npm install -g create-react-app
create-react-app [プロジェクト名]
このコマンドでreactの雛形作ることができます。

最初は意味がわからないと思うので、サンプルコードを参考に書いてもらうことがいいかもしれません。
参考にしてください。
https://github.com/Tsukuni/react-sample-app

終わりに

入門2も書くのでお待ちください。

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

スクリプト言語 KINX/ライブラリ(File/Directory)

ライブラリ(File/Directory)

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」でお届けしているスクリプト言語 Kinx。前回の記事で プレビュー・リリースしたぜ と勇み足ぎみに突っ走ったが、マニュアルが整っていないので、やはりなかなか使いづらいに違いない。現時点で参考になるのは以下しかないし、英語なので。

そこでだ、若旦那。

少しだけマニュアル代わりになるように、簡易説明をここに記すことにしましたよ。今回は File と Directory。一番実用的な感じがしたので。

重要:

v0.1.0 ではファイル系のインタフェースが不完全だったので、急遽 v0.2.0 を用意しました。下記の内容は v0.2.0 のものです。v0.1.0 では動作しないものが一部あります。
また、標準入力をサポートしました。リリース内容は下記を参照してください。
* リリース・リスト

ライブラリ

File クラス

File クラスはその名の通りファイルの読み書きをするクラスで、静的メソッドを持つ。また、new File(filename, attr) として個別のファイル・インスタンスを作成することもできる。

静的メソッド

File クラスの静的メソッドは、以下の通り。

メソッド 内容 復帰値(型) 引数 意味
File.load(path) ファイル内容をテキスト形式で一括読込み String path: String 中身を読み込むファイルパス
File.mkdir(path) ディレクトリの作成 1: 成功 path: String 作成するディレクトリ・パス
File.rename(oldname, newname) ファイルパスの変更 1: 成功 oldname: String 変更前のファイルパス
newname: String 変更後のファイルパス
File.unlink(path) ファイルの削除 1: 成功 path: String 削除するファイルパス
File.exists(path) ファイルの存在確認 1: 存在する path: String 確認するファイルパス
File.isDirectory(path) ディレクトリ名かどうかの確認 1: ディレクトリである path: String 確認するファイルパス
File.isSymlink(path) シンボリックリンクかどうかの確認 1: シンボリックリンクである path: String 確認するファイルパス
File.filesize(path) ファイルサイズの取得 Integer path: String 確認するファイルパス
File.filedate(path) ファイル日付の取得 { modified, accessed, creation } path: String 確認するファイルパス
File.setFiledate(path, obj) ファイル日付の設定 path: String 確認するファイルパス
obj: Object { modified, accessed, creation }

File インスタンス

File インスタンスは new で作る。サンプルは以下の通り。

var f = new File(filename, attr);

パラメータは以下の 2 つ。

パラメータ 取りうる値 意味
filename 文字列 ファイル名
attr 以下の値の論理和 オープン属性
File.READ 読込可能
File.WRITE 書込可能
File.NEW 新規作成モード
File.TEXT テキストモード
File.BINARY バイナリモード
  • オープン属性は以下のような感じで指定する。
    • File.READ|File.WRITE ... 追記モードで読み書き可能
    • File.READ|File.WRITE|File.NEW ... 新規作成モードで読み書き可能
    • File.READ|File.TEXT ... テキストモードで読み込み専用

File インスタンスのメソッドは以下の通り。すみません、peekgetchputch は v0.1.0 に含まれていません。。。

メソッド 内容 復帰値(型) 引数 意味
f.load() ファイルの中身を一括読み込み String
f.close() ファイルをクローズ
f.readLine() 一行読み込み String, 0: EOF
f.peek(ms) 入力があるか確認 1: 存在する、0: 存在しない ms: Integer タイムアウト(ミリ秒)
f.getch() 1 文字入力 Integer (取得文字コード)
f.putch(ch) 1 文字出力 ch: Integer 出力文字コード
f.print(...args) 改行文字なしで出力 出力文字数: Integer ...args 表示する要素(可変引数)
f.println(...args) 改行文字付きで出力 出力文字数: Integer ...args 表示する要素(可変引数)

File.open

個別に new した場合は close しなければならない(GC されたら勝手にクローズ自体はされる)が、スコープを決めて自動的にクローズさせたい場合は File.open を使うのがオススメ。スコープを抜けるとその場で自動的にクローズされる。こんな感じ。

File.open("README.md", File.READ, &(f) => {
    var l, n = 0;
    while ((l = f.readLine()).isString) {
        System.println("%4d: %s" % (++n) % l);
    }
});

File.open の内容としては、意味的には次のと概ね同等。

function FileOpen(filename, attr, func) {
    var f = new File(filename, attr);
    try {
        return func(f);
    } finally {
        f.close();
    }
}

Directory

ディレクトリをトラバースするために使う。サブディレクトリを見つけた時に再帰的に潜っていく場合は recursiveWalk、潜っていかない場合は walk を使う。

Directory.walk

Kinx のリポジトリ・フォルダで以下を実行してみるとなんとなくわかるかも("src" ディレクトリがあるので動作する、という意味。存在するディレクトリ名にすればどこでも OK)。

Directory.walk("src", &(name) => {
    System.println(name);
});

Directory.recursiveWalk

次のように実行すると違いがわかるはず("src" 配下にサブディレクトリがある前提)。

Directory.recursiveWalk("src", &(name) => {
    System.println(name);
});

おわりに

先日、昔のツェッペリンのインタビューを読んでみて、新しい何か、自分たちがやりたい何か、をガレージ・レベルからスタートさせてやっていく、ってのは改めて ロックだねえ、と感慨に耽っていました。ビッグになったロックスターたちも、最初は こういうのやりたいんだよ とガレージやクラブからスタートさせてる訳です。夢と希望を忍ばせて。

今あるものに満足せずに、また、今現在提供されているものに満足行かないようなら、文句を言うのではなく自分の手で実現させてしまうのが良いよね。そのアティテュードこそがまさに ロックンロール

まだまだ転がり続けようぜ、相棒。

で、最後はいつものおねだりの時間です。

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

Node.jsからAutoML Vision の鼓膜画像分類モデルを使ってみる

概要

普段は耳鼻科の開業医をしています。

前回の記事はこちら
GCP Cloud AutoML Vision を使った鼓膜画像分類

今回は作成したAutoML Visionの鼓膜画像分類モデルをNode.jsから使ってみました。

作成

1.プロジェクトの作成

こちらを参考にしました
Cloud AutoML: Node.js Client

index.js
require('dotenv').config();//.envを読み込む

const automl = require('@google-cloud/automl');
const fs = require('fs');

// Create client for prediction service.
const client = new automl.PredictionServiceClient();

/**
 * TODO(developer): Uncomment the following line before running the sample.
 */

const projectId = `The GCLOUD_PROJECT string, e.g. "my-gcloud-project"`;
const computeRegion = `region-name, e.g. "us-central1"`;
const modelId = `id of the model, e.g. “ICN723541179344731436”`;
const filePath = `local text file path of content to be classified, e.g. "./resources/flower.png"`;
const scoreThreshold = `value between 0.0 and 1.0, e.g. "0.5"`;

// Get the full path of the model.
const modelFullId = client.modelPath(projectId, computeRegion, modelId);

// Read the file content for prediction.
const content = fs.readFileSync(filePath, 'base64');

const params = {};

if (scoreThreshold) {
  params.score_threshold = scoreThreshold;
}

// Set the payload by giving the content and type of the file.
const payload = {};
payload.image = {imageBytes: content};

async function test(){
  // params is additional domain-specific parameters.
  // currently there is no additional parameters supported.
  const [response] = await client.predict({
    name: modelFullId,
    payload: payload,
    params: params,
  });
  console.log(`Prediction results:`);
  response.payload.forEach(result => {
    console.log(`Predicted class name: ${result.displayName}`);
    console.log(`Predicted class score: ${result.classification.score}`);
  });
}

test();

2.AutoML用認証キーの作成

コンソール画面左上のナビゲーションメニューから『APIとサービス』➡『認証情報』
image.png

『認証情報を作成』➡『サービスアカウント』
image.png

サービスアカウント名を適当に決めて『作成』を選択
image.png

ロールは『AutoML 予測者』を選択し『続行』
image.png

『+キーを作成』を選択
image.png

『JSON』➡『作成』を選択
image.png
JSONファイルがダウンロードされます
image.png

3.AutoML用認証キーのつなぎ込み

ダウンロードされたJSONファイルをindex.jsと同じフォルダーに入れる
image.png

.envを作成

GOOGLE_APPLICATION_CREDENTIALS=./ここにダウンロードされたJSONファイル名を記入

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

$ npm install @google-cloud/automl 
$ npm install dotenv 

4.認証情報を設定

index.jsを以下のように書き換える

index.js
const projectId = "自分のプロジェクト名";
const computeRegion = "us-central1";
const modelId = "ICNで始まる番号";
const filePath = "テストしたいローカル画像のフルパス"
const scoreThreshold = "0.5";

modelIdはこちらの赤丸の部分
image.png

今回テストしたローカル画像はこちら
急性中耳炎の画像です

WIN_20190529_08_40_52_Pro.jpg

フルパスは以下のように区切らないとうまく動きませんでした(windows10)。

const filePath = "C:\\Users\\***\\data\\test\\aom\\WIN_20190529_08_40_52_Pro.jpg";

5.デプロイ
今回デプロイし終わるまで20~30分ほどかかりました。
image.png

ちなみにデプロイしたままにすると1日3000円ほど課金されますので。使わないときはデプロイを解除しましょう。
モデルのデプロイ解除の方法はこちら

テスト

index.jsを実行します。
Prediction class name:aom(急性中耳炎)と正しく判定されています。
Prediction class score(信頼度 0.0〜1.0の値が入ります)は1なのでかなり自信があるようです。

image.png

考察

Node.jsからAutoML Vision の鼓膜画像分類モデルを使うことができました。
次はLINE Botに組み込んでみたいと思います。

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

magnific-popupで連続ポップアップ表示

※自分用メモ

■概要
最初にログインボーナスのポップアップ、次に複数件のお知らせのポップアップ表示。
・指定時間経過後、次のお知らせポップアップ表示
・閉じるボタン押下で次のお知らせポップアップ表示

Laravel+jQuery

@if( isset($login_bonus_flg) )
<div id="loginbonus" class="white-popup">
    <h1>ログインボーナス</h1>
    <p>{{ $login_bonus_msg }}</p>
</div>
@endif
@if( count($userinfo) > 0 )
    @foreach($userinfo as $index => $lines)
        <div id="userinfo{{ $index }}" class="white-popup disptime_{{ $lines->disptime }}">
            {!! $lines->userinfo !!}
        </div>
    @endforeach

@endif


<script async src="/js/jquery.magnific-popup.min.js"></script>
<script>
$(window).load(function(){
    var open_flg = '{{ $login_bonus_flg }}';
    if( open_flg ){
        var timerid;

        /*
         * ログインボーナスポップアップ表示のあとにお知らせポップアップ表示
         */
        $.magnificPopup.open({
            items:[{
                src:'#loginbonus',
                type: 'inline',
                modal: true
            }],
            callbacks:{
                close:function(){
                    clearTimeout(timerid);

                    /*
                     * class名から表示時間取得
                     */
                    var disptime = eval($('#userinfo0').attr('class').replace(/.+disptime_(\d+)/, "$1")) * 1000;
                    openPopup(0, disptime);
                }
            }
        });

        /*
         * 指定時間経過後、ポップアップを閉じる
         */
        timerid = setTimeout(function(){
            $.magnificPopup.close();
        }, {{ $login_bonus_disptime }});
    }
});

/*
 * 次のお知らせポップアップの表示
 */
function openPopup(id, closetime){
    if( document.getElementById('userinfo'+id) != null ){
        var timerid;

        /*
         * setTimeoutを使用することで前回のポップアップを終了させ、次のポップアップ表示に切り替える
     */
        setTimeout(function(){
            $.magnificPopup.open({
                items:[{
                    src:'#userinfo'+id,
                    type: 'inline',
                    modal: true
                }],
                callbacks:{
                    close:function(){
                        clearTimeout(timerid);

                        /*
                         * 次のIDの表示のためIDをインクリメント
                         */
                        id++;

                        /*
                         * class名から表示時間取得
                         */
                        if( document.getElementById('userinfo'+id) != null ){
                            var disptime = eval($('#userinfo'+id).attr('class').replace(/.+disptime_(\d+)/, "$1")) * 1000;
                            openPopup(id, disptime);
                        }
                    }
                }
            });

            /*
             * 指定時間経過後、ポップアップを閉じる
             */
            timerid = setTimeout(function(){
            $.magnificPopup.close();
            }, closetime);

        }, 1);
    }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCode1.43.0では即時関数内でtry-catchを使うとreturnが正しく解釈されない

VSCode(1.43.0と1.43.1で確認)では、以下のコードでconsole.logの部分が「到達できないコード」として判定されてしまう。

const message = (() => {
    try {
        /* エラーが発生しそうな処理 */
        return "success";
    } catch (e) {
        return "error:" + e;
    }
})();

console.log("message = " + message);


しかし、Node.js(12.16.1)で上記のコードを実行すると、console.logはちゃんと実行される。

message = success


即時関数の中でtry-catchは使うべきではないのだろうか?
そもそもこういった処理を即時関数化すべきではないのかも?
でもVSCode 1.42.1 以前はちゃんとconsole.logが到達可能と判定されていたし・・・。

VSCodeのissueに報告したいけど英語苦手だし。
直るまで1.42.1使うか。

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

JavaScriptによる入力チェック後にreCAPTCHAのトークンを得てSubmitする

フォームにGoogle reCAPTCHAを設置する最もシンプルな実装は下記のようなものです。
(なお、スコア評価が面倒なのでv2のinvisibleを使います)

<script src='https://www.google.com/recaptcha/api.js' async defer></script>
<form id="myform" action="server.php" method="POST">
    <dd>
        <dt>お名前 【必須】:</dt>
        <dd><input type="text" name="name" value="" /></dd>
    </dd>
    <div class="g-recaptcha"
         data-sitekey="GOOGLE_RECAPTCHA_SITE_KEY"
         data-callback="google_recaptcha_onSubmit"
         data-size="invisible">
        <input type="submit" value="送信する" />
    </div>
</form>

このコードのフローとしては、下記のようになっていて「JavaScriptによる入力チェック」は入れていません。

  • 1.送信ボタンを押す
  • 2.reCAPTCHAのトークンを得て、自動的にフォームが送信される

これに入力チェックを追加します。上記のHTML部分はそのままでJavaScriptの実装を追加します。

<script
  src="https://code.jquery.com/jquery-3.4.1.min.js"
  integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
  crossorigin="anonymous"></script>
<script src='https://www.google.com/recaptcha/api.js' async defer></script>
<script type="text/javascript">
// 面倒なのでjQueryに依存するように書いてます。
$(function() {
    $('#myform').on('submit', function (event) {
        if (! check_form()) { return false; }
        return true;
    });
});
// 入力チェック
function check_form() {
    var err = [];
    if ($('#myform input[name=name]').val() === '') {
        err.push('お名前は必須です。');
    }
    if (err.length > 0) {
        alert( err.join("\n") ); // エラーはalert()で出す簡易版
        return false;
    }
    return true;
}
function google_recaptcha_onSubmit(token) {
    $('#myform').submit();
}
</script>

フォームのonsubmitイベントハンドラで入力チェックを行い、もしNGだったらフォームを送信しない。入力チェックがOKならSubmitするようになっています。

しかし、入力内容を修正後に再度送信ボタンを押と、イベントが飛びません。

  • 1.送信ボタンを押す
  • 2.JSの入力チェック→NG
    →名前を入れる
  • 3.もう一度送信ボタンを押す
    →反応しない (2のイベントを受け付けない)

reCAPTCHA側でcallbackは1回しか送信しない仕様になっているからではないかと思うところです。

ではどうするか。

data-callback属性を設定する要素を変えます。

先程のコードでは、送信ボタンの親divにdata-callback属性を設定していました。これを空divにして送信ボタンは自由にします。

<form id="myform" action="server.php" method="POST">
    <dd>
        <dt>お名前 【必須】:</dt>
        <dd><input type="text" name="name" value="" /></dd>
    </dd>
    <div class="g-recaptcha"
        data-sitekey="GOOGLE_RECAPTCHA_SITE_KEY"
        data-callback='google_recaptcha_onSubmit'
        data-size="invisible">
    </div>
    <input type="submit" value="送信する" /> <!-- ←divの外に出した -->
</form>

JavaScriptの実装も、onsubmitイベントハンドラはトークンが取得できたのを確認してからSubmitするように変えます。

<script
  src="https://code.jquery.com/jquery-3.4.1.min.js"
  integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
  crossorigin="anonymous"></script>
<script src='https://www.google.com/recaptcha/api.js' async defer></script>
<script type="text/javascript">
// 面倒なのでjQueryに依存するように書いてます。
var google_recaptcha_token = null; // reCAPTCHAトークンがココに入る
$(function() {
    $('#myform').on('submit', function (event) {
        if (google_recaptcha_token !== null) { return true; }
        if (! check_form()) { return false; }
        grecaptcha.execute();
        return false;
    });
});
// 入力チェック
function check_form() {
    var err = [];
    if ($('#myform input[name=name]').val() === '') {
        err.push('お名前は必須です。');
    }
    if (err.length > 0) {
        alert( err.join("\n") ); // エラーはalert()で出す簡易版
        return false;
    }
    return true;
}
function google_recaptcha_onSubmit(token) {
    google_recaptcha_token = token;
    $('#myform').submit();
}
</script>

これでフローは下記のようになり、正常に入力チェックとSubmitができます。

  • 0.フォームのonsubmitに関数を割り当てておく
  • 1.送信ボタンを押す
  • 2.onsubmitが実行される
    • 2-1.recaptchaトークンが存在していたらtrueを返してフォームを送信する
    • 2-2.入力チェックでNGなら再入力を促してfalseで返す(トークンは生成しない)
    • 2-3.入力チェックがOKで、トークンが存在していないなら grecaptcha.execute() をしてfalseを返す
  • 3.grecaptcha.execute()より、data-callbackで指定した関数に返ってくるので、トークンを保存してもう一度submitイベントを発火させる
    →2-1に遷移するのでフォームが送信される

reCAPTCHAのドキュメントでも 空div にdata-callback属性を設定しているので、単に私が見誤ってハマっただけなのですけれども……。
https://developers.google.com/recaptcha/docs/invisible

以上、参考になれば幸いです。

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

Reactのメインコンセプト要点まとめ(1)

この記事の位置づけ

React公式ドキュメントの「メインコンセプト」について、実際にReactの開発を行う中で特に参考になった章を要点の解説付きで纏めたものである。実際にReact公式ドキュメントを読み解いてもらうのが一番だが、時間がない人に要点だけでも読んでもらえるようにしたいと思い、纏めている。

公式のメインコンセプトでは、以下の章立てで説明をしている。
1. Hello World
2. JSXの導入
3. 要素のレンダー
4. コンポーネントとprops
5. stateとライフサイクル
6. イベント処理
7. 条件付きレンダー
8. リストとkey
9. フォーム
10. stateのリフトアップ
11. コンポジションvs継承
12. Reactの流儀

当記事では、以下の3章分について要点まとめをしている。
1. Hello World
2. JSXの導入
3. 要素のレンダー

出典元資料

React公式ドキュメントのメインコンセプト

メインコンセプト

1. Hello World

一言コメント

まずは実践的なチュートリアルを使って、手を動かしてReactの動作を学習するのが望ましい。
まだチュートリアルをやっていない場合は、React公式チュートリアルを実際に作ってみて、感触を掴むことをお勧めする。

また、Reactは最新のJavaScript標準であるEcmaScript6でコードを記述することになる。
これまでJavaScriptでコードを書いたことがなかったり、最近のClassやアロー演算子といった書き方にまだ触れたことがない人は、まずはEcmaScript6基準のJavaScriptについて学習することをお勧めする。

2. JSXの導入

知っておくべきポイント

JSXを使う理由

UIがどのような見た目なのかを記述するために、ReactとともにJSXを使用することを公式は推奨している。
ReactでJSXを使うことは必須ではないが、JSXがあるためにReactは有用なエラーや警告をより多く表示できる。

hello.js
const element = <h1>Hello, world!</h1>;

JSXに式を埋め込む

以下のように中括弧を使用することで、JSX内に式を埋め込むことができる。

name.js
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

JSXで子要素を指定する

以下のように、JSX内で子要素を指定することで複数の要素を含め入れることも可能。

hello.js
const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

また、上記の書き方だとDIVタグが無駄に増えるという問題があるので、以下のような書き方もサポートされている。

hello.js
const element = (
  <>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </>
);

一言コメント

最初はキモいが、慣れるとJSXでUIを構築するのが当たり前になる。便利。

3. 要素のレンダー

知っておくべきポイント

Reactの「要素」とは何のことなのか

要素とはReactアプリケーションの最小単位の構成ブロックです。

ふむ。。。
もう少し噛み砕くと、以下のようにReactのUIを定義したオブジェクトはReact要素であると言える。

hello.js
const element = <h1>Hello, world</h1>;

公式でも「要素とコンポーネントを混同する人がいる」と指摘されているが、Reactの「要素」と「コンポーネント」は別の意味を持つ言葉である。公式では「要素とはコンポーネントを構成するもの」とされている。(要素はコンポーネントに内包されているUI定義)

ReactDOM.render() と React.Component.render() の違いは何なのか?

ReactDOM(公式ドキュメント)
React.Component(公式ドキュメント)

違いとしては、、、

  • ReactDOM.render(element, container)は、containerとして渡されたDOMに、React要素を実際にレンダリングしている
  • React.Component.render()は、React.Componentthis.propsthis.stateを調べた上でReact要素を返す(DOMノードのレンダリングはこの時点では行っていない)

ざっくりまとめると、以下のような使い分けになると思っている。

  • コンポーネント単位(UIの部品単位)のReact要素を定義するのはReact.Component.render()で行う
  • HTMLのどのDOM要素にReactを描画するか指定するのはReactDOM.render()を使用する
    • Create React Appで例えば、index.htmlの<div id="root"></div>にReact要素を描画している
    • 上記で指定したDOM要素にReact要素(Appコンポーネント)を描画するために、index.jsではReactDOM.render(<App />, document.getElementById("root"))と記述している
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <!--
      省略
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      省略
    -->
  </body>
</html>
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

レンダリングされたReact要素の更新

React要素はイミュータブルであり、一度React要素を作成すると変更することはできない。
イミュータブルということは、「変更」はできないが「置き換え」は可能ということである。
よって、ReactでUIを更新する唯一の方法は、新しいReact要素を作成してReactDOM.render()に渡してDOMを再描画することになる。

ただし、実際には大抵のReactアプリケーション(Create React App含め)では、ReactDOM.render()は一度しか呼び出さない。
それらのReactアプリケーションでは、ReactDOM.render(element, container)のelementで渡すことになるReact.Componentにおいて、React.Component.render()関数を使用してコンポーネント単位のUI部品を置き換えることになる。

一言コメント

チュートリアルをやっただけでは、ReactDOM.render()React.Component.render()の違いはイメージがつきにくいかもしれない。
ただ、DOMツリーとReactの紐付けをしているのはReactDOMであり、ここはぜひ抑えておいたほうがよいと思う。

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

Tinderの自動スワイプ最新版

以前まで使っていたTinderの自動右スワイプコードが使えなくなってしまったようなので新しいコードを書いてみました。初めて作ったコードなので雑かもしれないですがちゃんと動くので許してください...

auto = setInterval(
    function(){
        var elem = document.getElementsByClassName("focus-button-style");
        var i = elem.length -2;
        elem[i].click()
    }
,1000)

一応やり方も。
(1)ChromeでTinderのスワイプ画面を開く
(2)右クリック→検証
(3)Console画面に移動
(4)上に書いたコードを丸々コピペしてEnter
以上!

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

css/Javascript fixedで固定したボタンを特定の位置で止める

初投稿です。
駆け出しのエンジニアではありますが、質問やご意見等が御座いましたらお気軽にどうぞ。

概要

サイト制作をしていると、
スクロール時に追従してくるボタンなんかをよく実装すると思います。
ですがデザイン上、フッター等の背景色とボタンの色が被ってしまうこともあるでしょう。

今回はトップに戻るボタンを、footer等の要素手前辺りで停止させるのに色々試した結果、
Javascriptでの制御がとても解りやすかったので備忘録的に残しておきます。

制御部分のコード

Javascript
window.addEventListener('scroll', () => {
      const elem = '任意のボタン positionはfixedで右下に設定';
      const docHeight = document.body.clientHeight;
      const scrollCount = document.documentElement.scrollTop || document.body.scrollTop;
      const scrollPos = document.documentElement.clientHeight + scrollCount;
      const footerHeight = document.querySelector('footer(任意)').clientHeight;
      if (docHeight - scrollPos <= footerHeight) {
        elem.style.position = 'absolute';
        elem.style.bottom = footerHeight + 20 + 'px';       
      } else {
        elem.style.position = 'fixed';
        elem.style.bottom = 20 + 'px';
      }
 });

下準備として

ボタン部分はHTML/CSSで任意に作成しfixedで追従しておきます。
また、JSのイベント時にはabsoluteに切り替えますので、body下にrelativeを設定しておく必要があります。

部分的に解説

定数に格納

//ページ全体の高さ取得
const docHeight = document.body.clientHeight;
//対応ブラウザ別に縦のスクロール量取得
const scrollCount = document.documentElement.scrollTop || document.body.scrollTop;
//ページの表示領域の高さを取得し、スクロール量を足す。
const scrollPos = document.documentElement.clientHeight + scrollCount;
//この場合はfooterの高さを指定
const footerHeight = document.querySelector('footer(任意)').clientHeight;

スクロールイベントに必要なそれぞれの値を取得し、定数に格納します。
ブラウザによる違いや、スクロールバーを含むか含まないか等少しややこしいですが、
それぞれ意味が異なってきますので注意。

イベントの処理

if (docHeight - scrollPos <= footerHeight) {
        elem.style.position = 'absolute';
        elem.style.bottom = footerHeight + 20 + 'px';       
      } else {
        elem.style.position = 'fixed';
        elem.style.bottom = 20 + 'px';
      }

scrollCountによりscrollPosの値はスクロールとともに増えていきます。
ページ全体の高さがfooterの高さ以下になった時、
fixedからabsoluteに切り替わります。
位置設定はfooterの高さより20pxほどマージンを取るように設定しているだけなので、
デザインによりけりとなります。

範囲外では元に戻すように設定しております。
これで違和感なく切り替わります。

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

Javascript fixedで固定したボタンを特定の位置で止める

初投稿です。
駆け出しのエンジニアではありますが、質問やご意見等が御座いましたらお気軽にどうぞ。

概要

サイト制作をしていると、
スクロール時に追従してくるボタンなんかをよく実装すると思います。
ですがデザイン上、フッター等の背景色とボタンの色が被ってしまうこともあるでしょう。

今回はトップに戻るボタンを、footer等の要素手前辺りで停止させるのに色々試した結果、
Javascriptでの制御がとても解りやすかったので備忘録的に残しておきます。

制御部分のコード

Javascript
window.addEventListener('scroll', () => {
      const elem = '任意のボタン positionはfixedで右下に設定';
      const docHeight = document.body.clientHeight;
      const scrollCount = document.documentElement.scrollTop || document.body.scrollTop;
      const scrollPos = document.documentElement.clientHeight + scrollCount;
      const footerHeight = document.querySelector('footer(任意)').clientHeight;
      if (docHeight - scrollPos <= footerHeight) {
        elem.style.position = 'absolute';
        elem.style.bottom = footerHeight + 20 + 'px';      
      } else {
        elem.style.position = 'fixed';
        elem.style.bottom = 20 + 'px';
      }
 });

下準備として

ボタン部分はHTML/CSSで任意に作成しfixedで追従しておきます。
また、JSのイベント時にはabsoluteに切り替えますので、body下に親要素のrelativeを設定しておく必要があります。

部分的に解説

定数に格納

//ページ全体の高さ取得
const docHeight = document.body.clientHeight;
//対応ブラウザ別に縦のスクロール量取得
const scrollCount = document.documentElement.scrollTop || document.body.scrollTop;
//ページの表示領域を取得し、スクロール量を足す
const scrollPos = document.documentElement.clientHeight + scrollCount;
//この場合はfooterの高さを指定
const footerHeight = document.querySelector('footer(任意)').clientHeight;

スクロールイベントに必要なそれぞれの値を取得し、定数に格納します。
ブラウザによる違いや、スクロールバーを含むか含まないか等少しややこしいですが、
それぞれ意味が異なってきますので注意。

イベントの処理

if (docHeight - scrollPos <= footerHeight) {
  elem.style.position = 'absolute';
  elem.style.bottom = footerHeight + 20 + 'px';       
} else {
  elem.style.position = 'fixed';
  elem.style.bottom = 20 + 'px';
}

scrollCountによりscrollPosの値はスクロールとともに増えていきます。
ページ全体の高さがfooterの高さ以下になった時、
fixedからabsoluteに切り替わります。
位置設定はfooterの高さより20pxほどマージンを取るように設定しているだけなので、
デザインによりけりとなります。

範囲外では元に戻すように設定しております。
これで違和感なく切り替わります。

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

ウェブサービスの主な機能について

主な機能一覧

ユーザー管理

1.ユーザー登録機能
2.ログイン機能
3.ログアウト
4.プロフィール登録
5.プロフィール編集
6.退会
7.パスワード変更
8.パスワードリマインダー
9.管理者ユーザー登録

コンテンツ管理

10.コンテンツ登録
11.コンテンツ編集
12.コンテンツ削除
13.コンテンツ一覧表示
14.コンテンツ詳細表示
15.コンテンツ検索
16.お気に入り登録
17.決済

基本的には上記の機能を実装し、そこに追加で機能を付け足していくことでほとんどのウェブサービスを作ることが可能になる。

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

横浜の気温をOpenWeatherMap APIでHTMLに表示する

JSじゃなくてRuby派だという方はこっちをご覧ください。(僕の記事じゃないです。)

OpenWeatherMap APIとは

世界中の都市の気象情報を表示できるWeb APIで、DarkSkyAPIなどと比較されることが多い。時たまとんでもなく外れた気象予報を出すことがある。

HTMLに表示する手順

1. API Keyを取得する

OpenWeatherMap公式サイトにてサインアップし、マイページでAPI Keyを確認する。
(Rakuten Rapid APIでも登録できるがクレカ登録など色々めんどいので公式からの手順を書いておきます)

2. fetchメソッドとjQueryで横浜の現在の天気を表示する

公式サイトで言うところのCurrent weather dataをHTMLに埋め込みます。

OWMapAPI.js
let API_KEY = '**********************'; //1.で取得したAPI Key
let elem=document.getElementById("output"); //outputは表示したいHTMLのタグ名。
function getWeather(latitude, longtitude) {
  $.ajax({
    url: 'http://api.openweathermap.org/data/2.5/weather',
    data: {
      lat: latitude,
      lon: longtitude,
      units: 'metric',//華氏の場合は"imperial"
      APPID: API_KEY
    },
    success: data => {
       console.log(data);
       elem.innerHTML=data["main"]["temp"]+"";
    }
  })
}

getWeather(35.4437, 139.6380);//データを表示したい都市の緯度経度

このコードはStack overflowの記事(英語版)を「参考にした」コードです。

XMLHttpRequestでもトライしてみたのですが頑なにChromeがブロックしてきます。これは僕のChromeの問題なのか、XMLHttpRequestがオワコンなのか、原因が分かる方がいればコメント欄でお願いします。

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

横浜の気温をOpenWeatherMap APIでHTMLに表示する [初心者のための初心者による備忘録]

JSじゃなくてRuby派だという方はこっちをご覧ください。(僕の記事じゃないです。)

OpenWeatherMap APIとは

世界中の都市の気象情報を表示できるWeb APIで、DarkSkyAPIなどと比較されることが多い。時たまとんでもなく外れた気象予報を出すことがある。

HTMLに表示する手順

1. API Keyを取得する

OpenWeatherMap公式サイトにてサインアップし、マイページでAPI Keyを確認する。この後は公式サイトにお世話になることはないので、APIページの
(Rakuten Rapid APIでも登録できるがクレカ登録など色々めんどいので公式からの手順を書いておきます)

2. fetchメソッドとjQueryで横浜の現在の天気を表示する

公式サイトで言うところのCurrent weather dataをHTMLに埋め込みます。

OWMapAPI.js
let API_KEY = '**********************'; //1.で取得したAPI Key
let elem=document.getElementById("output"); //outputは表示したいHTMLのタグ名。
function getWeather(latitude, longtitude) {
  $.ajax({
    url: 'http://api.openweathermap.org/data/2.5/weather',
    data: {
      lat: latitude,
      lon: longtitude,
      units: 'metric',//華氏の場合は"imperial"
      APPID: API_KEY
    },
    success: data => {
       console.log(data);
       elem.innerHTML=data["main"]["temp"]+"";
    }
  })
}

getWeather(35.4437, 139.6380);//データを表示したい都市の緯度経度

このコードはStack overflowの記事(英語版)を「参考にした」コードです。

XMLHttpRequestでもトライしてみたのですが頑なにChromeがブロックしてきます。これは僕のChromeの問題なのか、XMLHttpRequestがオワコンなのか、原因が分かる方がいればコメント欄でお願いします。

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

ブログ機能などの一覧でリンク先や詳細ページがないときにアニメーションを止める。

よく使うので忘備録として・・

パターン1

一番シンプルなやつ
#は環境に応じて使い分ける。

<ul class="blog-list">
  <li><a href="pathToTheBlog">ブログ始めました。</a></li>
  <li><a href="pathToTheBlog">詳細ページあります</a></li>
  <li><a href="#">詳細ページないです。</a></li>
</ul>
.blog-list {
  >li >a[href="#"] {
    pointer-events: none;
  }
}

パターン2

一番ありがちなやつ

<ul class="blog-list">
  <li>
    <a href="pathToTheBlog"><!-- 透明なアンカータグを要素全体の上に配置 --></a>
    <div class="img-wrap" style="background: url(pathToDefaultImg) no-repeat center/cover">
      <img src="pathToImg" alt="ユーザー投稿した画像です。">
    </div>
    <div class="txt-wrap">
      <p>テキスト入ります。</p>
    </div>
  </li>
  <li>
    <a href="#"><!--  --></a>
    <div class="img-wrap" style="background: url(pathToDefaultImg) no-repeat center/cover">
      <img src="pathToImg" alt="ユーザー投稿した画像です。">
    </div>
    <div class="txt-wrap">
      <p>テキスト入ります。</p>
    </div>
  </li>
</ul>
$(function() {
  $('.blog-list > li').each(function(){
    if ($(this).children('a').attr('href') == '#') {
      $(this).css('pointer-events', 'none');
    }
  });
});
.news-list {
  display: flex;
  justify-content: space-around;
  width: calc(100% + 15px);
  >li {
    width: calc(50% - 15px);
    margin-right: 15px;
    position: relative;

    >a {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 1;
    }

    .img-wrap {
      overflow: hidden;
      height: 200px;
      width: 100%;

       >img {
         height: 100%;
         width: 100%;
         object-fit: cover;
         transition: .3s transform;
       }
     }

     .txt-wrap {
       font-size: 16px
     }

     &:hover {
        .img-wrap {
          transform: scale(1.05);
        }
        .txt-wrap p {
          text-decoration: underline;
        }
     }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで外部ドメイン向けにリンクを生成する(ReactRouterだとできない?ので。。)

概要

Reactで外部ドメイン向けにリンクを生成する(ReactRouterだとできない?ので。。)

実装

↓で定義したLinkToOtherDomainコンポーネントを使う。

import React from "react";

export const LinkToOtherDomain = props => {

    const { to, children, ...other } = props;
    return (
        <a href={to} {...other} rel="noopener noreferrer">
            {children}
        </a>
    );
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ReactNatve】Platform.OSが必ずiosになった話

TL;TR

  • Platform.OSの値が常にiosになってしまった
  • 原因は初歩的なタイプミスだった
  • コミットの粒度はなるべく小さくしよう

何が起こったか

ざっくり説明するとReactNative製アプリをAndroidで動かした時に、iOSでしか表示されないUIが表示されていました。
下記のようにPlatform.OSを表示させてみると、出力されるのはios
なぜかOSの種類に関わらずiosとして扱われてしまっているようでした。

import { Platform } from 'react-native';

console.log('os : ', Platform.OS); // --> "os : ios" と出力される・・・

react-nativeの不具合ならもっとIssueで大々的に騒がれてそうだけど、そんな気配もない。
となると何らかのバグなのだが、Platform.OSの値が必ずiosになってしまうってどんな状況やねん・・・

と思いながら直近のコミットを細かく見ていくと、

import { Platform } from 'react-native';

if(Platform.OS = "ios") {
    // iOS限定の処理をつらつらと
}

ああああああああ!!!!
Platform.OS = "ios"って書いてるうぅぅぅ!!!!

この処理以降、Platform.OSを参照しても代入されたiosが取れてしまうわけです。

補足

結果的に比較演算子のタイプミスだったわけですが、そもそもこの書き方でif分岐がどうなるかを解説します。

if...else - JavaScript | MDNによると下記のような記述があります。

条件式の中での代入

代入はコードを眺めたときに等式と混同される可能性があるの?で、条件式の中で単純な代入を使わないほうが望ましいです。たとえば、次のコードを使わないでください。

if (x = y) {
   /* do the right thing */
}

なるほど、使わない方がいいけど文法的にはアリみたいです。

ちなみにこの場合、ifの評価は代入された後の変数で行うらしく、下記のような結果になります。

var a = 0;

if(a){
  // 通らない
}
else {
  // 通る
}

if(a = 10){
  // 通る
}
else {
  // 通らない
}

if(a = undefined){
  // 通らない
}
else {
  // 通る
}

つまり先のタイプミスをした

if(Platform.OS = "ios") {
    // iOS限定の処理をつらつらと
}

は評価としてはtrueになります。

まとめ

結果的に原因は初歩的なタイプミスだったわけです。
気付いてみればなんてことないんですが、大規模なコミットに埋もれていたため見落としてしまいました。
テストで発見できればよかったのですが、Android端末で問題となった処理を行った後でないとPlatform.OSが書き換わらないのですり抜けてしまったようです。

教訓としては、コミットの粒度を小さくしてタイプミスを発見できるようにする。です。

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

var,let,const の違い

JavaScriptでは、変数や定数を宣言する際に、var/let/constを使用します。
と言っても使用は必須ではなく、var/let/constを使用せずに変数や定数を宣言できます。

こんな感じです。

var a = 'Var';
let b = 'Let';
const c = 'Const';
d = 'Undeclared';

それぞれどのような違いがあるのでしょうか?
実際の動作をクイズ形式で確認してみます。

目的

var/let/const の違いを理解する

Q1

エラーとならないのは(a)~(d)のどの変数でしょうか?

console.log(a1);  // (a)
console.log(b1);  // (b)
console.log(c1);  // (c)
console.log(d1);  // (d)

var a1 = 'Var';
let b1 = 'Let';
const c1 = 'Const';
d1 = 'Undeclared';

正解は…





(a)!

Q1解説

var巻き上げが発生します。

console.log(a1);
var a1 = 'Var';

は、

var a1;
console.log(a1);
a1 = 'Var';

と見なされるので、エラーとはならず(a)は undefined が出力されます。
(b)(c)(d)はいずれも ReferenceError となります。

Q2

エラーとならないのは(a)~(d)のどの変数でしょうか?

{
    var a2 = 'Var';
    let b2 = 'Let';
    const c2 = 'Const';
    d2 = 'Undeclared';
}

console.log(a2);  // (a)
console.log(b2);  // (b)
console.log(c2);  // (c)
console.log(d2);  // (d)

正解は…





(a)(d)!

Q2解説

JavaScriptにブロックスコープの概念が追加されたのはECMAScript2015からです。

letconst で宣言した変数はブロックスコープが適用されます。
なので、{}の中だけで利用したい変数は let または const で宣言しましょう。

var で定義した変数はグローバル変数の扱いとなり{}の外からでも参照できます。
var/let/constのいずれも使用せずに宣言した変数もグローバル変数扱いになります。

Q3

エラーとならないのは(a)~(d)のどの変数でしょうか?

function method() {
    var a3 = 'Var';
    let b3 = 'Let';
    const c3 = 'Const';
    d3 = 'Undeclared';
}

console.log(a3);  // (a)
console.log(b3);  // (b)
console.log(c3);  // (c)
console.log(d3);  // (d)

正解は…





なし!(すべてエラーとなる!)

Q3解説

関数( function )の中で宣言された変数は関数の外から参照できません。

{}のブロックスコープと概念が異なることに注意しましょう。

Q4

エラーとならないのは(a)~(d)のどの変数でしょうか?

var a4 = 'Var';
let b4 = 'Let';
const c4 = 'Const';
d4 = 'Undeclared';

a4 = 'New Var';  // (a)
b4 = 'New Let';  // (b)
c4 = 'New Const';  // (c)
d4 = 'New Undeclared';  // (d)

正解は…





(a)(b)(d)!

Q4解説

const は定数を意味します。Javaでいうところの final と同じです。
そのため、新しい値を設定できず、(c)のみ TypeError となります。

Q5

エラーとならないのは(a)~(d)のどの変数でしょうか?

var a5 = 'Var';
let b5 = 'Let';
const c5 = 'Const';
d5 = 'Undeclared';

var a5 = 'New Var';  // (a)
let b5 = 'New Let';  // (b)
const c5 = 'New Const';  // (c)
d5 = 'New Undeclared';  // (d)

正解は…





(a)(d)!

Q5解説

letconst で宣言した変数は再宣言が不可となります。

letconst をうまく利用すれば、間違って参照したり宣言したりしてしまうことを防げます。

まとめ

var
- 変数の巻き上げが発生する
- グローバル変数である
- 再代入・再宣言できる

let
- 変数の巻き上げが発生しない
- ローカル変数である
- 再代入できる
- 再宣言できない

const
- 変数の巻き上げが発生しない
- ローカル変数である
- 再代入できない
- 再宣言できない

いずれも使用しない
- 変数の巻き上げが発生すしない
- グローバル変数である
- 再代入・再宣言できる

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

UserScript

概要

ブックマークレットはブックマーク一覧からユーザが「開く」ことでスクリプトを実行する一方、ユーザスクリプトはあるWebページのロード時に自動でスクリプトが実行されるように(URLフィルタなどで)設定することができる。

ユーザスクリプトの作成にあたって、バージョン情報やURLフィルタを埋め込むヘッダ(メタデータ)を付ける必要があるものの、それ以外では特別な記法などは特に要求されず、通常の(Webページ上で動作する)JavaScriptと同じように記述すればいいらしい。

さて、ユーザスクリプトの実行にはこれを実現するための拡張機能の導入が必要な場合がある。Chromeでは標準機能に含まれており、chrome://extensionsにローカルのjsファイルをD&Dすることで通常の拡張機能と並べてユーザスクリプトを扱える。一方、Firefoxでは(今のところ)別途拡張機能の導入が必要らしい。

代表例っぽいものを例示する。むかし同様のコンセプトのScriptAutoRunnerGitHub)というのを見かけたが、(これは更新されていないし)ちまたでは*monkeyというものが使われているらしい。

拡張機能のGreasemonkey、TampermonkeyにはどちらもGUIが存在し、GUI上でユーザスクリプトの追加、URLフィルタの設定(スクリプト外で別途指定する場合)、有効・無効化を行う。Greasemonkeyは管理はpopup上のみでエディタ用のpageがあり、Tampermonkeyはpopupもpageも管理に使えて、こちらもエディタ用のpageありっぽい。

これらは特殊API(@grant)以外の点では互換性がありそう。

環境

  • Google Chrome 80
  • Firefox 74
    • Greasemonkey 4.9
    • Tampermonkey 4.10

Greasemonkey(Firefox拡張機能)

新規作成時テンプレート

// ==UserScript==
// @name     Unnamed Script 000000
// @version  1
// @grant    none
// ==/UserScript==

すべてのWebページ(http, https)

// ==UserScript==
// @name     Unnamed Script
// @include  http*
// @version  1
// @grant    none
// ==/UserScript==

console.log("Hello!");

すべてのページ

// ==UserScript==
// @name     Unnamed Script
// @include  *
// @version  1
// @grant    none
// ==/UserScript==

console.log("Hello!");

example.com

// ==UserScript==
// @name     Unnamed Script
// @match    http://example.com/*
// @version  1
// @grant    none
// ==/UserScript==

console.log("Hello example.com!");

Tampermonkey(Firefox拡張機能)

新規作成時テンプレート

@matchには作成時に開いていたページのURLが自動入力される

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://example.com/
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();

example.com

// ==UserScript==
// @name         New Userscript
// @namespace    http://example.com
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://example.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log("Hello example.com!");
})();

メタデータの指定

キー
name スクリプト名
namespace 名前空間。作成者のWebページ等を指定し、ユーザスクリプトリポジトリが同名のユーザスクリプトを区別できるようにする
version バージョン
description 説明文
author 作成者
match URLフィルタ(推奨)。厳格なルールに基づいてパターンマッチングする(実行範囲の面でより安全)。複数指定可(仕様
include URLフィルタ。マッチするURLを含める。複数指定可(仕様
exclude URLフィルタ。マッチするURLを除外する。複数指定可(仕様
grant ユーザスクリプト用の特殊APIへのアクセス設定。複数指定可
require URLから外部js(ライブラリ)を呼び出し。複数指定可

必要なものを指定する。

特殊API(grant)

Greasemonkeyの場合、grantを省略するとnone扱い。Tampermonkeyの場合、省略するとスクリプト中で使用されているAPIが自動で設定される(らしい)。

Greasemonkey:GM.setValueGM.getValue

// @grant GM.setValue
// @grant GM.getValue

Tampermonkey:GM_setValue/GMApi.GM_setValueGM_getValue/GMApi.GM_getValue

// @grant GM_setValue
// @grant GM_getValue

これらは*monkeyで実行されるスクリプト間で共通のストレージの値を読み書きするらしい(GM.setValue - GreaseSpot Wiki)。

ライブラリ・外部js呼び出し(require)

// @require /js/example.js
// @require ../js/example.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js

CORS(Same-origin policy)

ブックマークレットのオリジンは表示中のWebページになる。ユーザスクリプトのオリジンはどうか。

// 比較用ブックマークレット
javascript:console.log(location.origin);
// ==UserScript==
// @name         Check origin
// @namespace    http://example.com
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://example.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log(location.origin);
})();

Chrome標準機能、Firefox Greasemonkey、Firefox Tampermonkeyでhttp://example.com

セキュリティ

セッションID(クッキー)でもフォームに入力されたパスワードでもなんでも読み出し/書き換え/送受信可能なので、任意ユーザの作ったスクリプトを使用する際は(他のプログラム同様)セキュリティに気をつけましょう..。

その他参考

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

Node.jsのworker_threadsに使えるバイナリフォーマットを考えた件

本記事はNode.jsのworker_threadsに何を渡すべきかの続きです。
よろしければそちらも是非お読みになってください。

1. 前回のquickおさらい

  • 大量のメモリを使う処理を並列で動かしたい。並列であることと省メモリであることが条件。
  • Node.jsには、メモリを共有できるマルチスレッドの仕組みとしてworker_threadsが備わっている。
  • ただしメモリ共有にはSharedArrayBufferを使う必要がある。
  • SharedArrayBufferはバイナリ配列なので、JSON-likeな複雑な構造は直接は表現できない。
  • JSON.stringify等のシリアライズ結果をBufferに載せることはできるがデシリアライズしないと使えないので省メモリにならない。
  • デシリアライズせずにJSON-likeな構造を扱えるシリアライザーが欲しい!作ろう!

そして作ったものをにroJson(read only JSON)という仮称を付けました。
現時点(2020/3/19)のソースはGistにあります。

2. 早速ベンチマーク取ってみる

最初に言っておきますが、Buffer全体をデシリアライズしないで必要時に必要なところだけ読む仕組みである関係上、何度も何度も同じところを読むような処理だと、部分的なデシリアライズを何度もしているのと同じなので不利です。
結局は得手不得手があるのでケースバイケースで使い分けましょうって話なんですが、今回は自作シリアライザに有利な処理ロジックとしました。

条件です。

  • ObjectのArrayに対して検索する、をマルチスレッドで2回実行する
  • 配列のサイズは50万
  • Objectにはいくつかキーがあるが、必ず"name"というkeyがあって、Array内で一意である
  • 検索したいものとして上記50万Objectのうち100個の"name"を対象としたリストsearchTargetを用意する
  • 検索処理ではArray.filter(a => searchTarget.includes(a.name))として、50万レコードに対して"name"が「検索したいものリスト」に合致するものがあるものを選び、結果として返却する
  • worker_threadsに渡すデータはあらかじめシリアライズしておく。検索の度にはシリアライズ処理を走らせない

検索2回だとマルチスレッドの効果があんまり出ませんが、手持ちのCPUが2コア/4スレッドゆえ・・・。

3. ベンチマーク結果

時間の単位はmsでconsole.time()を使用。
メモリの単位はMBでprocess.memoryUsage()からrssの値を使用。

シリアライザ シリアライズ時間(a) 検索2回の平均処理時間(b) トータル処理時間(c) 使用メモリ(最大時)
(参考:シングルスレッド)生Object/Array - 3,022 7,394 229
生Object/Array - 13,957 20,704 597
JSON 1,857 4,800 7,449 1,026
messagePack(msgpack-lite) 4,392 10,883 16,084 997
roJson 3,620 6,376 10,653 407

どうですか?シングルスレッドが最強という、なんとも微妙な結果になりました。。。

処理時間

シングルスレッドが1位ですが、
理論的にはシングルスレッドは検索回数分だけ線形に処理時間が伸びるのに対して、マルチスレッド勢はCPUコア数分までは時間がほぼ同じに収まるはずなので、シングルスレッドとroJsonは3スレッドでほぼ同等、4スレッド以上なら逆転できることになり、まあ、、、まあまあですかね・・・。

マルチスレッド勢で最強はやっぱりJSONですね。JSON憎し。2並列でもシングルスレッドとほぼ同等です。
messagePackが意外と遅かったですね。roJsonに対しても遅いのはどうしてなんでしょう。後ほど考察します。

マルチスレッドの生Objectは顕著に遅かったです。
生ObjectをWorkerのworkerDataに渡すと、structured clone algorithmという仕組みを通して子スレッド側にデータがコピーされるのですが、これはJSONなどよりもかなり効率が悪いようです。1
マルチスレッドの他のシリアライザの場合は(a)+(b)=(c)がだいたい成り立つんですが、生Objectの場合はこれが成り立っていません。これはstructured clone algorithmが親スレッド側で結構な時間、しかも(まだ親スレッド内なので)直列で処理されているからだと思われます。

メモリ使用量

これは狙い通りですね。
シングルスレッドが1位なのは当たり前なので置いておくとして、2位はroJsonになりました。もちろん並列度を上げてもほとんどメモリ使用量は増えません。逆にJSONやmessagePackは並列度を上げるのに従ってメモリ使用量も増えていきます。

ベンチマーク結果に対する総括

使いどころが難しい感もあるんですが、うまく使えば処理時間とメモリ使用量の削減になることが分かりました。

4. roJson作成の勘所

ポイントは、処理時間とメモリ使用量のバランスを取ることでした。
理想のイメージは両方が90点。
どちらかを95点にするためにどちらかが50点になってしまうのだったらやめましょう、ということです。2

というところを念頭に置きつつ、JSONでちょっといまいちだなと思うところを補完しつつやってみた、そのポイントを挙げていきます。

Objectのkey,valueあるいは配列要素へのアクセス時間を定数にする

時間効率+、メモリ効率-
これは前回も書きましたね。先頭から順繰りに読んでいってる暇はないのです。戦う相手は生Object/Arrayなのです。メモリ効率がやや犠牲になりますがこれは譲れないラインです。
逆に一番めんどくさいところでもあります。

keyの検索時間を定数化するのはおなじみのハッシュマップですね。roJsonではこのハッシュマップもシリアライズされたバイナリの中に埋め込んでます。
また、keyに対応するvalueがバイナリの何バイト目から配置されているのかを持たせることでvalueへのアクセス時間も定数化しています。

配列要素へのアクセスもほぼ同じです。
配列要素「へのアドレス」を持ったテーブルを用意することで、アドレスは固定バイト数(現状4byte)なので、オフセットアドレス+要素番号×4の場所にアドレスが入ってる、と即わかるようになってます。

Number型は適切な数値型でバイナリ化

時間効率-、メモリ効率+
これはmessagePackからアイデアを拝借しています。
Number型は本来double型なので64bit=8byteで、型識別子を付けると9byteにもなってしまうのですが、JSONに頻出するのって0とか1とかだったりしませんか?1+1byteで表現できるところを常に9byteではあまりにもったいないです。
ということで、数値の範囲を判定するという余分な処理を加わることになりますが、数値はそもそもシリアライズ対象の値の他にも、ハッシュマップのサイズだったり配列のサイズだったり文字列長だったりと結構使うところが多いので、入れたほうが良かろうと判断して取り入れました。
なんと0~15の4bitで収まる範囲は、型識別子と合わせても1byteで済むようにしています。3

疎配列に対応

時間効率+、メモリ効率+、対JSON+
疎配列っていうのは、値が定義されていない要素のあるArrayのことです。

const a = new Array(100000);
a[5000] = undefined;
console.log(a)
// (100000) [empty × 5000, undefined, empty × 94999]

undefinedですらないことに注意。本当に何もないんです。4
が、これをJSON.stringifyに渡すとご丁寧にnullで埋めてくれるんですよね・・・。
同じことをすると時間もメモリも無駄なので定義されている要素番号だけ保持するようにしました。
Objectのkey/valueとほぼ同じように扱えるので、ロジックはほぼ共通になってます。

文字列はUTF-16

時間効率+、メモリ効率-?
JavaScriptの内部文字コードはUTF-16です。常に1文字に対して2byte(サロゲートペア除く)が必要になります。
大抵のデータでString型の箇所(Objectのkeyも含む)は、半角英数なことが多いと思いますので、UTF-8に比べて倍のメモリを食うことになります。
ただ、UTF-16をUTF-8に変換する処理はそれなりに重い処理になると想定されるのでやめました。これは後述するmessagePackとの比較でも考察しています。
それに全角文字が多用されてるデータかもしれませんしね。その場合はUTF-16の方が有利です。

対JSONで他にもやりたいことがあります

下記は未実装ですが入れたいなと思ってるものです。

(1) 循環参照

JSONでは無理ですね。JSONで頑張って表現する方法を考えてる方もいますが、JSON仕様にない以上は実装依存にならざるを得ないので、一般的にはならないでしょう。roJsonは値の場所をアドレスで示す仕組みがあるので後は「Object/Arrayが同じもの」ってわかりさえすれば同じアドレスを指すだけなので簡単です。バイナリフォーマット仕様的には既に対応済みと言っても良いでしょう。

(2) undefined

JSON.stringifyに渡すとvalueがundefinedだと対応するkeyを消してくれたりだの、そうでなくてもnullにしてくれたりだのおせっかいが過ぎます(私見です5)。JSON縛りがないのなら、undefinedはundefinedのままにしといた方が幸せではないかと思うのです。
これはいつでもできますね。やってないだけです。

(3) 同一の文字列値は共通する一か所に格納する

今回扱ってるような巨大なJSONだったら、まず間違いなく繰り返し構造が存在しているはずです。リストにしろツリーにしろ。
ということは同じkeyが何度も出てくるはずなので、その度に何バイトかの領域を使うのはもったいないです。同じ場所を参照させてもいいでしょう。
これはメモリに効くことは自明(かつJSONには絶対真似できない)なんですが、将来、仮に読み出し専用じゃなくて更新も可能にしたいなあ、、、なんて色気を出し始める可能性を考えて二の足を踏んでるのが現状です。その文字列がまだ使われてるのかいないのか参照数を意識しないと捨てることができなくなっちゃいますからね。

msgpack-liteより速かった理由を考える

ベンチマークのコードが誤ってる可能性もありますが・・・。
特に(a)のシリアライズ処理のところですね。(b)はworkerData全体をデシリアライズしてから検索するというroJsonに対する不利な点があるのでまあそういうこともあるかな、という感じです。

roJsonはmessagePackに大いに影響を受けているのでシリアライズ方法は結構似通っているんです。加えて、ハッシュマップなどを余分に作ってる分遅くなると思ったんですよ・・・なんか大事な処理が漏れてるんだろうかと心配になります・・・。
それ以外に思いつくものを挙げてみました。

(1) StringをUTF-8にエンコードしている

一番に思いつく原因。
先にも書きましたがroJsonはJSの内部文字コード(charCodeAt()で取れる値)であるUTF-16の値をそのままUINT16型の値としてバイナリに書き込んでいます。一方、messagePackはメモリ効率を考慮してUTF-8に変換しています。これが遅い可能性。これであればmessagePackのバイナリフォーマットに起因する問題なので、msgpack-lite以外のライブラリであっても同じ理由で遅くなっている可能性があります。

(2) Bufferに書き込む値をbyte単位に分割して入れている

例えばUINT16な整数は2byteになるわけですが、上位byteと下位byteに分割して書き込む、というのをmsgpack-liteは自前でやってるようです(確信なし)。
それに対してArrayBufferをDataViewを通して使うとbyte単位に分割する処理は不要で済みます。DataViewの効率はかなり良いのだと聞きます。であればmsgpack-liteもDataViewを使うようにすれば速くなる可能性があります。

なお、messagePackはRPC等のデータ交換用途も考慮しているのでエンディアンを意識する必要があり、それがためにbyte単位の処理をしている可能性もあります。が、DataViewもsetXXX/getXXXはデフォルトでビッグエンディアン固定、引数指定によりリトルエンディアン固定にもできるのでDataViewで済ますことはできそうです。

(3) 多環境/異常系を考慮した堅牢な作りになっている

はいごめんなさい。その辺roJsonは結構雑です。
堅牢な作りの分遅くなっている、という可能性はありえそうです。

5. 以上

作ってみてそれなりの成果は得られたものの、正直微妙・・・。
もうちょっと処理自体を見直すか、もっと有用なユースケースを見つけるかして、いずれちゃんとnpmとして公開できたらな、と思ってます。


  1. 効率と引き換えにかなり広い範囲の組み込みクラスをカバーしているという汎用性もありますので「だからこいつはダメなんだ」などというつもりはないです。 

  2. もちろん点数は感覚的なものなので絶対的な指標があるわけではありません。 

  3. 実は参考にしているmessagePackはもっとラディカルで、7bitまでの正の整数を識別子と合わせて1byteで表現しています。つまり識別子は1bitなわけです。恐ろしい・・・。 

  4. ただし、empty要素に対して例えばa[0]===undefinedとかやったりするとtrueだったりします。やっかいです。 

  5. もちろんJSONである以上はemptyもundefinedもないのでnullにするしかないという事情は理解してます。 

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

配列

配列とは

複数の値をまるで1つのデータのように、
取り扱うことができるようにすることです。

作成方法

作成方法は2つ有り、

1.括弧を使ったリテラル記法
2.new演算子を使う方法

上記2点になります。

記述方法を見ていきましょう↓↓

// 配列は複数の値を格納できる

// リテラル 
const array = [1, 2, 3, 4];

// newで作成する
const array2 = new Array(1, 2, 3, 4);

値の呼び出し方法

// リテラル [ ]でまとめて宣言する
const sports = [ baseball , Football , basketball ];

// new でまとめて宣言する
const sports = new Array ( baseball , Football , basketball );

上記の記述で、値を呼び起こしたい時、
下記のような方法をとります。

sports [ 0 ]
//  baseballと表示されます

sports [ 1 ]
// Football と表示されます

sports [ 2 ]
// basketballと表示されます

上記のようなことを
配列へのアクセスと言います。
アクセスは括弧を使って行います。
インデックス(配列の要素番号)を指定することにより、
対応した番号の要素にアクセスすることが出来ます。

注意点として、
インデックスは0番から始まります。
上記コードで例えると、
('baseball','Football','basketball')
baseball→0番
Football→1番
basketball→2番

このようになります。

本日は以上になります。

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

永遠に続くクソ計算クイズつくた html css javascript

成果物

https://yuzuru2.github.io/neta0/dist/

リポジトリ

https://github.com/yuzuru2/yuzuru2.github.io/tree/master/neta0

環境

  • node.js 12.14.0
  • parcel 1.12.4

UI

無題.png

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

shortcut.jsでshortcut.removeしてもコールバック関数がイベントに登録されたままになる

環境

os : Windows 7

バグ内容

以下のコードでctrl + sを押したら2つ目と表示されてほしいのに両方のアラートが連続して表示されてしまう。

shortcut.add('ctrl+s', function(){
  alert('1つ目');
});
shortcut.remove('ctrl+s');

shortcut.add('ctrl+s', function(){
  alert('2つ目');
});

修正方法

shortcut.js
上記リンクがコード全体ですが219行目と220行目を入れ替えれば直ります。

shortcut.remove
-       if(ele.detachEvent) ele.detachEvent('on'+type, callback);
-       else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
+       if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
+       else if(ele.detachEvent) ele.detachEvent('on'+type, callback);
        else ele['on'+type] = false;
    }
 }

バグの理由

shortcut.addではaddEventListenerのチェックを先に行っているのにshortcut.removeではそdetachEventのチェックを先に行っていて対応したaddremoveになっていないため。

// shortcut.add
if(ele.addEventListener) ele.addEventListener(opt['type'], func, false);
else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func);
else ele['on'+opt['type']] = func;

// shortcut.remove
if(ele.detachEvent) ele.detachEvent('on'+type, callback);
else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
else ele['on'+type] = false;

気づいたのか経緯

【HTA】【jquery】【Marked】Markdown版ローカルwiki(v1.6)
MarkdownLocalWiki

上記をフォークしてwiki編集モードでのみctrl + sで保存できる機能を追加していました。
編集モード移行時にshortcut.add、それ以外に移行したらshortcut.removeとして制限を掛けていたのですが2回以上保存されることがありバグに気づきました。

その他

このことを本家に伝えたいのですが英語力が皆無なためQiitaに投稿しました。

リンク一覧

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

アロー関数について

アロー関数とは?

functionの定義をより短く記述できる方法で、
「=>」という記号を使って記述します。

記述方法についてみていきましょう↓↓

記述方法

function有り文

//function文
let color = function () {
  document.write( "white" );
}
color();//white

上記の記述方法がこれまでの、基本でした。

下記を見て頂くと、
アロー関数を使用することにより、
短い分で、関数を定義することが出来ます。

アロー関数 引数無し文

// アロー関数 引数無し (かっこいる)
let color = () => {
  document.write( "white" );
}
color(); //white

アロー関数 引数1つ

//引数が1つの場合は引数の()が不要
let test = n => n * 2;   
document.write(test(4));//8

アロー関数 引数2つ

// アロー関数 引数2つ以上の時 (かっこいる)
let test = (a,b) => {
  document.write(a+b);
}

test(2,3);//5

アロー関数を使用するにあたり、
注意点として、

引数無し

の記述方法で、()を忘れないようにしましょう。

まとめ

アロー関数は、最初は形が変わりすぎてしまい、
混乱すると思います。
しかし、慣れてくると、
文を短くすることが可能になり、
時間短縮に繋がります。
少しずつで良いので、アロー関数に慣れていくのを
お勧めします。

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

4歳娘「パパ、具体的な名前をつけないで?」

36歳ザコーダーの休日

ワイ「何やこのコード、全然動かへんやん」
ワイ「怖いな~怖いな~…なんか嫌だなあ~」

よめ太郎「(何で自分が書いたコード見て稲川淳二みたいに怯えとんねん・・・)」
よめ太郎「(そんな鳥肌立つようなクソコード書いてんのかいな・・・)」

娘(4歳)「ねぇ、パパ」

ワイ「なんや、娘ちゃん」

娘「ちょっと、作ってほしい関数があるの」

ワイ「またかいな」
ワイ「娘ちゃんはホンマに関数が大好きやなぁ」
ワイ「しゃあない、パパはプログラミング苦手やけど、頑張って作ったるわ」

娘「ありがとう、パパ」

今回の要件

ワイ「ほんで、今回はどんな関数を作ればええんや?」

娘「えっとね」
娘「'あ'という文字列を渡したら」
娘「['あ', 'あ', 'あ']という配列を返してくれる」
娘「そんな関数が欲しいの」

ワイ「そんなん何に使うんや・・・」

娘「その関数がなくて、友達が困ってるの

ワイ「何やて!?
ワイ「娘ちゃんの友達が!?」
ワイ「よっしゃ、もう細かい事情なんて聞いたりせん!」
ワイ「ワイが関数を書いたる!」

娘「ワ〜イ!」

ワイ「ワ〜イ!」

ただし

娘「ただし、間違って文字列以外のものを渡せないように」
娘「ちゃんとTypeScriptで書いてね」
娘「パパ、TypeScript書ける?」

ワイ「理論上は可能や
ワイ「QiitaでたまにTypeScriptの記事を読んどるからな」

実装開始

ワイ「ええと、作るべき関数は・・・」

'あ'という文字列を渡したら、
['あ', 'あ', 'あ']という配列を返してくれる。

ワイ「↑これやったな」
ワイ「1つの文字列を受け取ったら」
ワイ「その文字列を3つ詰めた配列を返せばええんやから・・・」

function stringToArray(str) {
  return [str, str, str];
}

ワイ「↑こんな感じやろ!」

よめ太郎「が書いてないやん」
よめ太郎「娘ちゃん、さっき『型をつけて』って言うてたで」

ワイ「おお、せやったわ」
ワイ「ちゃんとTypeScriptで書かな」

よめ太郎「ちなみに、TypeScriptの環境構築はやっておいたで!

ワイ「おお、流石よめ太郎や!」

TypeScriptで書き直す

ワイ「ほな、引数str文字列で」
ワイ「戻り値は、文字列が入った配列やから・・・」

function stringToArray(str: string): string[] {
  return [str, str, str];
}

ワイ「↑こうや!」

できた

ワイ「娘ちゃん、関数できたで!」
ワイ「これで友達も助か・・・」

娘「パパ、要件が増えちゃったの

ワイ「(どどどういうこと・・・?)」

娘「さっきの関数に、数値も渡せるようにしてほしいの」
娘「友達が困ってるの

ワイ「わ、分かったで・・・」

数値も渡せるようにしたい

ワイ「ええと」
ワイ「文字列型 または 数値型っていうのは」
ワイ「確かstring | numberっていう風に表すんやったな」
ワイ「せやから」

function stringToArray(str: string | number): (string | number)[] {
  return [str, str, str];
}

ワイ「こうや!」

ワイ「あれ、もうstringToArrayいう関数名はおかしいな」
ワイ「string型だけやなくてnumber型も受け取るもんな」
ワイ「関数名はstringOrNumberToArrayにしとこか」

ワイ「引数名もstrじゃなくて」
ワイ「strOrNumにしとこか」

function stringOrNumberToArray(strOrNum: string | number): (string | number)[] {
  return [strOrNum, strOrNum, strOrNum];
}

ワイ「できたで!娘ちゃん!」

またも要件追加

娘「パパ、やっぱりね・・・」
娘「引数として、オブジェクト数値の配列も許可して欲しいの・・・」

ワイ「ぐぬぬ

また関数を修正する

ワイ「文字列または数値またはオブジェクトまたは数値の配列・・・・」
ワイ「もうany型でええやろ」

よめ太郎「ごめん、あかんわ」
よめ太郎「環境構築したときにESLintの設定をしたんやけど」
よめ太郎「anyは禁止にしてもうたわ」

ワイ「ぐぬぬぬぬぬ

こんな機能があったらいいのにな

ワイ「もう、string | number | object | number[]とか書くのダルいから」
ワイ「関数を呼び出すときに型を指定できたらええのになぁ」

今回はこの関数に、文字列を渡して使いまっせ!

ワイ「↑こんな感じにしたいんや」
ワイ「つまり、関数を呼び出すときに、引数として型も渡す
ワイ「そんな機能があったらいいのになあ・・・」

娘(4歳)「あるよ

ワイ「ファッ!?」

娘「ジェネリクスを使えばいいんだよ」
娘「普通、関数を呼び出すときは・・・」

const stringArray = stringToArray('');

娘「↑こんな感じで呼び出すけど」
娘「ジェネリクスを使って・・・」

const stringArray = stringToArray<string>('');

娘「↑こう書いてあげればいいんだよ」

ワイ「おお・・・!」
ワイ「<string>の部分で型を渡すんやな・・・!」

娘「そう」

ワイ「でも、関数を定義している部分にも何か」
ワイ「その型引数を受け取るってことを書かないとあかんのやろ?」

娘「そうだよ」
娘「だから・・・」

function stringToArray<T>(str: T): T[] {
  return [str, str, str];
}

娘「↑こんな感じだね」

ワイ「なるほどな」
ワイ「stringToArray<T>の」
ワイ「<T>の部分でを受け取ってるんやな」
ワイ「ほんで、引数部分が(str: T)ってことは・・・」

引数strT型でっせ!

ワイ「ってことかぁ」
ワイ「今回の例だとTにはstringが入ってくるから・・・」

引数strstring型でっせ!

ワイ「ってことになるんやな」
ワイ「ほんで、返り値の部分はT[]って書いてあるから」

返り値はT型の値が入った配列でっせ!

ワイ「つまり」

返り値はstring型の値が入った配列でっせ!

ワイ「ってことになるんやな」
ワイ「よっしゃ、娘ちゃんありがとうやで!」

ジェネリクス完全理解ワイ

ワイ「ほな、ジェネリクスを使って」
ワイ「さっきの関数を完成させていくで!」

ワイ「ええと、この関数は」
ワイ「引数として、文字列または数値またはオブジェクトまたは数値の配列を受け取れるわけやから」
ワイ「関数名はstringToArrayやなくて、単にtoArrayにしておこか」
ワイ「引数もstrじゃなくて単にargにしとくわ」
ワイ「型引数は、何らかの型が入って来るってことが分かりやすいように」
ワイ「SomeTypeっていう名前にしておこか!」

function toArray<SomeType>(arg: SomeType): SomeType[] {
  return [arg, arg, arg];
}

ワイ「↑こうやな!」

完成

ワイ「娘ちゃん!」
ワイ「遂にできたで!!!」

娘「パパ、余計なことしないで?

ワイ「ファッ!!!???

娘「つまり」
娘「型引数に具体的な名前をつけないで?

ワイ「何でなん・・・」

抽象的すぎるやん

ワイ「Tとか抽象的すぎるやん・・・」
ワイ「もっと、何らかの型が入って来るってことが伝わるような」
ワイ「具体的な名前の方がええやん・・・」

娘「何らかの型っていう、すごく抽象的なものを表す名前なんだから」
娘「むしろ抽象的な名前がいいんだよ」

ワイ「でもさぁ、何らかの型なんやから」
ワイ「それを説明するための名前として」
ワイ「SomeTypeとかさ・・・」

娘「いや、<>の中には何らかの型が入って来るって決まってるんだから」
娘「その何らかの型ですよっていう説明、要る?」
娘「<T>を見たら分かるじゃん」
娘「パパは犬にマイドッグって名前をつけるの?」
娘「それに、SomeTypeだって結局すごく抽象的じゃない?」
娘「ならもうTでよくない?」

ワイ「ぐぬぬぬぬ・・・・」
ワイ「ほな最初から自分で作れや!

よめ太郎「(たしかに)

〜おしまい〜

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