JavaScriptは、現在のウェブ開発において、
欠かせないプログラミング言語の一つです。
フロントエンドからバックエンドまで幅広く使用されており、
その汎用性と強力な機能が多くの開発者に支持されています。
Javaと名前が似ていますが、
全く異なります。あるあるネタですが、
JavaScriptを略してJavaと言ってはいけません。
JavaScriptというと、
誰もが知っている言語であるような気がしますが、
実は色々掘り下げるとかなり複雑で、
そこには深遠な世界が広がっています。
(ECMAScriptという名称と、2015年にリリースされたES6と、
それ以降リリースされた、ECMAScriptの仕様としての、
ES2016、ES2017、ES2018など...
あとは実行環境としてブラウザとサーバの2つがあり、
それぞれ特徴があることなど)
さらに、そのJavaScriptの周辺を渦巻くFWやビルドツールなども
考慮するとフロントエンドは変化も早くかなり複雑怪奇なことでしょう。
ただ、裏を返せばそれだけ複雑なJavaScriptの世界を知ることは、
大きな武器になるということ。
JavaScriptを深く知ることができれば、
フロントエンド開発に強みを持てるだけでなく、
Node.jsとしてのバックエンド開発、
React Nativeなどによるモバイル開発、
Electronによるデスクトップアプリ開発、
さらにはGASなどによるGoogleツールの効率化スクリプトなどまで...
JavaScriptの可能性は無限大といっても過言ではないでしょう。
本記事では、そんなJavaScriptの基本的な概要と、
実際に開発を始めるための環境構築、
JavaScriptの文法として重要な変数について解説します。
JavaScriptの概要
JavaScriptは、ブラウザ上で動作するスクリプト言語として
Netscape社によって開発されました。
現在では、フロントエンド(クライアントサイド)とバックエンド(サーバーサイド)の両方で使用され、
動的なウェブページの作成や、ウェブアプリケーションの開発に不可欠です。
JavaScriptは動的型付け言語であり、変数の型を明示的に指定する必要がありません。
また、JavaScriptは、昨今の言語の中では珍しく、
プロトタイプベースのオブジェクト指向でした。
これは、RubyやJavaとかに代表されるクラブベースのオブジェクト指向とは異なり、
オブジェクトから新しいオブジェクトを作る手法で、
他の言語だと、luaとかでもこれを採用していると思います。
JavaScriptのプロトタイプベースについては、
詳しく話すとかなり複雑になるので、そちらはまた別の記事にて執筆します
JavaScriptでも、クラス定義をすることはできるようになったので、
今となっては、純粋なプロトタイプベースなのかどうかは甚だ疑問ではありますが、
JavaScriptのもともとの言語設計がプロトタイプベースというのは、
大きな特徴の一つだと思います。
ちなみにプロトタイプベースについては、
Wikipediaの解説が結構深く書かれており、
個人的には面白かったです。
またJavaScriptというと、イベント駆動型プログラミングのイメージが強いかと思います。
ユーザーの操作(クリックやキー入力など)に対して即座に反応するようなプログラミングを
気軽に行うことができるのも特徴でしょう。
おそらく最初にJavaScriptを学ぶときは、
このイベントの処理とかに戸惑ったりするのかもしれません
(私は初めてJavaScriptを学んだとき、配列やらオブジェクトやら型やらみたいな、
所謂The プログラミングの文法というよりは、addEventListenerとかをひたすら書いていた記憶しかありません)
また、JavaScriptというと非同期処理のイメージも強いかもしれません。
これも別にJavaScript固有の文法というわけではありませんが、
言語によってはあってもあまり使わなかったりすることもある気もしますが、
JavaScriptだと、API疎通などでほぼ必ず使う場面が出てくるので、
使用頻度でいうと、もしかすると一番多いのではないのかなと思います。
とまあ、JavaScriptには多くの特徴がありました。
一つ一つはJavaScript固有の特徴というわけではありませんが、
すべてを包含した、
- 動的型付け
- プロトタイプベースオブジェクト指向
- イベント駆動型プログラミングの使用頻度
- 非同期処理の使用頻度
という4つの特徴を持つ言語はそんなにはない気がします。
それにJavaScriptの文法も詳しく紐解いていくと、
これ以外にも結構面白い特徴があったりして、
JavaScriptはその著名さに対して、
結構マニアックな文法だったりとそのギャップが個人的には結構面白いと感じております。
【JavaScriptを試す】環境構築
さて、大まかにJavaScriptの特徴に触れたので、
実際に少し書いてためしてみようと思います。
JavaScriptを実行する方法として、
大きく二つの方法があります。
一つは、開発者ツールのコンソールでコードを入力することと、
もう一つはNode.jsをインストールして、nodeコマンドなどで対話モードを
起動させることです
まあ、一般的にはコンソールに直接打ち込んで、
実行するなんてことはほとんどしないのですが、
ちょっとした文法を試すぐらいの用途でしたらいいかもしれません。
手順としては、ブラウザを開いて、ウェブページ上で右クリックし、「検証」を選択。(MacならCmd + Option + Iでも開けます)
「コンソール」タブを選択したら、コンソールに以下のようなコードを入力して、JavaScriptを試すことができます。
console.log("Hello, JavaScript!");
もう一つの実行方法としては、
こちらはやはり定番のローカルでの環境構築です。
より本格的な開発を行うためには、必須になるでしょう。
基本的な手順としては、エディタのインストールと、Node.jsのインストールがあります。
エディタについては、
お好きなものを使用するのが良いとは思いますが、
私は別の記事や動画でNeovimやVimを推奨しているので、
興味がある人はこちらの記事 や動画もご参考ください。
-
参考ターミナルインストールから始めるVim/Neovim8つの基本キーバインド
本記事では、0からVimやNeovimについて、解説していきます。 Vimって何?っていう人や、ターミナルもほとんど使っていないという人でも、最低限これだけは知っておきたいVimのキーバインドを本記事 ...
続きを見る
Node.jsのインストールについては、
Node.jsの公式サイトにサクセスしてダウンロードするというやり方でも良いのですが、
Node.jsのバージョンは結構切り替えたりすることも多いので、
個人的には、nvmなどのバージョン管理ツールの導入をおすすめします。
nvmの導入手順については、下記の記事で解説しております。
-
参考【TypeScript環境構築手順】nvm経由でのNode.jsインストール
TypeScriptの実行環境を作るには、Node.jsを入れる必要がありますが、意外とNode.jsを入れたりするのは、面倒だったりします。 なので、本記事では、Node.jsのインストールからTy ...
続きを見る
Node.jsは、JavaScriptの実行環境であり、
バックエンド開発でも使用されます。
下記のコマンドをターミナルで打って、
バージョンが表示されていれば問題ないです。
node -v
npm -v
Expreeeで簡易サーバー立ててみる
さて、Node.jsもインストールしたので、
せっかくなので、Node.jsフレームワークのExpressを使って、
簡単なバックエンドサーバーを立ててみようと思います。
まずは、適当なディレクトリに移動して、
npm init -y
を実行して、package.jsonを作成します。
次にExpressをインストールします。
npm install express
node_modulesが作成されて、
package-lock.jsonが作成されていれば、
無事インストールされているかと思います。
早速、サーバーを起動するファイルを作成しましょう。
touch app.js
作成したら、app.jsを下記のように編集します。
const express = require('express');
const app = express();
const port = 3010;
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
あとは、このapp.jsを実行するだけです。
node app.js
http://localhost:3010 にアクセスしたら、
Hello Worldが表示されているはずです。
余談ですが、上記のコードの、require('express')
という記法は、CommonJSの記法で、
Node.jsではこちらの記法が一般的かと思います(とはいえ最近はESModulesに流れつつあるので、
正直この辺りは今後どうなるかはよくわからない)
https://nodejs.org/api/modules.html
一方で、ブラウザ上で実行するJavaScriptである、
ESModulesは、import
などでモジュールを取り込むのが一般的な文法です。
実はNode.jsとブラウザのJSはこれ以外にも色々違いがあって、
グローバルオブジェクトは、ブラウザにおいてはWindowオブジェクト、
Nodeにおいては、globalオブジェクトとなります。
https://developer.mozilla.org/ja/docs/Glossary/Global_object
JavaScriptには、主にCommonJsとESModuleの二つの
モジュールシステムが存在します。
これもJavaScriptの周辺世界が、しばしばカオスといわれる所以だと個人的には感じます
これも踏まえると、JavaScriptとは、
- 動的型付け
- プロトタイプベースオブジェクト指向
- イベント駆動型プログラミングの使用頻度
- 非同期処理の使用頻度
- CommonJSとESModule二つのモジュールシステムの存在
という実に複雑な歴史的背景を内包した一筋縄ではいかない言語に思えてきます。
変数と定数
さて、JavaScriptの概要説明が長引いてしまったので、
ここらで一旦、シンプルに文法の話をしようと思います。
まずは、定番ですが、変数と定数について解説します。
ちなみに、TypeScirptの変数については、
下記の記事でも解説しているので、
よろしければご参考ください
-
参考【TypeScriptの変数】let/constと変数として使えないもの
TS Playground TS Playgroundは、TypeScriptを試すことができるサンドバッグ的環境。 通常、プログラミング言語の学習などをする際は、自分のPCに実行環境を構築する必要が ...
続きを見る
let, const, var
さて、そもそも変数とは何かというと、
しばしば下記の画像のように値をいれる箱と言及される事が多いかと思います。
すなわち、Piという変数に3.14という値をいれるので、
let Pi = 3.14
のようにして変数を定義します。
JavaScriptでは、こうした変数定義として
主にletとconstを使って変数や定数を宣言します。
let test;
let test2 = 'hello';
const test3 = 'world'
test変数のように、宣言だけで初期値を代入しないこともできます。
その場合、undefinedが入ります。
letは変数なのに対し、constは定数なので、
初期化する必要があり、値を変更することができません。
さて、constは定数なので、
本来は変数ではないのですが、
JavaScriptにおける変数定義という文脈では、
このconstを使用することが一番多いです。
すなわち、プログラム実行中に変化しない変数も、
constとして定義し、可能な限りconstで定義するという考え方です。
一方で、定数本来の意味に立ちかえって、
変数はlet、定数(例えば円周率のように基本的には変わりようがないようもの)はconstという
ふうに書くみたいな考え方もあると思います。
どちらが一概に良いかは難しいですが、
個人的には、letで定義するということは、
その変数がどこかで書き換わる可能性があるということを示唆するため、
可能な限り、constで定義して、
変わるものだけletで定義するやり方のほうが可読性が高くなるのではないかなと感じます。
var
さて、基本的にES6以降においては、
JavaScriptの変数・定数定義はletかconstを使用すればいいのですが、
ES6より前では、変数宣言にvarというものを使っていました。
今から書くプログラムにvarを使う必要は基本的にはありませんが、
古いコードとかには時々見かけることもあるので、
varの特徴と注意点について触れておこうと思います。
※スコープの説明を含むため、先に本記事のスコープについての説明(varの次の節)を
読んでからのほうが理解が進むかもしれません。
関数スコープ
varで宣言された変数は、関数スコープを持ちます。
これは、変数が関数内であればどこでもアクセス可能であることを意味します。
ブロックスコープ(例えば、if文やforループ内)では新しいスコープが作られません。
function test() {
var x = 1;
if (true) {
var x = 2; // 同じスコープ内の変数を再定義
console.log(x); // 2が返される
}
console.log(x); // 2になってしまう
}
test();
ifブロック内でxを再定義すると、外側のxも変更されます。
letとかで定義すると、
function test() {
let x = 1;
if (true) {
let x = 2; // このブロックスコープ内でのみ有効な新しい変数
console.log(x); // 2
}
console.log(x); // もとの1が返される
}
test();
しっかりスコープが保たれているのがわかります
変数の巻き上げ(Hoisting)
varで宣言された変数は、変数の巻き上げ(hoisting)の対象となります。
これは、変数の宣言がそのスコープの先頭に移動されることを意味します。
ただし、値の代入は元の位置に残ります。
console.log(y); // undefined
var y = 'Hello';
console.log(y); // Hello
つまり、変数宣言だけ先頭にされて、
代入は、元の位置で行われるという感じなので、
最初のconsole.logではundefinedが出力される感じです
(これ自体はletで初期値を代入しないで宣言だけしたときにundefinedが入るのと似てますね)
グローバルオブジェクトのプロパティ
グローバルスコープでvarを使って宣言された変数は、
グローバルオブジェクト(ブラウザ環境ではwindow)のプロパティとして扱われます。
var globalVar = 'global var';
console.log(window.globalVar); // global var
そのため、意図せずライブラリのグローバルオブジェクトのプロパティなどと
名前衝突してしまい、複雑なエラーになったりするおそれがありそうです。
すなわち、varでの宣言は、globalThis.test = 'hello'
みたいな記法と酷似します。
変数と定数のスコープ
変数がどこまで有効かといった、その有効範囲を
スコープといいます。
letやconstについては、それらが宣言された、
ブロック内で有効となるブロックスコープを持ちますが、
varはブロックスコープを持ちません。
if (true) {
var blockVar = "I am not a block-scoped variable";
let blockLet = "I am a block-scoped variable";
const blockConst = "I am a block-scoped constant";
console.log(blockVar); // I am not a block-scoped variable
console.log(blockLet); // I am a block-scoped variable
console.log(blockConst); // I am a block-scoped constant
}
console.log(blockLet); // ReferenceError: blockLet is not defined
console.log(blockConst); // ReferenceError: blockConst is not defined
console.log(blockVar); // 値が読み取れてしまう
基本的に、letやconstについては、
このようにブロックスコープを持っているので、
スコープ毎に同じ名前の変数を定義したとしても、
それらは別の値を参照します。
let x = 1; // グローバルスコープ
if (x === 1) {
let x = 'hello'; // ブロックスコープなので別の値を参照できる
console.log(x);
}
console.log(x) // グローバルスコープの値が出力される 1
let x = 3; // 同一スコープ(この場合グローバルスコープ)での再宣言はエラーになる
なので、関数スコープと合わせると、
下記のようなコードも書こうと思えばかけます。
var x = true; // グローバルスコープ
let y = true;
const z = true
function test() { // 関数スコープ
var x = "varの変数です";
let y = "letの変数です";
const z = "constです";
console.log(x); // varの変数です
console.log(y); // letの変数です
console.log(z); // constです
if (true) { // ブロックスコープ
var x = 100;
let y = 200;
const z = 300;
console.log(x) // 100
console.log(y) // 200
console.log(z) // 300
}
console.log(x); // varはブロックスコープを持たないので100が出力される
console.log(y); // ブロックスコープを持つので、関数スコープのletの変数ですが出力される
console.log(z); // ブロックスコープを持つので、関数スコープのconstですが出力される
}
test()
console.log(x) // varも関数スコープは持っているのでグローバルスコープのtrueが出力される
console.log(y) // true
console.log(z) // true
letとconstがそれぞれのスコープ毎に値を保持している(有効範囲を保持している)のに対し、
varはブロックスコープを持たないので、ブロックスコープでの宣言が、
関数スコープにまで影響してしまっています。
また、同一スコープでの再宣言はエラーになるのですが、
varの場合、エラーにならないので、その特徴も相まって、
ブロックスコープでの再宣言が上書きのような挙動になっているように思えます
下記の画像のようにvarだと再宣言ができてしまいます。
分割代入
分割代入は、配列やオブジェクトから複数のプロパティや要素を一度に取り出し、
変数に割り当てるための便利な構文です。
やや応用的な文法ですが、
地味に結構使うので覚えておいたほうが良いと思います。
配列の分割代入
const array = [1, 2, 3];
// 配列の分割代入
const [first, second, third] = array;
console.log(first); // 1
console.log(second); // 2
console.log(third); // 3
このあたりは、まだ直感的ですが、
必ずしも、変数と値の数を合わせなくても代入できます。
const array = [1, 2, 3, 4];
// 2番目の要素をスキップ
const [first, , third, fourth] = array;
console.log(first); // 1
console.log(third); // 3
console.log(fourth); // 4
// 逆に要素が足りないときはundefinedが入る
const array2 = [1,2]
const [first2, second2, third2] = array2
console.log(first2) // 1
console.log(second2) // 2
console.log(third2) // undefined
//...を使うと残りの値を一つの変数に集めることもできます
const array3 = [1,2,3,4,5]
const [first3, ...second3] = array3
console.log(first3) // 1
console.log(second3) // [2,3,4,5]
//ちなみに配列の分割代入は代入するものは配列でなくてもいい
const [first4, ...rest] = "Hello"
console.log(first4) // H
console.log(rest) // ['e', 'l', 'l', 'o']
ちなみに、undefinedが入らないように、
デフォルト値を入れることもできます。
const array2 = [1,2]
const [first2, second2, third2 = 'test'] = array2
console.log(first2) // 1
console.log(second2) // 2
console.log(third2) // test
オブジェクトの分割代入
配列の分割代入も便利ですが、
個人的にはこちらのオブジェクトの分割代入のほうが
使用頻度や遭遇頻度は高い気がします。
オブジェクトの分割代入では、オブジェクトのプロパティを変数に割り当てることができます。
const obj = { name: 'John', age: 30 };
// オブジェクトの分割代入
const { name, age } = obj;
console.log(name); // John
console.log(age); // 30
オブジェクトの分割代入について、
変数名とプロパティ名は同じにする必要があります。
例えば、上記のコードで変数宣言をconst { name2, age2 } = obj
とかにすると、
これらにはundefinedが入ります。
そのため、分割代入において、
変数に別名を付けたいときは下記のように記述します。
const obj = { name: 'John', age: 30 };
// オブジェクトの分割代入
const { name: firstName, age: thirty } = obj;
console.log(firstName); // John
console.log(thirty); // 30
ちなみに、このオブジェクトの分割代入と別名宣言は、
Nuxt3のuseAsyncDataやuseFetchとかで頻繁に出てきます。
const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons'),
$fetch('/cart/offers')
])
return { coupons, offers }
})
この場合、dataというプロパティにdiscountsという別名を付けています。
また、async関数の中でも配列の分割代入を使っており、
このようにフレームワークの関数とかを使うときは、
分割代入は結構よく出てくるかと思います。
JavaScriptについては、
下記の本が非常に体系的に書かれており、
詳しく学ぶことができるので、
JavaScriptを極めたいと思っている人には、
非常におすすめの一冊です。