Edited at

JavaScriptの注意点とTipsのまとめ


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要素の末尾にもってくる

追記:type="module"を使えば、インラインでもDOM構築後に実行する

http://var.blog.jp/archives/77519253.html


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を使う。

追記:querySelectorAll()NodeListgetElementsByTagName()/getElementsByClassNameHTMLCollectionを返し、DOMが操作されたときの結果が静的/動的という違いがある。

参考:http://ginpen.com/2015/12/22/nodelist-vs-htmlcollection/


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(); //2
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...ofで回す

配列

const arr = [100, 200, 300];

for(const [index, value] of arr.entries()){
console.log(index, value);
}

オブジェクト

const obj = {

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


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 = {...a};

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


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