環境構築
<script src="index.js"></script>
- scriptタグはbodyの一番下に書く
- 実務では別jsファイルに分ける
- 実行結果はブラウザから確認する
- 実行結果はターミナルから確認する
node index.js
文法
基礎
undefined;
null;
0;
'';
NaN:
- 関数は原則モダンなアロー関数を用いて、特に変更するものでもないのでconstを用いる
const hoge = () {
}
window.alert('これはエラーです')
document.getElementsByTagName('button')
document.getElementsByTagName('button').addEventListener('click', () => {
})
- HTMLのid属性は一意となるためgetElementByIdを使って確実にデータを取得
document.getElementById('xx')
document.getElementById('xx').textContent = 'りんご'
- HTMLのオブジェクトをとってくる場合は変数名に$を付けるのが慣習
const $button = document.getElementsByTagName('button')
- eはイベントを表しe.targetでクリックしたボタンを取得する
const $button = document.getElementsByTagName('button')
$button[0].addEventListener('click', (e) => {
e.target
})
- 文字列と変数は+で結合可能
- グルーバル変数を汚染しないために即時関数で書く
(() => {
})()
const $content = document.querySelectorAll('[data-content]')
const init = () => {
$content[0].style.display = 'block'
}
init()
- e.preventDefault()でイベントを殺す(リンクに飛ばないなど)
- e.target.dataset.xxxでデータ属性(data-xxx)の値を取得
- classList.add('xxx')でクラス属性を全て取得し指定のクラス属性xxxを追加
- classList.remove('xxx')でクラス属性を全て取得し指定のクラス属性xxxを削除
- 同じ変数はなるべく一つにまとめる(保守性、パフォーマンスが増す)
- DOMとはHTML文書内の要素のこと
- xxx.nextElementSibilingでxxxの次の要素を取得
- クラスの中の関数はconstなどはいらない
- クラス内のthisはクラスを参照する
- クラス内からクラス内のメソッドを呼び出すにはthis.メソッド名とする
- strict modeで古い文法を排除し厳格かつ安全に実行できる
'user strict'
変数・データ型
- constは厳密には定数ではなく再代入できない変数
- 複数行の文字列は``で囲む
`
aaa
bbb
ccc
`
String(1);
const input = window.prompt("数字を入力してください", "42");
const num = Number(input);
- プリミティブ値は参照先の値、オブジェクトは参照がコピーされる
関数
- Rest parametersは、仮引数名の前に...をつけた仮引数のことで、配列となる
function fn(...args) {
console.log(args);
}
fn("a", "b", "c");
const fnC = x => {};
const mulB = x => x * x;
- 同じ名前の関数を複数回宣言した場合には、後ろで宣言された関数によって上書きされるため注意
- 引数として渡される関数のことをコールバック関数
- コールバック関数を引数として使う関数やメソッドのことを高階関数
function 高階関数(コールバック関数) {
コールバック関数();
}
const array = [1, 2, 3];
array.forEach((value) => {
console.log(value);
});
- メソッドを呼び出す場合は、関数呼び出しと同様にオブジェクト.メソッド名()
const obj = {
method: function() {
return "this is method";
}
};
const obj = {
method() {
return "this is method";
}
};
console.log(obj.method());
- ブロックで終わる文の末尾には、セミコロンが不要
- 渡す引数と受け取る引数は引数の順番で決まる(キーワード引数はない)
- 関数は実行可能なオブジェクト
- シンボルはプロパティーの重複を避けるために必ず一意の値を返す関数
const s = Symbol();
ループ
const array = [1, 2, 3]
array.forEach(value => {
console.log(value);
});
function isEven(num) {
return num % 2 === 0;
}
const numbers = [1, 5, 10, 15, 20];
console.log(numbers.some(isEven));
function isEven(num) {
return num % 2 === 0;
}
const array = [1, 5, 10, 15, 20]
console.log(array.filter(isEven));
- for...in文の利用は避け、Object.keysメソッドなどを使って配列として反復処理するなど別の方法を考えたほうがよい
const obj = {
'a': 1,
'b': 2,
'c': 3
};
Object.keys(obj).forEach(key => {
const value = obj[key];
console.log(`key:${key}, value:${value}`);
});
for (const i in obj) {
console.log(i);
console.log(obj[i]);
}
- 配列の繰り返しにはforEachの他にfor of分も使える
const array = [1, 2, 3];
for (const value of array) {
console.log(value);
}
- for文などの構文ではcontinue文やbreak文が利用できるが、配列のメソッドではそれらは利用できない
- 一方で配列のメソッドは、一時的な変数を管理する必要がないことや、処理をコールバック関数として書くという違いがある
/ |
object |
map |
array |
set |
キー |
文字列 |
指定なし |
重複OK |
重複NG |
for...in |
o |
x |
o |
x |
for...of |
x |
o |
o |
o |
const map = new Map();
const key1 = {};
map.set(key1, 'value1');
console.log(map.get(key1))
- イテレーターとは反復可能なオブジェクト
- ジェネレーターとは簡単にイテレーターを生成する関数
- スプレッド演算子...は配列を展開する
const arry1 = [1,2,3,4,5];
const arry2 = [...arry1];
function sum(...args) {
let r = 0;
for(let v of args) {
r += v;
}
return r;
}
const result = sum(1,2,3,4);
console.log(result);
オブジェクト
- オブジェクトはプロパティの集合であり、プロパティとは名前(キー)と値(バリュー)が対になったもの
- プロパティへのアクセスは基本的には簡潔なドット記法(.)を使い、ドット記法で書けない場合はブラケット記法([])を使う
const obj = {
key: value
}
console.log(obj.key)
console.log(obj['key'])
delete obj.key
- プロパティを初期化時以外に追加してしまうと、そのオブジェクトがどのようなプロパティを持っているかがわかりにくくなる。 そのため、できる限り作成後に新しいプロパティは追加しないほうがよい
- プロパティの存在確認にはinを使う
const obj = {
key: undefined
};
if ('key' in obj) {
}
const obj = {
"one": 1,
"two": 2,
"three": 3
};
console.log(Object.keys(obj));
console.log(Object.values(obj));
console.log(Object.entries(obj));
- オブジェクトのマージ
- 空のオブジェクトをtargetにすることで、既存のオブジェクトには影響を与えずマージしたオブジェクトを作ることができる。 そのため、Object.assignメソッドの第一引数には、空のオブジェクトリテラルを指定するのが典型的な利用方法
const objA = {a: 'a'};
const objB = {b: 'b'};
const merged = Object.assign({}, objA, objB);
console.log(merged);
- メソッドのthisはオブジェクトを参照する
- 関数のthisはグルーバルオブジェクトを参照する
- bindによりthisの参照先を変更することができる(実行しない)
- call, applyによりthisの参照先を変更することができる(実行する)
- コンストラクターへの関数の追加はメモリ効率の観点からprototypeを使う→現在はクラスで代替する
function Person(){
this.name = name;
}
Preson.prototype.hello = function(){
}
const bob = new Person('Bob', 18);
bob.hello();
if (arg instanceof Array) {
}
- getter, setterによりコンストラクターで定義した変数の前後に処理を加える
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
get name() {
return this._name;
}
set name(val) {
this._name = val;
}
}
const p = new Person('Bob', 18);
console.log(p.name)
p.name = 'Alice'
配列
const colors = [
{'color': 'red'},
{'color': 'blue'},
{'color': 'green'},
{'color': 'blue'}
]
const blueColor = colors.find((obj) => {
return obj.color === 'blue';
});
console.log(blueColor);
- 配列から真偽値を取得(配列に含まれているかどうかチェック)
const array = ["Java", "JavaScript", "Ruby"];
if (array.includes("JavaScript")) {
console.log("配列にJavaScriptが含まれている");
}
const array = ['A', 'B', 'C'];
const newArray = array.concat(['D', 'E']);
console.log(newArray);
破壊的な方法 |
非破壊な方法 |
array[index] = item |
Array.prototype.with |
Array.prototype.pop |
array.slice(0, -1)とarray.at(-1) |
Array.prototype.push |
[...array, item] |
Array.prototype.splice |
Array.prototype.toSpliced |
Array.prototype.reverse |
Array.prototype.toReversed |
Array.prototype.sort |
Array.prototype.toSorted |
Array.prototype.shift |
array.slice(1)とarray.at(0) |
Array.prototype.unshift |
[item, ...array] |
Array.prototype.copyWithin |
なし |
Array.prototype.fill |
なし |
- 破壊的なメソッドは、シンプルだが元の配列も変更してしまうため、意図しない副作用が発生しバグの原因となる可能性あり。 非破壊的なメソッドは、使い分けが必要だが元の配列を変更せずに新しい配列を返すため、副作用が発生することはない。そのため、まず非破壊的な方法で書けるかを検討し、そうではない場合に破壊的な方法を利用するとよい
- 改めて配列の繰り返し処理
const array = [1, 2, 3];
array.forEach((currentValue, index, array) => {
console.log(currentValue, index, array);
});
const array = [1, 2, 3];
const newArray = array.map((currentValue, index, array) => {
return currentValue * 10;
});
console.log(newArray);
console.log(array === newArray);
const array = [1, 2, 3];
const newArray = array.filter((currentValue, index, array) => {
return currentValue % 2 === 1;
});
console.log(newArray);
console.log(array === newArray);
文字列
- 正規表現は柔軟で便利だが、コード上から意図が消えてしまいやすい。 そのため、正規表現を扱う際にはコメントや変数名で具体的な意図を補足したほうがよい
- Stringメソッドで表現できることはStringメソッドで表現し、柔軟性や曖昧な検索が必要な場合はコメントとともに正規表現を利用する
- 文字列の置換
const str = "???";
console.log(str.replace("?", "!"));
console.log(str.replaceAll("?", "!"));
スコープ
- 内側から外側のスコープへと順番に変数が定義されているか探す仕組みのことをスコープチェーン
- クロージャー
- レキシカルスコープとはコードを書く場所によって参照できる変数が変わるスコープのこと
- ホイスティングとは変数や関数の定義をコード実行前にメモリに配置すること
- 即時関数とは宣言と同時に一度だけ実行される関数のこと
- 即時関数内でしか使えない変数や関数と、即時関数外でも使える変数や関数を定義できる
this
function fn1() {}
const fn2 = function() {};
const fn3 = () => {};
const obj = {
method1: function() {
},
method2: () => {
}
method() {
}
};
obj.method();
- Arrow Function以外におけるthisは「ベースオブジェクト。 ベースオブジェクトとはメソッドを呼ぶ際に、そのメソッドのドット演算子またはブラケット演算子のひとつ左にあるオブジェクトのこと。 ベースオブジェクトがない場合のthisはundefined」
fn();
obj.method();
- Arrow Functionにおけるthisは「自身の外側のスコープにあるもっとも近い関数のthisの値」
function outer() {
return () => {
return this;
};
}
const innerArrowFunction = outer();
console.log(innerArrowFunction());
- thisは状況によって異なる値を参照するため注意
- コンストラクタ関数内でのthisはこれから新しく作るインスタンスオブジェクト
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
- チェーンメソッドをするにはメソッドでthisを返す
class Person {
hello() {
return this;
}
introduce() {
}
const bob = new Person()
bob.hello().introduce()
}
クラス
- クラス名は大文字ではじめる
- クラスのメソッド(プロトタイプメソッド)
class クラス {
メソッド() {
}
}
class クラス {
メソッド: () => {}
メソッド: function(){}
}
const a = new クラス()
class 子クラス extends 親クラス {
}
class Japanese extends Person {
constructor(name, age, gender) {
super(name, age);
this.gender = gender;
}
hello() {
super.hello();
}
}
- クラスはコンストラクタ関数をクラス表記で書けるようにしたもの
例外処理
try {
console.log("try節:この行は実行されます");
undefinedFunction();
} catch (error) {
console.log("catch節:この行は実行されます");
console.log(error instanceof ReferenceError);
console.log(error.message);
} finally {
console.log("finally節:この行は実行されます");
}
try {
throw new Error("例外が投げられました");
} catch (error) {
console.log(error.message);
}
非同期処理
- 同期処理ではコードを順番に処理していき、ひとつの処理が終わるまで次の処理は行わない。 同期処理では実行している処理はひとつだけとなるため、とても直感的な動作となる
- 非同期処理はコードを順番に処理していくが、ひとつの非同期処理が終わるのを待たずに次の処理を評価する。 つまり、非同期処理では同時に実行している処理が複数ある
- JavaScriptでは一部の例外を除き非同期処理が並行処理(Concurrent)として扱われる。 並行処理とは、処理を一定の単位ごとに分けて処理を切り替えながら実行すること
- 並列処理とは、排他的に複数の処理を同時に実行すること
- Promiseにより非同期処理の状態や結果を表現する
function asyncPromiseTask() {
return new Promise((resolve, reject) => {
});
}
asyncPromiseTask().then(()=> {
}).catch(() => {
});
- Async Functionは必ずPromiseインスタンスを返す
async function doAsync() {
return "値";
}
doAsync().then(value => {
console.log(value);
});
- await式は右辺のPromiseインスタンスがFulfilledまたはRejectedになるまでその場で非同期処理の完了を待つ。 そしてPromiseインスタンスの状態が変わると、次の行の処理を再開する。
- await式を使うことで非同期処理が同期処理のように上から下へと順番に実行するような処理順で書ける。
async function asyncMain() {
await Promiseインスタンス;
}
- キューは実行待ちの行列となり、キューの中でタスクを管理する(非同期処理の実行順を管理)
- Promiseは簡単に可読性が上がるように非同期処理を書けるにようにしたもの
- Promiseは非同期のチェーン処理に適した書き方
new Promise(function(resolve, reject) {
resolve('hello');
}).then(function(data) {
}).catch(function(data) {
}).finally(function() {
});
- async/awaitはPromiseを直感的に記述できるようにしたもの
- asyncはPromiseを返却する関数宣言を行う
- awaitはasyncの非同期処理が完了するまで待つ
- 関数の中でawaitがある場合は必ず非同期処理となりその関数は必ずasyncを付ける
async function init() {
let val = await sleep(0);
val = await sleep(val);
val = await sleep(val);
val = await sleep(val);
}
その他
- Mapはキーと値の組み合わせからなるコレクションを扱うビルトインオブジェクト
const map = new Map();
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
map.set("key", "value1");
console.log(map.has("key"));
const results = [];
map.forEach((value, key) => {
results.push(`${key}:${value}`);
});
console.log(results);
- Setは重複する値がないことを保証した順序を持たないコレクションを扱うビルトインオブジェクト
- JSONは文字列
- JSONをオブジェクトに変換する
const json = '{ "id": 1, "name": "js-primer" }';
const obj = JSON.parse(json);
console.log(obj.id);
console.log(obj.name);
const obj = { id: 1, name: "js-primer", bio: null };
console.log(JSON.stringify(obj));
実行
const now = new Date();
console.log(now.getTime());
- ただし、JavaScriptにおける日付・時刻の処理は、標準のDateではなくライブラリを使うことが一般的
- 乱数を生成する
for (let i=0; i < 5; i++) {
console.log(Math.random());
}
import { foo, bar } from "./my-module.js";
console.log(foo);
console.log(bar);
アプリ作成時の気付き
- エントリーポイントとは、アプリケーションの中で一番最初に呼び出される部分のこと
- Fetch APIはHTTP通信を行ってリソースを取得するためのAPI
- fetch
- Promiseを返す
- thenメソッドが使える(Promiseを返すから)
- jsonメソッドが使える
const userId = "任意のGitHubユーザーID";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`);
- process.argv配列にnodeコマンドのコマンドライン引数が格納されている
- commanderを使ってコマンドライン引数をパースし扱いやすくする
const userId = "js-primer-example";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
.then(response => {
console.log(response.status);
return response.json().then(userInfo => {
console.log(userInfo);
});
});
import { program } from "commander";
program.parse(process.argv);
const filePath = program.args[0];
- Node.jsでファイルの読み書きを行うには、標準モジュールのfsモジュールを使う
- Node.jsの標準モジュールはnode:fsのようにnode:プリフィックスをつけてインポートできる。 プリフィックスを付けないfsでもインポートできるが、npmからインストールしたサードパーティ製のモジュールとの区別が明確になるため、付けておくことが推奨
// fs/promisesモジュール全体を読み込む
import * as fs from "node:fs/promises";
// fs/promisesモジュールからreadFile関数を読み込む
import { readFile } from "node:fs/promises";
- ファイルの読み書きは存在の有無や権限、ファイルシステムの違いなどによって例外が発生しやすいので、必ずエラーハンドリング処理を書く
- markedパッケージを使ってMarkdown文字列をHTML文字列に変換可能
- テスティングフレームワークのmochaパッケージをインストールし、npm testコマンドでmochaコマンドを実行することによりテストが可能
$ npm test
> mocha test/
- event.preventDefaultメソッドは、submitイベントの発生元であるフォームが持つデフォルトの動作をキャンセルする
- フォームが持つデフォルトの動作とは、フォームの内容を指定したURLへ送信するという動作
- form要素に送信先が指定されていないため、現在のURLに対してフォームの内容を送信
- 現在のURLに対してフォームの送信が行われると、結果的にページがリロードされる
- リロードさせないためにevent.preventDefaultメソッドを使う
formElement.addEventListener("submit", (event) => {
event.preventDefault();
console.log(`入力欄の値: ${inputElement.value}`);
});
- イベントが発生したことを元に処理を進める方法をイベント駆動(イベントドリブン)
- commanderでコマンドライン引数にオプションを付ける
import {program} from 'commander';
program.option('-m, --month <number>');
program.parse(process.argv);
const options = program.opts();
console.log(options.month;);
process.exit(1);
process.stdout.write('xxx');
i = i.toString().padStart(2, '0');
let fn_blank = (num) => {
return ' '.repeat(num);
}
- Webブラウザでモジュールを使用するためには、scriptタグにtype="module"属性を追加
<body>
<h1>ToDo List</h1>
<script type="module" src="./index.js"></script>
</body>
// TodoListModelはAppクラスの外からは触る必要がないためプライベートフィールドとする
export class App {
#todoListModel = new TodoListModel();
}
let result = confirm('削除OK?')
参考文献
happiness-chain教材
JavaScript入門・完全版コース/プログラミング初心者向け、コスパ最強講座
JavaScript Primer
Commander.jsを使用してNode.jsのCLI引数を処理する
Node.jsで終了ステータスコードを使うなら押さえておきたいasyncと終了ステータスコードの話
Node.jsでコンソールの表示を上書きする
【JavaScript】padStartメソッドで0詰め2桁の数字を作る関数
JSのプライベートフィールドは接頭辞に#(シャープ)を付けて表示する
JavaScriptによる削除する前に確認メッセージ
CodeMafia, 2024, 「【JS】ガチで学びたい人のためのJavaScriptメカニズム」, udemy, (2024/4/16取得,https://www.udemy.com/).