スコープ、プロトタイプ、非同期処理など難しい仕様が多すぎる
JavaScriptは分かりにくい概念が多くてなかなか学習が進まない
エラーの対処方法ってどうするのが良いんだろう
こんにちは。
これまで200本以上にわたりJavaScriptの技術記事を書いてきたテックライターのマサトです。
JavaScriptには独特の概念が多く存在するため、初学者の方はなかなか勉強が思うようにいかなくて困るケースも少なくありません。
特に、プロトタイプや非同期処理などの理解はプログラムのパフォーマンスに影響してくるため、知っているのと知らないのとでは大きな差が生まれます。
しかし、これらの概念はどのように学習するのが良いのでしょうか。
この記事では、初心者でも今日からJavaScriptのココがわからない!というポイントを理解するための方法について分かりやすく解説していきますので、ぜひ最後まで読んで理解を深めて頂ければ幸いです。
一人でも多くの方がJavaScriptの不安を解消できれば幸いです。
基本的な概念
この章では、JavaScriptにおいてもっとも基本と言える概念について見ていきましょう。主に、変数とスコープについて学習していきます。
変数の注意点
JavaScriptで変数宣言をする時はvar / let / constのいずれかを利用するわけですが、基本的にはletかconstのどちらかを利用するのが現状ではベストです。
なぜvarを使わないほうが良いのかについてはいくつか理由があるのですが、以下の3点を抑えておくと良いでしょう。
- 変数の巻き上げ現象
- 多重宣言と再代入
- ブロックスコープ
変数の巻き上げ現象というのは、変数がどこで宣言されても必ず先頭(スコープ内)で宣言されるような振る舞いを行うことです。
例えば、以下のコードは変数sampleを宣言する前にconsole.log()で利用しています。
console.log(sample); // undefinedが出力される var sample = 100;
普通ならエラーになるのですが、varの場合は巻き上げが行われてコードの先頭で変数sampleに初期値undefinedが代入されます。そのため、実行結果ではundefinedが出力されるわけです。
また、varを使うとまったく同じ変数名を何度も多重に宣言できてしまいます。
var sample = 100; var sample = 'abc'; var sample = 0;
何度繰り返してもエラーになることはありません。
もちろん、値を再代入してもエラーは起きません。
var sample = 100; sample = 0;
これはlet / constを利用するとエラーを出力してくれるようになります。特にconstは値の再代入もエラーになるためミスに気づきやすくなるわけです。
より詳しいlet / constの基本的な使い方およびサンプル事例については、次の記事でまとめているので参考にしてみてください。
また、エラーを未然に防ぐという意味ではstrictモードを利用するという方法もあるので、まだ知らない方は次の記事を参考にしてみてください。
スコープの考え方
先ほど、varを使わないほうが良い理由としてブロックスコープについてまだ解説していませんでした。そもそも、スコープとはどういう意味かご存知でしょうか?
簡単に言ってしまうと、変数などを利用できる範囲を決めるということになります。
一時的にだけ使いたい変数とか何度も再利用したい変数などいろいろあるわけですが、範囲を広げすぎると意図せずに変数を書き換えてしまうリスクもあるので難しいところです。
そして、ブロックスコープというのは、IF文やfor文など{ }で囲まれた中で宣言された変数を外側でも利用できるかどうか?ということです。
例えば、以下のコードはfor文の中に変数sampleを宣言しています。
for(var i=0; i<10; i++) { var sample = i; } console.log(sample); // 9が出力される
一般的なプログラミング言語なら{ }の中で宣言された変数は外側では利用できませんが、varの場合はfor文の外側に記述したconsole.log()でも利用できるのです。もちろんfor文のカウンタとして利用している変数iも外側で利用できます。
つまり、for文が終了しても利用していた変数はまだ生きているので注意が必要なのです。しかし、let / constを使えば{ }の中で宣言された変数はその中だけでしか使えないので、安心して利用できるわけですね。
スコープについてさらに深堀りして学習を進めたい方は、次の記事で体系的にまとめているのでぜひ参考にしてみてください!
重要な言語仕様
この章では、JavaScriptを学習するうえで重要となる概念について見ていきましょう。主に、関数・プロトタイプ・Promiseについて学んでいきます。
関数の基礎知識
関数はさまざまな処理を1つにまとめたり、プログラム内で再利用できるようになる便利なものです。関数の主な機能として、まずは引数・returnを理解しておくと良いでしょう。
引数は関数を実行する際に、任意の値を受け取って処理を行う際に利用することができるものです。
例えば、次の関数は任意の名前を受け取る引数が設定されています。
function user(name) { console.log('あなたは' + name + 'さんですね'); } user('太郎');
実行結果
あなたは太郎さんですね
関数を定義する際に( )の中へ任意の変数を指定するだけで引数は作れます。この例では、変数nameを設定しており、user()を実行する際に【太郎】という名前を指定しました。その結果、コンソールログには名前を利用した出力結果が表示されるというわけです。
returnに関しては、関数内で任意の値を返り値として返してくれるようになる機能です。次のサンプル例では、2つの引数を掛け算した結果をreturnしています。
function calc(num1, num2) { return num1 * num2; } console.log(calc(2, 5));
実行結果
10
このようにreturnに続けて返したい値を記述すると、関数を実行した際にその値が返り値になるわけです。上記の例であれば、関数を実行すると掛け算した結果を返してくれます。
関数について基本的な知識からさらなる活用方法まで学習したい方は、次の記事で体系的にまとめているのでぜひ参考にしてみてください!
プロトタイプとは何なのか
JavaScriptを特徴づける機能としてプロトタイプがあります。
これは簡単に言うとオブジェクトの最小テンプレートのようなものであり、このテンプレートをコピーすることでさまざまなオブジェクト構造を作ることができるわけです。
実はJavaScriptのオブジェクトはすべてこのプロトタイプがベースになっているので、言い換えればすべてのオブジェクトがプロトタイプを持っているわけです。これにより、便利なことがいくつかあります。
もっとも分かりやすい例としては、メソッド定義の効率化が挙げられるでしょう。例えば、次のようなコンストラクタがあるとします。
const User = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } }
これは2つのプロパティ(name, age)と1つのメソッド(getName)が定義されていますよね。
使い方としてはnewを利用してインスタンスを生成するのが一般的です。
const taro = new User('太郎', 30); console.log(taro.getName());
実行結果
太郎
特に問題ないように思えますが、この状態だとインスタンスを生成するたびにメソッドも一緒にコピーされるわけです。例えば、メソッドの定義が100個あったとしたら毎回それらも一緒にコピーされるので無駄なメモリを消費します。
ところが、プロトタイプを利用するとこの問題を解消できます。
メソッドの定義部分だけを切り離し、プロトタイプへ定義するように変更しましょう。
const User = function(name, age) { this.name = name; this.age = age; } User.prototype.getName = function() { return this.name; }
コンストラクタはプロパティの定義だけになってスッキリしましたね。
メソッド部分は【コンストラクタ.prototype】に続けて定義することで、プロトタイプに定義されたことになります。すべてのオブジェクトがベースにしているプロトタイプに定義することで、メソッドをコピーしなくても利用できるというわけです。
このプロトタイプについて、さらに詳しい知識や継承への応用などについて次の記事で解説しているので合わせて参考にするとより理解が進みますよ。
Promiseによる非同期処理
JavaScriptはシングルスレッドのため、普通にプログラムを書くと上から順番に処理されていきます。これを同期処理と言うのですが、途中で時間の掛かる処理を書くと終了するまでストップしてしまうという欠点があります。
例えば、サーバーと通信をしてデータを取得するような処理を書くと、結果を得られるまで処理がストップしてしまうわけです。
そこで登場するのが非同期処理という技術で、時間の掛かる処理を実行してもすぐに次の処理に移行することができます。そして、他の処理を実行している間に結果を取得したら、そのまま続きの処理を始められるのです。
同期処理 ↓ 非同期処理 → 結果が返ってきたら処理を続ける ↓ 同期処理(前の処理を待たなくてもOK) ↓ 同期処理
このような便利な技術を簡単に実装できるようにしてくれるのがPromiseというわけです。
Promiseの使い方は簡単で、まずはインスタンスを作成する際に実行したい処理を関数に記述します。
var result = new Promise(function(resolve) { // ここに処理を記述する // 処理の結果をresolveに設定する resolve(【処理の結果】); })
例えばサーバーからデータを取得するのであれば、そのデータをresolveの引数に設定するわけです。
そして、thenを使うと結果を取得したタイミングで任意の処理を続けて実行することができるようになります。
result.then( function(data){ console.log(data); } );
Promiseについてもっと知識を深めたい方は、次の記事で基本的な使い方から活用技までをまとめているのでぜひ参考にしてみてください!
エラーの対処について
JavaScriptに限った話ではありませんが、プログラムを書いていると正しく動作をせずにエラーになることも少なくありません。そんな時にどのような対処をしたら良いのか悩むこともあるでしょう。
そこで、まず最初に考え方としてエラーの種類を以下の2つに分けてみましょう。
- 文法エラー
- 論理エラー
文法エラーというのは単純なタイプミスによる原因が多くて、大抵の場合はエラーメッセージが表示されるので対処に悩むことは少ないでしょう。
問題になるのは論理エラーです。これはプログラム自体は正しく動作するものの期待した処理を行っていないエラーになります。
具体例を見てみましょう!
let result = 0; for(let i=0; i>10; i++) { result += i; } console.log(result);
実行結果
0
この例は、for文を使って1から9までの数字を足し算するという単純な処理です。結果は45になるはずなのですが、実際には0になっている点に注目してください。
理由は簡単でfor文の条件式がi>10となっているので、まったく繰り返し処理が行われていないわけです。しかし、特にエラーメッセージが出ることもなくプログラム自体は正しく動作して終了します。
このような論理エラーはバグを引き起こしやすいので、開発中はconsole.logやブラウザのDev Toolsなどを活用して変数などの値をチェックするように心がけるようにしましょう。
ちなみに、基本的なデバッグの手法については以下の記事で詳しくまとめているので合わせて参考にしてみてください!
まとめ
今回は、JavaScriptの学習においてわかりにくい概念やポイントなどについて学習をしました!
最後に、もう一度ポイントをおさらいしておきましょう!
- スコープについては有効な範囲を意識するようにしよう
- 関数・プロトタイプ・Promiseを活用してコードを効率化しよう
- エラーの種類を分けることで原因究明を素早く行おう
上記内容を踏まえて、ぜひ自分でもプログラミングに取り入れて活用できるように頑張りましょう!