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

JavaScriptでundefinedを判定する時の注意点

こんにちは、とくめいチャットサービス「ネコチャ」運営者のアカネヤ(@ToshioAkaneya)です。
JavaScriptでundefinedを比較する時の注意点です。

JavaScriptでundefinedを比較する時の注意点

typeofを使い、比較するのよりも直接===undefined比較するのが良いです。
下に例と解説を載せます。

console.log(typeof foo === "undefined"); // true
console.log(foo); // エラー
const foo = undefined;
console.log(foo); // undefined

このように、まだ宣言していない変数を評価するとエラーが発生してしまいます。
ですが、typeofで調べればこのエラーの心配はありません。
しかし、そのような場合はエラーだとはっきりわかった方が良いので、===を使って比較するべきです。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

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

5分以内に開発環境でJavascriptのAjaxを動かしてみたい人へ

「細かいことはいいから、JSのAjaxってどうやって書いて、どんな挙動する??」っていうのを知りたい人向けです。

こんなのができます :point_down_tone3:

js-ajax-sample.gif

やることはシンプルです。

  1. 以下のソースコードを、任意のファイル名+拡張子htmlで作成します。
  2. 作成ファイルをブラウザで開いてください。
  3. 以上で、JSのAjaxが使えるようになる、はずです。
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>CSS overflow</title>
  <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
  <script>

      $(function () {
          $('#ajax-button').click(
              function () {

                  $.ajax({
                      url: 'http://www.ekidata.jp/api/p/20.json',
                      type: 'GET',
                      dataType: 'script',
                      timeout: 1000,
                      success: function (data, dataType) {
                          for (var i = 0; i < 10; i++) {
                              var text = xml.data["line"][i]["line_name"];
                              document.write(i + 1, '番目の中身は、', text, 'でした</br>');
                          }
                      },
                      error: function (XMLHttpRequest, textStatus, errorThrown) {
                          alert("ng");
                          console.log("ng", XMLHttpRequest, textStatus, errorThrown);
                      }
                  });

              });
      });

  </script>
</head>
<body>
<input type="button" id="ajax-button" value="push button"/> <br/>
<div id="text"></div>
</body>
</html>

簡単に、ソースコードの説明

  1. ボタンをトリガーにJavascriptの処理が開始されます。
    1. その中でajaxでの処理も開始されます。
    2. Ajaxでは外部APIからデータを取得します。
    3. 取得したレスポンスから1つずつ順に取り出してブラウザに表示する、を10個繰り返します。
  2. そして終わりです。

今回使ったAPI
駅データ.jp が準備しているものを使わせていただきました。都道府県コードは固定値で20をセットしています。
[余談] POSTの中身を変えたり、ループする回数を変えても良さそう。

助けてくれた資料達...thanks!!

Ajaxとは「Asynchronous JavaScript + XML」の略です。簡単に言えばJavaScriptとXMLを使って非同期にサーバとの間の通信を行うことが出来ます。
 Ajaxを使用することで画面遷移をせずにHTMLを更新することが可能なので、ユーザビリティの向上やサーバ負荷の軽減に繋がります。AngularJSなどで作成したシングルページアプリケーションはこの技術を中心に構成されています。


読んでいただいてありがとうございます。
ご不明点、または間違いあればコメントいただけると幸いです。

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

セキュリティ対策のため、target="_blank"のa タグに "noopener noreferrer"のrel属性を自動で付与するjavascript文

image.png
(”寝る人”さんのブログより、とてもわかりやすい画像を引用させていただきました)

脆弱性

target="_blank"のaタグには脆弱性があります。

遷移先から遷移元のページを不正に操作したり、オブジェクトにアクセスしたりできてしまいます。
これによって、フッシング詐欺攻撃を行う余地を生んでしまいます。

サイトで rel="noopener" を使用して外部アンカーを開く(Google Developers)

監査が重要である理由
target="blank" を使用して任意のページから別のページにリンクしている場合、リンク元のページとリンク先のページは同じプロセスで動作します。 そのため、リンク先のページで負荷の高い JavaScript が実行されていると、リンク元のページのパフォーマンスが低下するおそれがあります。
また、target="
blank" にはセキュリティ上の脆弱性もあります。リンク先のページでは window.opener を使用して親ウィンドウのオブジェクトにアクセスしたり、window.opener.location = newURL によって親ページの URL を変更したりできます。

対策

対策としては、target="_blank"を指定しいるaタグのrel属性に"noopener noreferrer"オプションを付与すれば良いです。

まずnoopenerですが、このオプションを指定することで「別タブの遷移先から、window.openerを参照できなくなる(≒不正操作ができなくなる)」という効果があります。
そのため、基本的な対策は noopenerだけで問題ありません。

しかし、Edgeなどの一部のブラウザではnoopener自体がサポートされていないこともあります。
その場合の対策としてnoreffererを指定することで、同じような挙動を実現することが可能です。

noreffererを指定することで、「遷移先のリソースからリファラーを送らないようにブラウザに指示を出す」ことが可能です。
そのため、noopener noreferrerの両方をしていすることが望ましいわけです。

ちなみに、rel属性をはじめ各ブラウザのオプション対応については以下のWebサービスで確認をすることができます。

Can I use... Support tables for HTML5, CSS3, etc

背景

既存のWebページ内で、target="_blank"オプションを指定したaタグが複数存在していました。

Reactであれば、eslintで危険なtarget="_blank"オプションが弾かれたり、wordpressであれば自動で上記のrel属性が付与されるようになっているので、あまり気にすることはありません。

しかし、生のhtmlで記述されたLPでは上記のようにデフォルトでtarget="_blank"を警戒することはできません。
ひとつひとつのaタグにrel属性を付与したりチームの規約にするのも大変なので、javascriptで自動的に属性を付与してあげるのが一番良さそうです。

スクリプト文

// aタグの中身を分解
var aTags = [].slice.call(document.getElementsByTagName('a'));

// 未対応であるIEを除外するため、ブラウザの種別を洗う
var userAgent = window.navigator.userAgent.toLowerCase();
var isIE = (~userAgent.indexOf('msie') || ~userAgent.indexOf('trident'));

function addRels() {
  if (!isIE) { //IEは弾く
    aTags.forEach(function (el) {
      if (el.target === '_blank') {
        var rels = el.rel.split(' ');
        if (!~rels.indexOf('noopener')) {
          rels.push('noopener');
          el.setAttribute('rel', rels.join(' ').trim());
        }
        if (!~rels.indexOf('noreferrer')) {
          rels.push('noreferrer');
          el.setAttribute('rel', rels.join(' ').trim());
        }
      }
    });
  }
};
// 関数を実行
addRels();

少しだけ解説

!~rels.indexOf(文字列)では、いわゆる「ビット反転演算子」を利用しています。

indexOfは「文字列が見つかれば文字列が見つかった場所(0以上)」を、「文字列が見つからなければ-1」を返します。
この挙動を利用し、「-1をビット反転演算子にかけ、0を返す」→「if文で0が真と判定される」という文字列マッチングの仕組みを実現しています。

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

旧DMM.R18で女優をフォローするChrome拡張をReact+Webpackで作った

モチベーション

自分はジャンルやシチュエーションより出演者や監督でAVを選ぶことが多い。しかし、DMM.R18(元FANZA)では作品のお気に入りは出来るけど出演者をフォローする機能がない。「検索条件を保存」 という機能があるがこれは10件のキューで上限を超えて追加されると古いものから削除されるため使い勝手が悪かった。
そこで制限なく出演者をフォロー出来るアプリが欲しかった。

スクリーンショット 2019-03-11 1.29.30.png

試しにTwitterで調べてみると同じこと考えてる人はそれなりにいるみたい。

作ったもの

og24715/DMM.pornstars https://github.com/og24715/DMM.pornstars

構成

DMM.pornstars
├── README.md
├── node_modules
├── chrome
│   ├── dist
│   └── src
│       ├── background.js
│       ├── detail.js
│       ├── favorites
│       │   ├── App.jsx
│       │   ├── component
│       │   │   ├── card
│       │   │   │   ├── index.js
│       │   │   └── navbar
│       │   │       └── index.jsx
│       │   ├── index.css
│       │   ├── index.html
│       │   └── index.js
│       └── manifest.json
├── package-lock.json
├── package.json
├── webpack.config.js
└── yarn.lock
パス 説明
chrome/ 実質 Chrome 拡張用のプロジェクトディレクトリ。
chrome/dist/ webpack によってバンドルされた諸々が吐かれるディレクトリ。Chromeにはここを読ませる。静的ファイルも src/ に置いて copy-webpack-plugin を使って dist/ 配置されるようにしたので当たり前だけどここは触らない。
chrome/src/background.js アイコンがクリックされたら一覧を表示するだけ。
chrome/src/detail.js DMM.R18 で作品の画面を表示しているときに、出演者の名前の横にフォローボタンを表示するだけ。
chrome/src/favorites/ 一覧画面。React使ってる。

ハマりどころ

CRA (create-react-app) を使いたい

やめたほうがよさそうです。
だいぶ前に試した記憶だと、CRAはSPAを作るときには非常に頼りになるスタートアップツールですが、react-scripts ejectをしない限り webpack の entry を複数設定することが出来ません。画面ごとに create-react-app {project_name} しなくてはらず地獄です。

local.storage.get の第一引数に文字列で与えたのに第2引数のコールバック関数への引数がキーバリューになる

これはドキュメントちゃんと読んでください。
local.storage.get は第一引数に与えられた文字列もしくは文字列配列のキーをもとにキーバリューのオブジェクトを第二引数に与えられたコールバック関数へ引数として与えます。
第一引数に文字列を与える意図としては、キーを決定したので対応するバリューを返して欲しいってことなんですが、第一引数が文字列だろうと配列だろうとキーバリューをオブジェクトを返します。

コードにすると

// こうじゃなくて
chrome.storage.local.get('following', following = [] => { /* noop */ }));
// こう
chrome.storage.local.get('following', ({ following = [] }) => { /* noop */ }));

こんな感じ。
今の所 following ってキーでしか値を保存していないんですが、キー一つ与えたところでその値は返って返ってこないってことです。
ブラウザ実装の Window.localStorage.getItem と挙動が違うのでドキュメント見ないで実装するとハマります。

localStorage.getItem() - Web APIs | MDN https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem
chrome.storage - Google Chrome https://developer.chrome.com/apps/storage

静的ファイルを src/ に置きたい

dist/ は極力触りたくありませんでした。
今回のアプリで静的ファイルは拡張のマニフェストファイルである manifest.json と Reactのエントリーポイントとなる index.html があります。
これらは素の webapck を通しても dist/ へ配置されないので webpack-copy-plugin を使うしかなさそうです。

