26
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptの注意点とTipsのまとめ

Last updated at Posted at 2018-10-10

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
26
23
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?