Edited at

JS困りごとメモ

どうももう直ぐ新卒三年目エンジニアです。

ちょうど仕事で生のJavaScriptを書くことになったのが1年半前です。

技術的なことはともかくとしても、だいぶ読める(慣れた)ようにはなりました。

実際にはjqueryでは書いてはいたので、全くのゼロベースからのスタートというわけではないのですが、正直書いていたとはいっても、それなりに慣れるまでには時間がかかりました。

そんなことで、躓いたり学びになったことを書きおろしながらJSライフを振り返り、若いうちに間違ってたら恥かいておこうと思います(備忘録込みなんで困るたびに追記予定)。


オブジェクトは参照渡し

JSに限った話じゃないですね。

知らなければ、躓くんじゃないでしょうか。実務に入る前にプログラミングの本を一冊も読まなかったのが効いてますね(読めば書いてあるので)。

私の場合は、オブジェクト(という概念を知った上で、実際には書いてました)を利用するような開発に携わったのはJSが先でしたので知った時には「あー、だから引数の無いslice()とかconcat()がちょろちょろいんのか」、「シャローコピーなるほどね(そんなわかってない)」って感じでした。


sample1.js

// 参照渡し

var a1 = [1, 2, 3, 4],
a2 = [];
a2 = a1;
a2[1] = 5;
console.log(a1);// [1, 5, 3, 4]
console.log(a2);// [1, 5, 3, 4]

// 値渡し
var a3 = [6, 7, 8, 9],
a4 = [];
a4 = a3.slice();
a4[1] = 10;
console.log(a3);// [6, 7, 8, 9]
console.log(a4);// [6, 10, 8, 9]

// 実際には上のようなやつじゃなく陥るのはこれじゃないかな
a1 = [];// 空にしてるわけじゃない、新しい参照先に向けてるだけ
console.log(a1)// []
console.log(a2)// [1, 5, 3, 4]



argumentsオブジェクトって何の役にたってるの?正直まだ実務で書く場面出会ってない気がする。

これってみなさんどういうタイミングで知るんですかね。

私みたいに、誰かのコード中で「なんぞ!?」ってなるってことなんでしょうか。

内容で言えば、「全ての関数で利用可能な変数で、引数の値をひとまとめにしたオブジェクト」ってことらしいです。どっかでそういってました。


sample2.js

function sample2(x, y, x1, y1, x2, y2) {

console.log(x, y, x1, y1, x2, y2);// 1, 2, 3, 4, 5, 6
for (var i = 0; i < arguments.length; i++) {
arguments[i] = Math.round(arguments[i]*10);
console.log(arguments[i]);// [10,20,30,40,50,60]
}
}