plugins: [
  new CopyWebpackPlugin([
    { from: './chrome/src/favorites/index.html', to: 'favorites.html' },
    { from: './chrome/src/manifest.json' },
  ]),

たかがコピーするだけなのにプラグインを追加し無くてはならない歯がゆさに gulp 時代の混沌をに似たものを感じます。
なにかいい方法があれば教えてください。

性欲駆動開発

https://twitter.com/search?q=%E6%80%A7%E6%AC%B2%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA

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

IME(全角)入力におけるjsイベント現状調査

この記事で対象としているブラウザはChrome(mac/win), Safari(mac), Firefox(mac/win), Edge(win) の最新バージョンで、以降これらをモダンブラウザと表記する。

tl;dr

  • Safariで全角入力におけるJSイベントの挙動がおかしかったため調査したところ、仕様(draft含む)上はInputEventだけで判定できそうで期待したが、現状の挙動はベンダー毎に異なった
    • 本命のinputEvent.isComposingはSafari/Edgeで対応してなかったりChrome/Firefoxでも挙動が違って使い辛い
    • inputEvent.inputTypeは使える場面がありそう
  • CompositionEventはモダンブラウザ全てに実装されており最も有用
    • inputイベントと組み合わせる場合はFirefoxのcompositionendイベントの発火順に注意
  • KeyboardEvent.isComposingはEdge以外のモダンブラウザで実装され、特にkeyupイベントでの値は各ブラウザで揃っていた

仕様

UI EventsのW3C Working Draft(8 November 2018)の入力とイベント発火の表が非常にわかりやすい

ただ標準仕様に対するベンダーの実装が必ずしも仕様に則しきれていないのは2019年も相変わらず。

InputEvent

  • 仕様上はinputTypeisComposingプロパティでIME入力状態を判別可能
    • ただしisComposingはSafari未実装
    • そしてChrome/Firefoxで挙動が異なる(下記参照)
  • inputTypeのENUM値はInput Events Level 2の仕様(draft)を参考
  • dataプロパティでマルチバイト判定する手もあるが、現状Safari/Chromeのみ

モダンブラウザのinputType/isComposing/data

  • 入力に対するinputType(string)/isComposing(boolean)/data(string)の値を記載
    • -はundefined
  • Edge(win)はどちらのプロパティもないため省略
入力 Chrome Firefox Safari
1(全角、未確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/true/- 'insertCompositionText'/-/'1'
Enter(上記確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/false/-1 'deleteCompositionText'/-/null
'insertFromComposition'/-/'1'
2(半角) 'insertText'/false/'2' 'insertText'/false/- 'insertText'/-/'2'
  • 全角入力がinputType: 'insertCompositionText'で半角入力がinputType: 'insertText'であることは共通
  • Enter押下での確定には一貫性がない
    • compositionendイベント発火前にinputイベントが発火した場合にisComposing: true。つまりChromeとFirefoxとでcompositionendの発火タイミングが異なる(後述)
    • safariはinputイベント発火数が他のブラウザより多い
  • マウスで確定させた場合は、Chrome/Safariでは確定時にinputイベントは発火しない一方、FirefoxではEnterと同じ値で発火した

CompositionEvent

IMEによるテキスト編集監視に用いるイベント。半角入力時は当然発火しない。

KeyboardEvent

キーボード操作、keydown/keyupイベントで監視

  • if (KeyboardEvent.keyCode === 229)でIME入力を判定しているスクリプトを見かけるがKeyboardEvent.keyCodeはDeprecated
  • Enter押下を検知して力技で......
    • IME確定以外でのEnter押下を区別できるのか疑問
    • ライブ変換とか確定に必要なEnter回数はIMEの設定によって異なる気がする
    • マウスからの確定は当然検知できない
    • とはいえIEサポートしたいならこれしかない?
  • KeyboardEvent.isComposingの実装はEdge以外は済んでいる
    • このbool値の仕様はinputEvent.isComposing同様、compositionstartとcompositionendの間がtrueでそれ以外はfalse
    • しかしSafariだけ下表の通り異なる
    • だがkeyupは揃っており、判定には有益
    • KeyboardEventの発火順は各ブラウザともW3Cの仕様に即している

入力に対するイベント発火の流れ

  • 値はEvent.type
  • 太字はisComposingがtrue
  • Edgeでのマウスクリック確定の方法がわからず未調査
入力 Chrome Firefox Safari Edge
1(全角、未確定) keydown keydown keydown keydown
compositionstart compositionstart compositionstart compositionstart
compositionupdate compositionupdate compositionupdate compositionupdate
input input input keyup
keyup keyup keyup input
Enter押下で確定 keydown keydown keydown compositionend
compositionupdate compositionend input
input input input
compositionend keyup compositionend
keyup keyup
マウスクリックで確定 compositionend compositionend compositionend ?
input1 ?

その他の手段

changeイベント

  • 入力確定後のEnter押下やfocusが外れた時に発火
  • 入力時にリアルタイムに処理したい場合には使えない

Appendix

調査スクリプト

  • 単純に各イベントリスナーにconsole.logを入れるだけだとダメ
    • ロギング順と発火順が同じとは限らない
  • 各種Eventオブジェクトのtimestampを見てsortして表示
check_inputEvent.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
<body>
<input id="x"/>
<button id="y">out</button>
<script>
var stores = [];
var store = ({timeStamp, type, isComposing, inputType, data, key}) => {
  stores.push({timeStamp, type, isComposing, inputType, data, key});
};
var x = document.getElementById('x');
var y = document.getElementById('y');
x.addEventListener('input', store);
x.addEventListener('keyup', store);
x.addEventListener('keydown', store);
x.addEventListener('compositionstart', store);
x.addEventListener('compositionupdate', store);
x.addEventListener('compositionend', store);
y.addEventListener('click', (e) => {
  console.table(stores.sort((a, b) => a.timeStamp > b.timeStamp));
});
</script>

調査背景

  • 入力値に対してフォーマットした値を再代入するJSを書いた
    • 例えば、郵便番号入力欄で、全角半角問わずリアルタイムに3桁目と4桁目の間にハイフンを補ってあげる処理
  • Safariのみ全角入力時に重複入力される現象が起きた
    • 下記は全角1234と入力した場合の挙動

safari_bug.gif

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
<body>
<input id="x"/>
<script>
var formatFn = (v) => v.replace(/^([^-]{3})([^-]{1,})/, '$1-$2');
var inputElement = document.getElementById('x');
inputElement.addEventListener('input', (e) => {
  e.target.value = formatFn(e.target.value);
});
</script>

全角入力は確定までフォーマットしない、という方針なら、例えばinputイベント部分を下記のようにすればモダンブラウザ対応可能

let isComposing = false;
inputElement.addEventListener('compositionstart', (e) => {
  isComposing = true;
});
inputElement.addEventListener('input', (e) => {
  if (!isComposing) {
    e.target.value = formatFn(e.target.value);
  }
});
inputElement.addEventListener('compositionend', (e) => {
  e.target.value = formatFn(e.target.value);
  isComposing = false;
});

またはinputTypeをみる(Edgeでは常にundefinedだが重複入力問題はSafariのみ)

inputElement.addEventListener('input', (e) => {
  if (e.inputType !== 'insertCompositionText') {
    e.target.value = formatFn(e.target.value);
  }
});

イベントリスナーの確認

何故Safariだけ上記バグがあるのか、Safari(を含むモダンブラウザ)のコンソールではgetEventListeners(node)というAPIが使えるので調べて見たが、特に異常はなかった。

safari_api.png

またmonitorEvents(document)2で発火イベントを全体的に監視してみた。SafariではtextInputイベントが全角確定時の一度だけ発火し、Chromeではinputイベントが全角入力の度に発火していた。
(念の為inputElement.removeEventListener('textInput', listener);してみたが効果なし)

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

IME(全角)入力におけるjsイベント現状調査

ブラウザの対象はChrome(mac/win), Safari(mac), Firefox(mac/win), Edge(win) の最新バージョンのみ

tl;dr

  • 全角入力におけるJSイベントについて調査し、仕様(draft含む)上はInputEventだけで判定できそうで期待したが、現状の挙動はベンダー毎に異なった
    • 本命のinputEvent.isComposingはSafari/Edgeで対応してなかったりChrome/Firefoxで挙動が違って使い辛い
    • inputEvent.inputTypeは使える場面がありそう
  • 現状はCompositionEventの方が安定しているが、やはりブラウザによって挙動は異なった

仕様

UI EventsのW3C Working Draft(8 November 2018)の入力とイベント発火の表が非常にわかりやすい

ただ標準仕様に対するベンダーの実装が必ずしも仕様に則しきれていないのは2019年も相変わらず。

InputEvent

  • 仕様上はinputTypeisComposingプロパティでIME入力状態を判別可能
    • ただしisComposingはSafari未実装
    • そしてChrome/Firefoxで挙動が異なる(下記参照)
  • inputTypeのENUM値はInput Events Level 2の仕様(draft)を参考
  • dataプロパティでマルチバイト判定する手もあるが、現状Safari/Chromeのみ

モダンブラウザのinputType/isComposing/data

  • 入力に対するinputType(string)/isComposing(boolean)/data(string)の値を記載
    • -はundefined
  • Edge(win)はどちらのプロパティもないため省略
入力 Chrome Firefox Safari
1(全角、未確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/true/- 'insertCompositionText'/-/'1'
Enter(上記確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/false/- 'deleteCompositionText'/-/null
'insertFromComposition'/-/'1'
2(半角) 'insertText'/false/'2' 'insertText'/false/- 'insertText'/-/'2'
  • 全角入力がinputType: 'insertCompositionText'で半角入力がinputType: 'insertText'であることは共通
  • Enter押下での確定には一貫性がない
    • compositionendイベント発火前にinputイベントが発火した場合にisComposing: true。つまりChromeとFirefoxとでcompositionendの発火タイミングが異なる(後述)
    • safariはinputイベント発火数が他のブラウザより多い
  • マウスで確定させた場合は、Chrome/Safariでは確定時にinputイベントは発火しない一方、FirefoxではEnterと同じ値で発火した(後述)
入力 Chrome Firefox Safari
1(全角、未確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/true/- 'insertCompositionText'/-/'1'
Enter(上記確定) 'insertCompositionText'/true/'1' 'insertCompositionText'/false/- 'deleteCompositionText'/-/null
'insertFromComposition'/-/'1'
check_inputEvent.html
<html>
<head>
  <meta charset="utf-8">
<body>
<input id="x"></input>
<script>
document.getElementById('x').addEventListener('input', console.log);
</script>

CompositionEvent

IMEによるテキスト編集監視に用いるイベント

入力に対する発火イベントの種別を記載

入力 Chrome Firefox Safari Edge
1(全角、未確定) compositionstart compositionstart compositionstart
compositionupdate compositionupdate compositionupdate
input input input
Enter押下で確定 compositionupdate compositionend input
input input input
compositionend compositionend
マウスクリックで確定 compositionend compositionend compositionend
input
2(半角) input input input

その他の手段

changeイベント

  • 入力確定後のEnter押下やfocusが外れた時に発火
  • 入力時にリアルタイムに処理したい場合には使えない

KeyboardEvent

keydown/keyupイベントで頑張る

  • if (KeyboardEvent.keyCode === 229)でIME入力を判定しているスクリプトを見かけるがKeyboardEvent.keyCodeはDeprecatedなので注意
  • KeyboardEvent.isComposingがIME確定状態判定に使えそうだが、現状こちらも未対応ブラウザが多く決定力に欠ける
  • Enter押下を検知して力技で......
    • IME確定以外でのEnter押下を区別できるのか疑問
    • ライブ変換とか確定に必要なEnter数はIMEの設定で異なるけど大丈夫?
    • マウスからの確定は?
  • とはいえIEサポートしたいならこれしかない?

調査の背景

  • 入力値に対してフォーマットした値を再代入する下記のようなJSを書いた
    • 例えば郵便番号入力欄で、リアルタイムで3桁目と4桁目の間にハイフンを補うなど
  • Safariのみ全角入力時に重複入力された
before.js
inputElement.addEventListener('input', (e) => {
  e.target.value = formatFn(e.target.value);
});

例えば今回の調査結果を加味すると下記のように書ける(ベストプラクティスかは自信ないが......)

after.js
inputElement.addEventListener('input', (e) => {
  if (e.inputType !== 'insertCompositionText') {
    e.target.value = formatFn(e.target.value);
  }
});
inputElement.addEventListener('compositionend', (e) => {
  e.target.value = formatFn(e.target.value);
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript の XMLHttpRequest オブジェクトの使い方

JavaScript の XMLHttpRequest オブジェクトの使い方については
こちらの記事が詳しく書いてあり、分かりやすかったです。

個人的には初歩的ですがopenとsendメソッドの違いが
よく分かってなかったので参考になりました!

http://webos-goodies.jp/archives/50548720.html

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

Koa.jsのエラーハンドリングで404の場合の対応方法

expressよりいい感じらしいので、Koa.jsを使っています。

「さて、エラーハンドリングはどうするんだ~」と思い、公式のwikiがあるじゃないかと喜び、そして、404が一向にキャッチされなくて困りました。キャッチされないくせに、404とだけ文字列で表示されるという。

404の場合は、catchには入ってこない

koa-routerも併用していたので、そっちでハンドリングしなきゃいけないのかなと考えて、解決策を探していたところ、見つけました

// error handling

app.use(async (ctx, next) => {
  try {
    await next();
    if (ctx.status === 404) {
      ctx.app.emit("error", ctx, {status: 404, message: "u f**ked up"})
    }
  } catch (err) {
    console.log('error', err)
    ctx.status = err.status || 500;
    ctx.body = err.message;
    ctx.app.emit("error", ctx, err);
  }
});

app.on("error", (ctx, err) => {
  console.log("? Error!?Error!?Error!? ?");
  ctx.redirect("/error");
});

? Error!?Error!?Error!? ?

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

プログラミング初心者がUdemyを使って勉強してみた。(前編)

・初学者がUdemyで2日間フロントを勉強してみた(前編)

まず自己紹介

  • 6ヶ月前にプロゲートをやってみるも2日も続かず挫折
  • 某プログラミングスクールに1ヶ月通う事にするも、通学がきつく挫折×2
  • 人口知能に興味を持ち、自分も人工知能のWebサービスを作りたい!と奮起してドットインストールのプレミアム会員になるも1週間で挫折、、×3

.しかしまたあることがきっかけでまたプログラミングに挑戦。。。

プログラミングに何度も打ち拉がれた筆者がなぜまたプログラミングに挑戦したか。

  1. 気づいたら最新の言語や、テクノロジーについて調べていたから。
  2. 正社員になってみたが向いていない為、転職する、or フリーランスとして稼ぐ為。
  3. まなブログさんのブログに影響された。
  4. やっぱエンジニアかっこいいって思った。

そんな理由で再びチャレンジ!!だが、、、

フリーランスとして稼げるようになるには大体200時間くらい必要らしい、スクールに通うお金もない、、、

Web記事を漁っているとUdemyというサービスがあるということを発見。『んーYouTubeの広告で出てくる、pythonエンジニアが長々と話しているあれか、、』と思ったが、まあとりあえず調べてみるか、とサービスを見てみると、、、 

『なんやこれ、、めっちゃ面白いやん、、某pythonエンジニアのSさんの動画めちゃ面白いやん!』ということで、再びプログラミングに挑戦。

pythonいいけど、やっぱり稼げるようになるにはやっぱフロントからという記事を見つけたのでとりあえずUdemyのフロント(HTML、CSS、Javascript,jQuery,ruby)の動画を購入。

学習内容

・とりあえず200時間の学習を最短でクリアできるための学習計画

  • 毎日通算4.4時間フロントエンドについて学習する
  • HTML、CSS、Javascript、jQueryに絞って学習
  • 毎日Qitaに記事をあげる

とりあえず土日で学習してみた結果、感想。

・1日目マックで通算6時間13分学習。(HTML基礎)
・2日目ココスの朝食バイキングでバカ食いしながら5時間24分学習。(HTML応用)

あれ?意外と楽しくないか?なんか前より楽しく、学習できている、、果たしてこれが気のせいなのか、、

次回へ続く。

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

javascript初心者の学んだことまとめ

筆者はjavascript歴半年(初めて触れたのが2018年10月)の初心者です。
自分の覚書用に学んだことを整理してみました。

プロトタイプという概念

プロトタイプ=日本語に訳すと「原型」=ほかの言語でいうクラスに当たる=親
インスタンス=子

どのインスタンスでも同じになるもの(メソッドとか)はプロトタイプに定義する。
(そうしないとインスタンス生成の度にプロトタイプが生成され、メモリを圧迫してしまう)

グローバルとローカルの違い

トップ(functionの外)に書くとグローバル変数・関数として扱われてしまう。
バグの温床となり好ましくないので、できるだけfunction内に書くこと。
また、varをつけ忘れると、グローバルとして扱われてしまうので必ずつけること。

this

var self = this; でselfにオブジェクトを格納することで、ビルドエラーを防ぐ。
(書く場所によってthisが指すものが異なるため)

A = B || C という書き方

Bがfalseとみなされる値の場合、Cで置き換える。
・C#でいう A = B ?? C
・SQL文でいうCOALESCE
にあたる

※Bがfalseとみなされる値・・・null,undefined,空文字など

withは使うべからず

推奨される代替案は、参照したいプロパティを持つオブジェクトを一時変数に代入することです。
参照)https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/with

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

【GAS】Font Awesome でくるくる回るローディングアイコンを実装する

目的

何らかの処理を行っている間だけローディングアイコンを出したい。

外部ライブラリを使ってもできるのですが、デザインがいまいちしっくり来なかったので、FontAwesomeのローディングアイコンをつかって実装しました。

Fontawesomeとは

ウェブサイトでよく使いそうなアイコンを無料で提供してくれるサービスです。(有料プランもありますが今回は無料プランのアイコンを使います)

https://fontawesome.com

動作

名称未設定.mov.gif

ボタンを押したら何らかの処理が走って、処理が終了するまでローディングアイコンがくるくる回ります。

ソース

HTML
 <!-- ボタン -->
 <button>ここを押すと何らかの処理が始まる</button>

 <!-- ローディングアイコン -->
  <div class="loading">
    <i class="fas fa-spinner fa-5x fa-spin"></i>
  </div>

css
.loading {
  display: none; /* 通常時は非表示 */
  z-index: 1;
  position: fixed;
  top: 50%;
  left: 50%;
  color: gray;
}
javascript
  $('button').on('click', function(){
    // ローディングアイコンを表示
    $('.loading').css('display', 'block');

    // doSomethingでなにか処理を実行し、処理が終わったらダイアログを出す
    google.script.run
    .withSuccessHandler(function(){
      $('.loading').css('display', 'none');
      alert('成功しました');
    })
    .doSomething();
  });
GoogleAppsScript
  function doSomething(){
    // なにかする
    return true;
  }

解説

まずHTML側でローディングアイコンを用意しておきます。
classにfa-spinを指定することでアイコンがくるくる回ります。

 <!-- ローディングアイコン -->
  <div class="loading">
    <i class="fas fa-spinner fa-5x fa-spin"></i>
  </div>

しかし、用意したアイコンはCSSからdisplay:none;で非表示にしておき、ボタンが押されたタイミングでdisplay:block;にし、画面上に表示させます。

ローディングアイコンを表示させたら、その後行いたい処理をdoSomething()で実行、終わったらアイコンをdisplay:none;で消して、ダイアログを表示します。

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

DialogFlowで作るFacebookメッセンジャー、LINE、Slack、GoogleAssistant対応のチャットボットを作成する

はじめに

 DialogFlowと出会い、チャットボット開発が面白くなって、ここ数日、暇があれば開発方法について調べていました。
 その時、Qiitaで掲載されている方の記事のおかげでチャットボットが作成できたので、恥ずかしながらアップさせていただくことにしました。
 何かしらどなたかの助けになれば幸いです。

ボットの紹介

キャラクター

うさ仙人image.png
クラフトホリックのウサギ型宇宙人ですがうちでは仙人風のしゃべり方をさせます。
(/ω\)
 

内容

英単語ゲームができるようにしました。

会話の流れ

FB Messenger LINE SLACK
jvnyg-2ej4d.gif dpoz1-54b01.gif 73c2m-pdyr2.gif

準備

使用するサービス

・DialogFlow
 LINEやSLACKなどのチャットツールと連携してメッセージを送受信してくれます。

・Actions on google
 google assistantと連携するのに必要。

・LINE Developer
 DialogFlowと連携するのに必要

・Slack
 DialogFlowと連携するのに必要

大まかな流れ

肝となるチャットツールとの連携はDialogFlowがやってくれます。

こんな感じ↓
 LINEなどのチャットツール→DialogFlow→Firebaseプログラム

開発手順

設計

 アプリケーション開発と同じで設計が重要です。何度も何度もインテントを書き直しました。
 ほんとしておかないと後悔します。

Dialogflowで少しずつ作りこみには限界がある
この記事を参考にさせていただきました。

DialogFlow

 先にこちらの記事を読むことをおすすめします。
 サルにもわかる Dialogflow FAQ

インテント

image.png

Default Welcome Intent

image.png
 MainMenuコンテキストをセットしてメインメニューを表示している状態を他のインテントに認識させます。
image.png
 Welcomeイベントはデフォルトのまま
image.png
 Training phrasesは最初にボットを呼びかけるような言葉を設定します。
image.png
 呼びかけられたら返す言葉を設定します。
 Default、Googleアシスタント、SLACK、LINEタブがあり、まずはDefaultタブで設定した言葉がチャットツール側に返答され、その後で各チャットツールのところで設定されたものが返答されます。
image.png
 LINEタブではQuick repliesで設定しているのでLINE側にボタン選択型メッセージが表示されます。

IntQuestionEJ

 Default Welcome Intentで1が選択されると呼び出されるインテントです。
image.png
 InputContextでMainMenuをセットしているのはDefault Welcome Intentから呼び出された場合にのみ有効ということを意味します。複数セットするとand条件になります。
 OutputContextのIntQuestionEJ-followupを設定し、このインテントが呼び出されていることを他のインテントに認識させます。MainMenuのライフスパンを0にしているのは、このコンテキストが残ることがあったので明示的に削除することを意味します。
image.png
 このイベントはプログラムからこのインテントへ遷移させるときに使用します。インテントのIDだと思えばわかりやすいかも。。。?
image.png
 トレーニングフレーズはこんな感じにしてあります。
image.png
 レスポンスでは単語をランダムに返すように設定しています。
image.png
 FulfillmentをONにしています。
 ここで気づいた方もいるかもしれませんが問題はプログラムで出力するはずなのにここでレスポンスを設定しています。
 その理由は2つあります。1つはコーディングを書く前段階でイメージをつかむためにです。もう1つはプログラムでレスポンスを返さずに終了した場合、このレスポンスが使用されます。まあ、できた後では削除してしまってもいいのですが、削除するのも面倒ですし、また使用する機会が生まれるかもしれないので残しています。

IntAnswerEJ

image.png
 タイトルのところがON/OFF設定できます。これをONにしている=fallback intentということになります。
image.png
 そして、InputContextにIntQuestionEJ-followupがセットされているので、IntQuestionEJが呼ばれた後に何かしら文字を入力された場合に呼び出されるということになります。
image.png
 FullfillmentがONになっているので、このインテントが呼び出されると直ちにWebhookプログラムが呼び出されることになります。

IntAnswerEJ_correct

 このインテントは正解時にWebhookプログラムから呼び出されます。
image.png
image.png
 パラメータにはプログラムから送られた正解値を受け取れるようにする為に用意しています。
image.png
 レスポンスには正解のメッセージをランダムで返答するようにしています。
image.png
 LINEタブの設定で選択型メッセージを返すようにしています。

IntAnswerEJ_miss

 このインテントは不正解時にWebhookプログラムから呼び出されます。
image.png
image.png
image.png
 正解時のインテントとは逆のメッセージが表示されるようにしています。ここでセットしている。「正解:$correct」の部分がプログラムから受け取ったパラメータ値を出力しています。

Default Fallback Intent

IntQuestionJE

IntAnswerEJ - claim

IntAnswerEJ - end

IntAnswerEJ - next

IntBadWord

IntEnd

Webhook(Firebase, Node.js)

Dialogflowだけでオリジナルの3択クイズを作ろう
を参考に作らせていただきました。

// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';

const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion, Suggestions, LinkOutSuggestions} = require('dialogflow-fulfillment');

process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements

const quesion_ej = [
    { question : "action\n[ˈækʃən]", one : "行動", two : "事柄", three : "機関", correct : "行動"},
    { question : "weather\n[wéðɚ]", one : "風上", two : "天気", three : "勇気", correct : "天気"},
    { question : "athlete\n[ˈæθliːt]", one : "動き", two : "動作", three : "運動選手", correct : "運動選手"},
    { question : "barbeque\n[bɑ́rbɪkjù]", one : "焼き鳥", two : "バーベキュー", three : "焼肉", correct : "バーベキュー"},
    { question : "birthday\n[bˈɚːθdèɪ]", one : "誕生", two : "誕生日", three : "生まれる", correct : "誕生日"},
    { question : "business\n[bíznəs]", one : "トレンド", two : "職業", three : "仕える", correct : "職業"},
    { question : "clothes\n[klóʊ(ð)z]", one : "水着", two : "服", three : "靴", correct : "服"},
    { question : "February\n[fébruèri]", one : "3月", two : "10月", three : "2月", correct : "2月"},
    { question : "hierarchy\n[hάɪ(ə)rὰɚki]", one : "階層", two : "規律", three : "法律", correct : "階層"}
];

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));

  function welcome(agent) {
    agent.add(`Welcome to my agent!`);
  }

  function fallback(agent) {
    agent.add(`I didn't understand`);
    agent.add(`I'm sorry, can you try again?`);
  }

  function answer_ej_next(agent) {
    agent.setFollowupEvent('StartQuestionEJ');
  }

  function answer_ej_end(agent) {
    agent.setFollowupEvent('Welcome');
  }

  function question_ej(agent) {
    console.log("question_ej");

    var random = Math.floor( Math.random() * 9 );

    let myQuestionEJ = quesion_ej[random];

    // 質問を出力
    agent.add(myQuestionEJ.question);
//    agent.add(new Suggestion({title : 'どれにするかの?'}));
    agent.add(new Suggestion(myQuestionEJ.one));
    agent.add(new Suggestion(myQuestionEJ.two));
    agent.add(new Suggestion(myQuestionEJ.three));

    //答えを格納
    agent.setContext({ name: 'question_ej_answer', lifespan: 1, parameters: { correct: myQuestionEJ.correct}});

  }

  function answer_ej(agent){
    console.log("answer_ej");

    // 回答を取得
    var answer1 = request.body.queryResult.queryText;
    console.log("answer1 : " + answer1);

    // 正解を取得
    var context = agent.getContext('question_ej_answer');
    console.log("context : " +JSON.stringify(context));

    var correct = context.parameters.correct;
    console.log("correct : " + correct);

    var eventName = '';
    if(answer1 == correct){
        eventName = 'answer_ej_correct';
    } else {
        eventName = 'answer_ej_miss';
    }

    // 答えをeventのパラメータで返す
    let callEvent = {
      name: eventName,
      parameters: {correct: correct},
      languageCode: 'ja',
    };
    agent.setFollowupEvent(callEvent);
  }

  function googleAssistantHandler(agent) {
    let conv = agent.conv(); // Get Actions on Google library conv instance
    //agent.requestSource = agent.ACTIONS_ON_GOOGLE;
    //conv.close('Hello from the Actions on Google client library!'); // Use Actions on Google library
    conv.ask('Hello from the Actions on Google client library!'); // Use Actions on Google library
    agent.add(conv); // Add Actions on Google library responses to your agent's response
  }

  // Run the proper function handler based on the matched Dialogflow intent name
  let intentMap = new Map();
  intentMap.set('Default Welcome Intent', welcome);
  intentMap.set('Default Fallback Intent', fallback);
  intentMap.set('IntQuestionEJ', question_ej);
  intentMap.set('IntAnswerEJ', answer_ej);
  intentMap.set('IntAnswerEJ - next', answer_ej_next);
  intentMap.set('IntAnswerEJ - end', answer_ej_end);
  agent.handleRequest(intentMap);
});

Actions on googleとの連携

LINEとの連携

こちらを参考にさせていただきました。
Dialogflowと連携してLINE Botを作る
DialogflowでLINE Botの応答をWebhookで成形してみた

Slackとの連携

こちらを参考にさせていただきました。
DialogflowからSlackのBotとして動かす

Facebook messengerとの連携

こちらの記事を参考にさせていただきました。
DialogflowからFacebook MessengeのBotとして動かす

Facebookページを作成
FacebookのTOPページ→Facebookページ→Facebookページを作成ボタンをクリック

image.png

image.png
あとはとりあえずスキップ

次に作成したFacebookページと連携させるアプリを登録します

DialogFlowでCallbackURLを生成
image.png

image.png

Facebook Developer Consoleへ移動
https://developers.facebook.com/

マイアプリから新しいアプリを追加する
image.png

image.png

image.png
image.png

Messengerの設定ボタンをクリック

image.png
ウィザードに従って進みます

image.png
その後でアクセストークンが生成されます

image.png

image.png

image.png

Facebook messengerに「うさ仙人」が表示されるのでメッセージを送信して返答があれば完了です。

ハマったところ

インテントとwebhook(google firebase)との連携

・インテントのトレーニングフレーズは誤検知されやすい
  たとえば、メニューで「1、2、3から選んでね」として、123だけを検知するインテントを作成。しかし、テストで「5月」と入力したのにこのインテントにヒットすることが判明。
・アウトプットはインテントを使った方が便利
 プログラムからメッセージを返すようにしようかと考えたが、どのように返すのかを調べるのに時間がかかった。知識がつくまではインテントを使った方がよさそう。

・最初、Contextの使い方がよくわからなかったがフラグ変数と考えればスッキリした。

・FirebaseでDEPLOYした直後にテストをすると前のバージョンが動く
image.png
 ここでDEPLOYして完了していても、ちゃんと反映されるまでに数分かかるもよう。。。

分からなかったこと

 知っている方、コメントいただければ幸いです。

・メッセージを返した後にインテントを移動する方法
 インテントAとBがあり、
 A→webhook→B→チェットメッセージ表示はできるが、
 A→webhoo→メッセージ出力→Bとするとメッセージは出力されずにそのままBへ移るだけ。

 addしたあとにeventをするとだめ
 
・webhookプログラムを使わずにDialogFlowのインテントだけでインテント間を移動ができないか

・DialogFlowのインテントで条件設定できないか?

・FollowUpインテントを作成して、そのinput contextを削除して一旦セーブするとcontextを戻してもインテント一覧ではfollowupとならない。バグ?

・Googleアシスタントでsuggetion chipsを返すとcancelボタンが標準でついてくるがこれを消す方法

・webhookプログラムからsuggetion chipsを出力するとタイトルがつかない
image.png
「choose an item」となる。

その他参考文献

この本はオールカラーで見やすく、また内容も濃く、何度も読み返しました。おすすめです!

【Google Home対応】ステップバイステップで力がつく Googleアシスタントアプリ開発入門
image.png

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

webpackの勉強

はじめに

以下の記事を参考にしてます!
この記事で使っているwebpackのバージョンは4以上なので、一部古い情報から更新してます。
開発用のtwitterのアカウント作ったのでよかったらフォローしてね!twitter: @susiyaki_dev

参考

webpack入門 - Qiita
これから始めるwebpack - webpackの基礎知識 | CodeGrid

勉強のきっかけ

周りのエンジニアの会話でwebpackという単語が出てきていたが、自分だけ理解ができていないようでついていけなかった。
また、最近勉強しているVue.jsの開発では、Vue CLIが勝手に処理してくれるようだが、理解するのは大事だと思ったのでLet's study!

webpackとは

webpackの役割

  • javascriptのファイルを一つにまとめる役割。
  • まとめる過程で、他の設定ファイルを用いてes6やReactなどさまざまなディストリビューションで書かれたものをトランスパイルしてブラウザで閲覧可能にする。

webpackの開発経緯

開発以前のモジュールシステムには、BrowserifyやGrunt、glupを組み合わせたモジュールシステムの組み合わせがポピュラーであった。
しかし、これらは一つのファイルに結合して出力するという形式であったため、大規模開発の場合には、

  • 初回アクセス時のユーザの読み込み時間が莫大になってし まう
  • 少しの変更でもプロジェクト全体の処理が更新されるためキャッシュが非効率になる

と言った問題があった。
これを背景として、アクセスページに依存関係のあるコードだけが含まれるファイルを読み込むためのwebpackが開発された。

使い方

パッケージの準備

npm initでpackage.jsonの作成

npm init時の対話内容

質問 説明
package name: (sample_webpack) package名(default: カレントディレクトリ名)
version: (1.0.0) バージョン設定
description: packageの概要説明
entry point: (index.js) 他のjsを参照するファイルの設定(default: index.js)
test command: testコマンドを用いる場合入力
git repository: githubなどのリポジトリを設定する場合入力
keywords: npm公開時等に使用するキーワードの設定をする場合入力
author: npm公開時に表示される作者情報を設定する場合入力
license: (ISC) npm公開時に適用する権利情報を設定する場合入力

webpackの導入

npm i -D webpack webpack-cli
を実行。webpackのバージョンが4以上の場合はwebpack-cliが必要

注釈-D = --save-dev

babelのインストール

本サンプルで用いるbabelとはes6をトランスパイルするモジュール。
npm install babel-core babel-loader@7 babel-preset-es2015 -D
webpackのバージョンが4以上の場合はbabel-loader@7でバージョン指定しなきゃいけないっぽい

touch webpack.config.js

設定ファイルの編集

webpack.config.js

このファイルにwebpackコマンドを実行した際の動作を記述する。

形式はjson。

webpack.config.js
module.exports = {
  entry: './index.js',          // 他のjsファイルを参照しているファイル
  output: {   
    path: __dirname + '/dist',  // 出力ファイル先
    filename: 'bundle.js'       // 出力ファイル名
  },

  module: {
    rules: [                    // webpackのバージョンが4未満の場合は rules -> loaders
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        query: {
          presets: ['es2015']
          }
        }
      }
    ]
  }
};

