よかったところ
- 第一部でJavaScriptの基本文法(変数、配列、条件分岐、関数など)を学べます。どの項目も厳格なルールに沿ったサンプルコードが豊富でした。またNG例も多数ありコードのお作法をより詳しく知ることができました。以下のコードは変数名に使える名前のルールのサンプルコードです。
正しい例
let $; // OK: $が利用できる
let _title; // OK: _が利用できる
let jquery; // OK: 小文字のアルファベットが利用できる
let TITLE; // OK: 大文字のアルファベットが利用できる
let es2015; // OK: 数字は先頭以外なら利用できる
let 日本語の変数名; // OK: 一部の漢字や日本語も利用できる
誤った例
let 1st; // NG: 数字から始まっている
let 123; // NG: 数字のみで構成されている
- 第二部応用編でAPIを叩いたり、Todoアプリといったアプリケーションなどを作ります。実践的なコードで動かしながら第一部で学んだ基礎文法がどのようにして使われているのかをイメージすることができました。
こちらはGitHubのAPIを呼び出す処理で実装したものになります。「Get user info」 というボタンを押すとGitHubのAPIからユーザー情報を取得し表示します。
学んだところ
簡単ではありますが、ざっくりまとめてみました。詳しく知りたい方はJavaScript Primerをご覧ください。
変数と宣言
const
は、再代入できない変数の宣言とその変数が参照する値(初期値)を定義できます。
const bookTitle = "JavaScript Primer";
let
は値の再代入が可能な変数を宣言できます。const
と使い方はほとんど同じです。
let bookTitle = "JavaScript Primer";
let
で宣言した変数に対しては何度でも値の代入が可能です。
let count = 0;
count = 1;
count = 2;
count = 3;
console.log(count); // => 3
データ型
データ型を大きく分けると、プリミティブ型とオブジェクトの2つに分類されます。プリミティブ型(基本型)は、真偽値や数値などの基本的な値の型のことです。オブジェクトはプリミティブ型以外のデータであり、オブジェクト、配列、関数、クラス、正規表現、Dateなどです。
typeof
演算子を使うことで値がどのデータ型なのかを調べることができます。
- 数値型(Number)
console.log(typeof 42); // => "number"
- 文字列型(String)
console.log(typeof "JavaScript"); // => "string"
- 論理型(Boolean)
console.log(typeof true);// => "boolean"
- Null
console.log(typeof null); // => "object"
- Undefined
console.log(typeof undefined); // => "undefined
- Symbol
console.log(typeof Symbol("シンボル"));// => "symbol"
- BigInt
console.log(typeof 9007199254740992n); // => "bigint"
演算子
インクリメント演算子
インクリメント演算子(++)
は、オペランドの数値を+1する演算子です。 オペランドの前後どちらかにインクリメント演算子を置くことで、オペランドに対して値を+1した値を返します。
let num = 1;
num++;
console.log(num); // => 2
デクリメント演算子(--)
let num = 1;
num--;
console.log(num); // => 0
比較演算子
等価演算子
等価演算子(==)
は異なる型の値であった場合に、 同じ型となるように暗黙的な型変換をしてから比較します。意図しない挙動となることがあるため、暗黙的な型変換が行われる等価演算子(==)
を使うべきではありません。 代わりに、厳密等価演算子(===)
を使い、異なる型を比較したい場合は明示的に型を合わせるべきです。
//等価演算子(==)
console.log(1 == 1); // => true
// 文字列を数値に変換してから比較
console.log(1 == "1"); // => true
// "01"を数値にすると`1`となる
console.log(1 == "01"); // => true
//厳密等価演算子(===)
console.log(1 === 1); // => true
console.log(1 === "1"); // => false
不等価演算子
不等価演算子(!=)
は、2つのオペランドを比較し、等しくないならtrue
を返します。不等価演算子も、等価演算子(==)
と同様に異なる型のオペランドを比較する際に、暗黙的な型変換をしてから比較します。そのため、不等価演算子(!=)
は、利用するべきではありません。 代わりに暗黙的な型変換をしない厳密不等価演算子(!==)
を利用します。
//不等価演算子
console.log(1 != 1); // => false
console.log("JavaScript" != "ECMAScript"); // => true
console.log(1 != "1"); // => false
//厳密不等価演算子(!==)
console.log(1 !== "1"); // => true
条件(三項)演算子(?と:)
条件演算子は条件式を評価した結果がtrueならば、Trueのとき処理する式の評価結果を返します。 条件式がfalseである場合は、Falseのとき処理する式の評価結果を返します。
条件式 ? Trueのとき処理する式 : Falseのとき処理する式;
const valueA = true ? "A" : "B";
console.log(valueA); // => "A"
関数と宣言
関数とは、ある一連の手続き(文の集まり)を1つの処理としてまとめる機能です。 関数を利用することで、同じ処理を毎回書くのではなく、一度定義した関数を呼び出すことで同じ処理を実行できます。関数を定義するためにfunction
キーワードを使います。
次のコードでは、引数で受け取った値を2倍にして返すdoubleという関数を定義しています。
function double(num) {
return num * 2;
}
// `double`関数の返り値は、`num`に`10`を入れて`return`文で返した値
console.log(double(10)); // => 20
関数はオブジェクト
関数は関数オブジェクトとも呼ばれ、オブジェクトの一種です。 関数はただのオブジェクトとは異なり、関数名に()をつけることで、関数としてまとめた処理を呼び出すことができます。
function fn() {
console.log("fnが呼び出されました");
}
// 関数`fn`を`myFunc`変数に代入している
const myFunc = fn;
myFunc();
関数式
関数式とは、関数を値として変数へ代入している式のことを言います。 関数宣言は文でしたが、関数式では関数を値として扱っています。 先ほどのコードでは、関数宣言をしてから変数へ代入していましたが、最初から関数を値として定義できます。
// 関数式
// 関数式は変数名で参照できるため、"関数名"を省略できる
const 変数名 = function() {
// 関数を呼び出したときの処理
// ...
return 関数の返り値;
};
[ES2015] Arrow Function
関数式にはfunction
キーワードを使った方法以外に、Arrow Function
と呼ばれる書き方があります。 名前のとおり矢印のような=>(イコールと大なり記号)を使い、無名関数(名前を持たない関数)を定義する構文です。
// Arrow Functionを使った関数定義
const 変数名 = () => {
// 関数を呼び出したときの処理
// ...
return 関数の返す値;
};
コールバック関数
引数として渡される関数のことをコールバック関数と呼びます。以下のコードを実行すると、"Hello World"
がコンソールに出力されます。ここではsayHello関数がコールバック関数で、executeCallback
関数の中で呼び出されています。
// コールバック関数を受け取る関数
function executeCallback(callback) {
callback();
}
// コールバック関数
function sayHello() {
console.log("Hello World");
}
// コールバック関数を引数として渡す
executeCallback(sayHello);
メソッド
オブジェクトのプロパティである関数をメソッドと呼びます。関数と機能的的な違いはありませんが、定義されている場所により呼び方が変わります。
オプジェクト名.メソッド名
で呼び出せます。
// 関数は独立しています
function method(){
console.log("this is method");
}
method(); // => "this is method"
//メソッドはオブジェクトの中で定義されています
const obj = {
method() {
return "this is method";
}
};
console.log(obj.method()); // => "this is method"
反復処理
配列のforEachメソッド
配列にはforEach
メソッドというfor
文と同じように反復処理を行うメソッドがあります。先ほどのコールバック関数と組み合わせて使うこともできます。
const array = [1, 2, 3];
// forEachは"コールバック関数"を受け取る高階関数
array.forEach(コールバック関数);
forEach
メソッドのコールバック関数には、配列の要素が先頭から順番に渡されて実行されます。 つまり、コールバック関数の仮引数であるcurrentValue
には、1から3の値が順番に渡されます。
const array = [1, 2, 3];
array.forEach(currentValue => {
console.log(currentValue);
});
// 1
// 2
// 3
// と順番に出力される
mapメソッド
配列の要素を順番にコールバック関数へ渡し、コールバック関数が返した値から新しい配列を返す非破壊的なメソッドです。 配列の各要素を加工したい場合に利用します。
const array = [1, 2, 3];
// 各要素に10を乗算した新しい配列を作成する
const newArray = array.map((currentValue, index, array) => {
return currentValue * 10;
});
console.log(newArray); // => [10, 20, 30]
// 元の配列とは異なるインスタンス
console.log(array === newArray); // => false
reduce
累積値(アキュムレータ)と配列の要素を順番にコールバック関数へ渡し、1つの累積値を返します
const array = [1, 2, 3];
// すべての要素を加算した値を返す
// accumulatorの初期値は`0`
//accumulatorは累積値、currentValueは現在処理している配列の要素
const totalValue = array.reduce((accumulator, currentValue, index, array) => {
return accumulator + currentValue;
}, 0);
//1回目 accumulator=0, currentValue=1 returnした値0+1
//2回目 accumulator=1, currentValue=2 returnした値1+2
//3回目 accumulator=3, currentValue=3 returnした値3+3
// 0 + 1 + 2 + 3という式の結果が返り値になる
console.log(totalValue); // => 6
配列のfilterメソッド
配列から特定の値だけを集めた新しい配列を作るにはfilterメソッドを利用できます。filterメソッドには、配列の各要素をテストする処理をコールバック関数として渡します。 コールバック関数がtrueを返した要素のみを集めた新しい配列を返します。
function isEven(num) {
// 2で割れる数の新しい配列を返す
return num % 2 === 0;
}
const array = [1, 2, 3, 4, 5];
// isEvenがコールバック関数
console.log(array.filter(isEven)); // => [2, 4]
for...in文
for (プロパティ in オブジェクト) {
実行する文;
}
次のコードではobjのプロパティ名をkey変数に代入して反復処理をしています。 objには、3つのプロパティ名があるため3回繰り返されます
const obj = {
"a": 1,
"b": 2,
"c": 3
};
// 注記: ループのたびに毎回新しいブロックに変数keyが定義されるため、再定義エラーが発生しない
for (const key in obj) {
const value = obj[key];
console.log(`key:${key}, value:${value}`);
}
// "key:a, value:1"
// "key:b, value:2"
// "key:c, value:3"
オブジェクトに対する反復処理のためにfor...in文は有用に見えますが、多くの問題を持っています。 for...in文は、対象となるオブジェクトのプロパティを列挙する場合に、親オブジェクトまで列挙可能なものがあるかを探索して列挙します。 そのため、オブジェクト自身が持っていないプロパティも列挙されてしまい、意図しない結果になる場合があります。
配列の内容に対して反復処理を行う場合は、for文やforEach
メソッド、後述するfor...of文を使うのが推奨されます。
[ES2015] for...of文
for...of文では、iterableオブジェクトから次の返す値を1つ取り出し、variableに代入して反復処理を行います。Arrayはiterableオブジェクトです。
for (variable of iterable) {
実行する文;
}
次のようにfor...of文で、配列から値を取り出して反復処理を行えます。 for...in文とは異なり、インデックス値ではなく配列の値を列挙します。
const array = [1, 2, 3];
for (const value of array) {
console.log(value);
}
// 1
// 2
// 3
オブジェクト
// プロパティを持つオブジェクトを定義する
const obj = {
// キー: 値
"key": "value"
};
const languages = {
ja: "日本語",
en: "英語"
};
console.log(languages.ja); // => "日本語"
文字列
[ES2015] タグつきテンプレート関数
テンプレートとなる文字列に対して一部分だけを変更する処理を行う方法として、タグつきテンプレート関数があります。テンプレートはバックティック(``) を使って定義します。
// スプレッド構文を使って、可変長引数として書けます。
function tag(strings, ...values) {
// stringsは文字列のパーツが${}で区切られた配列となる。``で囲った文字が入る
console.log(strings); // => ["私は "です。" 歳です。"]
// valuesには${}の評価値が順番に入る
console.log(values); // => ["田中", 12]
}
const name = "田中";
const age = 12;
// ()をつけずにテンプレートを呼び出すこともできます
tag`私は ${name}です。${age}歳です。`;
タグつきテンプレートの内容をそのまま結合して返す、String.rawメソッドもあります。エスケープシーケンスを無視して文字列をそのままの形で作成する特別なタグ関数です。
const name = "田中";
const age = 12;
const str = String.raw`私は ${name}です。${age}歳です。`;
console.log(str); // "私は 田中です。12歳です。"を出力します
関数とスコープ
ローカルスコープ(関数スコープ)
関数の内部で定義された変数や関数はその関数の中でしか参照・変更できません。
function fn() {
const x = 1;
// fn関数のスコープ内から`x`は参照できる
console.log(x); // => 1
}
fn();
// fn関数のスコープ外から`x`は参照できないためエラー
console.log(x); // => ReferenceError: x is not defined
ブロックスコープ
変数がそのブロック({
と}
で囲まれた範囲)内部だけで利用可能となり、ブロック外部からは参照や操作ができないというスコープです。このブロック内で、let
またはconst
を使って宣言するときに作られます。
// ブロック内で定義した変数はスコープ内でのみ参照できる
{
const x = 1;
console.log(x); // => 1
}
// スコープの外から`x`を参照できないためエラー
console.log(x); // => ReferenceError: x is not defined
このようにif
文やwhile
文などと一緒に使用します。
// if文のブロック内で定義した変数はブロックスコープの中でのみ参照できる
if (true) {
const x = "inner";
console.log(x); // => "inner"
}
console.log(x); // => ReferenceError: x is not defined
グローバルスコープはプログラムのどこからでも参照・変更することができます。
// グローバル変数はどのスコープからも参照できる
const x = "グローバル";
// ブロックスコープ
{
// ブロックスコープ内には該当する変数が定義されてない -> 外側のスコープへ
console.log(x); // => "グローバル"
}
// 関数スコープ
function fn() {
// 関数ブロックスコープ内には該当する変数が定義されてない -> 外側のスコープへ
console.log(x); // => "グローバル"
}
fn();
クロージャー
クロージャーとは「外側のスコープにある変数への参照を保持できる」という関数が持つ性質のことです。関数が状態を持てるのでグローバルの汚染を防止し、コードの保守性を高めることができます。
JavaScriptはガベージコレクションを持つ言語で、メモリ管理は自動的に行われます。ガベージコレクションは、どこからも参照されていないメモリ(不要なデータ)を自動的に解放します。しかし、クロージャーによって参照されている変数は、その関数が存在し続ける限りメモリから解放されません。
// `increment`関数を定義して返す関数
function createCounter() {
let count = 0;
// `increment`関数は`count`変数を参照
function increment() {
count = count + 1;
return count;
}
return increment;
}
// `myCounter`は`createCounter`が返した関数を参照
const myCounter = createCounter();
myCounter(); // => 1
myCounter(); // => 2
// 新しく`newCounter`を定義する
const newCounter = createCounter();
newCounter(); // => 1
クロージャー関数は実行するたびに新しく定義されるため、異なる変数に代入した場合は、それぞれ異なる状態を持ちます
クロージャーはグローバル変数の使用を減らし、コードの保守性を向上させるという重要な役割も果たします。グローバル変数を多用すると、コードのどの部分がその変数を変更したのかが分かりにくくなるため、バグを生じやすくなります。クロージャーを使用することで、変数のスコープを制限し、そのような問題を防ぐことができます。
関数とthis
関数宣言や関数式におけるthis
関数がただの関数として(つまりメソッドやコンストラクタとしてではなく)呼び出された場合、this
の値はundefined
となります。これは関数fn1がどのオブジェクトにも所属していないため、this
には関連付けられるオブジェクトが存在しないからです。この結果、関数内のthis
はundefined
を返します。
//"use strict";はJavaScriptの厳格モードを有効にする指示子
"use strict";
function fn1() {
return this;
}
console.log(fn1()); // => undefined
メソッド呼び出しにおけるthis
メソッドの場合は、そのメソッドが何かしらのオブジェクトに所属しています。
const obj = {
// 短縮記法で定義したメソッド
method() {
return this;
}
};
// メソッド呼び出しの場合、それぞれの`this`はベースオブジェクト(`obj`)を参照する
// メソッド呼び出しの`.`の左にあるオブジェクトがベースオブジェクト
console.log(obj.method()); // => obj
なのでメソッドの中から同じオブジェクトに所属する別のプロパティをthisで参照できます。オブジェクトperson内でsayAge
というメソッドがあります。このメソッド内でthis
を使用すると、this
はそのメソッドを含むオブジェクト、つまりpersonを指します。
const person = {
age: 30,
sayAge: function() {
// `this`は`person`を指し、`this.age`は`person.age`と同じ
return this.age;
}
};
// `person.age`を出力する
console.log(person.sayAge()); // => 30
thisが問題となるパターン
this
は非常に便利な概念ですが、その振る舞いは関数がどのように呼び出されるかによって変わるため、問題や混乱を引き起こすことがあります。
thisを含むメソッドを変数に代入した場合
say( );を呼び出してperson.age
の'30の出力を期待します。しかし、 このsay
関数はどのオブジェクトにも所属していないのでthis
はundefinedとなります
const person = {
age: 30,
sayAge: function() {
// `this`は`呼び元によって異なる
return this.age;
}
};
//person.sayAgeをsay変数に代入する
const say = person.sayAge
say();// => TypeError: Cannot read property 'age' of undefined
コールバック関数の中のthis
setTimeout
のコールバック関数がグローバルコンテキストで実行されるため、this.valueはundefined
を返します。
"use strict";
// strict modeを明示しているのは、thisがグローバルオブジェクトに暗黙的に変換されるのを防止するため。これがないとそのthisはグローバルオブジェクト(ブラウザ環境ではwindow)を指すようになります
const myObj = {
value: 'Hello, World!',
print: function() {
setTimeout(function() {
//コールバック関数として呼び出すとき、この関数にはベースオブジェクトはありません。
//そのためcallback関数のthisはundefinedとなります。
console.log(this.value);
}, 1000);
}
};
myObj.print(); // 期待する出力は 'Hello, World!' ですが、実際は undefined になります
対処法としてArrow Functionでコールバック関数を扱う方法があります
Arrow Function
はこの暗黙的なthis
の値を受け取りません。 アロー関数内のthis
は、print関数が呼び出された時のthis
(つまりmyObj)を参照します。
const myObj = {
value: 'Hello, World!',
print: function() {
setTimeout(() => {
// アロー関数では`this`が`print`関数の`this`を継承します。
// そのため、ここでの`this`は`myObj`を指します。
// this.value = myObj.valueとなります
console.log(this.value);
}, 1000);
}
};
myObj.print(); // 'Hello, World!' を出力します。
this
は呼び出し方によって異なる値を参照する性質を持っているので、とても難しいです。Arrow Function
でコールバック関数を扱う方法以外にも、call、apply、bind
メソッドやthis
を一時変数へ代入する対処法があります。
クラス
データ(プロパティ)とそれに関連する操作(メソッド)をまとめるための仕組みです。クラスは「設計図」や「モデル」のようなもので、実際のデータ(インスタンス)を作るための基本的な構造を定義します。 クラスからはインスタンスと呼ばれるオブジェクトを作成でき、インスタンスはクラスに定義した動作を継承し、状態は動作によって変化します
// "Person"という名前のクラスを定義します
class Person {
// コンストラクタは、クラスから新しいインスタンスを生成するときに実行される特別なメソッドです
// this.nameはクラス内で定義されたプロパティです。つまり以下のように
//const john = new Person("John Doe", 25);の場合はthisはjohnになります。
constructor(name, age) {
this.name = name;
this.age = age; // this.ageも同様です
}
// クラスにはメソッドも定義できます
sayHello() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
// クラスからインスタンスを生成します(具体的な"人"を作ります)。
//`new`演算子で引数とともに呼び出します
const John = new Person("John Doe", 25);
const Kebin = new Person("Kebin", 20);
// それぞれのインスタンスは異なるオブジェクト
console.log(john === Kebin); // => false
// インスタンスのメソッドを呼び出します
John.sayHello(); // "Hello, my name is John Doe and I'm 25 years old."を出力します
Kebin.sayHello(); // "Hello, my name is Kebin and I'm 20 years old."を出力します
クラスフィールド
ES2022で、クラスのインスタンスが持つプロパティの初期化をわかりやすく宣言的にする構文として、クラスフィールド構文が追加されました。
class MyClass {
//定義したプロパティはクラスをインスタンス化した後に外からも参照できます。
// そのため、Publicクラスフィールドと呼ばれます
myPublicField = "public field"; // publicクラスフィールド
//一方で外からアクセスされたくないインスタンスのプロパティも存在します。
#myPrivateField = "private field"; // privateクラスフィールド
}
printFields() {
console.log(this.myPublicField); // "public field"
console.log(this.#myPrivateField); // "private field"
}
}
const myInstance = new MyClass();
myInstance.printFields();
クラスフィールドとthisは、Arrow Functionと組み合わせると強力
Arrow Function
で定義した関数におけるthis
は、どのような呼び出し方をしても変化しません。つまりthis
はクラスのインスタンスを参照し続けます。
class MyClass {
myField = "Hello, World!";
// アロー関数を使うことで、このメソッドの`this`は常にこのクラスのインスタンスを参照します
myMethod = () => {
console.log(this.myField);
}
}
const myInstance = new MyClass();
myInstance.myMethod(); // "Hello, World!"
// コールバック関数として使用する場合でも、thisはmyInstanceを参照し続けます
setTimeout(myInstance.myMethod, 1000); // "Hello, World!"を出力
非同期処理
非同期処理はコードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を評価します。 つまり、非同期処理では同時に実行している処理が複数あります。
Promise
Promise
は、JavaScriptにおける非同期処理の一つの形式を表現するオブジェクトです。Promise
は、「未完了の処理」や「その処理の結果」を表すことができます。一般的には、サーバからのデータ取得やタイマー処理など、時間を要する処理を扱う際に利用されます。
Promiseは3つの状態を持ちます
- Fulfilled(履行済み)t
非同期処理が成功したとき、Promiseはfulfilled(履行済み)状態になります。 - Rejected(拒否済み)
非同期処理が失敗したとき、Promiseはrejected(拒否済み)状態になります。 - Pending(未定)
FulfilledまたはRejectedではない状態
new Promise
でインスタンスを作成したときの初期状態
そしてthen
メソッドを使って非同期処理の結果を扱います。then
メソッドは2つの引数、すなわち2つのコールバック関数を受け取ることができます。1つ目は非同期処理が成功したとき(fulfilled)に呼び出され、2つ目は非同期処理が失敗したとき(rejected)に呼び出されます。
// `Promise`インスタンスを作成。Promiseの状態はPending
const promise = new Promise((resolve, reject) => {
// 非同期の処理が成功したときはresolve()を呼ぶ
// 非同期の処理が失敗したときにはreject()を呼ぶ
});
const onFulfilled = () => {
console.log("resolveされたときに呼ばれる");
};
const onRejected = () => {
console.log("rejectされたときに呼ばれる");
};
// `then`メソッドで成功時と失敗時に呼ばれるコールバック関数を登録
promise.then(onFulfilled, onRejected);
Promiseチェーン
非同期処理が終わったら次の非同期処理というように、複数の非同期処理を順番に扱いたい場合もあります。then
で繋げて後続の処理を書くことができます。
function delay(message, time) {
// 非同期操作を模倣するためのPromiseを返します
return new Promise(resolve => {
setTimeout(() => {
console.log(message); // 非同期操作が完了したらメッセージを表示
resolve(); // Promiseをresolve(成功)状態にします
}, time);
});
}
// Promiseチェーンを作成します
delay("Step 1", 2000) // 2秒後に"Step 1"と表示
.then(() => delay("Step 2", 1000)) // さらに1秒後に"Step 2"と表示
.then(() => delay("Step 3", 500)) // さらに0.5秒後に"Step 3"と表示
.then(() => delay("Step 4", 500)); // さらに0.5秒後に"Step 4"と表示
しかし、then
を繋げてpromise
のネストが深くなるとコードがの可読性が悪くなり、より複雑になります。そのため、async/await
構文が導入され、より直感的な非同期コードの記述が可能となりました。
async/await
Async/awaitは非同期処理を同期処理のように簡潔に記述できます。 asyncは、「非同期関数」を表します。awaitは、asyncの中でのみ使える構文で、指定したPromiseの結果(resolveかrejectか)が分かるまで、次の行の処理の実行を待ってくれます。
function delay(message, time) {
// 非同期操作を模倣するためのPromiseを返します
return new Promise(resolve => {
setTimeout(() => {
console.log(message); // 非同期操作が完了したらメッセージを表示
resolve(); // Promiseをresolve(成功)状態にします
}, time);
});
}
// async function内で非同期処理を行います
async function processSteps() {
await delay("Step 1", 2000); // 2秒後に"Step 1"と表示
await delay("Step 2", 1000); // さらに1秒後に"Step 2"と表示
await delay("Step 3", 500); // さらに0.5秒後に"Step 3"と表示
await delay("Step 4", 500); // さらに0.5秒後に"Step 4"と表示
}
// 非同期処理を実行します
processSteps();
難しかったところ
- 第一部:基本文法ではスコープ、コールバック関数、クラス、非同期処理、this辺りが特に難しかったです。頭の中を整理する意味も含めて上記のようにコードにコメントをしました。
- 第二部でTodoリストを作成しましたが、1周では分からなかったので、ハンズオンで3周やりました。TodoリストとTodoアイテムをという情報をJavaScriptクラスとしてモデル化して管理しましたが、それぞれのモデルの状態が変化した時(Todoリストにアイテムを追加や削除)に動作する
chage
イベント(親要素に子要素を追加)が発生するタイミングが分からずに苦戦しました。