LoginSignup
7
14

More than 5 years have passed since last update.

酒飲みがJavaScriptで個人的に気をつけている所

Posted at

はじめに

友人がJavaScript書いてて辛そう楽しそうなので、私が書いてて気をつけている点とかその他引っかかりそうな所をまとめてみた
また実行環境は Windows10 / GoogleChrome 最新版

バージョン

前提としてJavaScriptのバージョンをどうするか
ES6ではクラス構文とかが追加されてだいぶ書きやすくなった
一方ES5までしか対応していない環境もある

とはいうものの、メジャーなブラウザはほぼES6に対応している
またどうしてもES5が必要な場合でも、Babel先生に頼めばES5に落としてくれる

Babel

という訳でES5で書く必要はほぼ無いと思う
以降ES6を前提に書いていく

変数宣言

ES5まではvarしか無かった
ES6でconstとletが登場したので、これらの違いについて見てみる


function sake() {
    sake0 = 'Shinomine';
    var sake1 = 'Kujira';
}

sake();
console.log(sake0); // Shinomine
console.log(sake1); // sake1 is not defined

varが無いとグローバルスコープになる
varを付けるとスコープは関数内

次にletについて


if (true) {
    var sake2 = 'Tsukasabotan';
}

if (true) {
    let sake3 = 'Senchuhassaku';
}

console.log(sake2); // Tsukasabotan
console.log(sake3); // sake3 is not defined

varでもifは通り抜けるが、letはifの中にスコープが収まっている
もう一つvarとletの違いがある


var sake4 = 'Harushika';
var sake4 = 'Akishika';
console.log(sake4); // Akishika

let sake5 = 'Katanosakura';
let sake5 = 'Michizakura'; // Identifier 'sake5' has already been declared

varは上書き宣言できるがletはできない
なおletとconstは値が上書きできるかどうかの違いだけ
以上より、letとconstだけで済ませるべきだと思う

比較

等価演算子には==と===の二つがある
後者のほうが厳密であるが、実際どう違うか少し見てみる


1 == 1 // true
1 == '1' // true
1 == true // true

こちらは大体等しければtrueを返す


1 === 1 // true
1 === '1' // false
1 === true // false

こちらは厳密に一致した場合だけtrueを返す

前者は左右を同じ型に変換してから等しいかどうかを調べているらしい
後者なら形が違った時点でfalseを返す
なお、否定の場合は!=と!==のようになる

気をつけたい比較の一つにNaNがある


NaN === NaN // false

NaN同士の比較はfalseになる
この場合Number.isNaNを使う


Number.isNaN(NaN) // true

なお、Number.isNaNはES6から実装された
それ以前はisNaNを使用する

が、これがまた問題がある


Number.isNaN(null) // false
Number.isNaN(undefined) // false

isNaN(null) // false
isNaN(undefined) // true

isNaN(undefined)がtrueを返しているので注意

nullとundefined

似ているようで違う
まず比較すると以下のようになる


null == undefined // true
null === undefined // false

やはり似て非なるらしい
では何が違うのかを見ていこう
まずはundefinedから


let sake;
console.log(sake); // undefined
sake = 'Akabu';
console.log(sake); // Akabu
console.log(sake.alcohol); // undefined

宣言だけされた変数にはundefinedが入っている
同じく初期化していないプロパティもundefined


function manotsuru() { }
console.log(manotsuru());

何もreturnしない関数の場合、戻り値がundefinedになる
returnだけ書いた場合も同様

このように、undefinedとはまだ作られていない状態、つまり未定義である

一方のnullだが、これは明示的にnullを使用しないと発生しない


let sake = 'Daimon';
console.log(sake); // Daimon
sake = null;
console.log(sake); // null

一度定義したものを空っぽにするときに使う
ニュアンス的な違いになるが、nullは「空っぽ」、undefinedは「未定義」と考えると良いと思う

ではこれらの使い分けをどうするかだが…実際の所あまり違いはないと思う
ただし、nullは明示的な空であるのに対して、undefinedは自然発生的な空とも取れる
そのため、変数を空にする目的でundefinedを代入すると以下のようになる


let sake0 = 'Takesuzume';
let sake1 = 'Gassan';

// ---- この間非常に長いコード ----