この例では、webpackコマンドを実行すると、dist/bundle.jsが生成される。

module内では、loadersで使用するモジュールとしてbabel-loaderを指定している。また、対象ファイルをtestで、除外するファイルをexcludeで指定する。

queryにはloaderに渡す引数を書く。

package.json

package.json
   {
    "name": "sample_webpack",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
 +  // webpackのバージョンが4未満の場合は webpack --mode development --open -> webpack
 +    "build": "webpack --mode development --open"            
    },
     "author": "",
     "license": "ISC",
     "devDependencies": {
       "babel-core": "^6.26.3",
       "babel-loader": "^8.0.5",
     "babel-preset-es2015": "^6.24.1"
     }
   }

使用方法

ソースファイルの準備

ディレクトリ、ファイルを作成する
touch index.html index.js
mkdir src
touch src/foo.js

内容はこんな以下の通り。

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <script src="./dist/bundle.js"></script>
</head>

<body>
<h1>es6 example</h1>
</body>

</html>

ただのテンプレート

foo.js
export const foo = 'Foo';

exportでデータを渡す。(参照可能にするデータを入れ込む)

index.js
import {foo} from './src/foo'
console.log(foo);

importでコンポーネントから利用するデータを持ってくる。
consoleに表示できれば動作確認OK

