JavaScript
es6

JavaScriptの注意点とTipsのまとめ

はじめに

JavaScript初心者によるメモです。
間違え等あればご指摘お願いします:bow:

1. ==と===

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

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

とした場合ブラウザは

var a;
a = 1;

と見なす。

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

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

let a = 1;
let a = 2; //Error: 'a' has already been declared
  • ES6で登場
  • 再宣言が不可

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

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

const

const a = 1;
a = 2; //Error: 'a' has already been declared
const a = 2; //Error: 'a' has already been declared
  • ES6で登場
  • ブロックスコープ
  • 再宣言と再代入が不可
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を使う。

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

3. script要素の位置

<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を読み込まれる前にアクセスしようとするとエラーになる

<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要素の末尾にもってくる
<body>
  <h1 id='header'>Hello</h1>
  <p>Welcom to World Wide Web.</p>
  <script>
    document.getElementById('header').innerText = 'See you';
  </script>
</body>
  • JavascriptでDOMの読み込みを待つ
<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属性を使う

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

main.js

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

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

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

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

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

4. コメント

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

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

/* */の入れ子コメントはエラーになる

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

5. strictモードを使う

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

6. HTML要素へのアクセス

getElementBy系

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

querySelector

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

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

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

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

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

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

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

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

基本的には、querySelectorを使う。

7. 関数

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

//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による関数宣言は、意図せぬ上書きの危険がある

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

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

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

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

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

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

8. 変数のスワップ

従来の書き方

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

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

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

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

分割代入を使う。

9. 文字列リテラル

従来の書き方

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

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

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

バッククォートで囲む。

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

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

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

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

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行目)

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}`);

ワンライナーにすると

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

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

10. [参考]forを短く書く

配列
→forEach(ただしbreakは使えない→someを使う)か、for of(値のみ)

const ary = [100, 200, 300];
//for
for (let i = 0; i < ary.length; i++) {
  console.log(i); //0 1 2
  console.log(ary[i]); //100 200 300
}
//forEach
ary.forEach(v, i) => {
  console.log(i); //0 1 2
  console.log(v); //100 200 300
};
//for of
for (let v of ary){
  console.log(v); //100 200 300
}

インデックスのみのときはfor in使える

オブジェクト
→for in(ただしプロトタイプチェーンも参照する)か、Object.entries()

const obj = {
  first: 100,
  second: 200,
  third: 300
};
//for
const obj_key = Object.keys(obj);
for (let i = 0; i < obj_key.length; i++) {
  console.log(obj_key[i]); //first second third
  console.log(obj[obj_key[i]]); //100 200 300
}
//for in
for (let i in obj) {
  console.log(i); //first second third
  console.log(obj[i]); //100 200 300
}
//Object.values()
Object.entries(obj).forEach(v => {
  console.log(v[0]); //first second third
  console.log(v[1]); //100 200 300
});

値だけほしいとき→Object.value()
プロパティだけほしいとき→Object.keys()

単純なforが一番高速なので、無難ではある。

11. addEventListenerの第一引数

document.addEventListener('pointerdown', function);

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

pointer系を使う。


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

12. 参照渡しと値渡し

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

配列の場合

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のスプレッド演算子を使えばシンプルに書ける

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

オブジェクトの場合

const a = {
  one: 1,
  two: 2
};
const b = Object.assign({}, a);

値渡しする場合、配列はスプレッド演算子、オブジェクトはObject.assignを使う

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

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

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

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

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

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

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

短絡評価

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

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]

短絡評価

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

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

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

switchを無くす
switch

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"]

短絡評価

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な値を返そうとするとうまく動かない。
例えば、正の数かどうか判定する関数を書くとき

const f1 = n => n >= 0 && true || false;
const f2 = n => n < 0 && false || true;
f1(-1); //false
f2(-1); //true

f2()は常にtrueを返してしまう。
選択肢が2つの場合は、三項演算子のほうが安全

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

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

DOMParserを使う

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)

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

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

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

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

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はインデックスやキーからアクセスすると値の上書きが可能

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で防ぐ

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階層しか保護されないので二次元になると上書きできる

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する必要がある

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

クラスにも使える

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で複数変数を回す

for (let i = 0, j = 9; i <= 9; i++, j--) {
  console.log(i, j);
}

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

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の中括弧省略

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

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

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にかかるため。
インデントを正しくすると

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()

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

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

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

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

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

1.23..toString(); //Uncaught SyntaxError

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

1.23.toString(); //1.23

でよい。
また、指数表記の場合、指数は小数点にならないので.を加えるとエラーになる

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

Array.prototype.toString()

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

Object.prototype.toString()

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

String()

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

null/undefinedの扱い

(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オブジェクトに格納した場合もエラーにはならない

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

22. isNaN()/isFinite()

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

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

isFInite()も同様に

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

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

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

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

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

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

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

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

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]可変長の引数

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

27. [Tips]名前付き引数

分割代入を使う

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とか書きたくない。

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

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