// 個人的にはこういうの便利そうに思う。使ったことないけど
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
function list() {
return slice(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// sample2中で
var args = [...arguments];// [1, 2, 3, 4, 5, 6] ※記載位置によっては10倍


MDNさんはすごいです、欲しいコードが書いてあること多いです。


挙動が違って困ったswitch

JSのswitch文は各caseでの値の比較が同値演算子と同じなので、型まで比較される。

defaultでの基本値(というかデフォルト値)を利用して開発してると、漏れがち、気づかないで爆死するので要注意(リファクタリングしたつもりが常にdefaultをとるバグを作り出し、こっそり直したのはいい思い出です)。

こちらは結構驚いたので、別記事でも書きましたので、割愛します。

PHPとJavaScriptのswitch文の違い

追加で突っ込むと型比較してほしいなら、以下で書くといいらしいです(どっかで見た)。


sample3.js

var obj = {"1": "1", "2"  : 1}

for (var key in obj) {
switch(true) {
case obj[key] === 1:
console.log("int");
break;
case obj[key] === "1":
console.log("str");
break;
default :
console.log("default");
break;
}
}


Array Like とかまぎらわしい、勘弁してくれ。

型がないからというより、きちんと型宣言を行うとか意識して使わないので、Arrayな感じのオブジェクトを扱ってる(この時はNodeList)というレベルの認識で開発して痛い目というか「ホワイっ!?」ってなりました。

具体的には、Array.prototype.map()を使おうとして、.map is not a function...

まじかよなんでないの、、、。

(なぜないのかを考えずに)for inでいいかとかやってました。


Array Likeの条件

1.プロパティ名が数字

2.lengthプロパティを持っている

先のargumentsオブジェクトとかも実はそう。


そういうのをcallしたりするのか!


sample4-1.js

Array.prototype.map.call(...);

[].map.call(...);

神かよって思いましたね。

正直call()とか使い道わかんねぇとかまじで思ってましたが、なるほど、こういうとこからなら(便利だし)理解できるわって感じでした。

とても奥深そうなので、まだまだ遠い道です。


applyも結構使った。

似たように謎いところのapply()に関しては、こんな感じの出会いでした。


sample4-2.js

var numberArray = [3, 6, 29, 2, 5, 34, 5, 15, 4];

Math.max.apply(null, numberArray);

ループさせなくてもapplyを使うことで一行で済む。

素晴らしいですね。知識が全てな感じがしてとてもかっこいいです。

ただ、注意事項的に引数上限が~とあり、できればループで分割してくれってことなので、用法容量は守らないといけないわけですね。


我らはネストと可読性とのはざまで戦い続けているんだ

操作性っていうのか自由度の高いアプリケーション開発にはつきものというのか、どうしても分岐とか増えちゃうんですよね。

あぁぁ...これ以上ネストさせたくない...やばい...ってことは往々にしてありました。


sample5-1.js

if (true) {

if (true) {
console.log('hogehoge');
}
}
if (true) {
if (false) {
console.log('hugahuga')
}
}

どうなんですかね。1つめのif文は通したいけど次はいやみたいなね。

そもそもなんでそうなるようにつくったとかそういうことは以下略なのですが、JSでこんな風にラベルが使えるとは思っていませんでしたので、勉強になる書き方でした(結局使ってはいないのですが)。


sample5-2.js

if (true)LABEL1: {

if (!true) break LABEL1;
console.log('hogehoge');
}
if (true) LABEL2: {
if (!false) break LABEL2;
console.log('hugahuga')
}

いやぁ~、一つネスト消えるだけですっきり(見せかけだけですが)。

よみにくいですね?


座標の一致ってどうするの?マウス操作で動かしたりしてたら絶対少数出るじゃん。

私がたまたま開発していたものが、座標だとかマトリクスだとかで計算処理が非常に多く、小数点の誤差にはとても苦労しました。

そもそもちゃんと積算除算して、なんでこんな小数点が出てくるんだとか思いました(IEEE754)。

なんだかんだ、座標一致とかで処理するケースは多いので、いろいろと値を持たせたりして分岐させたんですが、一番シンプルにできるなと思った一致処理はこれでした(実際には小数点以下無視してるので正しくはないのですが、この時はこれでうまくいきました)。


sample6.js

function comparePoint(point1, point2) {

var resultX = compareOneSidePoint(point1[0], point2[0]),
resultY = compareOneSidePoint(point1[1], point2[1]),
matchFlg = false;
if (resultX && resultY)
matchFlg = true;
return matchFlg;
}
function compareOneSidePoint(value1, value2) {
var resultValue = abs(value1 - value2),
matchFlg = false;
// 小数点部分を除去して考えるため1以下は一致とみなす
if (resultValue < 1)
matchFlg = true;
return matchFlg;
}
// このabsは結構いろいろなとこでこうだみたいなこと言ってたので持ってきました。
function abs(val) {
return val < 0 ? -val : val;
}

この小数点となる可能性のある座標がなぜかx,yプロパティとして文字列で格納されてたので、parseInt()してたせいでバグってた(切り捨てのため一致せず)みたいなので、ある意味一致の条件をゆるめてやった感じですね。


PHPよりは大きくなくてもそれでも速度に違いあり?なんだこれ。

PHPではループ文に長さのカウントを入れると、毎度計算して処理が遅くなるというのはご存知かなと思います(私はその時まで知りませんでした)。

当然私はJSばかり使うので、こっちはどうなんだろうって思ってやってみました。


sample7.js

let a = [...Array(1000).keys()];

let l = a.length;
console.time("loop2");
for (let j = 0; j < l; j++) {
console.log('');
}
console.timeEnd("loop2");// loop2:85.72607421875ms

console.time("loop1");
for (let i = 0; i < a.length; i++) {
console.log('');
}
console.timeEnd("loop1");// 96.4140625ms


このレベルならどっちを使っても同じでしょうかね。

でも比較的・基本的には事前にやっておくほうが早いみたいですね(たぶん)。


元々の仕様をちゃんと知れ。

JSに慣れてきたら、なんかXMLHttpRequestで非同期通信書いてみたい(他の人と違うことしたい)って思ったのって私だけじゃないはず。

XHRの基本的な書き方を知らずに、こうやれば動くみたいなお手本に従って書くと、そうではなかった場合に時間を無駄にするので、何をするにしても、仕様を見る、まずは。

どこかで聞いた話ですが、1にドキュメント、2にコードでそれでもだめなら作者に問い合わせる(twitterすげー)というのがライブラリとかで困った時の対応というかやり方とのこと。

ネットの情報は参考にしてもいいが、信用はするな(応用効かないその場合だけのコードの場合がある)。

話はずれましたが、何をやってしまったかと言いますと、

formDataオブジェクトの場合Content-Typeは記述しません。これはmultipart/form-dataというデフォルト値を持ってるので再度setRequestHeader()する必要ないということなのですが、そういう書き方でできるということを知ったため、formDataを使わないときにも記述せず、「あれ、、値取れない、、」というのをやってしまいました。


sample8-1.js

var form = new FormData();

form.append('file', file);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhttpreq.readyState == 4 && xhttpreq.status == 200) {
// ここにswitchでうまいことやってる人マジで天才かなと思いました。
}
};
xhr.open("POST", "url", true);
xhr.send(formdata);