いざ実行

npm run buildwebpack.config.jsに設定した内容に基づいてビルドしてくれる。

buildされたindex.htmlの実行結果

実行結果.png

consoleにFooが出ている!

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

JavaScript : Array,Objectを再帰的にdeepFreezeする関数

JavaScriptでは、constで宣言しても配列やオブジェクトの要素は変更可能ですよね。
中味が変わらないといいなあと思ったことはありませんか?

再帰的にfreezeして中味も変更不可にした新しい配列、オブジェクトを返す関数deepFreezeを作ってみました。
対象は、配列、オブジェクトとプリミティブな値。関数もいけるかな? Map、Setはfreezeできないみたいです。

const mapForObj = f => a =>{
  const b = {}
  Object.keys(a).forEach( e => b[e] = f(a[e]) );
  return b
}
const forEachObj = f => a =>{
  Object.keys(a).forEach(e=>f(a[e]))
}
const isObj = a => Object.prototype.toString.call(a)==="[object Object]"
const clone = a => 
  Array.isArray(a)? a.map( clone )
  : isObj( a )? mapForObj( clone )( a )
  : a
const freezeR = a =>
  Array.isArray( a )? ( a.forEach(freezeR), Object.freeze(a) )
  : isObj( a )? ( forEachObj(freezeR)(a), Object.freeze(a) )
  : Object.freeze(a)
const deepFreeze = a => freezeR( clone( a ) )

const obj = {
  a:1
  , b:"1"
  , c:[0,1,[0,1,2], 3]
  , d:undefined
  , e:null
  , f:{x:1, y:2}
  , g:{
    a:1
    , b:"1"
    , c:[0,1,[0,1,2],3]
    , d:undefined
    , e:null
    , f:{x:1, y:2}
  }
}

const frozen = deepFreeze(obj)
>   
=> undefined
> frozen
=> { a: 1,
  b: '1',
  c: [ 0, 1, [ 0, 1, 2 ], 3 ],
  d: undefined,
  e: null,
  f: { x: 1, y: 2 },
  g: 
   { a: 1,
     b: '1',
     c: [ 0, 1, [Array], 3 ],
     d: undefined,
     e: null,
     f: { x: 1, y: 2 } } }
> Object.isFrozen(frozen)
=> true
> Object.isFrozen(frozen.c)
=> true
> Object.isFrozen(frozen.c[2])
=> true
> Object.isFrozen(frozen.f)
=> true
> Object.isFrozen(frozen.g)
=> true
> Object.isFrozen(frozen.g.c)
=> true
> Object.isFrozen(frozen.g.c[2])
=> true
> Object.isFrozen(frozen.g.f)
=> true
//ちなみにobjは凍っていない(当然ですが):
> Object.isFrozen(obj)
=> false

ちゃんと中味も凍っているようです。

ご使用は自己責任で。
参照がループしてると無限ループに陥いる可能性とwindowとか凍らせて大変なことになる可能性があるそうな。

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

[123MoviE] Watch Happy Death Day 2U ONLINE (2019) Full and FrEe Movie HD Sub En lhib

Watch HD Copy Paste In Address Bar =>> nufilm.live/movie/512196/watch-free-hd-full-movie-happy-death-day-2u-leaked.mp4

SINOPSIS :
Tree Gelbman learns that dying over and over again was surprisingly easier than the dangers that lie ahead.

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

[123MoviE] Watch What Men Want ONLINE (2019) Full and FrEe Movie HD Sub En ifzm

Watch HD Copy Paste In Address Bar =>> nufilm.live/movie/487297/watch-free-hd-full-movie-what-men-want-leaked.mp4

SINOPSIS :
Magically able to hear what men are thinking, a sports agent uses her newfound ability to turn the tables on her overbearing male colleagues.

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

[123MoviE] Watch Fighting with My Family ONLINE (2019) Full and FrEe Movie HD Sub En bbgo

Watch HD Copy Paste In Address Bar =>> nufilm.live/movie/445629/watch-free-hd-full-movie-fighting-with-my-family-leaked.mp4

