- 投稿日:2020-08-02T23:53:06+09:00
Javascript覚え書き
日付の取得
今日の日付を取得
var today = new Date();指定した日付を取得
var date = new Date( '2019/11/5 20:30' );そこからさらに、すきな要素を取得
date.getFullYear(); // 日付の「年」を取得する(4桁) date.getMonth(); // 日付の「月」を取得する(0 - 11) date.getDate(); // 日付の「日」を取得する(1 - 31) date.getDay(); // 日付の「曜日」を取得する(0 - 6) date.getHours(); // 日付の「時」を取得する(0 - 23) date.getMinutes(); //日付の「分」を取得する(0 - 59) date.getSeconds(); // 日付の「秒」を取得する(0 - 59)
- 投稿日:2020-08-02T23:30:45+09:00
配列の基礎
配列についてまとめます.
Reactの勉強をしているので,記述例がReactの構文になっています.配列の作り方
基本的な文法は,
var 配列名 = []
です記述例
import React, { Component } from 'react'; class App extends Component{ render(){ const array = [1,2,3,4,5,6,7,8,9,10]; return( <div> <h1>{array}</h1> </div> ) }; } export default App;ある要素を表示する方法
配列から,ある要素を表示するためには
配列名[順番]
です.
<注意>順番は0から数えます!記述例
import React, { Component } from 'react'; class App extends Component{ render(){ const array = [1,2,3,4,5,6,7,8,9,10]; return( <div> <h1>{array[0]}</h1> <h1>{array[1]}</h1> <h1>{array[2]}</h1> <h1>{array[3]}</h1> <h1>{array[4]}</h1> <h1>{array[5]}</h1> <h1>{array[6]}</h1> <h1>{array[7]}</h1> <h1>{array[8]}</h1> <h1>{array[9]}</h1> </div> ) }; } export default App;配列の要素の更新・追加・削除
更新
更新するためには,
配列名[順番]=更新させた要素
追加
追加するためには,
①配列の末尾に追加する場合,配列名.push(追加する要素)
const array = [1,2,3,4,5]; array.push(6);②配列の先頭に追加する場合,
配列名.unshift(追加する要素)
const array = [1,2,3,4,5]; array.unshift(0);③配列の指定した場所に追加する場合,
配列名.splice(無視する要素の先頭からの個数,無視しない要素のうち削除する要素の先頭からの個数,追加する要素)
const array = [1,2,3,4,5]; array.splice(2,0,3.5);削除
削除するためには,
①配列の末尾の要素を削除する場合,配列名.pop()
const array = [1,2,3,4,5]; array.pop();②配列の先頭の要素を削除する場合,
配列名.shift
const array = [1,2,3,4,5]; array.shift();参考
- 投稿日:2020-08-02T23:14:15+09:00
JS 正の整数なら前からN個、負の整数だったら後ろからN個返す
Lodashのnth関数を作成してみた
当初はすごく複雑に考えていた
var array = ["a", "b", "c", "d", "e", "f"] const nth = (values, selectNum) => { if (0 < selectNum) { return values[selectNum] } else { let absolute = Math.abs(selectNum) let newArray = [] for (let i = 0; i < values.length; i++) { const array = values.length - (i + 1) newArray.push(values[array]) } return newArray[absolute - 1] } } console.log(nth(array, 3)) // => d console.log(nth(array, -5)) // => bもっとシンプルに記述できた
const nth = (values, num) => { return 0 < num ? values[num] : values[values.length + num] } console.log(nth(array, 3)) // => d console.log(nth(array, -5)) // => b
- 投稿日:2020-08-02T22:57:49+09:00
【javascript】テトリス作成に向けて
テトリス作成に向けて調べた関数などを書いていきます。
配列内の値を一括で変更する
Array.prototype.fill()
メソッドは、配列中の開始位置から終了位置までの要素を固定値で設定します。その際、終了位置は含まれません。
出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill構文
arr.fill(value[, start[, end]])
引数 説明 value 配列に設定する値 start 開始する位置。デフォルトは0 end 終了する位置。デフォルトはthis.length 例
bord.forEach((item) => { bord[i].fill(0) });これでbordの配列の値をすべて0にできる。
2つの配列を比較する
Array.prototype.filter()
構文
let newArray = arr.filter(callback(element[, index, [array]])[, thisArg])
callback引数 説明 element 現在処理中の要素 index 現在処理中のインデックス array filterが実行されている配列 thisArg callbackを使う時にthisとして使用する値 返り値
フィルターされた配列
-0(マイナスゼロ)に遭遇
console.log(-1*0)//-0このような結果に遭遇した
これについてMDNではこう述べられている
Number 型には、2 種類の表現を持つ数値がひとつだけあります。それは 0 であり、-0 および +0 で表します ("0" は +0 の別名です)。実用上、影響はほとんどありません。例えば、+0 === -0 は true です。ただし、ゼロ除算を行った場合は違いに気づくでしょう
出典:JavaScript のデータ型とデータ構造+0 === -0//trueこれがtrueになるなら問題ないかと思いスルー
絶対値
Math.abs()NodeListをforEachで使えるようにする
Array.from(Nodelist).forEach()forEachではreturnは使えない
forEachでreturnで処理を終わらせようとしたところ最後まで処理をしていることがわかった。
配列のディープコピー
let arr=[[0,0],[0,1]] let arr2=JSON.parse(JSON.stringify(arr));数字の四捨五入
// 切り捨て var a = Math.floor( 1.5 ) ; // 切り上げ var b = Math.ceil( 1.5 ) ; // 四捨五入 var c = Math.round( 1.5 ) ;参考
- 投稿日:2020-08-02T22:56:30+09:00
transitioned
- 投稿日:2020-08-02T22:21:14+09:00
【JavaScript】レスポンシブな条件分岐にはwindow.matchMediaを使う
JavaScriptを書いていて良く使用するレスポンシブな条件分岐は下記2つのパターンだと思います。どちらもwindow.matchMediaを使えば簡単に実装することができます。
- デバイス幅が〇〇px以上と以下で処理を分岐したい
- 特定のデバイス幅を通過したときに処理を実行したい
今回はこちらの2つの頻出パターンについて紹介します。
デバイス幅が〇〇px以上と以下で処理を分岐したい
こちらの条件分岐は、PCとスマホで処理を分けたいときなどに重宝します。一番良く使いますね。
JavaScriptconst matchMedia = window.matchMedia('(max-width:1000px)'); if (matchMedia.matches) { // 1000px以下で行う処理 } else { // 1001px以上で行う処理 }ちなみにif (!matchMedia.matches)とすることで、条件にマッチしないときといった条件分岐を行うこともできます。
特定のデバイス幅を通過したときに処理を実行したい
特定のデバイス幅を通過したときに何か必ず実行したい処理があるときなんかに便利です。
JavaScriptconst matchMedia = window.matchMedia('(max-width:1000px)'); // 1000px通過時にmyFuncを実行する matchMedia.addListener(myFunc);Twitterアカウント
Twitterも更新していますので、よかったらフォローお願いします!
Twitterアカウントはこちら
- 投稿日:2020-08-02T21:46:41+09:00
JS 配列の最初以外を返す
Lodashのtail関数を作成してみた
メソッド使用せずに作成した場合
const tail = (array) => { const tailNewArray = [] for (let i = 1; i < array.length; i++) { tailNewArray.push(array[i]) } return tailNewArray } console.log(tail([2, 4, 5])) // => [4, 5] console.log(tail([1])) // => []filterメゾッドを使用した場合
const tail = (numbers = []) => { return numbers.filter((value, index, array) => { return array.indexOf(value) !== 0 }) } console.log(tail([2, 4, 5])) // => [4, 5] console.log(tail([1])) // => []
- 投稿日:2020-08-02T20:23:42+09:00
Audio/Video DOM のcurrentTime1とは
Audio/Video DOMとは
bodyブロックに書く音声や映像の情報。
例)
<audio data-key="76" src="sounds/tink.wav"></audio>
current Timeの使い方
window.addEventListener('keydown', function (e){ const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`); //再生時間を取得 var time =audio.currentTime; });例えば
audio.currentTime=0;とすると
再生時間が最初に戻る
- 投稿日:2020-08-02T20:08:22+09:00
javascriptで回帰直線を求める
回帰直線とは
組となるデータの、中心的な分布傾向を表す直線。
最小二乗法( least squares method )によって求められます。最小二乗法の式
?最小二乗法(wikipedia)より引用
これを、javascript( ES6 )で関数化します。
Σ
をreduce
で表現しています。// 回帰直線を求める(最小二乗法) const lsm = coordinates => { const n = coordinates.length; const sigX = coordinates.reduce((acc, c) => acc + c.x, 0); const sigY = coordinates.reduce((acc, c) => acc + c.y, 0); const sigXX = coordinates.reduce((acc, c) => acc + c.x * c.x, 0); const sigXY = coordinates.reduce((acc, c) => acc + c.x * c.y, 0); // a(傾き)を求める const a = (n * sigXY - sigX * sigY) / (n * sigXX - Math.pow(sigX, 2)); // b(切片)を求める const b = (sigXX * sigY - sigXY * sigX) / (n * sigXX - Math.pow(sigX, 2)); return { a, b }; }?動作確認
ドライバを作成して呼び出してみます。
const coordinates = [ { x: 200, y: 100 }, { x: 250, y: 150 }, { x: 300, y: 150 }, { x: 312, y: 200 }, { x: 330, y: 210 }, { x: 100, y: 80 }, { x: 110, y: 40 }, ] const { a, b } = lsm(coordinates) console.log(a, b)結果
0.6227602344112315 -9.665985075256124?グラフを書いて確認
canvasにグラフを描画して確認してみました。
青のドットがサンプルデータ、赤が回帰直線です。
確認に使ったソースコードは以下です。
html
<html> <head></head> <body> <canvas id="graph" width="500" height="500"></canvas> <script type="text/javascript" src="main.js"></script> </body> </html>javascript
main.js// 回帰直線を求める(最小二乗法) const lsm = coordinates => { const n = coordinates.length const sigX = coordinates.reduce((acc, c) => acc + c.x, 0) const sigY = coordinates.reduce((acc, c) => acc + c.y, 0) const sigXX = coordinates.reduce((acc, c) => acc + c.x * c.x, 0) const sigXY = coordinates.reduce((acc, c) => acc + c.x * c.y, 0) // a(傾き)を求める const a = (n * sigXY - sigX * sigY) / (n * sigXX - Math.pow(sigX, 2)); // b(切片)を求める const b = (sigXX * sigY - sigXY * sigX) / (n * sigXX - Math.pow(sigX, 2)); return { a, b } } const canvas = document.getElementById('graph'); if (canvas.getContext) { const ctx = canvas.getContext('2d'); // x軸の描画 ctx.beginPath(); ctx.moveTo(100, 0); ctx.lineTo(100, 500); ctx.stroke(); // y軸の描画 ctx.beginPath(); ctx.moveTo(0, 400); ctx.lineTo(500, 400); ctx.stroke(); const coordinates = [ { x: 200, y: 100 }, { x: 250, y: 150 }, { x: 300, y: 150 }, { x: 312, y: 200 }, { x: 330, y: 210 }, { x: 100, y: 80 }, { x: 110, y: 40 }, ] // サンプルデータの点を描画 ctx.fillStyle = "rgb(0, 0, 255)" coordinates.forEach(c => { ctx.fillRect(c.x + 100, -c.y + 400, 3, 3) }) // 回帰直線の描画 const { a, b } = lsm(coordinates) ctx.strokeStyle = "rgb(255, 0, 0)"; ctx.beginPath(); ctx.moveTo(-100 + 100, -(-100 * a + b) + 400); ctx.lineTo(400 + 100, -(400 * a + b) + 400); ctx.stroke(); }コードのダウンロードはここ。
以上。
- 投稿日:2020-08-02T19:43:40+09:00
いままでのリード・ライトに、もうひとついれたパパ・ラッチ
ラッチ機構をいれることにより、ビジネスの範囲にまで進めたんだろうね。
あと、もう一歩だ。
- 投稿日:2020-08-02T18:43:16+09:00
【Vue.js】CDN版でも単一ファイルコンポーネント(.vue)を使いたい!
こんにちは。
CDN版ライブラリの使用については皆さん賛否両論あると思いますが、私は容認派です。
手軽に使える便利さは正義!そんなわけで、ビルド不要なCDN版Vue.jsでも単一ファイルコンポーネントシステム(
.vue
ファイル)を実現したくね!?と思ったので色々と試してみたお話です。そもそもそんなことできるの?
できました。
しかし一筋縄では行かず、クライアントサイドで多少のゴリ押しが必要となります。
ゴリ押すためには、以下のキーポイントを押さえる必要があります。非同期通信
いわゆるAjaxと呼ばれるやつです。
XMLHTTPRequest
やfetch
など色々ありますが、ここではaxios
という便利な非同期通信ライブラリを使用します。非同期コンポーネントとミックスイン
Vue.jsは、非同期でコンポーネントを追加できる、非常に柔軟な機構を持っています。
これらを使用し、非同期通信で取得した.vue
ファイルをパースし、既存のコンポーネントへ追加します。VueRouter
Vue.jsで仮想ルーティングを実装するためのプラグインです。
実はVueRouterもCDN版が存在し、ビルド不要で仮想ルーティングを実装できます。
(更に言うとステート管理プラグインのVuexもCDN版が存在します!)VueRouterと単一ファイルコンポーネントを組み合わせ、ビルドシステムさながらのページを構築することができます。
サンプルコード
/index.html
: メインページ/component.vue
: Vueコンポーネントファイル/paths.json
: 仮想パスとコンポーネントファイルの物理パスを列挙したマッピングデータこれら3個のファイルのみで、Vue.jsアプリケーションを構築してみます。
UIはVuetifyを使用しました。なぜマッピングデータが必要かというと、静的配信においてクライアントはサーバーがどのファイルを配信しているかを知る術がないからです。
マッピングデータを用意してやることにより、クライアントがコンポーネントファイルの場所を解決できるようになります。最初のうちはマッピングデータをメインページのJavaScript内で記述しても問題ないと思いますが、拡張性を考えると外へ書き出しておいたほうが良いかなと思います。
index.html
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@latest/dist/vuetify.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> <script src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue-router@latest/dist/vue-router.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vuetify@latest/dist/vuetify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js"></script> <title>Vue</title> </head> <body> <div id="vue"> <v-app> <v-app-bar app dark height="52" color="secondary"> <v-toolbar-title>Vue</v-toolbar-title> </v-app-bar> <v-main> <router-view></router-view> </v-main> </v-app> </div> </body> <script> const loadComponent = axios.create({ method: "get", baseURL: ".", responseType: "text", transformResponse(data){ const doc = new DOMParser().parseFromString(data, "text/html"); const template = doc.head.getElementsByTagName("template")[0]?.innerHTML; const script = doc.head.getElementsByTagName("script")[0]?.innerText const style = doc.head.getElementsByTagName("style")[0]; if(style){ document.head.appendChild(style); } return { template(){ return template ? template : ""; }, script(){ return script ? new Function(script.trim().replace(/^(export\s+default|module\.exports\s*?=)\s*?/i, "return"))() : {}; } }; } }); const loadMap = axios.create({ method: "get", baseURL: "./components", responseType: "json", transformResponse({parents, paths}){ return Object.entries(paths).map(([virtual, physical]) => [`${parents.virtual}${virtual}`, `${parents.physical}${physical}`]); } }); const vue = (async()=>{ const maps = [ "/paths.json" ]; const paths = []; for await(const {data} of maps.map(map => loadMap(map))){ paths.push(...data); } return new Vue({ el: "#vue", vuetify: new Vuetify(), router: new VueRouter({ routes: paths.map(([virtual, physical])=>{ return { path: virtual, async component(res){ const {data} = await loadComponent(`${physical}.vue`); res({ template: data.template(), mixins: [data.script()] }); } }; }) }) }); })(); </script> </html>コンポーネントローダー
最初に
axios.create()
で、コンポーネントファイルをロードするためのaxiosインスタンスを作ります。
以後、このインスタンスをコンポーネントローダーと呼称します。レスポンスデータは
transformResponse()
でVueコンポーネントとして使えるよう加工します。加工する内容は、まずVueコンポーネントファイルをテキスト形式で取得し、DOMツリーへと変換します。
これは、Vueコンポーネント内の<template>
/<script>
/<style>
を分離するためで、正規表現マッチによるタグ検出も不可能ではないですが、より簡単で正確なDOMParser
を使用します。なお、VueコンポーネントはHTMLとして解析されるため
<template>
/<script>
/<style>
は自動的にDOMツリーの<head>
内へ移動されます。テンプレート文字列
<template>
はそのままinnerHTML
で中身を返します。
問題はJavaScript文字列の<script>
で、これは少しテクニカルです。JavaScript文字列の評価にはしばしば
eval()
が用いられますが、このeval()
は速度面でも安全面でも良くないという話は周知の事実かと思います。そこで、同じくJavaScript文字列から評価を行える、関数コンストラクタ
new Function()
を使います。
関数を作成する場合、通常であればfunction xxx(){}
と書きますが、関数コンストラクタを用いることでも作成できます。関数コンストラクタは、中身となるJavaScript文字列を引数として受け取り、それを評価し関数オブジェクトとして返します。
eval()
とは違い、評価結果が関数スコープ内へ封じ込められるため安全という訳です。関数コンストラクタは関数を返すため
new Function()()
としてやれば、評価した関数をそのまま即時実行することもできます。なお、評価の直前でJavaScript文字列の先頭
export default
またはmodule.exports
をreturn
へと書き換えています。
これは、コンポーネントオブジェクトをexport
へ出力するのではなく、関数の返り値とするためです。この時の動作を表すと、以下のようになります。
// "return" へ書き換えた後に関数コンストラクタの引数へ代入 // "export default{data(){},...}" => "return{data(){},...}" return new Function("return{data(){},...}")(); // => {data(){},...} // 上記の関数コンストラクタの評価結果(等価) return (function(){ return { data(){ }, ... }; })(); // => {data(){},...}この、評価後に返されるコンポーネントオブジェクトを後述のミックスインとして追加している訳です。
もし書き換えなければ、コンポーネントオブジェクトは正体不明の
export
へ出力されることになってしまいます。なお、テンプレートやスクリプトの処理結果を、オブジェクトプロパティではなく関数として返しているのは、参照渡しではなく値渡しするためです。
<style>
は問答無用でドキュメント内へ挿入しています。
(将来的に乱数もしくはハッシュ値を取ってscoped
対応もできれば良いなと考えています)マップローダー
再び
axios.create()
を使い、マッピングデータをロードするためのaxiosインスタンスを作成します。
以後、このインスタンスをマップローダーと呼称します。このレスポンスデータも
transformResponse()
でマッピングデータとして使えるよう加工します。マッピングデータは 後述 のようなデータ構造となっています。
parents
プロパティで仮想パスと物理パスそれぞれに親パスを宣言できるため、親パスとpaths
オブジェクト内の子パスを結合してあげます。返り値は
Array<["仮想パス", "物理パス"]>
のような連想配列とすることで、その後のイテレーション処理を簡単にしています。非同期コンポーネント
まず、VueRouterのパスが非同期で取得されるマッピングデータへ依存しているため、Vueインスタンス全体を
async
即時関数でラップします。なお、複雑なパスの構築へ備え、複数のマッピングデータファイルを結合できるような作りになっています。
マップローダーでマッピングデータを取得し終えたら、Vueインスタンスの構築へ移ります。VueRouterの
path
プロパティへ仮想パスを、コンポーネントローダーへ物理パスを渡すことで、それぞれマッピングの整合をとっています。Vue(VueRouter)の
component
プロパティは、コンポーネント内容を直接記載する場合はオブジェクトとしますが、コンポーネント内容が非同期な場合は、引数resolve
で解決されるPromise
を返すcomponent()
関数となります。
template
プロパティには、コンポーネントローダーの返り値template()
でテンプレート文字列を渡します。
mixins
プロパティには、コンポーネントローダーの返り値script()
でコンポーネントオブジェクトを渡します。component.vue
component.vue<template> <div> <v-btn>foo</v-btn> <v-btn>{{test}}</v-btn> </div> </template> <script> export default { data(){ return { test: "aaa" }; } }; </script>普通のコンポーネントファイルと同様に記述していきます。
ただ実際にロードされるときは、上述の通り先頭がexport default
またはmodule.exports
からreturn
へ自動で書き換えられます。paths.json
paths.json{ "parents": { "virtual": "", "physical": "" }, "paths": { "/": "/component" } }
paths
プロパティで"仮想パス":"物理パス"
のようにマッピングしていきます。
なおparents
プロパティで一括して親パスを設定できるため、実際のパスはparents
+paths
となります。仮想パスについては
:id
のようなVueRouterの動的ルートマッチング機能も使えます。
物理パスに拡張子を含めるか含めないかは好みですが、今回は実質.vue
以外には成り得ないため、ここでは記述しないほうがミスは減るかなと思います。まとめ
だいぶパワープレイになりましたが、無事CDN版Vue.jsでも単一コンポーネントシステムを実現することができました。
と言うかこれもう完全に人力ビルドシステムだなおいもっと良い方法があるよ!とか参考になった!とかありましたらコメント頂ければ幸いです!
- 投稿日:2020-08-02T18:33:30+09:00
コールバック関数
引数に関数を渡す。
Javaから始めた自分としては考えられない。var callBackTest1 = (callback) => { console.log("1"); callback(); }; var callBackTest2 = () => { console.log("2"); } // 呼び出し callBackTest1(callBackTest2); // "1" // "2"上記を無名関数に置き換えることもできる。
callBackTest1(function() { console.log("2"); });引数がある関数を呼ぶ場合
var callBackTest1 = (callback, name) => { console.log("1"); callback(name); }; var callBackTest2 = (name) => { console.log(name); } callBackTest1(callBackTest2, "hoge"); // 1 // "hoge"
- 投稿日:2020-08-02T17:36:04+09:00
Objectのコピー(deep copy)
JSON.stringify() メソッドと、JSON.parse() メソッドを使う。
var obj = {name : "yamada", id : "001"}; var copyObj = JSON.parse(JSON.stringify(obj));
- 投稿日:2020-08-02T17:23:36+09:00
ド初心者がWEBデザイナーになる話。 #1
超初心者が独学でWEBデザイナーを目指す話。
一人で心が折れそうなのと
どれくらいまでやったかよくわかんなくなってきたので整理のために書こうと。
道標というよりかは必死にあがいてる様を垂れ流すだけの場所とする。今のところやった事
- htmlとCSSの本でサンプルサイト作成 →それを自分用にアレンジ
- Javascriptを甘噛み →なんとなくそこはかとなく書いてあることはわかる(詳しくはわかんない)
- jQueryを甘噛み →なんとなく以下略
- 簡単そうなサイトを模写をする(トップページのみ)
- 1ページのオリジナルサイト作成 →CSSおしゃれ装飾やJsのコピペしてちょこっと変えたりして使ってみる
- WordPress甘噛み中(本使って勉強中) →現状なんとなく以下略な感じ
WordPress難しすぎて脳みそが爆発しそう。
頑張らないといけない…。
正直こんな勉強方法であってるかどうかもわからない。
確実に言えるのは何となくはわかる…ただそれだけでは意味が無いので確実に理解そして応用できるようにならねば。。。道は険しい、ゲボでそう()
- 投稿日:2020-08-02T17:18:17+09:00
〔JavaScript〕未来の自分が読みやすいコードにするために出来る事
はじめに
私はJavaScriptを勉強し始めて3ヶ月経ちました。
難しい問題も解けるようになり始めましたが、深いネストが多く見直した時に何をしているのかわからないコードを書いてしまいます。
深いネストの解消の仕方調べても難しそうな関数がたくさんで今すぐに改善するのは大変そう、、、、と思ってしまいました。
今回は、読みやすいコードにするためにする方法をたくさん調べ、今からでも意識して変えられる方法をまとめて書きたいと思います。マジックナンバーには名前をつける
ハードコーディングされている謎の数字のことをマジックナンバーと言います。
// この「50」は何? const pages = Math.ceil(records.length / 50);マジックナンバーは混乱させる原因になります。できるだけ値には名前をつけるようにしましょう。
const maxPage = 50; const pages = Math.ceil(records.length / maxPage);とすることで最大のページ数が50であることがわかります。
もちろん名前をつけなくてもいい数字もあります。
例えば角度をラジアンに変換するとき、「180」に名前をつける必要はないと思います。const radian = degree * Math.PI / 180;識別子を短くしない
識別子(関数名、変数名など)をむやみに短くしないというのも大事です。名前が省略されていると、本来の名前を推測する必要が出てきます。そうすると無駄な時間がかかりますし、最悪の場合は全く違う理解をしてしまうことがあります。
私の場合、関数の意味あっているものをつけるだけではなく、似たような意味を持つ関数の時に違いがわからないような名前をつけてしまうので気をつけたいです。否定形は、ほどほどに
名前に否定形はあまり入れないほうがいいです。
何か処理するのは表示するとき?しないとき?と混乱します。関数名のおすすめサイト
目的を名前にする
こちらも名前関連で、うっかりすると、関数名で「手段」を名前につけてしまうことがあります。
後から手段が変わったときに実態と異なる名前になってしまい、バグのもとです。
名前には「目的」をつけましょう。コメントを書く
どうわかりやすくすればいいかわからなくなった時は私はだいたいコメント書いてしまいます。
初歩的な事柄であるわりに効果が大きいのがコメントです。コメントは、コードを読む人が意味を理解するための大きな助けになります。深いネストの解消
関数を使うネストの解消は私には難しく少し勉強しないと使えないかなと思ったので、すぐできそうな解消の仕方を紹介します。
if(...){ if(...){ if(...){ 処理... }else{ エラー処理①... } }else{ エラー処理②... } }else{ エラー処理③... }if文が3つネストしています。
この場合はエラー処理をifで定義してネストを解消することができます。if(...){ エラー処理③ return or exception } if(...){ エラー処理② return or exception } if(...){ エラー処理① return or exception } 処理...他にも改善できる書き方があります。
数珠つなぎの条件
if($a == 1){ if($b == 2){ // 何らかの処理 } }こうやって繋げていくのはやめて
if($a == 1 && $b ==2){ // なんらかの処理 }こうできるといいと思います。
最後に
紹介した他にもたくさん良くする方法はあります。
もっとこうすると良いんじゃないかっと思ったらコメントイダ抱けると嬉しいです。参考リンク
他人に読んでもらうJavaScriptコードを書くために
読みやすいコードを書くために
新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則
【プログラミング】ネストの減らし方 〜可読性UPのシンプルな原則〜
- 投稿日:2020-08-02T16:07:52+09:00
ES6、ES5メモ
英語で書いた練習問題の自分用メモです。
ES5, ES6の場合ですと、以下の通り重要なポイントがあります。// change everything below to the newer Javascript! // let + const let a = 'test'; const b = true; const c = 789; a = 'test2'; // Destructuring // var person const person = { firstName: "John", lastName: "Doe", age: 50, eyeColor: "blue" }; const { firstName, lastName, age, eyeColor } = person; // var firstName = person.firstName; // var lastName = person.lastName; // var age = person.age; // var eyeColor = person.eyeColor; // Object properties // var a = 'test'; // var b = true; // var c = 789; // var okObj = { // a: a, // b: b, // c: c // }; const a = "test"; const b = true; const c = 789; var okObj = { a, b, c } // Template strings // var message = "Hello " + firstName + " have I met you before? I think we met in " + city + " last summer no???"; const message = `Hello, ${firstName} Havae I met you before? I think we met in ${city} last summer NO???` // default arguments // default age to 10; // function isValidAge(age) { // return age // } const isValidAge = (age = 10) => age; // 这里使用这个const定义函数的含义是,不允许这个变量继续编程别的东西了。他只能指向这个函数。 // Symbol // Create a symbol: "This is my first Symbol" // Arrow functions // function whereAmI(username, location) { // if (username && location) { // return "I am not lost"; // } else { // return "I am totally lost!"; // } // } const whereAmI = (username, location) => { if (username && location) { return "I am not lost"; } else { return " I am totally lost"; } }
- 投稿日:2020-08-02T15:51:06+09:00
配列のコピー
変数を代入する
変数を代入した場合、コピーとはならず同じデータを参照されるようになる。
そのため片方で行った変更がもう片方に影響を与えてしまう。var a = [1, 2, 3]; var b = a; // 変数bに対して行った値の変更が変数aにも影響を与えてしまう。 b[0] = 9; console.log(b); // [9, 2, 3] console.log(a); // [9, 2, 3]コピーには種類があり、浅いコピーと深いコピーがある。
他の変数に影響を与えないようにしたい場合、深いコピーをする必要がある。slice(浅いコピー)
var array = [ {name: "yamada"}, {name: "tanaka"}, ]; var copyArray = array.slice(); copyArray[0].name = "sasaki"; console.log(array); // {name: "sasaki"},{name: "tanaka"} console.log(copyArray); // {name: "sasaki"},{name: "tanaka"}concat(浅いコピー)
var array = [ {name: "yamada"}, {name: "tanaka"}, ]; var copyArray = array.concat(); copyArray[0].name = "sasaki"; console.log(array); // {name: "sasaki"},{name: "tanaka"} console.log(copyArray); // {name: "sasaki"},{name: "tanaka"}JSON.stringifyを使用する(深いコピー)
この方法では、Date オブジェクトや function など 文字列化すると壊れるオブジェクトが含まれていると正しくコピーできないようだ。
var array = [ {name: "yamada"}, {name: "tanaka"}, ]; var copyArray = JSON.parse(JSON.stringify(array)); copyArray[0].name = "sasaki"; console.log(array); // {name: "yamada"},{name: "tanaka"} console.log(copyArray); // {name: "sasaki"},{name: "tanaka"}他にはjQueryのExtend関数やlodashのcloneDeepを使うと
実現できるようだ。
- 投稿日:2020-08-02T15:46:21+09:00
JavaScriptからletを絶滅させ、constのみにするためのレシピ集
はじめに
本記事では、constこそが唯一神であることを証明したあと、letを使いがちな場面でいかにしてconstを使うかをまとめていきます。なお、ES2017までの基本構文(async/await)とJSプログラマ御用達の便利メソッド詰め合わせであるLodashのみを使用します。導入コストほぼ0で、チーム開発でも個人開発でも、明日からすぐに実践できるものを紹介していきます。
対象者
初心者(文法覚えたて。letとconstの違いはわかる)~中級者(Promiseを理解し、async/awaitやthen/catch, try-catchが使える)くらいを想定しています。
letに対する誤解
「varは使っちゃだめ! letやconstを使いましょう!」という言い回しをよく聞きます。
varは関数全体にスコープが漏れ出してしまうのが理由です。【JavaScriptのvarが嫌われる理由】
— きよしろー@Web系エンジニアになりたい人 (@kiyoshiro944) May 24, 2020
宣言した関数内ならどこでも使えてしまうから
つまり、if文の中で宣言したのに外でも使えてしまう
これは例えると、鍵アカでつぶやいたツイートが日本中に公開されるくらいやばいpic.twitter.com/lUQ3jMwiG9varはダメという主張自体は間違いないと思うのですが、「letやconstを使いましょう」というと、letとconstが同等の地位であるかのような印象を初学者の方に与えてしまいます。
違います。
constこそが唯一神であり、それと比べてしまえばletとvarの差など微々たるもの
なのです。constが唯一神である理由
4歳娘「パパ、constしか使わないで?」
こちらの記事が非常にわかりやすくまとまっております。
この記事でご説明いただいている通り、letは「いつ再代入されるかわからない」という恐怖を読み手に与えてしまいます。
これに加えて、うっかりグローバル変数を爆誕させてしまう危険性があります。実は危ないコードconst main = () => { let c = 1 // 何らかの処理 c = 2 console.log(c) // 2 } main() console.log(c) // エラーさて、let c = 1の行が消えたらどうなるでしょうか
グローバル変数爆誕の瞬間const main = () => { // 何らかの処理 c = 2 // var, let, const何もついてないのでグローバル変数が爆誕 console.log(c) // 2 } main() console.log(c) // 2まあ、
use strict
つけたり、eslintのno-implicit-globalsを設定すればグローバル変数の爆誕は防げますが。
いずれにせよ$const > let$であることが説明できました。冒頭で$let > var$は証明済みのため、まとめると$const > let > var$です。これでconstが唯一神であることが証明できました$Q.E.D.$letをconstに変えるレシピ集
letを使いがちないくつかの場面に対して、constに変える方法を伝授していきます。
環境
- ES2017(async/awaitが必要)
- Lodash 4.17.15
import _ from 'lodash'している前提です。
また、awaitをトップレベルで使っている際は以下のようにasync関数の一部を切り出しているものとしてお考えください(そもそもNode 14.3.0からtop level await使えるから許してくれ)async function main() { /** サンプルコード**************** * * *****************************/ } main()これらを念頭に置いて以下のサンプルコードをお読みください。
初級
10回繰り返したいfor文
これについてはfor文の中だけのスコープになるので許しても良い気もしますが、例外を認めるとletが根絶できません。
beforefor (let i = 0; i < 10; i++) { console.log(i) }after_.range(10).forEach(i => { console.log(i) })Pythonの
range
みたいなやつをLodashは提供してくれています。
(ちなみにこの例はもう少しスマートにできます。)スマートにできる_.range(10).forEach(console.log)数値配列の合計値を算出
beforeconst arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3] let sum = 0 for (let i = 0; i < arr.lenth; i++) { sum += arr[i] } console.log(sum)afterconst sum = _.sum(arr) console.log(sum)素のJSでも
reduce
を使えば合計は求められますが、こちらのほうがわかりやすいはずです。オブジェクトの配列の合計値を算出
beforeconst users = [{ name: 'person1', age: 10 }, { name: 'person2', age: 20 }, { name: 'person3', age: 30 }] let sumOfAge = 0 for (const user of users) { sumOfAge += user.age } console.log(sumOfAge)afterconst sumOfAge = _.sumBy(users, 'age') console.log(sumOfAge)Lodashの
sumBy
の使い所です。if文
beforelet tax if (持ち帰り) { tax = 0.08 } else { tax = 0.1 }afterconst tax = 持ち帰り ? 0.08 : 0.1三項演算子を使ってはいけないと誰かに言われたら、先ほどのconstが唯一神である証明でも見せて黙らせましょう。三項演算子はネストさせない限りは使っても問題ありません。
じゃあswitch文どうするのよ
beforelet message switch (response.status) { case 200: message = 'OK' break case 204: message = 'No Content' break // ...省略 } console.log(message)afterconst getMessageByStatus(status) => { switch (status) { case 200: return 'OK' case 204: return 'No Content' // ...省略 } } const message = getMessageByStatus(response.status)関数に切り分けましょう。breakする必要もなくなって行数が減りました。
(ちなみにこの例だとオブジェクトで宣言しといたほうが良い)オブジェクトで宣言しとくconst statusToMessage = { 200: 'OK', 204: 'No content' } const message = statusToMessage[response.status]中~上級
try-catchとの兼ね合い
beforelet response try { response = await requestWeatherForecast() // 天気予報APIを叩く } catch (err) { console.error(err) response = '曇り' // APIから取得できなかった場合は適当に曇りとか言ってごまかす } console.log(response)afterconst response = await requestWeatherForecast().catch(err => { console.log(err) return '曇り' })Promiseのcatchメソッド内でreturnした値はawaitを通せば外界の変数に代入することができます
例外catchしたら早期returnしたいんだが
beforelet response try { response = await requestWeatherForecast() // 天気予報APIを叩く } catch (err) { console.error(err) return } console.log(response)afterconst shouldReturn = Symbol('shouldReturn') // 普通に文字列の'shouldReturn'でも良いか? const response = await requestWeatherForecast().catch(err => { console.error(err) return shouldReturn }) if (response === shouldReturn) return console.log(response)先ほどの応用です。ちなみにこの方法は筆者の思いつきで、このようなコードを書いている人を観測したことはありません。
リトライ処理
before// 天気予報APIを叩く。エラーが出たら10回までリトライする const MAX_RETRY_COUNT = 10 let retryCount = 0 let response while(retryCount <= MAX_RETRY_COUNT) { try { response = await requestWeatherForecast() // 天気予報APIを叩く break } catch (err) { console.error(err) retryCount++ } } console.log(response)after// 与えられた関数をmaxRetryCount回までリトライする関数。 const retryer = (maxRetryCount, fn, retryCount = 0) => { if (retryCount >= maxRetryCount) return undefined return fn().catch(() => retryer(maxRetryCount, fn, retryCount + 1)) // retryCountを1増やして再帰呼び出し } const response = await retryer(MAX_RETRY_COUNT, requestWeatherForecast)
retryer
のようなラップ関数をつくりましょう。番外編(不変じゃないconst)
有名な話ではありますが、constは再代入できないだけで、constで宣言した配列に要素を追加したり、constで宣言したオブジェクトにプロパティを追加することはできてしまいます。
constでも変更を加えられる例const arr = [] arr.push(1) // arr: [1] const obj = {} obj.a = 1 // obj: { a: 1 }これらの行為はconstという唯一神をletと同じ地位まで貶める愚行です。以下で、配列やオブジェクトを変更しがちな例を紹介し、その代替案を紹介します。
配列から条件に合うものだけ抜き出す
before// 偶数だけを抜き出す const arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3] const result = [] for (const n of arr) { if (n % 2 === 0) result.push(n) // 愚行 } console.log(result)afterconst result = arr.filter(n => n % 2 === 0)filterを使いましょう。ちなみにfilterは宣言しておいた関数を渡すと可読性が高まります。
可読性が高いconst isEven = num => num % 2 === 0 // 関数を用意 const result = arr.filter(isEven)変数がundefinedじゃないときだけオブジェクトに追加
before// トークンがundefinedじゃなかったら、Authorizationヘッダーを追加 const header = { 'Content-Type': 'application/json' } if (token !== undefined) header.Authorization = `Bearer ${token}` // 愚行afterconst header = { 'Content-Type': 'application/json', ...(token === undefined ? {} : { Authorization: `Bearer ${token}` }) }三項演算子と
...
のスプレッド構文の組み合わせです。空のオブジェクトをスプレッド構文で展開すると消えるというテクニックは便利です。オブジェクトの値部分に処理を加える
before// 値部分をNumberに変換し、値が偶数のところだけ抜き出す。 const obj = { a: '1', b: '2', c: '3', d: '4', /* ... */ } cosnt result = {} for (const [key, value] of Object.entries(obj)) { const number = Number(value) if (number % 2 === 0) { result[key] = number // 愚行 } } console.log(result) // { b: 2, d: 4, ... }afterconst isEven = num => num % 2 === 0 // 偶数かを判定する関数を用意 const result = _.pickBy(_.mapValue(obj, Number), isEven)配列にmapメソッドがあると思いますが、Lodashの
mapValue
はそれのオブジェクト版だと考えると話が早いです。pickBy
は先ほどでてきた配列のfilter
のオブジェクト版です。
ちなみに、Lodashは以下のように使うとメソッドチェーンがはかどりますメソッドチェーン_(obj).mapValue(Number).pickBy(isEven) // { b: 2, d: 4, ... }さいごに
他にもletを使いたくなる場面があったらご指摘いただけますと幸いです。
また、記事に不備がございましたらご指摘いただけますと幸いです。
- 投稿日:2020-08-02T15:00:43+09:00
null、undefinedの判定
以下で判定可能
var nullOrUndefined = (val) => { // ==演算子を使用する。 // ===にすると厳密な比較が行われ、undefinedの場合falseになる if (val == null) { console.log("null or undefined"); } else { console.log(val); } }; var a; nullOrUndefined(a); // "null or undefined" var b = null; nullOrUndefined(b); // "null or undefined" var c = "hoge"; nullOrUndefined(c); // "hoge"コーディング規約によっては==の使用が禁止されていて
===しか使用できないことがある(体験談…)その場合の代替案をいくつか調べてみた。
1.自分でメソッドを作る
無ければ作ればいいじゃないの精神。
var isNullOrUndefined = (val) => { if (val === null) return true; if(val === undefined) return true; return false; };2.!hogeによる判定
0、false、空文字の場合もtrueと判定されることを理解して使用する。
if (!hoge) { //null, undefined, 0, 空文字(''), falseの場合trueとなりこのブロックに入る }自分は判定対象の値に0が入っていることもあり1の方法を使用していたが、
2つ以上の値を判定する場合、記述数が多くなりモヤモヤしていた。// もしも変数名が長いと手に負えない if (isNullOrUndefined(a) && isNullOrUndefined(b)){}2つ以上の値のチェックが頻繁に必要な場合、可変長で引数を受け取るメソッドを作成しておいても良いのかもしれない。
var isNullOrUndefinedAll = (...args) => { for (let val of args) { if (val !== null && val !== undefined) return false; } return true; };
- 投稿日:2020-08-02T14:00:20+09:00
JavaScript var,let,const 使い分け
はじめに
実装でjsを書いている際に、変数宣言(var,let,const)の使い分けが整理できていなかったのでこの機会にまとめておこうと思う。
var
再宣言・再代入が可能で、初期化なしの宣言が可能。
var x = 1; console.log(x); //1 x = 2; console.log(x); //2 再代入 var x = 3; //再宣言 console.log(x); //3 var s; // 初期化なしの宣言スコープは関数スコープ
変数xは関数のスコープ内でのみ参照できる。function (){ var x = 0; console.log(x); //0 } console.log(x); //error x is not definedlet
再代入は可能だが再宣言は不可となっている。初期化なしの宣言が可能。
let x = 1; console.log(x); //1 x = 2; console.log(x); //2 再代入 let x = 3; //SyntaxError 再宣言はできない let s; // 初期化なしの宣言スコープはブロックスコープ
ブロック内で宣言された変数は、スコープ内でのみ参照でき、スコープの外側からは参照できない。{ let x = 1; console.log(x); // => 1 } console.log(x); // => ReferenceError: x is not definedfunction fn(){ let x = 1; console.log(x); { let x = 2; console.log(x); } console.log(x); } fn()結果はこんな感じ
// => 1 2 1const
再宣言、再代入が不可で、更に初期化なしの宣言が不可。
const x = 1; console.log(x); //1 const = 2; console.log(x); //2 TypeError const x = 3; //SyntaxError 再宣言はできない const s; //SyntaxErrorスコープは
let
と同じくブロックスコープ。キーワードなしの宣言
var, let, constをつけなくても変数宣言は成立する。ただしグローバルに参照できる変数となるためバグの元となり非推奨。
変数の巻き上げ
JavaScript の変数に独特な点として、例外を発生させることなく後に宣言した変数を参照できる点がある。これを巻き上げという。
関数内で宣言したローカル変数をすべてundefinedで初期化するということ、その関数の先頭で宣言されたものと見なされるというもの。
例としてvar x = 0; function (){ console.log(x); var x = 1; console.log(x); } // => undefined 1使い分け
varは使わず、let/constを使う。理由として
varは書き換えが可能なこと、巻き上げ時のバグを生み出しやすいことがあげられる。
letやconstを使っていれば、書き換えてしまってもエラーを出してくれるので、バグ発見に繋がりやすい。
- 投稿日:2020-08-02T13:13:56+09:00
PythonでEDINETコードリストを取得する
金融界隈で定量的な分析やデータサイエンスをやっている9uantです.
twitterもやってるので,興味ある方はぜひフォローしていただけると!証券コードとEDINETコードを対応させる表データの取得が面倒だったため,共有したい.
EDINETコードと証券コード
証券コードは,上場企業の株価情報を取得する際に使用する4桁の数字である.実際には末尾に0を加えて5桁とする.
EDINETコードは,決算情報のデータベースであるEDINET上で企業情報を取得する際に使用するアルファベット+数字である.4桁の証券コードをEDINETコードに変換する方法を紹介する.
EDINETコードリストを取得する
EDINETタクソミノ及びコードリストの下部に,「EDINETコードリスト」がある.今回はPythonを使ってこのcsvデータを取得する.
chromedriverを事前にダウンロードする必要がある.
chromedriverのダウンロードサイトから,ご自身のchromeのバージョンに合わせてダウンロードされたい.get_edinet_code_csv.pyimport glob import os import shutil import time from selenium import webdriver import zipfile def get_edinet_code_csv(edinetcode_dir): ''' EDINETコードリストのcsvファイルを指定したディレクトリにダウンロードする Prameter: edinetcode_dir: str EDINETコードリストのcsvファイルをダウンロードするディレクトリ Return: edinet_code_list_path: str EDINETコードリストのcsvファイルが存在するパス ''' ''' # ディレクトリが既に存在する場合は,ディレクトリを削除する if os.path.exists(edinetcode_dir): shutil.rmtree(edinetcode_dir) ''' # seleniumでchromeからzipファイルをダウンロード chromeOptions = webdriver.ChromeOptions() prefs = {"download.default_directory" : edinetcode_dir} # 保存先のディレクトリの指定 chromeOptions.add_experimental_option("prefs",prefs) chromeOptions.add_argument('--headless') # ブラウザ非表示 driver = webdriver.Chrome('chromedriverのパス', chrome_options=chromeOptions) # EDINETのEDINETコードリストにアクセス driver.get('https://disclosure.edinet-fsa.go.jp/E01EW/BLMainController.jsp?uji.bean=ee.bean.W1E62071.EEW1E62071Bean&uji.verb=W1E62071InitDisplay&TID=W1E62071&PID=W0EZ0001&SESSIONKEY=&lgKbn=2&dflg=0&iflg=0') driver.execute_script("EEW1E62071EdinetCodeListDownloadAction('lgKbn=2&dflg=0&iflg=0&dispKbn=1');") time.sleep(5) driver.quit() # ダウンロードしたzipファイルのパスを取得 list_of_files = glob.glob(edinetcode_dir+r'/*') # ワイルドカードを追加 latest_file = max(list_of_files, key=os.path.getctime) #作成日時が最新のファイルパスを取得 # zipファイルを同じディレクトリに展開 zip_f = zipfile.ZipFile(latest_file) zip_f.extractall(edinetcode_dir) zip_f.close() # zipファイルを削除 os.remove(latest_file) list_of_files = glob.glob(edinetcode_dir+r'/*') # ワイルドカードを追加 return max(list_of_files, key=os.path.getctime) # 展開したcsvファイルのパスを返す証券コード→EDINETコード
証券コードの配列を,EDINETコードの配列に変換する.
stockcode_to_edinetcode.pyimport numpy as np import pandas as pd edinet_code_path=get_edinet_code_csv(r"EDINETコードリストのcsvのダウンロード先ディレクトリのパス") edinet_code_df=pd.read_csv(edinet_code_path,encoding="cp932",header=1,usecols=['EDINETコード', '提出者名', '証券コード']) def stockcode_to_edinetcode(codes): ''' 証券コード(の配列)に対応するEDINETコードの配列を取得する Parameter: codes: int or float or str or list 証券コード,またはその配列 Return: edinet_codes: list 引数の順番に対応したEDINETコードの配列 ''' # 全ての引数を配列に変換 if type(codes) in (str, int, float): codes = [int(codes)] edinet_codes = [] for code in codes: # 4桁の証券コードを5桁に変換 if len(str(int(code)))==4: code = str(int(code))+'0' tmp = edinet_code_df[edinet_code_df['証券コード']==int(code)]['EDINETコード'] if len(tmp)==0: # 対応するEDINETコードが存在しない場合np.nanを返す edinet_codes.append(np.nan) else: edinet_codes.append(tmp.to_list()[0]) return edinet_codes
- 投稿日:2020-08-02T10:35:14+09:00
今までJW Playerを使っていたけどplyr.jsをカスタマイズしてみた(3)
前回まで
前回まで でボタンの色や配置をカスタマイズすることはできました。
ここからは独自のボタンを追加するなどさらなるカスタマイズに進みます。オリジナルのボタン追加
下記の公式情報のように、HTMLをJavaScriptにガシガシ書くことで追加できます。
公式の情報const controls = ` <div class="plyr__controls"> <!-- もう一度再生ボタン --> <button type="button" class="plyr__control" data-plyr="restart"> <svg role="presentation"><use xlink:href="#plyr-restart"></use></svg> <span class="plyr__tooltip" role="tooltip">Restart</span> </button> <!-- 10秒戻るボタン --> <button type="button" class="plyr__control" data-plyr="rewind"> <svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg> <span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span> </button> <!-- 再生、停止ボタン --> <button type="button" class="plyr__control" aria-label="Play, {title}" data-plyr="play"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Pause</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span> </button> <!-- 10秒進むボタン --> <button type="button" class="plyr__control" data-plyr="fast-forward"> <svg role="presentation"><use xlink:href="#plyr-fast-forward"></use></svg> <span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span> </button> <!-- プログレスバー --> <div class="plyr__progress"> <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek"> <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress> <span role="tooltip" class="plyr__tooltip">00:00</span> </div> <!-- 現在時間 --> <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div> <!-- 全体の再生時間 --> <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div> <!-- ミュート、ミュート解除ボタン --> <button type="button" class="plyr__control" aria-label="Mute" data-plyr="mute"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span> </button> <!-- ボリューム --> <div class="plyr__volume"> <input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume"> </div> <!-- 字幕表示/非表示ボタン --> <button type="button" class="plyr__control" data-plyr="captions"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span> </button> <!-- フルスクリーンボタン --> <button type="button" class="plyr__control" data-plyr="fullscreen"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span> </button> </div> `; // 「controls:」はいらない const player = new Plyr('#player', { controls });実はこの方法
2つ制約があり
- ピクチャーインピクチャとAirPlayは追加できるが、ブラウザが対応しているのかは自分で実装する必要がある。
- 設定メニューは非対応。
そうなのです。画質変更、再生速度変更などの設定メニューはこの方法では自分で実装するしかないのです。
再生速度は「plaer.speed=2」のように瞬殺だったのですが、画質変更がえげつなく難しかったです。
ただ、出来てしまえば「まあ、当然ですよね。」という内容でした。myplayer.js// 画質ごとの動画ファイルのパスを格納する変数 var video_url 1080 = "video/file_1080p.mp4"; var video_url 720 = "video/file_720p.mp4"; var video_url 480 = "video/file_480p.mp4"; $(function() { const controls = ` <div class="plyr__controls"> <!-- もう一度再生ボタン --> <button type="button" class="plyr__control" data-plyr="restart"> <svg role="presentation"><use xlink:href="#plyr-restart"></use></svg> <span class="plyr__tooltip" role="tooltip">Restart</span> </button> <!-- 10秒戻るボタン --> <button type="button" class="plyr__control" data-plyr="rewind"> <svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg> <span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span> </button> <!-- 再生、停止ボタン --> <button type="button" class="plyr__control" aria-label="Play, {title}" data-plyr="play"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Pause</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span> </button> <!-- 10秒進むボタン --> <button type="button" class="plyr__control" data-plyr="fast-forward"> <svg role="presentation"><use xlink:href="#plyr-fast-forward"></use></svg> <span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span> </button> <!-- プログレスバー --> <div class="plyr__progress"> <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek"> <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress> <span role="tooltip" class="plyr__tooltip">00:00</span> </div> <!-- 現在時間 --> <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div> <!-- 全体の再生時間 --> <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div> <!-- ミュート、ミュート解除ボタン --> <button type="button" class="plyr__control" aria-label="Mute" data-plyr="mute"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span> </button> <!-- ボリューム --> <div class="plyr__volume"> <input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume"> </div> <!-- 字幕表示/非表示ボタン --> <button type="button" class="plyr__control" data-plyr="captions"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span> </button> <!-- フルスクリーンボタン --> <button type="button" class="plyr__control" data-plyr="fullscreen"> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg> <span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span> </button> <!-- 画質選択ボタン --> <button type="button" class="plyr__control" data-plyr="settings" id="plyr_quality"> <span class="plyr__label">480</span> <span class="plyr__tooltip" role="tooltip">画質選択</span> </button> <div id="quality_menu" class="hidden pa tc"> <div val="1080" class="quality_select cp">1080p</div> <div val="720" class="quality_select cp">720p</div> <div val="480" class="quality_select cp">480p</div> </div> </div> `; // 「controls:」はいらない const player = new Plyr('#player', { controls }); // 画質ごとのファイルをsourceに設定 player.source = { type: "video", sources: [ { src: video_url_480, type: "video/mp4", size: 480 }, { src: video_url_720, type: "video/mp4", size: 720 }, { src: video_url, type: "video/mp4", size: 1080 } ], poster: overview_url }; // 最初は画質リストは非表示 $("#quality_menu").hide(); // 画質選択ボタンの動作を追加 $("#plyr_quality").click(function() { // 画質リストを表示 $("#quality_menu").toggle(); }); // 画質リストから選択した時の動作 $(".quality_select").click(function() { // 経過時間を取得 cur_time = player.currentTime; setPlayerSource(player, $(this).attr("val")); // 0秒以上なら再生されているとみなして、同じ時間から再生 if (cur_time > 0) { player.play(); player.currentTime = Number(cur_time); } $("#quality_menu").hide(); }); }); function setPlayerSource(player, cur_val) { if (cur_val == "1080") { player.media.src = video_url; } else if (cur_val == "720") { player.media.src = video_url_720; } else { player.media.src = video_url_480; } }そうです。要するにやっていることは「video要素のsrc属性を変える」だけです。
ただ、私の英語力の問題か、公式の文書から「playerのmediaプロパティにvideo要素が入っている」ということが読み解けなかったので、単純にコンソールで色々いじって見つけました。
なので、公式推奨のやり方ではないです。そうするとこのように表示されます。
見た目がものすごいイマイチです...。myplayer.js// ... 略 <div class="plyr__progress" style="width:100%;"> <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek"> <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress> <span role="tooltip" class="plyr__tooltip">00:00</span> </div> // ... 略 <div id="quality_menu" style="position: absolute; top: -50px; background: red;"> <div val="1080" class="quality_select cp">1080p</div> <div val="720" class="quality_select cp">720p</div> <div val="480" class="quality_select cp">480p</div> </div>とりあえず上記2箇所だけ修正すればプレイヤーっぽくなって、画質メニューを開いても幅がずれたりはしなくなります。
参考リンク:
公式のコントローラーの説明
https://github.com/sampotts/plyr/blob/master/CONTROLS.md
- 投稿日:2020-08-02T07:14:25+09:00
カリー化チートシート
拙作『不動点コンビネータを用いた無名再帰関数の実行まとめ』の補足説明として書き始めたところ,カリー化関数を記述・利用するための独立したチートシートとした方が少なくとも約1名(自分自身)には役立ちそうだったので,新しく記事にした.なお,カリー化してくれる関数の定義ではないことに注意.複数言語にわたっている都合上,各言語に精通している方々のツッコミ歓迎.
記法のみの一覧は次の通り.
言語 各引数の戻り値 各引数の指定 備考 Haskell \
->
による無名関数(・・・(f a) a)・・・a
自動的にカリー化 Scheme lambda
による無名関数(・・・((f a) a)・・・a)
Python 関数内部で定義した関数 f(a)(a)・・・(a)
無名関数も使用可 Ruby ->
を用いた無名関数f.(a).(a)・・・.(a)
JavaScript =>
やfunction
を用いた無名関数f(a)(a)・・・(a)
Scala =>
を用いた無名関数f(a)(a)・・・(a)
カリー化関数あり,強い型付け Perl sub
を用いた無名サブルーチンf(a)->(a)・・・->(a)
$f->(a)・・・
の場合ありGo言語 func
を用いた無名関数f(a)(a)・・・(a)
強い型付け PHP function
とuse
を用いた無名関数f(a)(a)・・・(a)
Haskell(GHC)
Haskellでは,複数引数で定義しても自動的にカリー化される.カリー化関数の引数指定は
(・・・(関数 引数) 引数)・・・引数
である.Prelude> func x y z = if x > 0 then y else z Prelude> func (-100) 0 (-1) -1 Prelude> ((func (-100)) 0) (-1) -1
\
および->
を用いた無名関数を戻り値にして実現する方法は次の通り.ただし,この場合でも引数の複数指定が可能.Prelude> func = \x -> \y -> \z -> if x > 0 then y else z Prelude> ((func (-100)) 0) (-1) -1 Prelude> func (-100) 0 (-1) -1 Prelude> func = \x y z -> if x > 0 then y else z Prelude> ((func (-100)) 0) (-1) -1 Prelude> func (-100) 0 (-1) -1Scheme(Gauche)
lambda
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は(・・・((関数 引数) 引数)・・・引数)
である.gosh> (define (func x y z) (if (> x 0) y z)) func gosh> (func -100 0 -1) -1 gosh> (define func (lambda (x) (lambda (y) (lambda (z) (if (> x 0) y z))))) func gosh> (((func -100) 0) -1) -1Python(Python3,Python2)
lambda
を用いた無名関数を用いることもできるが,無名関数を直接変数に代入するのがPEP8非推奨ということもあり,関数内部で定義した関数を戻り値にする方法が一般的である.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.>>> def func(x, y, z): return y if x > 0 else z ... >>> func(-100, 0, -1) -1 >>> def func(x): ... def func(y): ... def func(z): return y if x > 0 else z ... return func ... return func ... >>> func(-100)(0)(-1) -1 >>> func = lambda x: lambda y: lambda z: y if x > 0 else z # PEP8非推奨 >>> func(-100)(0)(-1) -1Ruby(CRuby,JRuby)
->
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数.(引数).(引数)・・・.(引数)
である.def func(x,y,z) return x > 0 ? y : z end p func(-100,0,-1) # => -1 func = -> x { -> y { -> z { return x > 0 ? y : z } } } p func.(-100).(0).(-1) # => -1JavaScript(Node.js)
=>
やfunction
を用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.function func1(x,y,z) { return x > 0 ? y : z } console.log(func1(-100,0,-1)) // => -1 func2 = x => y => z => x > 0 ? y : z console.log(func2(-100)(0)(-1)) // => -1 function func3(x) { return function (y) { return function (z) { return x > 0 ? y : z } } } console.log(func3(-100)(0)(-1)) // => -1Scala(Scala 2.11 + Java VM 12)
Scalaでは,カリー化するメソッド
curried
が用意されている.ただし,複数引数の無名関数を=>
によって一度定義してからcurried
を適用する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.scala> def func(x: Int, y: Int, z: Int): Int = if (x > 0) y else z func: (x: Int, y: Int, z: Int)Int scala> func(-100,0,-1) res0: Int = -1 scala> val func = (x: Int, y: Int, z: Int) => if (x > 0) y else z func: (Int, Int, Int) => Int = <function3> scala> val func_curried = func.curried func_curried: Int => (Int => (Int => Int)) = <function1> scala> func_curried(-100)(0)(-1) res1: Int = -1
=>
のみを用いた無名関数によって実現する方法は次の通り.強い型付け言語であるため,関数全体の型の推移を明示する必要がある.scala> val func: Int => (Int => (Int => Int)) = (x: Int) => (y: Int) => (z: Int) => if (x > 0) y else z func: Int => (Int => (Int => Int)) = <function1> scala> func(-100)(0)(-1) res2: Int = -1Perl(perl 5)
sub
を用いた無名関数(サブルーチン)を戻り値にして実現する.カリー化関数の引数指定は関数(引数)->(引数)・・・->(引数)
である.なお,関数本体の名前も無名関数とした場合は$関数->(引数)->(引数)・・・->(引数)
である.sub func { my ($x,$y,$z) = @_; $x > 0 ? $y : $z; }; print func(-100,0,-1), "\n"; # => -1 sub func_curried { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; }; print func_curried(-100)->(0)->(-1), "\n"; # => -1 my $func_curried2 = sub { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; }; print $func_curried2->(-100)->(0)->(-1), "\n"; # => -1Go言語(gc)
func
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.なお,強い型付け言語であるため,扱う引数が増えるほど,各引数の関数の戻り値に対する型付け記述が増えていく.package main import "fmt" func func1 (x, y, z int) int { if x > 0 { return y } else { return z } } func func2 (x int) func(int) func(int) int { return func(y int) func(int) int { return func(z int) int { if x > 0 { return y } else { return z } } } } func main() { fmt.Println(func1(-100,0,-1)) // => -1 fmt.Println(func2(-100)(0)(-1)) // => -1 }PHP(PHP 7)
function
とuse
を用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.<?php function func1($x,$y,$z) { return ($x > 0) ? $y : $z; } echo func1(-100,0,-1) . PHP_EOL; // => -1 function func2($x) { return function($y) use ($x) { return function($z) use ($x,$y) { return ($x > 0) ? $y : $z; }; }; } echo func2(-100)(0)(-1) . PHP_EOL; // => -1備考
カリー化の概要
カリー化とは,高階関数の機能を利用して,複数の引数を指定する関数を,ひとつの引数のみを指定する関数の繰り返しに変換することである.処理記述の共有が可能な『部分適用』の手段として有名であるが,あくまで利用例のひとつである.カリー化自体の特徴は,ラムダ計算などの数学的な理論を適用しやすいことに加え,引数ごとに値の適用を調節したり,データ構造を要素ごとに受け取ったりすることで,より簡潔で柔軟なプログラミングが可能となることである.
なお,下記のPythonの例のように,今回の記述方法を用いることで,複数引数をもつ既存の関数のカリー化も容易に行える.ただし,複数引数をもつ既存の関数をカリー化関数に変換してくれる関数やマクロは作成できない.理由は,既存関数の引数の数が不定であり,任意の数の無名関数や内部関数を(クロージャ機能を含めて)生成できないためである.Scalaの
curried
は個別に型定義された複数引数をもつ無名関数から変換しており,Haskellは処理系がカリー化関数のみを扱っている(関数定義が行われ引数の数や型を解析する時点でカリー化を行っている)Pythonでのカリー化関数利用例
>>> def is_t(t): ... def r(v): return isinstance(v, t) ... return r ... >>> T = 10, "hoge", 20.4, False, "hage" >>> tuple(map(is_t(str), T)) (False, True, False, False, True) >>> tuple(map(is_t(int), T)) (True, False, False, True, False) >>> def func(y): ... def func(z): ... def func(w): ... def func(x): return y if x > 0 else z if x < 0 else w ... return func ... return func ... return func ... >>> func1 = func('positive')('negative') >>> func2 = func1('zero') >>> func2(1) 'positive' >>> func2(-1) 'negative' >>> func2(0) 'zero' >>> func2 = func1('ゼロ') >>> func2(0) 'ゼロ' >>> T = (3, -2, 0, 1, -7) >>> dict(zip(T, map(func2, T))) {3: 'positive', -2: 'negative', 0: 'ゼロ', 1: 'positive', -7: 'negative'} >>> def recur(f, t): return f if not t else recur(f(t[0]), t[1:]) ... >>> dict(zip(T, map(recur(func, ('正', '負', 'ゼロ')), T))) {3: '正', -2: '負', 0: 'ゼロ', 1: '正', -7: '負'}(同様の内容をSchemeで記述したものはこちら)
変更履歴
- 2020-08-02:PHPを追加
- 2020-08-02:記法のみの一覧を追加
- 2020-08-02:Go言語を追加
- 2020-08-02:初版公開(Haskell,Scheme,Python,Ruby,JavaScript,Scala,Perl,Python利用例)
- 投稿日:2020-08-02T05:20:27+09:00
複数の要素でスクロール検知してアニメーションを行う
IntersectionObserver
".made-item"が付いている要素を取得して、それぞれの要素が特定のスクロールポイントに来たときにclassを付与、消去するプログラムです。
function() { const items = document.querySelectorAll('.made-item') const cb = function(entries, observer) { entries.forEach(entry => { if (entry.isIntersecting) { console.log('はいったときの処理'); console.log(entry.target); entry.target.classList.add('inview') } else { console.log('でたときの処理'); entry.target.classList.remove('inview') } }) } const options = { root: null, //交差する要素を設定できる 変更することはあまりない rootMargin: '0px 0px -300px 0px', //交差点の場所をmarginのように指定できる threshold: 0 // 初期値は0 0は交差点に入ってくる要素の下の部分 1は上の部分でcbが呼ばれる // 配列で[0, 0.5, 1とすると要素の上、真ん中、下でそれぞれcbが呼ばれる } const io = new IntersectionObserver(cb, options) items.forEach(item => { io.observe(item) // io.observe(item2) // io.observe(item3) // ioの中に↑こんな感じで要素を複数入れられます }) }
- querySelictorAllで複数のclassを指定
- forEachでそれを回して変数io(newしたIntersectionObserver)に代入
- 上記の要素がcbの引数entriesに入るので入ったとき、出たときの処理を記入
- 交差点の要素はentry.targetで確認できる
scrollY
let scroll = 0 function() { scroll = window.scrollY console.log(scroll); }
- スクロールで上端からのピクセル数を検知
- 投稿日:2020-08-02T05:20:27+09:00
複数の要素をスクロール検知してアニメーションを行う
よくポートフォリオサイトなどで見かけるスクロールすると画像がフェードインしてくる感じのものが作れます。
IntersectionObserver
".made-item"が付いている要素を取得して、それぞれの要素が特定のスクロールポイントに来たときにclassを付与、消去するプログラムです。
function() { const items = document.querySelectorAll('.made-item') const cb = function(entries, observer) { entries.forEach(entry => { if (entry.isIntersecting) { console.log('はいったときの処理'); console.log(entry.target); entry.target.classList.add('inview') } else { console.log('でたときの処理'); entry.target.classList.remove('inview') } }) } const options = { root: null, //交差する要素を設定できる 変更することはあまりない rootMargin: '0px 0px -300px 0px', //交差点の場所をmarginのように指定できる threshold: 0 // 初期値は0 0は交差点に入ってくる要素の下の部分 1は上の部分でcbが呼ばれる // 配列で[0, 0.5, 1とすると要素の上、真ん中、下でそれぞれcbが呼ばれる } const io = new IntersectionObserver(cb, options) items.forEach(item => { io.observe(item) // io.observe(item2) // io.observe(item3) // ioの中に↑こんな感じで要素を複数入れられます }) }
- querySelictorAllで複数のclassを指定
- forEachでそれを回して変数io(newしたIntersectionObserver)に代入
- 上記の要素がcbの引数entriesに入るので入ったとき、出たときの処理を記入
- 交差点の要素はentry.targetで確認できる
scrollY
let scroll = 0 function() { scroll = window.scrollY console.log(scroll); }
- スクロールで上端からのピクセル数を検知
- 投稿日:2020-08-02T03:31:29+09:00
非同期の双方向更新を実現するためのテクニック
近年、Fluxのように双方向の更新を極力避けて、単方向の更新に還元させようという流れがあります。ただ、どうしても双方向の更新が避けられない場合はあるでしょう。Goole Docsのように、複数の人が同じドキュメントを編集する場合。画面上の複数の入力コンポーネントが相互に同期しているような場合です。
特定の言語に依存しない話ですが、Javascriptで使うことが多いとは思われるため、タグにJavascriptを入れています。
1. 一番単純な実装
まず、一番シンプルに、Aが更新されたらBに反映させる、Bが更新されたらAに反映させる、という実装を考えます。
2. 外部から更新を受け入れた場合は、更新の報告をしない
この際、単純に実装するとA→B→A→Bという無限ループになってしまいます。これを解決するためには以下のような方法が考えられます。
それは、「外部から更新を受け入れた場合は、更新の報告をしない」ことです。
3. サーバ側でupdateIdを管理し、最後のコミットを反映させる
ただ、これだと、AとBが同時に更新した場合、最終的に、どちらのサーバ反映が早かったかに関わらず、AではBが反映され、BではAが反映されることになります。つまり、AとBの現在状態がずれてしまいます。
これに対応するためには、サーバ側で、更新の受付を管理し、最新の状態を確定させる必要があります。
これは、socket.ioを使う場合、フレームワーク側で更新順序の管理するので、サーバ側で特別な対応は不要です。
この場合、クライアント側では、自分が行った更新がサーバを通して通知されてきた時に無視せずに、受け入れるような実装が必要です。自分が行った更新がサーバを通して通知されてきた時に無視するような実装にすると、更新がAとBで同時に行われて、Aの方が先にサーバに到達した時、本来、現在状態はBの更新でなければいけないのに、Bの側ではそれを受け取れず、現在状態がずれてしまいます。
4. 更新中の通知の無視
しかし、これにはまた問題があります。
自分が行った更新がサーバを通して通知されてきた時に受け入れるとなると、
そのラウンドトリップの間に行われた変更が一時的にであれ破棄されることになってしまいます。
これは、ユーザから見ると、行った変更が一時的に巻き戻されるように見えることになり、
受け入れられるものではありません。これを解決するための方法は、
「自分が更新を行ってそれが通知されるまでの間、更新の受け入れを先延ばしする」
ことです。
5. 更新中の自己更新の無視
しかし、これにもまた問題があります。それは、外部からの更新の受け入れもストップしてしまうため、AとBがずっと更新し続けると、永遠にAとBの状態がずれたままになることです。
これを解決するには、上のルールを以下のように修正すれば良いことになります。
「現在状態が、自己の更新の結果によるものである場合、自分が行った更新の通知は無視する」
つまり、自分が更新をしている間でも、他の人の更新は受け入れます。そして、いったん他の人の更新を受け入れれば、その後に到着した自分が行った更新は受け入れます。
これで初めて、現在状態のズレがなくなり完全な同期が実現することになります。
6. まとめ
こうやって見ると、双方向更新を実現するため考慮しないといけないことが想像以上に多いことが分かると思います。
ここから分かる重要なことは、双方向更新が必要になったとき、一番最初に考えないといけないことは「双方向更新をしないで良いように設計できないか検討すること」です。それがどうしても無理だった場合、この記事のことを思い出してもらえると幸いです。
- 投稿日:2020-08-02T03:25:51+09:00
極限まで単純化したPromiseのサブセット
例外処理等はなく、とにかくthenableにすることだけに特化したPromiseのサブセットです。あまり実用的な意味はありませんが、Promiseの内部の実装を理解する上で参考になればと思います。
const Promise = window.Promise || function Promise(executor) { const callbacks = []; let isResolved = false; let resolvedValue = null; const resolved = (value) => { isResolved = true; resolvedValue = value; callbacks.forEach(callback => callback(resolvedValue)) } const rejected = (err) => { callbacks.splice(0); console.error(err); } executor(resolved, rejected); this.then = (callback) => { if (isResolved) { callback(value); } else { callbacks.push(callback); } }; }
- 投稿日:2020-08-02T02:56:26+09:00
SQL文で変数を使う方法 in JavaScript
はじめに
Node.jsでExpress+Mysqlを用いてRestApiを作成する際、postでの処理に詰まったため備忘録として記事を書きます。
問題
id(int) name(String) 1 test このようなテーブルに対して、以下のソースを用いてpostでレコードを挿入する際、数値は入るが文字列はエラーが出て入れることができなかった。
sample.jsid=req.body.id; name=req.body.name"; //jsonで{id:2,name:”sample”}というデータを送信 sql="INSERT INTO api_tbl VALUES("+id+","+name+")"; connection.query( sql,function (error, results, fields) { if (error) throw error; res.send("ok"); });原因としては、postリクエストを送った際、sql文をセットしている変数
sql
に以下のような文字列が格納されていたからと考えた。
sql="INSERT INTO api_tbl VALUES(2,sample)"
ここからnameの文字列を「""」で囲うために、変数
name
の値を以下のように書き換えることで実装できた。
name= '"'+req.body.name+'"'
おわりに
SQL文はあまり学習していないので変なところで詰まってしまった…反省。
基本的な構文はすらすら書けるようしっかり学習します。
今回の手法、もっと簡単にできるやり方があったら教えてください!(2020/8/2 追記)
@nagtkk さんのコメントで、プレースホルダ(?
)をつかってsql文に値をセットするやり方を教えてもらいました!
実際に試してみたらしっかりと動作し、またソースも見やすく簡単に実装できたためこちらの手法を推奨します。
- 投稿日:2020-08-02T02:22:36+09:00
Javascript で天ざるそばをつくる
天ざるそばを作るとき、天ぷらを揚げてから、そばを茹でたら、天ぷらは冷えてしまう。そばを茹でてから天ぷらを揚げたら、そばにコシがなくなってしまう。できれば同時に作って、コシのあるそばでカラッとした天ぷらを食べたい。そんなわけでJavascriptでうまい天ざるそばを作る様子をシミュレートしてみよう。
ここで、天ざるそばのレシピを振り返ってみる。
A 天ぷらをつくる
(1) 素材準備 (5秒)
(2) 揚げる (10秒)B そばを茹でる
(1) はかる (2秒)
(2) ゆでる (7秒)
(3) あらう (3秒)C A,Bをもりつける (5秒)
手順としては、A->B->Cではなく、 (A|B)->C でなければ、いけない。
カッコ内は、仮の調理時間。
念のために、絵で確認しておこう。
準備
このシミュレーションでは、揚げたり、ゆでたりするそれぞれの作業を始めたときに「〜開始」、その作業が終わったときに「〜完了」と報告するようにしたい。
料理作業を次のように定義することで、要件を満たす。
まずはじめに、天ぷらをつくるA-(1)を定義する。A-(1)function prepare(resolve, cooking_time){ console.log("てんぷら素材準備開始"); setTimeout(()=>resolve("てんぷら素材準備完了+cooking_time"),cooking_time*1000) } function prepare_tempra(sec){return new Promise((resolve)=>{prepare(resolve,sec)});} prepare_tempra(5).then(r=>console.log(r))これを実行すれば、次のように出力される。
"てんぷら素材準備開始" <- 実行直後に表示される "てんぷら素材準備完了5" <- 5秒後に表示される次に、天ぷらをつくるA-(2)を定義する。
A-(2)function fry(resolve, cooking_time){ console.log("てんぷら揚げ開始"); setTimeout(()=>resolve("てんぷら完了"+cooking_time),cooking_time*1000) } function fry_tempra(sec){return new Promise((resolve)=>{fry(resolve,sec)});} fry_tempra(10).then(r=>console.log(r))これを実行すれば、次のように出力される。
"てんぷら揚げ開始" <- 実行直後に表示される "てんぷら完了10" <- 10秒後に表示されるこれらを順番に実行すれば、天ぷらが完成する。
Aconst tempra = async () => { await prepare_tempra(5).then(r=>console.log(r)) await fry_tempra(10).then(r=>console.log(r)) }これを実行すると、次のように出力される。
"てんぷら素材準備開始" <- 実行直後に表示される "てんぷら素材準備完了5" <- 5秒後に表示される "てんぷら揚げ開始" "てんぷら完了10" <- 15秒後に表示されるひとまず、天ぷら手順は、OKだ。
同様にして、そば手順を定義していく。Bfunction measure(resolve, cooking_time){ console.log("そばをはかる"); setTimeout(()=>resolve("そば計量完了"+cooking_time),cooking_time*1000) } function boil(resolve, cooking_time){ console.log("そばをゆでる"); setTimeout(()=>resolve("そばゆで完了"+cooking_time),cooking_time*1000) } function wash(resolve, cooking_time){ console.log("そばをあらう"); setTimeout(()=>resolve("そばあらい完了"+cooking_time),cooking_time*1000) } function measure_soba(sec){return new Promise((resolve)=>{measure(resolve,sec)});} function boil_soba(sec){return new Promise((resolve)=>{boil(resolve,sec)});} function wash_soba(sec){return new Promise((resolve)=>{wash(resolve,sec)});} const soba = async () => { await measure_soba(2).then(r=>console.log(r)) await boil_soba(7) .then(r=>console.log(r)) await wash_soba(3) .then(r=>console.log(r)) }かなり、冗長ではあるが、ひとまず、A、Bが定義できた。
いよいよA、Bを同時並行で実行させたい。それで、両方とも終了したときに、C:もりつけを実行したい。
さて、どうするか?A、Bを同時並行で実行させる部分は、次のように定義できる。
(A|B)const tempra_and_soba = async () =>{ let t = tempra() let s = soba() await t await s }これが終了した後に、もりつけを実行する。
(A|B)->Cfunction serve(resolve, cooking_time){ console.log("もりつける"); setTimeout(()=>resolve("天ざるそばもりつけ完了"+cooking_time),cooking_time*1000) } function serve_temzarusoba(sec){return new Promise((resolve)=>{serve(resolve,sec)});} const temprasoba = async () => { await tempra_and_soba() await serve_temzarusoba(5) .then(r=>console.log(r)) }実行結果
"そば計量完了2" "てんぷら素材準備完了5" "そばゆで完了7" "そばあらい完了3" "てんぷら完了10" "天ざるそばもりつけ完了5"これで、うまい天ざるが食べられる。めでたし。
おまけ
お蕎麦屋さんを作ってみました。
お客さんが食べる時間などは省略。
sobaya.jsfunction prepare(resolve, cooking_time){ /*console.log("てんぷら素材準備開始");*/ setTimeout(()=>resolve("てんぷら素材準備完了"+cooking_time),cooking_time*1000) } function fry(resolve, cooking_time){ /* console.log("あげる") */; setTimeout(()=>resolve("てんぷら完了"+cooking_time),cooking_time*1000) } function measure(resolve, cooking_time){ /* console.log("そばをはかる") */; setTimeout(()=>resolve("そば計量完了"+cooking_time),cooking_time*1000) } function boil(resolve, cooking_time){ /* console.log("そばをゆでる") */; setTimeout(()=>resolve("そばゆで完了"+cooking_time),cooking_time*1000) } function wash(resolve, cooking_time){ /* console.log("そばをあらう") */; setTimeout(()=>resolve("そばあらい完了"+cooking_time),cooking_time*1000) } function serve(resolve, cooking_time){ /* console.log("もりつける") */; setTimeout(()=>resolve("もりつけ完了"+cooking_time),cooking_time*1000) } function open_shop(resolve, reject, sales_time){ /* console.log("開店") */ setTimeout(()=>{if (this.order==0) {resolve("お店終了"+sales_time)} else {reject("まだお客さんいるけど、お店終了"+sales_time)}},sales_time*1000) } function open_sobaya(sec){return new Promise((resolve,reject)=>{open_shop(resolve,reject,sec)});} function prepare_tempra(sec){return new Promise((resolve)=>{prepare(resolve,sec)});} function fry_tempra(sec){return new Promise((resolve)=>{fry(resolve,sec)});} function measure_soba(sec){return new Promise((resolve)=>{measure(resolve,sec)});} function boil_soba(sec){return new Promise((resolve)=>{boil(resolve,sec)});} function wash_soba(sec){return new Promise((resolve)=>{wash(resolve,sec)});} function serve_soba(sec){return new Promise((resolve)=>{serve(resolve,sec)});} const tempra = async () => { await prepare_tempra(5).then(r=>console.log(r)) await fry_tempra(10).then(r=>console.log(r)) } const soba = async () => { await measure_soba(2).then(r=>console.log(r)) await boil_soba(7) .then(r=>console.log(r)) await wash_soba(3) .then(r=>console.log(r)) } const tempra_and_soba = async () =>{ let t = tempra() let s = soba() await t await s } const temprasoba = async () => { await tempra_and_soba() await serve_soba(5) .then(r=>console.log(r)) } const zarusoba = async () => { await soba() await serve_soba(5) .then(r=>console.log(r)) } const sobaya = async () =>{ console.log("開店") let shop = open_sobaya(90).then(r=>console.log(r)).catch(r=>console.log(r)) console.log("一人目") order=1 await temprasoba() order=0 console.log("二人目") order=1 await zarusoba() order=0 console.log("三人目") order=1 await temprasoba() order=0 } var order=0 sobaya()実行結果。
"開店" "一人目" "そば計量完了2" "てんぷら素材準備完了5" "そばゆで完了7" "そばあらい完了3" "てんぷら完了10" "もりつけ完了5" "二人目" "そば計量完了2" "そばゆで完了7" "そばあらい完了3" "もりつけ完了5" "三人目" "そば計量完了2" "てんぷら素材準備完了5" "そばゆで完了7" "そばあらい完了3" "てんぷら完了10" "もりつけ完了5" "お店終了90"以上、Promiseを使った動作サンプルでした。
- 投稿日:2020-08-02T01:35:52+09:00
JavaScript: 一見メモ化しにくいような数列もメモ化できるかも? たとえば素数とか
JavaScript: クロージャーでメモ化してくれる関数を作ってみたという記事を以前書きました。
前記事のまとめ : 漸化式でかけるような数列は、汎用のメモ化関数でメモ化できる。
例えば:
//フィボナッチ数列 const fibo = n => (n === 0)? 0 : (n === 1)? 1 : fibo(n-1) + fibo(n-2) //階乗 const facto = n => (n === 0)? 1 : (n === 1)? 1 : n * facto(n-1)は、汎用のメモ化関数 memoizer を使って:
//フィボナッチ数列 const fibo = memoizer( [0,1] )( a => n => a(n-1) + a(n-2) ) //階乗 const facto = memoizer( [1,1] )( a => n => n * a(n-1) )とすると、計算した値をメモ化することで高速で計算できる、ということでした。
でも...ぶっちゃけそんなに使わなくね?
わあ、めっちゃ便利!
と当時は思ったのですが、 んじゃあそれから今まで、業務や個人的にでも使ったか?といわれると、全然使ってません。
考えるに理由はふたつ。
- メモ化が必要なケースになかなか出会わない
- 単純な漸化式で表わされるような数式ってなかなか思いつかない
1はまあしょうがないとして、2のほうはもうちょっと何とかならないかなあ、と思ったわけです。
数学とか得意な人は使いまくれるのでしょうが、漸化式といっても知ってるのはここにあるフィボナッチと階乗くらいで、あと何にも思いつかない...漸化式で表わせないときは、便利な汎用のmemoizer を使うのをあきらめて個別にメモ化関数を書いていたんだけど、気がついたら同じようなことをしてるし、
あれ? これって思いこみ?漸化式でなくてもよくね?
というのが、この記事の主旨です。今のところ予想です。
例えば 素数列。
面倒くさそうで漸化式で表わせなさそうなのということで選びました。
漸化式では表わせないようですが、今まで求めた数列から次の値が計算できる、と考えれば望みはありそうです。
できあがったやつはそれなりに実用になったほうがいいので:
- 素数かどうかの調べ方 : 試し割り
- あらかじめ2と3の倍数を除いた素数候補から
- その数の平方根以下の素数で割って試す
ということでやってみます。
こんな風にしたい:
// 素数列の初期数列。とりあえず最初のみっつだけ。 const primeInits = [2, 3, 5] // 漸化式っぽいもの。 // 素数列 a の n 番目の素数は、 // n-1 番目の素数 から作られる素数候補 を順に試して、 // 試し割りで最初に割り切れなかったやつ、という意味。 const primeRule = a => n => { for (const nominee of nomineeG( a(n - 1) ) ) { if ( didPassTrialDivision( a )( nominee ) ) return nominee } } // 素数のメモ化関数を作る // primeAt(n)で 素数列の n 番目の素数を返します。 const primeAt = memoizer( primeInits )( primeRule )こんな風になればいいなと。今のところ希望です。
以下で、未定義の、まだふわっとしているところをちゃんと詰めていきます。
素数候補を返すジェネレータ
引数の 素数 aPrime より大きくて、2の倍数でも3の倍数でもないものが素数候補です。
const nomineeG = aPrime => function*(){ let m = Math.ceil( aPrime / 6 ) * 6 if ( aPrime === m - 5 ) yield m - 1 while (true) { yield m + 1 yield m + 5 m = m + 6 } }()試し割りで割り切れない(=素数)かどうかを返す関数
引数に素数列 a と 素数候補 nominee をとって、nomineeの平方根以下の a の要素で試し割りして、割り切れちゃったら偽、割り切れなかったら真を返します。
const didPassTrialDivision = a => nominee => { const end = Math.sqrt( nominee ) for (let i = 0; a(i) <= end; i++){ if (nominee % a(i) === 0) return false } return true }さあこれで実装は終ったはずです。ちゃんと動くかな?
使ってみる
とりあえず、30個の素数列を求めてみました。
[...Array(30).keys()].map(primeAt) /* [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113 ] */ちゃんと動いているようです。やった!
ということでこの例から言えること...
今回のまとめ
漸化式で表わせない、表わしにくいものでも、前に求めた数列の値から次の値が求められるのなら、汎用のメモ化関数でのメモ化はできそうです。
今回使ったコードです。まとめて。
// 汎用のメモ化関数 const memoizer = inits => rule =>{ const memo = [...inits] const a = n => { if(n >= memo.length) { for(let i = memo.length; i <= n; i++) { memo[i] = rule( a )( i ) } } return memo[n] } return a } // memoizer を特化して、素数列のメモ化関数を作る const primeInits = [2, 3, 5] const primeRule = a => n => { for (const nominee of nomineeG( a(n - 1) ) ) { if ( didPassTrialDivision( a )( nominee ) ) return nominee } } const primeAt = memoizer( primeInits )( primeRule ) // 素数列のメモ化関数のための補助的な関数 const nomineeG = aPrime => function*(){ let m = Math.ceil( aPrime / 6 ) * 6 if ( aPrime === m - 5 ) yield m - 1 while (true) { yield m + 1 yield m + 5 m = m + 6 } }() const didPassTrialDivision = a => nominee => { const end = Math.sqrt( nominee ) for (let i = 0; a(i) <= end; i++){ if (nominee % a(i) === 0) return false } return true } // 使ってみる [...Array(30).keys()].map(primeAt)