そりゃ、送信データの解析方法渡してないんでから、取れないわな。


sample8-2.js

var xhr = new XMLHttpRequest();

xhr.open("POST", 'url', true);
// これを忘れると痛い目に合う。一度上で成功体験得てるエンジニアで知らずに書いてた私みたいなやつは。
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send('param=value');


違いがとかよくわからんわ

私自身の開発では、ほぼChrome対応だけ(当然他のブラウザでも動くようには作ってるつもりです)でしたので、時々対応してなくて、あれってなっちゃうんですよね。

今回はbeforeunloadイベントでの挙動の違いでちょっと困りました。

・returnValueは空文字がダメなので注意

・イベント発生にはクリック等の操作がいる

1つ目はともかく、2つ目はちょっと困りました(別にそれでOKだったのでことなきを得ましたが)。


終わりに

1年半に及ぶ初心者のJSライフは数ヶ月前に終わり、今では初心者Rubyエンジニアになりました。

もっと細かいところでのつまずきもいっぱいあったはず(毎日のように頭抱えながらやってた気がします)ですが、結局書く時になるとほとんど覚えてないものです。つまずきはもっとこまめにメモを取ろうと思いました。まあ、そもそもJSでのつまづきよりはコメントが日本語じゃなくて(ローマ字に近いけどたまにミミズが登場する言語)、助けてGoogle先生ってことの方が多かった気がします。続けていれば、すごい実力がつくというよりかは地道に値を確認して、いろいろ推理していく、諦めないみたいな心の強さが育つのかなと思います。いやー、それにしてもだいたいどこかで見たことありますね。

参考載せ忘れてるところ他にもある気がしますが、ご愛嬌。


参考

Array.prototype.slice()

Array.prototype.apply()

FormData は multipart/form-data で application/x-www-form-urlencoded は URLSearchParams

XMLHttpRequest についてのメモ

onbeforeunloadが変わってた