SINOPSIS :
A former wrestler and his family makes a living performing at small venues around the country, while his kids dream of joining World Wrestling Entertainment.

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

[123MoviE] Watch How to Train Your Dragon: The Hidden World ONLINE (2019) Full and FrEe Movie HD Sub En ywjg

Watch HD Copy Paste In Address Bar =>> nufilm.live/movie/166428/watch-free-hd-full-movie-how-to-train-your-dragon-the-hidden-world-leaked.mp4

SINOPSIS :
As Hiccup fulfills his dream of creating a peaceful dragon utopia, Toothless’ discovery of an untamed, elusive mate draws the Night Fury away. When danger mounts at home and Hiccup’s reign as village chief is tested, both dragon and rider must make impossible decisions to save their kind.

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

[123MoviE] Watch Isn't It Romantic ONLINE (2019) Full and FrEe Movie HD Sub En aghb

Watch HD Copy Paste In Address Bar =>> nufilm.live/movie/449563/watch-free-hd-full-movie-isnt-it-romantic-leaked.mp4

SINOPSIS :
For a long time, Natalie, an Australian architect living in New York City, had always believed that what she had seen in rom-coms is all fantasy. But after thwarting a mugger at a subway station only to be knocked out while fleeing, Natalie wakes up and discovers that her life has suddenly become her worst nightmare—a romantic comedy—and she is the leading lady.

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

駆け出しエンジニアが受けたフロントエンドのコードレビューをさらす

はじめに

前回の記事 では駆け出しrailsエンジニアとして受けたレビューを紹介しました。
フロントエンドの実装にも首を突っ込む機会があったので、今回はjs(厳密に言うとtypescriptですが)の実装で受けたコードレビューを紹介しようと思います。

晒してばっかりですが、駆け出しのうちは恥をさらしてなんぼではないでしょうか。
いってみよう。

マークアップとの依存を低くすることを意識しよう

parentElement等のhtml構造に大きく依存するDOM取得方法は避けること。
html構造に依存している=htmlの変更でjsがすぐバグる
ということなので、マークアップエンジニアやコーダーの人がjsの実装を気にしなければいけなくなってしまう。

例: ボタンのクリックイベントでそのボタンが属する<tr>をとってきたいとき

NG
<table>
  <tr>
    <td><button id="ng-btn" type="button">NG</button></td>
  </tr>
  ...
</table>

<script>
const $button = document.querySelector("#ng-btn");
$button.addEventListner("click", function(){
            <!-- このとり方だと<td>直下がボタンじゃなくなったら動かなくなる -->
            const $tr = $button.parentElement.parentElement;
            ...
        });)
</script>

OK
<table>
  <tr class="hoge-row">
    <td><button id="ok-btn" type="button" data-raw-class='hoge-row'>OK</button></td>
  </tr>
  ...
</table>

<script>
const $button = document.querySelector("#ok-btn");
$button.addEventListner("click", function(){
            const $tr = document.querySelector(`.${$button.dataset.rawClass}`);
            ...
        });)
</script>

だいぶ雑な例示ですが、大雑把な方針としては、

  • 子→親の直接の取得はhtml構造に必ず依存するので避ける
  • 子には親に関する情報をもたせた上で、その情報から改めて親を取得する

jsにstyleの情報を持たないようにしよう

styleの管理はcssにまとめたほうが煩雑にならない。
jsではclassの付け替えをする等にとどめておき、cssでそのclassにたいしてスタイルを振れば良い。

例: 動的にdisplay:noneつけたいとき

NG
<div id="ng">ng</div>

<script>
const $div = document.querySelector("#ng");
$div.style.display="none"
</script>
OK
<style type="text/css">
.is-hidden {display:none;}
</style>

<div id="ok">ok</div>

<script>
const $div = document.querySelector("#ok");
$div.classList.add('is-hidden')
</script>

パフォーマンスを意識できるようになろう

addEventListenerを使うときは無名関数は使わない

addEventListenerを使うときは無名関数は使わないようにする。
新しくfunctionを切ってそれを呼ぶようにする。

メリットは大きく以下

  • そのlistenerを使い回せる
    → 他のeventにも使える

  • eventを削除できるようになる
    → 不要なeventが残ってしまうのは健全ではない。無名関数だとremoveするのに苦労する。

  • メモリ的にもやさしい
    → 無名関数だと呼ばれるたびにメモリを新たに消費するが、functionとして切り出してあればキャッシュされる。

例:あるボタンへのクリックイベント

NG
$button.addEventListner("click", function(e){
            console.log(e.target)
            ...
        });
OK
$button.addEventListner("click", _handleBtnClick);

function _handleBtnClick(e){
  console.log(e.target)
  ...
}

appendChildは毎回リフロー・リペイントされるから工夫して使う

そもそもリフロー・リペイントとは。

リフロー ・・・ 各HTML要素を解釈して、それぞれが占めるスペースを算出する
リペイント ・・・ 算出したスペースのなかに、内容を描画していく

「Reflowを制するものはDOMを制す」

そしてリフロー・リペイントは以下をトリガーとしておきる。

  • DOM ノードの追加、削除、更新
  • display: none (リフローとリペイント)、あるいは visibility: hidden (位置の変更は起きないので、リペイントのみ) による DOM ノードの見た目の変更
  • ページ中の DOM ノードの位置の移動やアニメーション
  • スタイル属性のちょっとした変更のためのスタイルシート追加
  • windowサイズの変更やフォントサイズの変更、そしてスクロールなどの、ユーザーの操作

「ブラウザ動作の理解-リフローとリペイント及びその最適化」

appendChildはもろにDOMノードの追加にあたるので、連続で呼ぶのはよろしくない。
こういうときは、createDocumentFragmentを使おう。
描画されているDOMにappendされるまではリフロー・リペイントされない。

例:trタグにtdタグを複数appendしたいとき

NG
$tr.appendChild($td1);
$tr.appendChild($td2);
$tr.appendChild($td3);
OK
const $fragment = document.createDocumentFragment();
$fragment.appendChild($td1);
$fragment.appendChild($td2);
$fragment.appendChild($td3);
$tr.appendChild($fragment);

NGの例では3回もDOMノードの更新が走るが、OKの例では1度で済む

最後に

こういうポイントも意識するべきなどありましたら、ぜひご指摘のほどよろしくお願いします。

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

Firebase Hosting でCloud Functions for Firebase を使用するとCookieが使用できない件の対応

Cloud Functions for Firebase をFirebase Hosting 経由で使っていて Cookieをつかおうとしたら、Cloud Functions による動的コンテンツの配信 この制約に引っかかるようで、Cookieを下ろしてくれないみたい。

うーん、とりあえず下記のとおりresponse.setHeader('Cache-Control', 'private')で解決できるぽいので、備忘メモ。

index.ts
import * as functions from 'firebase-functions'
import * as cookie from 'cookie'

export const addCookie = functions.https.onRequest((request, response) => {
  response.setHeader('Cache-Control', 'private') // Hosting経由だと、これがないとset cookieが削除される
  _addCookie(response, 'key', 'value')
  response.send('Hello from Firebase!')
})

export const getCookie = functions.https.onRequest((request, response) => {
  //   response.setHeader('Cache-Control', 'private')
  const cookies = cookie.parse(request.headers.cookie || '')
  const sessionState = cookies.state
  response.send(sessionState)
})

function _addCookie (res, key, value) {
  const expiresIn = 60 * 60 * 24
  const options = { maxAge: expiresIn, httpOnly: true }
  // const options = { maxAge: expiresIn, httpOnly: true, secure: true }
  res.setHeader('Set-Cookie', cookie.serialize(key, value, options))
}
$ curl http://localhost:5000/addCookie -i
HTTP/1.1 200 OK
x-powered-by: Express
cache-control: private
pragma: no-cache
expires: 0
set-cookie: key=value; Max-Age=86400; HttpOnly ← ちゃんとおろしてる
content-type: text/html; charset=utf-8
content-length: 20
etag: W/"14-z3iZXchEt5DVWZKsMncy8Wl4KSQ"
date: Sun, 10 Mar 2019 02:18:40 GMT
connection: close
vary: Accept-Encoding, Authorization, Cookie

Hello from Firebase!

$ curl http://localhost:5000/getCookie -H 'Cookie: state=6; state1=7'
6  // サーバ上での取得も問題なし
$

サーバからのCookieの受領も、サーバへのCookieのアップも問題なさそうですね。

この事象と「ブラウザはアクセスURLが localhost だと、下ろしてくる set-cookieを無視する?」事象が合わさって、ずいぶんトラブルシューティングに時間がかかりましたorz。localhost問題の方は結局 /etc/hosts で client.example.com などホスト名をつけて対応。

$ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1       localhost
255.255.255.255 broadcasthost
::1             localhost 
127.0.0.1 client.example.com
$

おつかれさまでした。

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

JavaScriptの関数名の全て

JavaScriptに限った話ではありませんが、関数というのは名前を持っていたり持っていなかったりします。関数名というのは普通はプログラムの読みやすさくらいにしか影響しませんが、JavaScriptでは必ずしもそうではありません。

例えばReactで関数コンポーネントを使う場合は関数名がコンポーネント名となり、React用開発者ツールなどで見ることができデバッグに役立ちます。また、Gulp v4もエクスポートした関数名がタスク名となります。

関数名は、関数オブジェクトのnameプロパティで取得できます。

function foo() {
  console.log('foo!');
}

console.log(foo.name); // "foo"

この例では関数宣言時にfooという名前を付けています。function式の場合も同様に名前を付けた関数を作ることができます。

const myFunc = function bar() {
  console.log('I am bar');
};

console.log(myFunc.name); // "bar"

その一方で、function式では名前を付けずに関数を作ることもできるのでした。この場合、関数は無名関数となり……

const noNameFunc = function() {
  console.log("I don't have a name");
};

Screenshot from Gyazo. console.logにより"noNameFunc"と表示されている

……あれ?

ゴシゴシ(目をこする音)

上と同じ画像

??????????

というわけで、これがこの記事のテーマです。上の例では、名前なしでfunction式で関数を作ったはずなのに親切にもいつのまにか"noNameFunc"という名前が関数についています。この記事では、関数の名前はいつどのように決められるのかについて隅から隅まで解説します。なお、関数のnameプロパティは文字列です。

名前付きで宣言された関数の場合

さっきの例のように、名前付きで宣言された関数はそれがそのまま名前となります。これは明らかですね。

// 名前は"foo"
function foo() {
  console.log('foo!');
}

// 名前は"bar"
const myFunc = function bar() {
  console.log('I am bar');
};

ですので、ここからは名前無しで宣言された関数を考えることにします。

そのまま使う場合は無名

最後の例は、作った無名関数を変数に代入したのがとても怪しいですね。一応確認ですが、無名で作った関数をそのまま(変数に代入したりせずに)使用する場合は名前はありません。nameプロパティは""となります。

console.log((function(){}).name); // ""

変数に代入する場合

結論から言えば、無名関数を変数に代入する場合はその変数の名前が関数名となります

const noNameFunc = function() {
  console.log("I don't have a name");
};

この例の場合は、無名関数をnoNameFuncという変数に入れたので名前がnoNameFuncとなったのでした。

面白いことに、この挙動は形が重要です。変数 = function(...) { ... }という形の場合にのみ、この挙動が発生します。この形から少しでも外れると、関数に名前は付きません。

const noNameFunc = [function() {}][0];
console.log(noNameFunc.name); // ""

この例では、無名関数を配列に入れてから取り出すという無意味なことをしていますが、これは上で説明した形とは違うので関数に名前は付きません。

一つだけあの形から外れても大丈夫な場合があります。次のように、function式を括弧で囲むだけならセーフです。

const myFunc = (function(){ console.log("I'm myFunc"); });
console.log(myFunc.name); // "myFunc"

注意すべき点としては、オブジェクトのプロパティへの代入では関数に名前がつきません変数への代入のみです。

const obj = {};
obj.foo = function() { console.log("I'm not foo"); };

console.log(obj.foo.name); // ""

オブジェクトリテラルの場合

オブジェクトのプロパティに関数を入れる場合は、オブジェクトリテラルを使う方法もありました。下の例のように、オブジェクトリテラルによりオブジェクトを作るときにプロパティに関数を入れることができます。実は、この場合は関数に自動的に名前が付加されます。

const obj = {
  foo: function() {
    console.log("I'm foo");
  }
};

console.log(obj.foo.name); // "foo"

このように、その関数が入ったプロパティ名が関数の名前となります。

この場合も先ほどと同様に形が重要です。プロパティ名: function(...) { ... }という形のみが特別扱いされます。ちょっとでも余計なことをすると名前は付きません。

メソッド宣言の場合

