JavaScriptの注意点とTipsのまとめ



## **1. ==と===**

```javascript
1 == '1'; //true
1 === '1'; //false

true == '1'; //true
false == 0; //true
true === 1; //false

null == undefined; //true
null === undefined; //false
```

JavaScriptでは`==`を使う場合、型変換される。
詳細：https://qiita.com/zawascript/items/a25eaf7a222ac3671275

**基本的には、`===`を使う。**


## **2. 変数**

**var**

```javascript
var a = 1;
b = 1;
```

- ES5以前から唯一あった変数宣言。
- 変数宣言なしで変数を表記した場合は自動的にvarになる。(関数内だとグローバル変数)
  (strictモードではエラー)
- 再宣言が可能。
- 巻き上げが起こる

```javascript
a = 1;
var a;
```

とした場合ブラウザは

```javascript
var a;
a = 1;
```

と見なす。

**varのスコープ(関数スコープ)**
関数スコープのため、意図しない上書きの可能性がある。

```javascript
var a = 3;
(() => {
  //巻き上げによりここにvar a;がくるとみなされる
  console.log(a); //undefined(7行目がなければ3)
  a = 2; //ローカル変数aを更新(7行目がなければグローバル変数aを上書き)
  b = 50; //グローバル変数bを宣言
  var a;
  var c = 100; //ローカル変数cを宣言
  console.log(c); //100
})();
(() => {
  var a = 'hi'; //ローカル変数aを宣言
  console.log(a); //hi
  console.log(c); //Error: c is not defined(実際には処理はここで止る)
})();
console.log(a); //3(7行目がなければ2)
console.log(b); //50
console.log(c); //Error: c is not defined
```

**let**

```javascript
let a = 1;
let a = 2; //Error: 'a' has already been declared
```

- ES6で登場
- 再宣言が不可

**letのスコープ(ブロックスコープ)**
ブロックとは`{ }`で囲まれた部分

```javascript
(() => {
  let a = 1;
  var b = 1;
  {//ブロック
    let a = 2;
    var b = 2;//上書き
  }
  console.log(a,b); //1 2
})();
```

**const**

```javascript
const a = 1;
a = 2; //Error: 'a' has already been declared
const a = 2; //Error: 'a' has already been declared
```

- ES6で登場
- ブロックスコープ
- 再宣言と再代入が不可

```javascript
const a = {
  first : 100
};
a.second = 200;
const b = {};
b.first = 100;
b.second = 200;
delete b['first'];
b = {}; //Error: Assignment to constant variable
```

ただし、オブジェクトのプロパティは再代入可能なので注意

**基本的には、letとconstを使う。**

```javascript
for (let i = 0; i < 5; i++); //forでもletを使う
```

## **3. script要素の位置**

```html
<script type="module" src="module.js"></script>
<script type="text/javascript" src="main.js"></script>
<script>//typeを省略した場合、自動的にtype="text/javascript"にな
  console.log(1);
</script>
```

script要素が複数ある場合、先頭に書いたものから読み込まれる
→ライブラリはhead要素内で読み込むことが多い

HTMLは、1行目から順々に読み込まれるのでscript要素をどこに置くかは重要
DOMを読み込まれる前にアクセスしようとするとエラーになる

```html
<body>
  <script>
    document.getElementById('header').innerText = 'See you';
    //Cannot set property 'innerText' of null
  </script>
  <h1 id='header'>Hello</h1>
  <p>Welcom to World Wide Web.</p>
</body>
```

回避する方として

- script要素をbody要素の末尾にもってくる

```html
<body>
  <h1 id='header'>Hello</h1>
  <p>Welcom to World Wide Web.</p>
  <script>
    document.getElementById('header').innerText = 'See you';
  </script>
</body>
```

- JavascriptでDOMの読み込みを待つ

```html
<body>
  <script>
    window.addEventListener("DOMContentLoaded",() => {
      document.getElementById('header').innerText = 'See you';
    });
  </script>
  <h1 id='header'>Hello</h1>
  <p>Welcom to World Wide Web.</p>
</body>
```

