JS初心者がハマったところ


自分の復習も兼ねて、ハマったところや学んだことなどまとめてみました。

はじめに

(!)注意:ES2015以上前提で書いています

最近はBabalとかPolyfill使ってES2015以上の文法が使える環境で開発することが
多いと思うので大丈夫かなと思いますが、
何らかの理由でES2015以上で書けないという方にはすみません

そもそも ES2015 とは?という方は、以下の記事が参考になるかも

変数の宣言

ES2015から var に加えて新たに const let を使って変数を宣言できるようになりました。
それぞれの宣言方法の特徴をざっと挙げます。

  • var

    • 再宣言、再代入 可
    • 関数スコープ
    • 変数の巻き上げが起こる、参照可能
  • let

    • 再宣言 不可
    • ブロックスコープ
    • 変数の巻き上げが起こるが、参照不可能
  • const

    • 再宣言、再代入 不可
    • ブロックスコープ
    • 変数の巻き上げが起こるが、参照不可能
    • 配列やオブジェクトの場合は、中の要素やプロパティの書き換えは可能

基本的に、変数に何度も再代入する必要のある機会はほとんどないでしょう。
変数はできる限りスコープを小さくすることで可読性の向上やバグの抑制につながりますので、
for文のイテレータ変数(iとか)や、どうしても再代入が必要なときだけ let
それ以外は基本的に const を使うで問題ないと思います。

配列の繰り返し処理

JSには配列の繰り返し処理を行う構文がいくつか存在します。
通常の用途であれば for ... of を使うのがよさそうです。


const fruits = [
  { name: 'みかん', price: 100 },
  { name: 'りんご', price: 200 },
  { name: 'バナナ', price: 300 }
]

for(let item of fruits) {
  console.log(item.name) // => みかん , りんご, バナナ
}

配列等の繰り返し処理を行う構文として、forEachというメソッドも存在します。
こちらも比較的新しい構文ですが、 async/awaitやbreak文が使えません
また、forEachの場合関数呼び出しを挟むためパフォーマンス的にも微妙です。

※ async/await については今回割愛します。 Promise で調べてみてください。

配列の操作(破壊的/非破壊的)

配列の操作には、
破壊的なもの(元の配列に影響を与えるもの)
非破壊的なもの(元の配列に影響を与えないもの)とがあります。

こちらの記事にわかりやすくまとめてくださっていますので、
ぜひ参考にして適切な処理をしてください。

配列・オブジェクトのコピー

JSでは、 配列・オブジェクト を他の変数に直接代入した場合は参照渡しとなります。

配列は、他の言語でも参照渡しとなることが多いと思うので意識できるとして、
オブジェクトも参照渡しとなるので注意が必要です。

たとえば、次のように素直にオブジェクトをコピーすると...

NG

const obj = {
  key: 'value'
}
const copy = obj
copy.key = 'hoge'

console.log(obj.key) // => 'hoge'
console.log(copy.key) // => 'hoge'

もとのオブジェクトの値も書き換わってしまいました。

こうしましょう。

OK

const obj = {
  key: 'value'
}
const copy = { ...obj }
copy.key = 'hoge'

console.log(obj.key) // => 'value'
console.log(copy.key) // => 'hoge'

...objスプレッド演算子 と呼ばれる記法で、ES2018から使えます。※1
ちなみに配列もスプレッド演算子を使って同じようにコピーできます。

しかし、注意してください。
ネストされた配列・オブジェクトはスプレッド演算子でコピーできません。

配列

const ary = [
  { key: 'value' }
]

const copy_ary = [ ...ary ]
copy_ary[0].key = 'hoge'

console.log(ary[0].key) // => hoge
console.log(ary_copy[0].key) // => hoge

オブジェクト

const obj = {
  child: { key: 'value' }
}

const copy_obj = { ...obj }
copy_obj.child.key = 'fuga'

console.log(obj.child.key) // => fuga
console.log(obj_copy.child.key) // => fuga