ES2015からは、メソッド宣言という新しい記法で関数を宣言できます。これはオブジェクトリテラル(やクラス内)で使える以下の例のような構文です。

const obj = {
  foo() {
    console.log("I'm foo");
  }
};

console.log(obj.foo.name); // "foo"

これはobj.fooに入る関数を宣言しています。当然ながら、この場合もこの関数には"foo"という名前が与えられます。

分割代入のデフォルト値の場合

ES2015では、分割代入というとても便利な構文があります。オブジェクトの中身を取り出してまとめて変数に代入できる機能です。

// objのfooプロパティとbarプロパティをそれぞれ変数fooと変数barに代入
const {foo, bar} = obj;

そして、このときfoobarobjに存在しなかった場合1のためにデフォルト値を設定可能です。下の例ではbarのデフォルト値を設定しています。

// objにbarが無かった場合はbarには0が入る
const {foo, bar = 0} = obj;

そして、このデフォルト値に例の形が来た場合はやはり変数名がその関数に与えられます。

const { foo = function(){} } = {};
console.log(foo.name); // "foo"

これはオブジェクトの分割代入でしたが、配列(イテレータ)の場合も同様です。

const [bar = function(){}] = [];
console.log(bar.name); // "bar"

これらはなかなか普段発生しないレアケースですね。

export defaultの場合

export defaultで無名関数をエクスポートする場合は関数名が"default"となります。defaultという名前が嫌な場合はちゃんと自分で名前を宣言しないといけませんね。

export default function() {
  console.log("I'm default!");
}

関数式として認められるもの

ここまで何度も形が重要と述べてきましたが、実は関数式の部分はいくつかバリエーションが可能です。ここまで見てきた基本の形は以下のような関数式です。

const foo = function(){};
console.log(foo.name); // "foo"

これに加えて、ジェネレータ関数式やasync関数式を作る関数式もあります。実は、これらもOKです。

const myGenerator = function*(){};
const myAsyncFunc = async function(){};

console.log(myGenerator.name); // "myGenerator"
console.log(myAsyncFunc.name); // "myAsyncFunc"

さらに、アロー関数でもOKです。

const myArrowFunc = ()=>{};
console.log(myArrowFunc.name);

また、ご存知の方も多いかと思いますが、JavaScriptではクラスは関数の一種です。よって、クラスもnameプロパティを持ちます。

class Foo {}
console.log(Foo.name); // "Foo"

クラスもclass式によって無名クラスを宣言することができます。これもやはり同様にnameがセットされます。

const MyClass = class {};
console.log(MyClass.name);

Functionコンストラクタの場合

実は、JavaScriptはFunctionを用いて文字列から関数を作る機能があります。これによって作られた関数の名前は"anonymous"となります。

const func = new Function("return 100;");
console.log(func.name); // "anonymous"

ゲッタやセッタの場合

オブジェクトリテラルにおいては、ゲッタとセッタによりプロパティを宣言することができます。

const obj = {
  // プロパティfooに対するゲッタを定義
  get foo() {
    return 100;
  }
};
console.log(obj.foo); // 100

今回はゲッタを宣言するget プロパティ名() { ... }の構文によってプロパティfooのゲッタを宣言しました。obj.fooが参照されるたびにこの関数が呼び出されます。

ここではfooのゲッタとなる関数が作られました。この関数の名前はどうなっているのでしょうか。

これはobj.fooとしても参照することができませんが、fooのプロパティデスクリプタを取得することでアクセスできます。

const obj = {
  // プロパティfooに対するゲッタを定義
  get foo() {
    return 100;
  }
};
// fooのプロパティデスクリプタを取得
const fooDesc = Object.getOwnPropertyDescriptor(obj, "foo");
console.log(fooDesc.get); // fooのゲッタとなっている関数が表示される
console.log(fooDesc.get.name); // "get foo"

というわけで、fooのゲッタの名前は"get foo"でした。名前にスペースが入っているのが面白いですね。このように、ゲッタとして作成された関数は"get プロパティ名"に設定されます。セッタの場合は"set プロパティ名"です。

bindにより作られる関数の場合

bindは関数オブジェクトが持つメソッドで、ある関数のthisの値を固定したり、一部の引数が既に決められたりしている新しい関数を作ってくれます。新しい関数が作られるということで、このとき作られる関数の名前はどうなるのか気に成りますね。早速結果を見ましょう。

const foo = function(){};
const foo2 = foo.bind(null);
console.log(foo2.name); // "bound foo"

const foo3 = foo2.bind(null);
console.log(foo3.name); // "bound bound foo"

このように、bindの結果は元々の関数名に"bound "が追加された名前を持ちます。名前のない関数をbindした場合は"bound "という関数名になります。最後のスペースがいい味を出していますね。

プロパティ名がシンボルだとどうなるのか

以上で関数の名前が自動で設定される場合を全部列挙しました(ES2018時点の情報です)。いくつかの場合は、関数が代入されるプロパティの名前が関数名となりました。

ここで、鋭い読者の方はひとつの疑問を抱くでしょう。それは、プロパティ名はシンボルかもしれないという点です。それに対して、関数名は文字列のみです。では、シンボルの名前を持つプロパティに関数を入れると何が起こるのでしょうか。

実は、この場合の挙動は2種類あります。プロパティ名のシンボルにdescription(説明)がセットされているかどうかによって分かれます。シンボルのdescriptionというのは、Symbolによって新しいシンボルを作るときにSymbolの引数に渡された文字列です。

// 新しいシンボル(descriptionなし)を作成
const prop = Symbol();

const obj = {
  [prop]: function() { console.log("What's my name?"); }
};
console.log(obj[prop].name); // ""

このように、description無しのシンボルがプロパティ名の場合は関数名は""になります

次にdescriptionありの場合を見ましょう。

// 新しいシンボル(descriptionあり)を作成
const prop = Symbol("prop");

const obj = {
  [prop]: function() { console.log("What's my name?"); }
};
console.log(obj[prop].name); // "[prop]"

このように、descriptionを持つシンボルがプロパティ名の場合は関数名は"[description]"になります。分かりやすさのために、関数名に使われるかもしれないシンボルにはいい感じにdescriptionを設定しておいたほうがよいかもしれません。

この[]で囲むという慣習には組み込みの関数も従っています。例えば文字列は[Symbol.iterator]関数を持っていますので、そのnameを見てみましょう。

console.log("123"[Symbol.iterator].name); // "[Symbol.iterator]"

余談:関数のnameを変える方法

関数のnameプロパティはwritable属性がfalseに設定されているため、プロパティに代入しても書き換えられません。

const foo = ()=> console.log("I'm foo");
foo.name = "FOOOOOO";
console.log(foo.name); // "foo"

しかし、configurable属性はtrueに設定されているため、Object.definePropertyなどで無理やり書き換えることは可能です。関数の名前を書き換えたいときは使ってみましょう。

const foo = ()=> console.log("I'm foo");
Object.defineProperty(foo, "name", {
  value: "FOOOOOO",
  configurable: true,
});
console.log(foo.name); // "FOOOOOO"

余談2: nameプロパティはいつ存在するのか

この記事で紹介したように、いくつかの場合には関数オブジェクトにいい感じにnameプロパティが設定されます。設定されなかった場合はnameプロパティは""となります。

実は、これはFunction.prototype.name由来の""です。つまり、名前のない関数はnameプロパティを持っておらず、Function.prototype.nameが参照されています。Function.prototype.name""が入っています。このことは仕様にも書いてあります

Anonymous functions objects that do not have a contextual name associated with them by this specification do not have a name own property but inherit the name property of %FunctionPrototype%.

……と言いたいところなのですが、実際はそうではなく、ChromeやFirefoxなど複数のブラウザで関数は常に自分のnameプロパティを持っています。

console.log((function(){}).hasOwnProperty("name")); // true

つまり、仕様と実情が乖離しているのです。このような例は重箱の隅をつつけばそう珍しいことでもありませんが。この件については議論が亀の歩みでされており、仕様を現状に合わせて修正する方向でそのうちまとまりそうな感じがしています。

まとめ

この記事では、関数のnameプロパティがどのように決定されるのかをまとめました。関数名はソースコードをminifyしたときに大抵は消えるのでnameのことを真剣に考えなければいけない機会はそう多くはありませんが、もしそんな機会が発生したらこの記事を思い出してみてください。

ところで、記事の最後に「いいねお願いします」というと本当にいいね数が増えるという説を見たので実験したいと思います。この記事の内容がいいと思ったらぜひ「いいね」を押してください。よろしくお願いします。

関連記事

記事を公開したあとに内容が結構重なる記事を発見しました。この記事では網羅性を高めるように頑張ったので許してください。

余談3: 仕様書を読むコーナー

こういう記事を書く時にはガチ勢を目指す人向けに仕様書に言及するようにしています(上の余談2で少しフライングしてしまいましたが)。

関数のnameプロパティを設定する処理は仕様書の9.2.13 SetFunctionNameに記述されています。この記事のコンテンツは、SetFunctionNameが使われている場所を全部列挙して関係ありそうなところをまとめ直してできたものです。

例えば代入式 (LeftHandSideExpression = AssignmentExpression)の評価の定義を見てみましょう(12.15.4 Runtime Semantics: Evaluation)。左辺と右辺を評価した後、1-eで IsAnonymousFunctionDefinition(AssignmentExpression) と IsIdentifierRef of LeftHandSideExpression が両方trueであるかどうかを判定しています。これはようするに、右辺のAssignmentExpressionが上で見たような関数式の形をしているかどうか、そして左辺が単なる変数であるかどうかを判定しています。

次の1-e-iは、右辺の評価結果の関数が既にnameプロパティを持っているかどうか判定しています。これにより、右辺が既にnameを持っている場合(function foo(){}のように自分で名前をつけている場合など)にnameを上書きするのを避けています。そうでなければ、1-e-iiでSetFunctionNameにより関数の名前を設定しています。

SetFunctionNameが使われている箇所はだいたいこんな感じです。

以上のように、JavaScriptの特定の挙動を調べたいとき(今回の場合は関数のnameがセットされるときの挙動)は、仕様書でそれを行う手順が定義されているのを見つけるのがよいです。今回の場合はSetFunctionNameがそれに相当します。あとはそれを参照しているところを探せばだいたい理解することができます。たまにSetFunctionNameのようにまとまっておらずあちこちに点在している場合があって大変ですが。


  1. 正確には値がundefinedだった場合。 

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

[123MoviE] Watch Captain Marvel ONLINE (2019) Full and FrEe Movie HD Sub En pygb

Watch HD Click Here =>> nufilm.live/movie/299537/watch-free-hd-full-movie-captain-marvel-leaked.mp4

SINOPSIS :
The story follows Carol Danvers as she becomes one of the universe’s most powerful heroes when Earth is caught in the middle of a galactic war between two alien races. Set in the 1990s, Captain Marvel is an all-new adventure from a previously unseen period in the history of the Marvel Cinematic Universe.

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

passport localでmongodbのユーザーコレクションを認証

はじめに

http://www.passportjs.org/でのpassport-localの解説が個人的にわかりにくいように感じたのでできる限りシンプルなログインおよびログイン状態の維持、未ログイン時に任意のルートへのアクセスをリダイレクトする方法を載せようと思いました。

前提知識

passport.jsを使おうと思う人がこれらを全く知らないことはないと思いますが、以下の知識は最低限あると仮定して書きます。

  • node
  • express
  • mongoose

npmパッケージ

今回のサンプルに使用するnpmパッケージは以下の通りです。

package.json
"dependencies": {
    "body-parser": "^1.18.3",
    "cookie-parser": "^1.4.4",
    "express": "^4.16.4",
    "express-session": "^1.15.6",
    "mongoose": "^5.4.15",
    "passport": "^0.4.0",
    "passport-local": "^1.0.0"
  }

基礎的なルーティング

とりあえずルートを設定します。
index.htmlには/adminへのパスがありますが、後でログイン状態でなければルートへ戻されるようにします。

server.js
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const passport = require('passport');
const { Strategy } = require('passport-local'); 
const mongoose = require('mongoose');
const session = require('express-session');
const cookieParser = require('cookie-parser');

const server = express();
const port = process.env.PORT || 8080;

server.get('/', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html/');
});

server.listen(port);
index.html
<!DOCTYPE html>
<html>
    <body>
        <form action="/admin" method="POST">
            <input type="text" name="username" placeholder="user name">
            <input type="password" name="password" placeholder="password">
            <button>login</button>
        </form>
    </body>
</html>

ユーザーデータベースの作成

user.json
{
  "username": "test",
  "password": "test"
}