sake0 = undefined;
sake1 = null;

// ---- この間非常に長いコード ----

console.log(sake0); // undefined
console.log(sake1); // null

前者は空なのだが、これが単なる未定義なのか、使用した後に消されたのかがわからない
後者は明示的に空になったのがわかる
以上より、明示的に空にするならnullを使用するべきで、undefinedを代入するのは避けたほうが良いと思う

同じことは関数にも言える


function okura(canGet) {
    if (canGet) return 'Okura';
    return undefined;
}

function sennorikyu(canGet) {
    if (canGet) return 'Sennorikyu';
    return null;
}

console.log(okura(false)); // undefined
console.log(sennorikyu(false)); // null

こちらも後者なら明示的に空だとわかるだろう

名前空間

javascript に名前空間なんてものはない
だが、それ風味なものは実装できる
一つだけグローバルなオブジェクトを用意しておいて、残りはそいつのプロパティとして実装する方法である


Sake = { };

Sake.Dewazakura = class { } // OK
Sake.Kokushimusou = class { } // OK

なお、名前空間自体が競合した場合を考えて以下のように書くのが良いらしい


var Sake = Sake || { };

この場合はvarでないとエラーになるようだ

クラス宣言

クラスの宣言方法は二種類ある


class Ippongi { }
Suginishiki = class { }

これだけならほとんど違いはない
しかし、先述の名前空間の兼ね合いで、後者の書き方をしたいと思う


var Sake = Sake || { };

class Sake.Bijofu { } // Unexpected token .
Sake.Urakasumi = class { } // OK

もちろん関数も同じ


function Sake.Umenoyado() { } // Unexpected token .
Sake.Shirakiku = function() { } // OK

関数を返す


Box = class {
    constructor() {
        this.sake = ['Reisen', 'Suigei', 'Tengumai'];
    }

    show() {
        this.sake.forEach(a => console.log(a));
    }

    getShow0() {
        return this.show;
    }

    getShow1() {
        return () => this.show();
    }

    getShow2() {
        return function() { this.show() };
    }
}

const box = new Box();
box.show(); // Reisen Suigei Tengumai

getShowを三つ用意した
一つ目は普通にメソッドを返す、二つ目は無名関数で包んで返す
三つ目は後述する


const boxShow0 = box.getShow0();
boxShow0(); // Cannot read property 'sake' of undefined

メソッドをそのまま返すと実行できない


const boxShow1 = box.getShow1();
boxShow1(); // Reisen Suigei Tengumai

こちらは通る
メソッドを返すだけでは実行できず、メソッドを実行する関数を返す必要がある

しかしこれが可能なのはアロー関数を使用した場合に限る
ここで、アロー関数ではなく普通の関数宣言した場合の三つ目のshowを見てみる


const boxShow2 = box.getShow2();
boxShow2(); // Cannot read property 'show' of undefined

これはアロー関数の特性の一つで、アロー関数は宣言した時点でthisの値が確定する
逆に言うと、getShow0やgetShow2のthisは宣言した時点で確定していない
これらは呼び出された時点でその左辺を見て決定されるらしい
つまりboxShow0やboxShow2は左辺が無いためthisがおかしくなったようだ(この時のthisはグローバルオブジェクトを指している…はず あまり確かでない)

あとがき 前編

まだまだ書ききれないほど罠が散らばっているが、今回はここらへんで終わりとしておく
全部書いているとあまりにも多すぎるので、特に引っかかる可能性が高そうなポイントに絞った

なお、敢えて最後に説明すると、私見が混ざっている文章の語尾には「思う」と付けた
あくまで私はこう思ったという部分である
正直それ以外の部分はググればいくらでも出てくるし、この記事はその寄せ集めに過ぎない
だが、この「思う」部分は私にしか語れない
なので、「思う」の部分を取捨選択しながら読んでいただけるとこの記事の意味が出てくると思う

ちなみにこの説明を最後に持ってきたのは、一回目読んでる最中に余計な思考を入れて欲しくなかったからだ
読み終わった上で取り入れるかどうかもう一度考えてほしい

あとがき 後編

このソースコードはノンフィクションですが、実在する酒とは関係ありません
関係ありませんが、酒は美味いです

7
14
0

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
7
14