"にわかJavascripter"による学習メモ
let
にすること
ループ処理のときは、変数宣言をpaizaでの例題がvar
で実装されていて、にわかJavascripterだった自分がconst
に全部書き換えていたところ下記でつまづいてしまった。
問題として、Hello,world!
を10回表示するプログラムが出題されていたので、以下のプログラムを最初は書いていた。
process.stdin.resume();
process.stdin.setEncoding('utf8');
const num = 0;
while (num < 10) {
console.log("Hello,world!");
num += 1;
}
↑これだと、const
が原因でnum
に値が代入できない!
下記の記事からの引用ですが、以下の表がわかりやすかった。
let | var | const | |
---|---|---|---|
再宣言 | 不可能 | 可能 | 不可能 |
再代入 | 可能 | 可能 | 不可能 |
スコープ | ブロックスコープ | 関数スコープ | ブロックスコープ |
繰り返し構文 | 可能 | 可能 | 不可能 |
なので、変数宣言をlet
にしたところ成功した!
process.stdin.resume();
process.stdin.setEncoding('utf8');
let num = 0;
while (num < 10) {
console.log("Hello,world!");
num += 1;
}
👉️ つまり、再代入をするなら、let
を使うべき!
console.log
ではなくconsole.table
【JavaScriptでアルゴリズムのコレ便利①】 例えば、下記のような入力があったとします。
1
2 3
test
これを :thinking_face: 「ちゃんと取れているかな〜」と確認したいときに、console.table(標準入力);
にすると視覚的にわかりやすい!
実際に書いてみると、、、、
function Main(input) {
input = input.split("\n");
console.table(input);
}
Main(require('fs').readFileSync('/dev/stdin', 'utf8'));
そうすると、こんな出力になる!
┌─────────┬────────┐
│ (index) │ Values │
├─────────┼────────┤
│ 0 │ '1' │
│ 1 │ '2 3' │
│ 2 │ 'test' │
│ 3 │ '' │
└─────────┴────────┘
【JavaScriptでアルゴリズムのコレ便利②】 空白を入れて出力は区切るだけ
例えば、こんなコードがあったとします。これを、6 test
という感じに「半角スペースを入れて出力したい!」って時に
const num = 6;
const str = "test";
console.log();
のカッコ内でコンマで区切るだけで良い。
const num = 6;
const str = "test";
console.log(num, str);
6 test // ←半角スペースがちゃんと入っている!
何が良いかって、Rubyだと明示的に空白を入れないといけない! 😠
num = 6
str = "test"
puts num.to_s + " " + str
【JavaScriptでアルゴリズムのコレ便利③】 Stringで出た数値をIntにしたい
例えば、こんなコードがあったとします。
const num = "6";
🤔「Stringではなく、数値(Int)で出したいんだよなあ・・・。」ってときは、冒頭に+
をつけるだけ!
const num = 6;
console.log(+num);
6 // ←ちゃんと数値!
これを 「単項プラス演算子」 というらしい。数値以外にも反映できるのが素晴らしくスマート!
配列の書き出し方
const userNames = ['Alice', 'Bob', 'Charlie'];
for (let i = 0; i < userNames.length; i++){
console.log(userNames[i]);
}
userNames.forEach(function(name) {
console.log(name);
});
for (const name of userNames) {
console.log(name);
}
オブジェクトの書き出し方
const user = {
id: 101,
name: 'Bob',
email: 'bob@example.com',
age: 30
};
ユーザー名:Bob, メールアドレス:bob@example.com
console.log("ユーザー名:"+user.name+", メールアドレス:"+user.email);
console.log(`ユーザー名:${user.name}, メールアドレス:${user.email}`);
const { name, email } = user;
console.log(`ユーザー名: ${name}, メールアドレス: ${email}`);
オブジェクトの変換
const products = [
{ id: 'p001', name: '高性能コーヒーメーカー', price: 9800, category: 'kitchen' },
{ id: 'p002', name: '静音設計ケトル', price: 6500, category: 'kitchen' },
{ id: 'p003', name: 'レトロ調トースター', price: 8200, category: 'kitchen' },
{ id: 'p004', name: 'コードレス掃除機', price: 15000, category: 'home' }
];
期待するのは、あとで読みやすいように、下記の出力をdisplayProducts
という配列に入れて出力してね。というもの。
[
'高性能コーヒーメーカー: 9800円',
'静音設計ケトル: 6500円',
'レトロ調トースター: 8200円',
'コードレス掃除機: 15000円'
]
自分で書いたコードはこう。
const displayProducts = [];
products.forEach(product => {
displayProducts.push(`${product.name}: ${product.price}円`)
});
console.log(displayProducts);
でも、このコードだと、「一度、空配列を作る」→「別のところでpush」なので行が空いてしまって読みづらい(この間にコードを追記される懸念もある)。
なので、書き換えるとこう。
const displayProducts = products.map(product => {
return `${product.name}: ${product.price}円`;
});
console.log(displayProducts);
↑map
を使うことで、displayProducts
の定義がわかりやすく読める。
async/awaitとtry/catchの組み合わせの書き方
function fetchProduct(productId) {
console.log(`サーバーに商品ID「${productId}」のデータをリクエストしています...`);
return new Promise((resolve, reject) => {
setTimeout(() => {
// 実行するたびに、成功するか失敗するかがランダムに変わります
if (Math.random() > 0.5) {
const productData = {
id: productId,
name: '新世代スマートウォッチ',
price: 35800
};
console.log("...データ取得に成功しました。");
resolve(productData); // 成功した場合は、resolveでデータを渡す
} else {
console.log("...データ取得に失敗しました。");
reject(new Error('商品在庫が見つかりませんでした。')); // 失敗した場合は、rejectでエラーを渡す
}
}, 100);
});
}
async function main() {
try {
const productData = await fetchProduct('p-001');
console.log(`商品名:${productData.name}, 価格:${productData.price}円`);
} catch (e) {
console.log(`エラーが発生しました:${e.message}`);
}
}
main();
サーバーに商品ID「p-001」のデータをリクエストしています...
...データ取得に成功しました。
商品名: 新世代スマートウォッチ, 価格: 35800円
サーバーに商品ID「p-001」のデータをリクエストしています...
...データ取得に失敗しました。
エラーが発生しました: 商品在庫が見つかりませんでした。
Promise.allとasync/awaitの使い方
// 1. ユーザーのプロフィールを取得する(0.5〜1.5秒かかる)
function fetchUserProfile(userId) {
console.log(`[API] ユーザー(${userId})のプロフィールを取得開始...`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`[API] プロフィール取得完了。`);
resolve({
id: userId,
name: 'Sato',
email: 'sato@example.com'
});
}, Math.random() * 100 + 50);
});
}
// 2. ユーザーの投稿一覧を取得する(0.5〜1.5秒かかる)
function fetchUserPosts(userId) {
console.log(`[API] ユーザー(${userId})の投稿一覧を取得開始...`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`[API] 投稿一覧取得完了。`);
resolve([
{ postId: 'p001', content: '今日のランチは最高でした!' },
{ postId: 'p002', content: '新しいPCを買いました。' }
]);
}, Math.random() * 100 + 50);
});
}
{
user: { id: 'user-123', name: 'Sato', email: 'sato@example.com' },
posts: [
{ postId: 'p001', content: '今日のランチは最高でした!' },
{ postId: 'p002', content: '新しいPCを買いました。' }
]
}
async function main() {
try {
const [user, posts] = await Promise.all([fetchUserProfile('user-123'), fetchUserPosts('user-123')]);
const dashboardData = {
user: user,
posts: posts
};
console.log(dashboardData);
} catch {
console.log("APIの取得にエラーが発生しました");
}
}
main();
for文の中でawaitを使う・filterでincludesで絞り込み
function fetchLatestPostIds() {
console.log("API: 最新記事のIDリストを取得しています...");
return new Promise(resolve => {
setTimeout(() => {
// 'id-003'は存在しないIDだと仮定する
resolve(['id-001', 'id-002', 'id-003', 'id-004', 'id-005']);
}, 50);
});
}
function fetchPostDetails(postId) {
console.log(`API: 記事ID「${postId}」の詳細データを取得しています...`);
return new Promise((resolve, reject) => {
const postDatabase = {
'id-001': { title: 'Reactの新しい状態管理法', tags: ['React', 'JavaScript'] },
'id-002': { title: 'CSS in JSの未来', tags: ['CSS'] },
'id-004': { title: 'TypeScriptと非同期処理', tags: ['TypeScript', 'JavaScript'] },
'id-005': { title: 'Promise.allの便利な使い方', tags: ['JavaScript', '非同期処理'] },
};
setTimeout(() => {
if (postDatabase[postId]) {
resolve(postDatabase[postId]);
} else {
reject(new Error(`エラー: 記事ID「${postId}」は存在しません。`));
}
}, 70);
});
}
エラーが発生しました: エラー: 記事ID「id-003」は存在しません。
[
'Reactの新しい状態管理法',
'TypeScriptと非同期処理',
'Promise.allの便利な使い方'
]
async function main() {
const id_lists = await fetchLatestPostIds();
const successfulPosts = [];
for (const id of id_lists) {
try {
const post = await fetchPostDetails(id);
successfulPosts.push(post);
} catch (e) {
console.log(e.message);
}
}
const jsPosts = successfulPosts.filter(post => post.tags.includes('JavaScript'));
// console.log(jsPosts);
const jsPostsTitle = jsPosts.map(post => post.title);
console.log(jsPostsTitle);
}
main();
reduce
で対応する
入力情報が増えた時のためにconst tasks = [
{ id: 1, title: 'ユーザー認証機能の実装', status: 'In Progress' },
{ id: 2, title: 'ヘッダーコンポーネントの作成', status: 'To Do' },
{ id: 3, title: 'データベースのバックアップ', status: 'Done' },
{ id: 4, title: '決済処理のAPI連携', status: 'In Progress' },
{ id: 5, title: '単体テストの追加', status: 'To Do' },
{ id: 6, title: '利用規約ページの作成', status: 'Done' },
];
{
'To Do': [
{ id: 2, title: 'ヘッダーコンポーネントの作成', status: 'To Do' },
{ id: 5, title: '単体テストの追加', status: 'To Do' }
],
'In Progress': [
{ id: 1, title: 'ユーザー認証機能の実装', status: 'In Progress' },
{ id: 4, title: '決済処理のAPI連携', status: 'In Progress' }
],
'Done': [
{ id: 3, title: 'データベースのバックアップ', status: 'Done' },
{ id: 6, title: '利用規約ページの作成', status: 'Done' }
]
}
単なるループ処理で書けるじゃん!・・・と思ったので、下記のように書いた。
const toDoTask = [];
const inProgressTask = [];
const doneTask = [];
tasks.forEach(task =>{
if (task.status === "In Progress") {
inProgressTask.push(task);
} else if (task.status === "To Do") {
toDoTask.push(task);
} else {
doneTask.push(task);
}
});
const groupedTask = {
'To Do': toDoTask,
'In Progress': inProgressTask,
'Done': doneTask
}
console.log(groupedTask);
↑でも、こうすると、statusに新しい要素(例:Reviewing
)が出たときに対応できない。
なので、変更していく。
const groupedTask = tasks.reduce((grouped, task) => {
const status = task.status;
// console.log(status);
// 'In Progress'とかの箱がなければ作る
if (!grouped[status]) {
grouped[status] = [];
}
// 作られている箱に追加する
grouped[status].push(task);
// 累積値を更新
return grouped
}, {});
console.log(groupedTask);
↑こうすると、箱はもともとのtask
に準じる形になるので、対応できる!
クロージャー
単にカウントアップするだけのプログラムで、下記のようなコード書くとcount
が他の関数にやられてしまい、プログラムが狂ってしまう。
let count = 0;
function createCounter() {
return ++count;
}
console.log(createCounter()); // 1
console.log(createCounter()); // 2
count = 100; // ←ここで書き換えられてしまう。。。
console.log(createCounter()); // 101
そうではなく、「クロージャー」という関数外から干渉できなくなる仕組みを使うと良い。
// 「クロージャー」は、子関数を親関数で囲むことによって、変数の値を保持できる仕組み
// 親関数
function createCounter(initialValue = 0) {
let count = initialValue;
// 子関数
return function() {
count ++;
return count;
};
}
// 【動作確認】
// 1. 最初のカウンター(A)を作成(初期値なしなので0からスタート)
const counterA = createCounter();
console.log(counterA()); // 1
console.log(counterA()); // 2
// 2. 別のカウンター(B)を初期値10で作成
const counterB = createCounter(10);
console.log(counterB()); // 11
console.log(counterB()); // 12
// 3. counterAを再度呼び出しても、Bの影響を受けずに自分のカウントを覚えていることを確認
console.log(counterA()); // 3 (←保持されている!)