シンプルに済ませたいので上記のドキュメントを"database"というデータベース、"users"コレクションに作成します。
今回はテストですので、リスクも気にせずログインのためのURIはハードコードすることにします。
データベースURIの例

uri.txt
mongodb://<dbuser>:<dbpassword>@ds00000.mlab.com:61335/sample

パスポートとセッションの設定

ログインのための認証ストラテジーと、ログイン状態維持のためのセッションの設定をしていきます。
以下をserver.jsに追加します。

server.js
mongoose.connect(YOUR_DATABASE_URI,{useNewUrlParser: true});
const db =  mongoose.connection;
const Schema = mongoose.Schema;

const userSchema = new Schema({
    username: String,
    password: String
});

db.on('error', console.error.bind(console, 'connection error:'));

server.use(bodyParser.urlencoded({extended: true}));
//body-parserはrequest streamをまるまる抽出してreq.bodyとして扱えるようにするミドルウェア。
//後でindex.htmlのformから受け取った値を認証するための関数に渡すために必要。

server.use(cookieParser());//cookieをparse
server.use(session({ resave: false, saveUninitialized:false, secret: 'something quite long and nonsense',
    cookie: {
        secure: false,
        maxAge: 3600000
    }
}));//expressアプリケーションにおいてセッションを用いることの宣言。
//これは下記のpassport.sessionより上位になければ正常に動作しない。
//secretは長くてあなたのオリジナルであればなんでもいい。

server.use(passport.initialize());//passport初期化のためのミドルウェア
server.use(passport.session());//cookieによるセッション維持を可能にするためのミドルウェア

passport.use(new Strategy //認証のための関数の宣言
    (async (username, password, done) => {
        try{
            await db.model('database', userSchema, 'users')
            .findOne({username: username}, (err, user) => {
                if(err){
                    return done(err);
                }
                if(!user){
                    return done(null, false);
                }
                if(user.toObject().password != password){
                    return done(null, false);
                }
                return done(null, user);
            })
        } catch(err) {
            console.log(err);
        }
    }
));

done()とは認証の終了後に呼び出されるコールバックです。
認証が成功した際にはドキュメントのオブジェクトを返し、失敗した際にはfalseを返すのがいいでしょう。
またそもそも認証以前にエラーがあった際の分岐も入れておくのが推奨されます。

これで認証するためのひな型ができました。
それでは実際に認証を通して/adminへ行くこと、認証に失敗すればルートへリダイレクトされること、および認証されていない状態ではadminからはリダイレクトされる処理へ進みましょう。

認証

server.js
server.post('/admin', passport.authenticate('local', {failureRedirect:'/' }),
    (req, res) => {
        res.send('Success!');
    }
);

server.get('/admin',(req, res) => {
    if(!req.user){//下記のデシリアライズ処理でユーザー情報がreq streamにあるか判断する
        res.redirect('/');
    }
    else{
        res.send('still logged in!')
    }
});

passport.serializeUser( (user, cb) => {
    cb(null, user);
});//認証に成功しセッションが確立されたら
//ブラウザのcookieに保存させるため情報をバイトコードに変換する処理

passport.deserializeUser( async (id, cb) => {
    try{
        await db.model('database', userSchema, 'users').findById(id, (err, user) => {
            cb(err, user);
        })
    } catch(err) {
        console.log(err);
    }
});
//passportミドルウェアより必要に応じてユーザー情報を最並列化して返す処理
//req.userでリクエストから情報を受け渡せるようになる

server.jsの最終的な全体

server.js
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const passport = require('passport');
const { Strategy } = require('passport-local'); 
const mongoose = require('mongoose');
const session = require('express-session');
const cookieParser = require('cookie-parser');

const server = express();
const port = process.env.PORT || 8080;

mongoose.connect(YOUR_DATABASE_URI,{useNewUrlParser: true});
const db =  mongoose.connection;
const Schema = mongoose.Schema;

const userSchema = new Schema({
    username: String,
    password: String
});

db.on('error', console.error.bind(console, 'connection error:'));

server.use(bodyParser.urlencoded({extended: true}));
server.use(cookieParser());
server.use(session({ resave: false, saveUninitialized:false, secret: 'something quite long and nonsense',
    cookie: {
        secure: false,
        maxAge: 3600000
    }
}));

server.use(passport.initialize());
server.use(passport.session());

passport.use(new Strategy 
    (async (username, password, done) => {
        try{
            await db.model('database', userSchema, 'users')
            .findOne({username: username}, (err, user) => {
                if(err){
                    return done(err);
                }
                if(!user){
                    return done(null, false);
                }
                if(user.toObject().password != password){
                    return done(null, false);
                }
                return done(null, user);
            })
        } catch(err) {
            console.log(err);
        }
    }
));

server.get('/', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html/');
});

server.post('/admin', passport.authenticate('local', {failureRedirect:'/' }),
    (req, res) => {
        res.send('Success!');
    }
);

server.get('/admin',(req, res) => {
    if(!req.user){
        res.redirect('/');
    }
    else{
        res.send('still logged in!')
    }
});

passport.serializeUser( (user, cb) => {
    cb(null, user);
});

passport.deserializeUser( async (id, cb) => {
    try{
        await db.model('database', userSchema, 'users').findById(id, (err, user) => {
            cb(err, user);
        })
    } catch(err) {
        console.log(err);
    }
});

server.listen(port);

最後に

passport-localによるログインは成功してみればとても簡単なのですが、どの言語やどのフレームワークでもそうかもしれませんが、慣れるまではずっと仕様書をたらいまわしにされている気分で目が回りますし理解できない自分がみじめになるものです。
一度でも自分の手で動作させることに成功してから読むほうがずっとわかりやすくなってより深い興味と理解につながると思います。
これからpassport-localを触ってみる人の助けに少しでもなればうれしいです。

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

`onBlur` がテストしづらい時はBreakpointを使おう

困っていたこと

https://jedwatson.github.io/react-select/

Peek 2019-03-10 11-11.gif

React用のSelectコンポーネント React-Select をテストするときに、コンポーネント以外の箇所をクリックすると選択肢が閉じちゃうんですが、 DeveloperToolをクリックしても反応する ので、開いた状態のDOM要素がどうなっているか見られず困っていました。

解決方法

DOM Breakpointを使う

image.png

Elements タブで、Breakpointを仕込みたい要素を右クリックして、 Break on > subtree modification を選択します。
この状態で当該要素をクリックすると、画面全体が暗転して、上の方にPaused in Debugger と出てきます。
image.png
一番右のimage.pngをクリックするとステップ実行されます。
DOMの状態を確認したいところまでポチポチ進めていきましょう。

Debuggerを解除したいときはimage.pngをクリックすると次のBreakpointまで実行されます(他にBreakpointが無ければ最後まで実行されます)。

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

個人または家族で使える自宅用のWebポータルシステム「MyHome Portal」

PHPで作成した個人または家族で使える自宅用のWebポータルシステム「MyHome Portal」をオープンソース/フリーソフトとして公開しています。
2008年から2016年にかけて、こつこつとバージョンアップを繰り返して作成したものです。
中級プログラマの作品ですので、ソースコードはあまりきれいとは言えません。バージョンアプップを繰り返したことでコードがきたなくなってしまっている部分もあります。
2016/08/02以降、バージョンアップしていません。Webサービス利用している機能など、一部は正常動作しません。

公開ページ

https://ok2nd.github.io/myportal/

ブログ

最近、更新はほとんどありません。
中級プログラマの自宅でPHP ブログ

コンセプト

  • 自宅でポータルとして使えるシステム。(家庭内グループウェア)
  • データはインターネット上ではなく、ローカルPCまたはローカルサーバに保管。
  • 初級、中級プログラマにも理解可能なシステム。
  • PHP、SQL、JavaScript、HTML、CSSの知識だけで理解可能なシステム。
  • PHPの良さを生かしたHTMLインラインコーディング。(なるべくprint文を使わない。)
  • ブラックボックスの少ないシステム。
  • オブジェクト指向型プログラミングを使わない。手続き型(関数型)プログラミングを採用。
  • MVCを使わない。
  • フレームワークに頼らないシステム。
  • テキストエディタ以外の開発ツールを必要としない。

対象者

  • 自分または家族専用のWebポータルサイトが作りたい。
  • Webポータルサイトをインターネット上ではなく、ローカルPCまたは自宅サーバーで立ち上げたい。
  • 日常的に良く使う機能が、個々のアプリケーションを起動せずに、ブラウザの中で出来たらうれしい。
  • スケジュールその他自分の個人情報をインターネット上に置きたくない。
  • パスワードなどのID情報をインターネット上に置きたくない。
  • Webプログラム開発初心者。
  • プログラミングを仕事でなく、趣味でやりたい。
    • オブジェクト指向が苦手。
    • MVCは面倒。
    • システム全体を把握したい。(ブラックボックスは少ない方が良い。)
    • フレームワークを使いたくない。(PHP、SQL、JavaScriptだけで開発したい。)

機能一覧

  • ホームページインデックス(ブックマーク)&検索
    • ブログパーツ貼り付け
  • 付箋
  • カレンダー(スケジューラ)
    • 天気出現率表示
  • 旅行記
  • ToDo
  • 掲示板
  • RSSリーダー
  • メモ
  • フォトアルバム
    • スライドショー
    • 動画サムネイル
    • FLV,MP4,WMV,MPG,MOV,M2TS(AVCHD)動画再生
  • 学習
  • 住所録
    • PDF葉書宛名書き
  • メール(一括受信)
  • SVGお絵かき
  • ペン画
  • チャット(Chat)
  • ID・パスワード管理
  • GPSログ(GPX)ビューワ
  • 預貯金管理
  • 縦計だけの表計算
  • HTML URL抽出&ファイルダウンロード
  • テキスト縦書き表示
  • Excel to グラフ
  • ソースコード表示&編集+ファイルマネージャー
  • MySQL管理ツール
  • ファイル暗号化/復号化
  • タイマーアラート

特徴・補足

  • 自宅で個人や家族で使えるシステムを目指して作りました。家庭内LANでの利用を想定しています。
  • 少人数の会社のイントラでの利用も可能かもしれません。 セキュリティは万全ではないので、スケジュール共有など機能を限定して利用願います。
  • ユーザー登録など、インターネット上での利用を想定した機能も付けています。ただし、セキュリティ等保障はできませんので、インターネット上での実利用は、自己責任にてお願いいたします。あくまで、LAN内またはPC単体での利用を想定しています。
  • 簡単なユーザー管理機能もあります。
  • 認証機能もあります。
  • ユーザー単位で他のユーザーに対して参照・書込の権限設定ができます。
  • レコード単位での非公開の設定もできます。
  • ただし、システム管理者はデータベースを直接見ることで全ての情報を参照ができてしまいますので、悪用しないように。
  • ID管理では、二重の認証が必要なしくみにしています。
  • ID管理では、パスワードを独自の方式で暗号化しています。ID情報は、2つのテーブルに分割して保存します。 暗号化のキーと2つのテーブルを別々にバックアップすれば、ID情報の復元はしづらい(?)と思います。
  • 簡単なログ機能も付けています。
  • 簡単なシステム管理者機能も付けています。
  • コンテンツを追加しやすい構造にしています。
  • ページ分割有りの一覧形式のページを簡単に作れる共有ライブラリを用意しています。一覧形式での編集もできます。 /myhome/common_/include-common-mp-list.php 各コンテンツのlist.php、category.phpを参考にしてください。
  • 一覧形式の表示部分のみを、独自のphpソースに置き換えられます。($mp_list_arg['template_view'])

開発環境

  • (Ver.3.27以降)XAMPP 1.7.1 (Windows)
    - Apache 2.2.11
    - MySQL 5.1.33 (Community Server)
    - PHP 5.2.9 + PEA

  • (Ver.3.26以前)XAMPP 1.6.6a (Windows)
    - Apache 2.2.8
    - MySQL 5.0.51a
    - PHP 5.2.5

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

Wordpressに、jQueryプログラムを反映させたい

やりたいこと

wordpressにチェックリスト(html,css,javascrpitで作成したもの)を反映したい。

以下のようなプログラムを作成しました。だだしデザインは未完成です

プログラム機能
  ・6個のチェックボックスがあります。[図1]
  ・ それぞれのチェックボックスに、チェックするかしないか自分で、入れます。
  ・チェックが終わったら、送信ボタンを押します。

 送信のした後、
 チェックしたチェックボックスが3個以上だと赤信号
 1、2個だと黄色信号[図2]
 0個だと 青信号

[図1]
スクリーンショット 2019-02-12 21.02.27.png

[図2]
スクリーンショット 2019-02-12 21.04.41.png