ただし、この場合script要素を読み込んだ後にDOMを読み込むことには変わりないので、scriptの読み込み時間分DOMの表示が遅くなる
→defer属性を使う

```html
<body>
  <script　src="main.js" defer></script>
  <h1 id="header">Hello</h1>
  <p>Welcom to World Wide Web.</p>
</body> 
```

main.js

```javascript
window.addEventListener("DOMContentLoaded",() => {
  document.getElementById('header').innerText = 'See you';
});
```

defer属性を使うとJavaScriptの実行をHTMLのパース完了後に行うので多少高速化する
ただし、

- 外部ファイルしか使えない
- パース完了後(DOMContentLoadedの直前)なのでDOMContentLoadedは必要

詳細：https://qiita.com/phanect/items/82c85ea4b8f9c373d684

**基本的には、script要素をbody要素の末尾にもってくる**

追記：`type="module"`を使えば、インラインでもDOM構築後に実行する
http://var.blog.jp/archives/77519253.html

## **4. コメント**

```javascript
//コメント
/*
複数行コメント
*/
/**
 * ドキュメンテーションコメント
 */
```

ドキュメンテーションコメントについてはこちらを参照のこと
https://ics.media/entry/6789

`/* */`の入れ子コメントはエラーになる
![](https://d2mxuefqeaa7sj.cloudfront.net/s_A91AE01CBDD7D4A7445E42508D1C9BF968D6DBFB8952E634865EF5B31A5FA67C_1511005321393_image.png)
![](https://d2mxuefqeaa7sj.cloudfront.net/s_A91AE01CBDD7D4A7445E42508D1C9BF968D6DBFB8952E634865EF5B31A5FA67C_1511005357279_image.png)

**基本的には、`//`でコメントする。**


## **5. strictモードを使う**
<script>
  'use strict';
</script>

strictモードにすることでより安全なコードを書けるようになる。
詳細：https://developer.mozilla.org/ja/docs/Web/JavaScript/Strict_mode


## **6. HTML要素へのアクセス**

**getElementBy系**

```javascript
document.getElementById('ID_NAME');
document.getElementsByClassName('CLASS_NAME');
document.getElementsByTagName('div');
```

**querySelector**

```javascript
document.querySelector('#ID_NAME');
document.querySelectorAll('.CLASS_NAME');
document.querySelectorAll('div');
```

このコードは上記のgetElementBy系のコードと同じ値を返す。

querySelectorはCSSのセレクタと同じ表記でDOMの操作が可能

```javascript
document.querySelectorAll('div > .CLASS_NAME');
```

これと同じ処理はgetElementBy系では書けない？？

また、以下のコードはそれぞれ同じ値を返す

```javascript
document.querySelector('.CLASS_NAME');
document.getElementsByClassName('CLASS_NAME')[0];
```

querySelectorの方が可読性が高く便利だが、getElementBy系の方が速い
詳細：https://developer.mozilla.org/ja/docs/Web/API/Document/querySelector

**基本的には、querySelectorを使う。**

追記：`querySelectorAll()`は`NodeList`、`getElementsByTagName()/getElementsByClassName`は`HTMLCollection`を返し、DOMが操作されたときの結果が静的/動的という違いがある。
参考：http://ginpen.com/2015/12/22/nodelist-vs-htmlcollection/

## **7. 関数**

以下のコードはそれぞれ同じ動作をする(functionの場合は巻き上げが起こる)

```javascript
//functionによる関数宣言
function test(){
  console.log(1);
}
//関数式
const test = function(){
  console.log(1);
};
//アロー関数
const test = () => console.log(1);
```

アロー関数は関数式の簡易表記(厳密には異なる)
詳細：https://qiita.com/mejileben/items/69e5facdb60781927929
※関数式の値のことを関数リテラルと呼ぶ
詳細：http://amimimi.hatenablog.com/entry/2013/11/16/174019

functionによる関数宣言は、意図せぬ上書きの危険がある

```javascript
function test(){
  console.log(1);
}
test(); //2
function test(){
  console.log(2);
}
test(); //2
```

関数式を使うと関数の上書きを防げる

```javascript
const test = function(){
 console.log(1);
}
test(); //1
const test = () => console.log(1); //Error: test has already been declared 
//実際には処理はここで止る
test(); //1
```

アロー関数は1行の時returnを省略できる

```javascript
const add = (x, y) => x + y;
console.log(add(1, 2)); //3
```

**基本的には、アロー関数を使う。**


## **8. 変数のスワップ**

従来の書き方

```javascript
var a = 1;
var b = 2;
var tmp;
tmp = a;
a = b;
b = tmp;
console.log(a, b); //2 1
```

モダンな書き方(分割代入)

```javascript
let [a, b] = [1, 2];
[a, b] = [b, a];
console.log(a, b); //2 1
```

詳細：https://qiita.com/amamamaou/items/1ec21316b8bf05ba9c34

**分割代入を使う。**


## **9. 文字列リテラル**

従来の書き方

```javascript
var tax = 1.08;
console.log('りんごが税抜き100円、税込みで' + 100 * tax + '円');
```

モダンな書き方(テンプレート文字列)

```javascript
const tax = 1.08;
console.log(`りんごが税抜き100円、税込みで${100 * tax}円`);
```

バッククォートで囲む。

ダブルクオーテーションをそのまま表記できる

```javascript
const str = `<div id="test"></div>`
```

詳細：https://qiita.com/kura07/items/c9fa858870ad56dfec12

タグ付きテンプレートリテラル

```javascript
const enc = str => encodeURIComponent(str);
console.log(enc`あいうえお`); //%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A
```

- 入力がテンプレート文字列
- 出力が文字列

のとき、関数を括弧を省略して呼べる

入力に`${}`を含む場合、第二引数を可変長にする(`${}`の中身がレンダリングされてやってくる
先頭に`${}`が来ている場合str2がstrより一つ短くなるので例外処理すること(5行目)

```javascript
const enc = (str, ...str2) => {
  let res = '';
  for (let i = 0; i < str.length; i++) {
    res += encodeURIComponent(str[i]);
    res += str2[i] ? encodeURIComponent(str2[i]) : '';
  }
  return res;
}
const dat = 'Hi!'
console.log(enc`${dat}あいうえお${1+1}かかいくけこ${dat}`);
```

ワンライナーにすると

```javascript
const enc = (str, ...str2) => str.map((v, i) => encodeURIComponent(v) + (str2[i] ? encodeURIComponent(str2[i]) : '')).join('');
```

**テンプレート文字列を使う**


## **10. [参考]配列、オブジェクトをfor...ofで回す**

**配列**

```javascript
const arr = [100, 200, 300];
for(const [index, value] of arr.entries()){
  console.log(index, value);
}
```

**オブジェクト**

```javascript
const obj = {
  first: 100,
  second: 200,
  third: 300
};
for(const [key, value] of Object.entries(obj)){
  console.log(key, value);
}
```

## **11. addEventListenerの第一引数**

```javascript
document.addEventListener('pointerdown', function);
```


pointer系を使うとクリックとタッチ両方対応
※現状clickでもタッチイベントは発行する
詳細：https://developers.google.com/web/fundamentals/design-and-ux/input/touch/?hl=ja#_5

~~**pointer系を使う。**~~

![](https://d2mxuefqeaa7sj.cloudfront.net/s_A91AE01CBDD7D4A7445E42508D1C9BF968D6DBFB8952E634865EF5B31A5FA67C_1539144706367_image.png)
https://caniuse.com/#feat=pointer
2018/10/10現在Safariは対応してないので、単純なクリックやタップなら’click’で
それ以上をやりたいときは両方書くしかないようです


## **12. 参照渡しと値渡し**

```javascript
const a = [1,2,3];
const b = a;
b[0] = 'あ';
console.log(a); //["あ", 2, 3]
console.log(b); //["あ", 2, 3]
```

配列とオブジェクトは参照渡しで、メモリ上のアドレスがコピーされるだけなので注意
詳細：https://qiita.com/migi/items/3417c2de685c368faab1

**配列の場合**

```javascript
const a = [1,2,3];
const b = [a[0],a[1],a[2]];
b[0] = 'あ';
console.log(a); //[1, 2, 3]
console.log(b); //["あ", 2, 3]
```

こうしてやるとa[0]を値渡しできるので、問題ない
ES6のスプレッド演算子を使えばシンプルに書ける

```javascript
const a = [1,2,3];
const b = [...a];
b[0] = 'あ';
console.log(a); //[1, 2, 3]
console.log(b); //["あ", 2, 3]
```

**オブジェクトの場合**

```javascript
const a = {
  one: 1,
  two: 2
};
const b = {...a};
```

**配列/オブジェクトの値渡しには、スプレッド演算子を使う**


## **13. [Tips]オブジェクトのプロパティに変数を使う**

```javascript
name = 'bob'
a = {
  name : 'boy'
}
console.log(a.bob); //undefined
console.log(a.name); //boy
```

nameのところにはbobが入ってほしい

```javascript
name = 'bob'
a = {
  [name] : 'boy'
}
console.log(a.bob); //boy
console.log(a.name); //undefined
```

ちなみに組み込みのメソッドでも使える

```javascript
console['log'](1); //1
```

## **14. [参考]短絡評価で条件分岐からの代入を短く書く**

**短絡評価**

```javascript
console.log(true || 1); //true
console.log(true && 'Hi' || 1); //Hi
console.log(1 || true); //1
console.log(false || 1); //1
```

||で区切って、trueならばその値をreturnする
詳細：https://qiita.com/Imamotty/items/bc659569239379dded55

**ifを無くす**
if

```javascript
const a = [1, -2, 3];
a.forEach((v, i) => {
  if(v >= 0) a[i] = v;
  else a[i] = -v;
});
console.log(a); //[1, 2, 3]
```

短絡評価

```javascript
const a = [1, -2, 3];
a.forEach((v, i) => {
  a[i] = v >= 0 && v || -v;
});
console.log(a); //[1, 2, 3]
```

ちなみに、この場合はmapとMath.abs()を使ったほうがシンプルだったりする

```javascript
const a = [1, -2, 3];
a = a.map(v => {
  return Math.abs(v);
});
console.log(a)
```

**switchを無くす**
switch

```javascript
const a = [2, 1, 2, 3, 1];
a.forEach((v, i) => {
  switch (v) {
case 1:
  a[i] = 'One';
  break;
case 2:
  a[i] = 'Two';
  break;
case 3:
  a[i] = 'Three';
  break;
  }
});
console.log(a); //["Two", "One", "Two", "Three", "One"]
```

短絡評価

```javascript
const a = [2, 1, 2, 3, 1];
a.forEach((v, i) => {
  a[i] = v == 1 && 'One' || v == 2 && 'Two' || v == 3 && 'Three';
});
console.log(a); //["Two", "One", "Two", "Three", "One"]
```

※短絡評価は、(`==`と同じように)型変換して評価するので、`||`の前にfalseやfalsyな値を返そうとするとうまく動かない。
例えば、正の数かどうか判定する関数を書くとき

```javascript
const f1 = n => n >= 0 && true || false;
const f2 = n => n < 0 && false || true;
f1(-1); //false
f2(-1); //true
```
`f2()`は常に`true`を返してしまう。
選択肢が2つの場合は、三項演算子のほうが安全

```javascript
const f2 = n  => n < 0 ? false : true;
f2(-1); //false
```

## **15. [Tips]文字列のHTMLをパースする**

DOMParserを使う

```javascript
const str = '<html><head><title>hello</title></head><body></body></html>';
const parser = new DOMParser();
const doc = parser.parseFromString(str, 'text/html');
console.log(doc.title); //hello
```

詳細：https://developer.mozilla.org/ja/docs/Web/API/DOMParser


## **16. 要素を追加する**

古い書き方(createElement)

```javascript
const element = document.createElement('div')
element.classList.add('test')
element.innerText = 'HelloWorld!';
document.body.appendChild(element);
```

モダンな書き方(insertAdjacentHTML + テンプレート文字列)

```javascript
const element = `<div class="test">Hello world!</div>`;
document.body.insertAdjacentHTML('afterend', element);
```

テンプレート文字列なら改行やダブルクオーテーションもOK

```javascript
const element = `
<div class="test">
Hello world!
</div>
`;
document.body.insertAdjacentHTML('afterend', element);
```

詳細：https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML

**insertAdjacentHTML + テンプレート文字列を使う**


## **17. [参考]Object.freezeで値の上書きを防ぐ**

constはインデックスやキーからアクセスすると値の上書きが可能

```javascript
const ary = [1, 2, 3];
const obj = {
  A: 1,
  B: 2,
  C: 3
};
ary[0] = 0;
obj.A = 0;
console.log(ary, obj); //[0, 2, 3] {A: 0, B: 2, C: 3}
```

これをObject.freezeで防ぐ

```javascript
const ary = Object.freeze([1, 2, 3]);
const obj = {
  A: 1,
  B: 2,
  C: 3
};
Object.freeze(obj);
ary[0] = 0;
obj.A = 0;
console.log(ary, obj); //[1, 2, 3] {A: 1, B: 2, C: 3}
```

ただし、1階層しか保護されないので二次元になると上書きできる

```javascript
const ary = Object.freeze([[1], 2, 3]);
const obj = {
  A: [1],
  B: 2,
  C: 3
};
Object.freeze(obj);
ary[0][0] = 0;
obj.A[0] = 0;
console.log(ary, obj); //[[0], 2, 3] {A: [0], B: 2, C: 3}
```

そのため、それぞれにObject.freezeする必要がある

```javascript
const ary = Object.freeze([Object.freeze([1]), 2, 3]);
const obj = Object.freeze({
  A: Object.freeze([1]),
  B: 2,
  C: 3
});
ary[0][0] = 0;
obj.A[0] = 0;
console.log(ary, obj); //[[1], 2, 3] {A: [1], B: 2, C: 3}
```

詳細：http://abouthiroppy.hatenablog.jp/entry/2017/12/05/100339

クラスにも使える

```javascript
const Dog = class {
  constructor(age, breed) {
    this.age = age;
    this.breed = breed;
    Object.freeze(this);
  }
};
const pochi = new Dog(2, 'Siberian Husky');
pochi.age = 5;
console.log(pochi.age); //2
```

## **18. [参考]forで複数変数を回す**
```javascript
for (let i = 0, j = 9; i <= 9; i++, j--) {
  console.log(i, j);
}
```

## **19. [Tips]引数のデフォルト値を指定する**

```javascript
const greet = (name = 'anonymous') => console.log(`Hello, ${name}`);
greet(); //Hello, anonymous
greet(undefined); //Hello, anonymous
greet(''); //Hello, 
greet(null); //Hello, null
greet('Bob'); //Hello, Bob
```

## **20. ifの中括弧省略**

```javascript
const [x, y] = [1, 2];
if (x === 1){
  if (y === 1){
    console.log('x === 1 && y === 2');
  }
}else{
    console.log('x !== 1');
}
```

このコードは何も出力しないが、中括弧省略すると

```javascript
const [x, y] = [1, 2];
if (x === 1)
  if (y === 1)
    console.log('x === 1 && y === 2');
else
  console.log('x !== 1');
```

出力

```
x !== 1
```

なぜこうなるかというと、中括弧省略のelseは直前のifにかかるため。
インデントを正しくすると

```javascript
const [x, y] = [1, 2];
if (x === 1)
  if (y === 1)
    console.log('x === 1 && y === 2');
  else
    console.log('x !== 1');
```

**基本的には、ifの中括弧は省略しない。**


## **21. [Tips]String()とtoString()の違い**

String()はGlobalオブジェクトで、toString()はオブジェクトごとに持っているメソッド

**Number.prototype.toString()**

```javascript
123.toString(); //Uncaught SyntaxError
(123).toString(); //"123"
```

数値から直接呼ぶ場合は、`()`でくくるhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/toString#.E4.BE.8B

数値のあとに` `(空白)か`.`をいれてるのでも良い

```javascript
123 .toString(); //"123"
123..toString(); //"123"
```

ただし以下はエラーになる

```javascript
1.23..toString(); //Uncaught SyntaxError
```

そもそも`.toString()`の`.`と小数点の`.`が区別できない
→整数の場合、明示的に`.0`を表記する
→0を略して`.`を追加する
という流れなので

```javascript
1.23.toString(); //1.23
```
でよい。
また、指数表記の場合、指数は小数点にならないので`.`を加えるとエラーになる

```javascript
123e-2..toString(); //Uncaught SyntaxError
123e-2.toString(); //"1.23"
```


**Array.prototype.toString()**

```javascript
[123].toString(); //"123"
```

**Object.prototype.toString()**

```javascript
({key:123}).toString(); //"[object Object]"
(new Object(123)).toString(); //"123"
```

**String()**

```javascript
String(123); //"123"
String([123]); //"123"
String({key:123}); //"[object Object]"
```

null/undefinedの扱い

```javascript
(null).toString(); //Uncaught TypeError
String(null); //"null"
```

String()はnull/undefinedでもエラーにならない
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String#%E4%BE%8B

null/undefinedをArrayオブジェクトに格納した場合もエラーにはならない

```javascript
(Array(null)).toString(); //""
[null].toString(); //""
```


## **22. isNaN()/isFinite()**

isNaN()にはGlobalオブジェクトの`isNan()`とNumberオブジェクトのメソッドとしての`Number.isNan()`があり挙動が違う。
`isNaN()`は引数が数値以外の場合、暗黙の型変換が行われる(文字列はNaNになる)

```javascript
isNaN('a'); //true
Number.isNaN('a'); //false
```

isFInite()も同様に

```javascript
isFinite('1'); //true
Number.isFinite('1'); //false
```

**基本的には、Number.isNan()/Number.isFinite()を使う。**


## **23. [Tips]encodeURI()とencodeURIComponent()とescape()の違い**

encode/decodeURI() → `;` `,` `/` `?` `:` `@` `&` `=` `+` `$`はエンコード/デコードされない
encode/decodeURIComponent() →  `;` `,` `/` `?` `:` `@` `&` `=` `+` `$`がエンコード/デコードされる
escape()/unescape() → ブラウザによって挙動が違う


## **24. [黒魔術]動的に変数、関数を生成する**

```javascript
eval('var a = 1'); //const,letは不可
eval('a = e => console.log(e)');
const a = new Function('e', 'console.log(e)'); //関数の引数、中身を動的生成可
```

※ハック以外にはオブジェクトやクラスを使う


## **25. [Tips]引数を必須にする**

```javascript
const required = () => new Error('Parameter requied');
const func = (a = required(), b = 0) => console.log(a, b);
func(); //Error: Parameter requied
func(1); //1 0
```

## **26. [Tips]可変長の引数**

```javascript
const func = (...input) => console.log(input);
func(1, 2, 3, ['a'], { b: 'c' }); //[1, 2, 3, ['a'], { b: 'c' }]
```

## **27. [Tips]名前付き引数**

分割代入を使う

```javascript
const func = ({ name, age }) => console.log(`Hi ${name}. You are ${age} y/o.`);
func({ name: 'Bob', age: NaN }); //Hi Bob. You are NaN y/o.
```

## **28. [Tips]constructorでの値の流し込みを楽にする**

いちいちthis.age = ageとか書きたくない。

```javascript
const Dog = class {
  constructor(age, breed) {
    Object.assign(this, { age, breed });
  }
};

const pochi = new Dog(2, 'Siberian Husky');
console.log(pochi.age); //2
```