スプレッド演算子によるコピーも、結局はシャローコピー(浅いコピー)ですので、
入れ子のオブジェクトまではコピーできないんですね...

複雑にネストされた配列・オブジェクトをコピーする時は、
loadash などのライブラリを使うと便利です。

スプレッド演算子については、こちらの記事が参考になります。

文字列の中に変数を埋め込む

変数と文字列を結合して出力する場合、普通に書くとこうなります。

NG

const name = '田中'
console.log('ようこそ' + name + 'さん')  // => ようこそ田中さん

普通に動くし、絶対ダメ!というわけではないのですが、
せっかくES2015が使える環境なら、
新しく導入されたテンプレートリテラルという記法を使った方が
すっきり書けていいと思うんです。

OK

const name = '田中'
console.log(`ようこそ${name}さん`)  // => ようこそ田中さん

テンプレートリテラルは文字列をバッククォートで囲うと使用できます。
このなかで変数を ${} で囲うと文字列として展開してくれます。

以下の内容でテンプレートリテラルを使っているので、
なんだこれ?とならないように一応説明も兼ねて書いておきました...

thisの扱い(アロー関数を使う)

一般的にJavaScriptでは、関数の中でthisを使うと、その呼び出し元のオブジェクトになります。
【JavaScript】アロー関数式を学ぶついでにthisも復習する話

そうなんです。
と言われてもよくわかりませんね。
上の引用元の例を少し変えて説明したいと思います。


const hoge = {
  message: 'message',
  printMessage: function () {
    console.log(`log1: ${this.message}`)
    setTimeout(function(){
      console.log(`log2: ${this.message}`)
    }, 1000)
  }
}

hoge.printMessage() // => log1: message, log2: undefined

あれ? log2 のほうではmessageがundefinedになっている...?

結論から言うと、
log1の時点でのthishogeオブジェクト
log2の時点でのthiswindowオブジェクト (ブラウザで実行した場合)だからです。

setTimeoutは windowオブジェクト のメソッドですので、
呼び出し元のオブジェクト = thisはwindowオブジェクトになる訳ですね。

ではどうすればsetTimeoutのコールバック関数のなかでも message を参照できるでしょうか?
まず、古いやり方です。

古いコード

const hoge = {
  message: 'message',
  printMessage: function () {
    console.log(`log1: ${this.message}`)
    const self = this
    setTimeout(function(){
      console.log(`log2: ${self.message}`)
    }, 1000)
  }
}

hoge.printMessage() // => log1: message, log2: message

thisを変数(self)に入れてあげれば、なんとか参照できます。
しかし、ES2015から取り入れられたアロー関数を使えば、もっとスマートに書けます!
上のコードを、アロー関数を使って書くとこうなります。

新しいコード

const hoge = {
  message: 'message',
  printMessage: function () {
    console.log(`log1: ${this.message}`)
    setTimeout(() => { // <- ここに注目
      console.log(`log2: ${this.message}`)
    }, 1000)
  }
}

hoge.printMessage() // => log1: message, log2: message

アロー関数は、宣言された時点でthisの値を確定するので、
コールバック関数の中でもlog1の時点と同じthisを参照することができました。

アロー関数については、詳しくは上の引用元の記事をご覧ください。

thisは初心者がハマりやすい箇所なので、意識して使いこなせるようになりたいところです。
ES2015の構文が使用できるのであれば、アロー関数がすっきり書けてベターでしょう。

おわりに

とりあえず思いついたものを挙げてみましたが、
他にも何か思いついたら書き足していきたいと思います。

ECMAScriptも毎年新仕様が策定されていますし、
キャッチアップしながら今後もよりよいコードを書いていけるよう精進したいと思います。

こうした記事を書いておきながら、自分もまだまだ駆け出しですので、
これも意識しておくといい、などのご意見等ありましたら
コメントにてご指摘いただけますと幸いです。

宜しくお願い致します。

注釈

※1 配列にはES2015から、オブジェクトにはES2018から使用できるようです。