プログラムのコード内容(ローカル環境で動くプログラム)

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Input to page</title>

    <link rel="stylesheet" href="style.css">

    <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>

      <script src="main.js"></script>

  </head>
 <body>
<div>
    <h1>健康診断</h1>
    <ul>
    <li>aの問い<input type="checkbox" id="check1"></li>
    <li>bの問い<input type="checkbox" id="check2"></li>
    <li>cの問い<input type="checkbox" id="check3"></li>
    <li>dの問い<input type="checkbox" id="check4"></li>
    <li>eの問い<input type="checkbox" id="check5"></li>
    <li>fの問い<input type="checkbox" id="check6"></li>
    <input type="button" id="Button" value="送信">
    <br>
    </ul>

  <p><div id="output"><div></p>
</div>    
 </body>
</html>

style.css
ul, ol {
  background: #fffcf4;
  border-radius :8px;/*角の丸み*/
  box-shadow :0px 0px 5px silver;/*5px=影の広がり具合*/
  padding: 0.5em 0.5em 0.5em 2em;
}
ul li, ol li {
  line-height: 1.0;
  padding: 0.5em 0;
}

h1 {
  background: #b0dcfa; /*背景色*/
  padding: 0.5em;/*文字周りの余白*/
  color: white;/*文字を白に*/
  border-radius: 0.5em;/*角の丸み*/
}

main.js
$(function() {
  $("#Button").click(function() {

    var count = 0;

    if ($("#check1").prop("checked")) {
      count += 1;
    }
    if ($("#check2").prop("checked")) {
      count += 1;
    }
    if ($("#check3").prop("checked")) {
      count += 1;
    }
    if ($("#check4").prop("checked")) {
      count += 1;
    }
    if ($("#check5").prop("checked")) {
      count += 1;
    }
    if ($("#check6").prop("checked")) {
      count += 1;
    }

    if (count >= 4) {

      $("#output").text("赤信号");

    }
    else if (count >= 1) {
      $("#output").text("黄色信号");
    }
    else {
      $("#output").text("青信号");
    }


  });

});

wordpressにjqueryのプログラムに反映させたい。(調査編)

 調べた結果、2つのやり方で反映させることが可能だと知った

   ・直接コードを打つ

   ・プラグインで設定する
    ->結果プラグインを入れなくても

まず直接コードを打ち込むことにした。

->結果動かない
理由は、jqueryコードだから。

javascrpitだと直接いけます。

下のURLは、wordpressにjavascrpitコードを反映させる記事です。

https://routecompass.net/post-838/#JavaScript

なんとかなりそう

やり方発見

jqueryを反映させる記事を見つけました

http://primarytext.jp/blog/1298

要約すると以下通りです。

WordPressでjQueryを使いたいのに動かない時は、「jQuery(function($){ });」で前後を囲んでカプセル化してやれば簡単です。
これで囲むと、その中では「$」が使えますので、サンプルコードもそのままコピーすれば動きます。カプセル化した中には普通のjQueryの書き方でOKです。特に修正は必要ありません。

実際のコード

qiita.rb
<meta http-equiv="content-type" charset="utf-8">

<div>
    <h1>健康診断</h1>
    <ul style="background:#fffcf4;">
    <li>aの問い<input type="checkbox" id="check1"></li>
    <li>bの問い<input type="checkbox" id="check2"></li>
    <li>cの問い<input type="checkbox" id="check3"></li>
    <li>dの問い<input type="checkbox" id="check4"></li>
    <li>eの問い<input type="checkbox" id="check5"></li>
    <li>fの問い<input type="checkbox" id="check6"></li>

    <br>
    </ul>
    <input type="button" id="Button" value="送信">

  <p><div id="output"><div></p>
</div>
<div>

<script type="text/javascript">
jQuery(function($){
  $("#Button").click(function() {
    var count = 0;
    if ($("#check1").prop("checked")) {
      count += 1;
    }
    if ($("#check2").prop("checked")) {
      count += 1;
    }
    if ($("#check3").prop("checked")) {
      count += 1;
    }
    if ($("#check4").prop("checked")) {
      count += 1;
    }
    if ($("#check5").prop("checked")) {
      count += 1;
    }
    if ($("#check6").prop("checked")) {
      count += 1;
    }
    if (count >= 4) {
      $("#output").html("<b>赤信号です。</b> 至急体重減らす");
      $("div b").append(document.createTextNode("!!!"))
            .css("color", "red");
    }
    else if (count >= 1) {
      $("#output").html("<b>黄色信号です。</b> 体重減らす");
      $("div b").append(document.createTextNode("!!!"))
            .css("color", "yellow");
    }
    else {
      $("#output").html("<b>青信号です。</b> 体重維持");
      $("div b").append(document.createTextNode("!!!"))
            .css("color", "blue");
    }
  });
});
</script>



参考文献

https://routecompass.net/post-838/

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

ブロックチェーン でカジノ(ルーレット)DAppを作ってみた

Etherを賭けて当たったらEtherが貰えるルーレットアプリを作ってみました。
Ropsten上にアップしたので良かったら遊んでください。
テストネットなので賭博法的なものには引っかからないです!はずです!

https://akira-19.github.io/roulette/

遊び方

メタマスクを使う場合にはRopstenのアカウントを選んでください。
あとは好きなマスを選んでEtherを賭けてStartボタンを押すだけです。

トランザクションが処理されたらルーレットが回り始めます。

当ったら選んだマスに応じてEtherがゲットできます。(一般的なルーレットの返還率)

コントラクト

胴元

デプロイ時に3etherをコントラクトに渡しています。
掛け金の最大は0.03ehterで、仮に数字を当てたとしても36倍の返金(1ether強)なので、誰かがいきなり数字を3回連続で当てなければ大丈夫でしょう。

判定

ブロックのタイムスタンプを使って乱数を出力しています(0~37)。
0は0で00は37です。

あとは別に大した処理は入れていません。偶数に賭けて当たればベットの2倍を返したり、そんな処理です。

ブロックのタイムスタンプに関してはマイナーが操作できちゃうっぽいのであまり良い乱数の生成方法ではないと思いますが、お遊びなので今回はその方法を使いました。
Javascriptで乱数を生成してコントラクトに送る方法も考えたのですが、それも結局ユーザーが操作できてしまうので、コントラクト上で乱数生成してます。

テスト

シンプルなDAppなので、簡単に2種類のテストを作りました。
1. デプロイときに1etherを渡したら、ちゃんとコントラクトが1etherを持っている。(コンストラクタが正常に動いている)
2. ベットが当たれば賭けた場所に応じた返金があり、外したらコントラクトの所持するEtherが増える

フロントエンド

ルーレットを回すのにCanvas機能を使いました。
Screen Shot 2019-03-09 at 3.09.34 PM.png

一度本を読んだ程度の知識しかなく、初めて実装したので正直ここに一番時間かかりました。
手順として、下記の手順で作りました。

ルーレット背景

  1. context.arc()を使って扇を書く。38要素あるので中心角は38/360度
  2. 上の扇を38個描画する
  3. 半径の小さい背景色白の扇を内側に同じように描画

ルーレット数字

基本的には0から順番に数字を入れました。基本的にはコンテキストの中心(ルーレットの中心)から右側に半径弱分移動してから数字を描画しました。ただ、各数字はその右側からずれているのでコンテキスト自体を回転させて各数字を描画しました。
1. コンテキストの左上をコンテキストの中心に移動
2. コンテキストをrotate()を使って回転
3. コンテキストを元の位置に戻す
4. コンテキストがルーレットの中心から回転しているので、この状態で右側半径弱分のところから数字を描画
5. これを38要素分行う

コンテキストは左上からしか回転が行えないので、コンテキストの左上を中心にずらして回転させてから元に戻しました。

ルーレット回転

ルーレットの回転はこちらのサイトを参考に作りました。回転を行うメソッドにどこで止まるかの引数を与えて、指定のところに止まる様に作りました。
どこで止まるかの引数はコントラクトが実行された時点でブロックチェーンからブラウザが情報を受け取る様になっています。

  1. 最初にルーレットが15回転くらい止まる様に設定
  2. 止める数字に応じてもう少しだけ回す
  3. 少しずつ減速して最終的に目的のところで止まる

一周が38要素あるのに対して11要素ずつ回る設定(11要素分移動したルーレットを描画する)をしたので、例えば始点を0とすると、38*2 % 11*6 = 76 % 66 = 10になるので6回描画すると10に止まります。

こんな感じで各数字の時にどのくらいの回数再描画すれば目的のところに止まるかを配列にして、メソッドに渡すことで、止めたいところにルーレットが止まる様にしました。

今考えると簡単なのですが、かなり時間かかりました。。。

まとめ

できたこと(学んだこと)

  • payableを使ってEtherを送るコントラクトの作成
  • Canvas機能の基本的な使い方

やりたいこと(疑問点)

  • 安全な乱数の生成(oraclizeとか)
  • privateとかinternalのファンクションはテストできない?(今回はテストの時だけpublicにした)

https://ten-weeks-dapps-project.herokuapp.com/

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

【Javascript】Arrayをconcatを使って値渡しできたと思ったら中のObjectは参照渡しのままだった件について

Array型の中にObject型があった際、Array.concat()だけすれば中のObjectも別のインスタンスにできると勘違いしていて詰まったので忘れないために記事にします。

結論から言ってしまうと解決するためにはArray.concat()してからObject.assign()すればよい

まずArray型とObject型の代入について

Array型

まずArray型は下のコードのようにただ変数にリストを代入しても参照が渡されるだけで新しいリストが作られるわけではない。

参照渡しの例
const list1 = ["first"];
const list2 = list1;
list1.push("second");
console.log(list1);  //["first","second"]
console.log(list2);  //["first","second"]

そこで値渡しをしたい場合は、下のコードのようにArray.concat()を使えばリストをコピーして新しいリストとして生成できる。

値渡しの例
const list1 = ["first"];
const list2 = list1.concat();
list1.push("second");
console.log(list1);  //["first","second"]
console.log(list2);  //["first"]

Object型

そして、下のコードを見れば分かるようにObject型もArray型と同じでそのまま変数に代入しても参照が渡されるだけ

参照渡しの例
const obj1 = {"key1": "value1"}
const obj2 = obj1;
obj1["key2"] = "value2";
console.log(obj1);  //{"key1":"value1","key2":"value2"}
console.log(obj2);  //{"key1":"value1","key2":"value2"}

なので値渡しをしたい場合は、下のコードのようにObject.assign()を使って新しいインスタンスにする必要がある

値渡しの例
const obj1 = {"key1": "value1"}
const obj2 = Object.assign({},obj1);
obj1["key2"] = "value2";
console.log(obj1);  //{"key1":"value1","key2":"value2"}
console.log(obj2);  //{"key1":"value1"}

本題

今回問題だったは下のコードのようなArray型の中にObject型が入っていた場合

const list 1 = [{"key1":"value1"}];

このようなリストの場合、Array.concat()で新しいリストにした場合でもリスト自体は値渡しできているがリストの中のObjectは参照を保っているという状態になる。

中のObjectは参照を保っている
const list1 =  [{"key1":"value1"}];
const list2 = list1.concat();
list1[0]["key2"] = "value2";
list1.push("second");
console.log(list1);  //[{"key1":"value1","key2":"value2"},"second"]
console.log(list2);  //[{"key1":"value1","key2":"value2"}]

解決策としては下のコードのようにリストに対してArray.concat()をしてそのコピーしたリストの中のObjectに対して更にObject.assign()をする。

解決策
const list1 =  [{"key1":"value1"}];
const list2 = list1.concat();
list2[0] = Object.assign({},list2[0]);
list1[0]["key2"] = "value2";
list1.push("second");
console.log(list1);  //[{"key1":"value1","key2":"value2"},"second"]
console.log(list2);  //[{"key1":"value1"}]

これでObjectが含まれているリストでも完全に別のリストとして扱えるようになる。

おまけとして付け加えると
もし下のコードのようにリストの中に複数のObjectがあるならArray.map()等のループ系のメソッドを使ってやるときれいに処理できる気がする。

おまけ
const list1 =  [{"key1":"value1"},{"key2":"value2"},{"key3":"value3"}];
let list2 = list1.concat();
list2 = list2.map((obj)=>{
    return Object.assign({},obj);
});
list1[0]["key4"] = "value4";
console.log(list1);  //[{"key1":"value1","key4":"value4"},{"key2":"value2"},{"key3":"value3"}]
console.log(list2);  // [{"key1":"value1"},{"key2":"value2"},{"key3":"value3"}]

メソッドの解説リンク

Array.concat() --MDN
Array.map() --MDN
Object.assign() --MDN


以上、お疲れ様でした。
もし指摘等がありましたらコメントにてお願いします。

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