🛠️ JavaScript関数・メソッド 基礎ガイド

Node-RED Functionノードで使う必須テクニック

このガイドでは、Node-REDの基本ノードでは表現しにくく、Functionノードで記述することの多いJavaScriptの関数・メソッドを紹介します。

📌 このガイドで学べること:

📦 1. 変数定義(let, const, var)

Functionノードでコードを書く際、最初に覚えるべきは変数の定義方法です。

🤔 なぜ変数が必要なの?

変数とは、データに名前を付けて一時的に保管しておく箱のようなものです。 料理に例えると、材料を入れておく「ラベル付きのボウル」と同じ役割を果たします。

🍳 料理の下ごしらえに例えると:

もしボウル(変数)がなかったら、切った材料がバラバラになって「どれが何だっけ?」と混乱してしまいます。 変数を使うことで、データを整理して、後から名前で呼び出せるようになります。

// 変数がないと... msg.payload.sensors[0].temperature * 9 / 5 + 32 // 何を計算してるか分かりにくい // 変数を使うと... const celsius = msg.payload.sensors[0].temperature; // 摂氏温度を取り出す const fahrenheit = celsius * 9 / 5 + 32; // 華氏に変換 // 処理の意図が明確になる!

📌 変数を使うメリット:

📝 変数定義の3つのキーワード

📝 変数定義キーワード
キーワード 再代入 再宣言 スコープ 推奨度
const ❌ 不可 ❌ 不可 ブロック ⭐⭐⭐ 最推奨
let ✅ 可能 ❌ 不可 ブロック ⭐⭐ 推奨
var ✅ 可能 ✅ 可能 関数 ⭐ 非推奨
// ===== const: 再代入不可(最も推奨)===== const PI = 3.14159; // 定数 const sensorId = "sensor_A"; // 変更しない値 const config = { timeout: 5000, retry: 3 }; // オブジェクト // PI = 3.14; // エラー!再代入できない // ただし、オブジェクトの中身は変更可能 config.timeout = 10000; // OK // ===== let: 再代入可能(値が変わる場合)===== let count = 0; count++; // OK: 1になる let temperature = 25.5; temperature = 26.0; // OK: 再代入可能 let message; // 後で代入することも可能 message = "Hello"; // ===== var: 古い書き方(使わない)===== // var は予期しない動作を引き起こすことがあるため非推奨 var oldStyle = "避けるべき";

📌 実践的なルール:

🔧 Node-REDでの変数定義例

// Functionノードでの典型的な変数定義 // 入力データの取得(const推奨:通常変更しない) const payload = msg.payload; const topic = msg.topic; const sensors = msg.payload.sensors; // 配列 // 設定値(const推奨) const THRESHOLD = 30; const MAX_RETRY = 3; // 計算結果を格納(let:値が変わる場合) let total = 0; let status = "unknown"; // ループ処理 for (let i = 0; i < sensors.length; i++) { total += sensors[i].value; } // 条件分岐で値が変わる if (total > THRESHOLD) { status = "warning"; } else { status = "normal"; } msg.payload = { total, status }; return msg;

📚 2. 配列操作メソッド

IoTデータ処理では、センサーデータの配列を操作することが非常に多いです。 Node-REDの基本ノードでは難しい、柔軟な配列処理をFunctionノードで実現できます。

map()

各要素を変換して新しい配列を作成

[1,2,3].map(x => x * 2) → [2, 4, 6]

filter()

条件に合う要素だけを抽出

[1,2,3,4].filter(x => x > 2) → [3, 4]

find()

条件に合う最初の要素を取得

[1,2,3].find(x => x > 1) → 2

reduce()

配列を1つの値に集約

[1,2,3].reduce((a,b) => a+b, 0) → 6

🎯 map() - 配列の各要素を変換

配列.map(要素 => 変換処理)
// map(): 各要素を変換した新しい配列を作成 // 例1: 数値を2倍にする const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); // 結果: [2, 4, 6, 8, 10] // 例2: センサーデータから温度だけを抽出 const sensors = [ { id: "A", temperature: 25, humidity: 60 }, { id: "B", temperature: 28, humidity: 55 }, { id: "C", temperature: 22, humidity: 70 } ]; const temperatures = sensors.map(s => s.temperature); // 結果: [25, 28, 22] // 例3: データ形式を変換 const converted = sensors.map(s => ({ name: s.id, temp: s.temperature, status: s.temperature > 25 ? "高温" : "正常" })); // 結果: [{name:"A", temp:25, status:"正常"}, ...] // 例4: インデックスも使用 const withIndex = sensors.map((s, index) => ({ ...s, order: index + 1 })); // 結果: [{id:"A", temperature:25, humidity:60, order:1}, ...]

🎯 filter() - 条件に合う要素を抽出

配列.filter(要素 => 条件)
// filter(): 条件を満たす要素だけの配列を作成 // 例1: 偶数だけを抽出 const numbers = [1, 2, 3, 4, 5, 6]; const evens = numbers.filter(n => n % 2 === 0); // 結果: [2, 4, 6] // 例2: 高温センサーだけを抽出 const sensors = [ { id: "A", temperature: 25 }, { id: "B", temperature: 35 }, { id: "C", temperature: 28 }, { id: "D", temperature: 32 } ]; const hotSensors = sensors.filter(s => s.temperature > 30); // 結果: [{id:"B", temperature:35}, {id:"D", temperature:32}] // 例3: 有効なデータだけを抽出(null, undefinedを除外) const data = [10, null, 25, undefined, 30, 0]; const validData = data.filter(d => d !== null && d !== undefined); // 結果: [10, 25, 30, 0] // より簡潔に(ただし0も除外されるので注意) const nonEmpty = data.filter(d => d); // 結果: [10, 25, 30] ※0も除外される // 例4: 複数条件 const alerts = sensors.filter(s => s.temperature > 25 && s.temperature < 35 ); // 結果: [{id:"C", temperature:28}, {id:"D", temperature:32}]

🎯 find() / findIndex() - 要素を検索

配列.find(要素 => 条件)   // 要素を返す 配列.findIndex(要素 => 条件) // インデックスを返す
// find(): 条件に合う最初の要素を取得(1つだけ) const sensors = [ { id: "A", temperature: 25 }, { id: "B", temperature: 35 }, { id: "C", temperature: 28 } ]; // 例1: IDで検索 const sensorB = sensors.find(s => s.id === "B"); // 結果: {id: "B", temperature: 35} // 例2: 条件で検索(最初にマッチしたもの) const hotSensor = sensors.find(s => s.temperature > 30); // 結果: {id: "B", temperature: 35} // 例3: 見つからない場合 const notFound = sensors.find(s => s.id === "Z"); // 結果: undefined // findIndex(): インデックス(位置)を取得 const index = sensors.findIndex(s => s.id === "B"); // 結果: 1 const notFoundIndex = sensors.findIndex(s => s.id === "Z"); // 結果: -1(見つからない場合)

🎯 reduce() - 配列を集約

配列.reduce((累積値, 現在値) => 処理, 初期値)
// reduce(): 配列を1つの値にまとめる // 例1: 合計を計算 const numbers = [10, 20, 30, 40]; const sum = numbers.reduce((total, n) => total + n, 0); // 結果: 100 // 処理の流れ: // total=0, n=10 → 0+10=10 // total=10, n=20 → 10+20=30 // total=30, n=30 → 30+30=60 // total=60, n=40 → 60+40=100 // 例2: 最大値を求める const max = numbers.reduce((a, b) => a > b ? a : b); // 結果: 40 // 例3: センサーデータの平均温度 const sensors = [ { id: "A", temperature: 25 }, { id: "B", temperature: 30 }, { id: "C", temperature: 28 } ]; const avgTemp = sensors.reduce((sum, s) => sum + s.temperature, 0) / sensors.length; // 結果: 27.666... // 例4: オブジェクトに変換(IDをキーにした辞書) const sensorMap = sensors.reduce((obj, s) => { obj[s.id] = s.temperature; return obj; }, {}); // 結果: { A: 25, B: 30, C: 28 } // 例5: グループ化 const data = [ { type: "温度", value: 25 }, { type: "湿度", value: 60 }, { type: "温度", value: 28 }, { type: "湿度", value: 55 } ]; const grouped = data.reduce((groups, item) => { const key = item.type; if (!groups[key]) groups[key] = []; groups[key].push(item.value); return groups; }, {}); // 結果: { "温度": [25, 28], "湿度": [60, 55] }

🎯 その他の配列メソッド

// ===== some() / every(): 条件チェック ===== const numbers = [1, 2, 3, 4, 5]; // some(): 1つでも条件を満たせばtrue const hasLarge = numbers.some(n => n > 4); // true const hasNeg = numbers.some(n => n < 0); // false // every(): 全て条件を満たせばtrue const allPositive = numbers.every(n => n > 0); // true const allLarge = numbers.every(n => n > 3); // false // ===== includes(): 要素の存在確認 ===== const fruits = ["apple", "orange", "banana"]; fruits.includes("orange"); // true fruits.includes("grape"); // false // ===== indexOf(): インデックス取得 ===== fruits.indexOf("orange"); // 1 fruits.indexOf("grape"); // -1(見つからない) // ===== sort(): 並び替え ===== const nums = [3, 1, 4, 1, 5]; // 昇順(数値の場合は比較関数が必要) const asc = [...nums].sort((a, b) => a - b); // [1, 1, 3, 4, 5] // 降順 const desc = [...nums].sort((a, b) => b - a); // [5, 4, 3, 1, 1] // オブジェクト配列のソート const sensors = [ { id: "B", temp: 30 }, { id: "A", temp: 25 }, { id: "C", temp: 28 } ]; const sorted = [...sensors].sort((a, b) => a.temp - b.temp); // 温度の昇順: [{id:"A",...}, {id:"C",...}, {id:"B",...}] // ===== slice(): 部分配列を取得(元配列は変更しない)===== const arr = [0, 1, 2, 3, 4]; arr.slice(1, 3); // [1, 2] インデックス1から3の手前まで arr.slice(2); // [2, 3, 4] インデックス2から最後まで arr.slice(-2); // [3, 4] 最後から2つ // ===== forEach(): 各要素に処理(戻り値なし)===== const values = [1, 2, 3]; values.forEach((v, i) => { node.warn(`インデックス${i}: ${v}`); }); // ===== join(): 配列を文字列に結合 ===== const words = ["Hello", "World"]; words.join(" "); // "Hello World" words.join(", "); // "Hello, World" words.join(""); // "HelloWorld"

⚠️ sort() の注意点:

sort()元の配列を変更します。元の配列を残したい場合は、先にコピー([...配列]slice())してからソートしてください。

📝 3. 文字列操作メソッド

センサーID、ログメッセージ、ファイル名など、文字列処理もよく使います。

split()

文字列を分割して配列に

"a,b,c".split(",") → ["a", "b", "c"]

replace()

文字列を置換

"Hello".replace("l","L") → "HeLlo"

trim()

前後の空白を除去

" abc ".trim() → "abc"

substring()

部分文字列を取得

"Hello".substring(0,2) → "He"
// ===== split(): 文字列を配列に分割 ===== const csv = "apple,orange,banana"; const fruits = csv.split(","); // 結果: ["apple", "orange", "banana"] const path = "/home/user/data/sensor.json"; const parts = path.split("/"); // 結果: ["", "home", "user", "data", "sensor.json"] const filename = parts[parts.length - 1]; // "sensor.json" // ===== replace() / replaceAll(): 置換 ===== const text = "Hello World"; // 最初の一致だけ置換 text.replace("o", "0"); // "Hell0 World" // 全て置換(正規表現を使用) text.replace(/o/g, "0"); // "Hell0 W0rld" // replaceAll(ES2021) text.replaceAll("o", "0"); // "Hell0 W0rld" // 正規表現でパターン置換 const log = "Error: 404 Not Found"; log.replace(/\d+/, "XXX"); // "Error: XXX Not Found" // ===== trim() / trimStart() / trimEnd(): 空白除去 ===== const padded = " Hello World "; padded.trim(); // "Hello World" padded.trimStart(); // "Hello World " padded.trimEnd(); // " Hello World" // ===== substring() / slice(): 部分文字列 ===== const str = "Hello World"; str.substring(0, 5); // "Hello" str.substring(6); // "World" str.slice(0, 5); // "Hello" str.slice(-5); // "World"(後ろから5文字) // ===== toLowerCase() / toUpperCase(): 大文字小文字 ===== "Hello".toLowerCase(); // "hello" "Hello".toUpperCase(); // "HELLO" // ===== includes() / startsWith() / endsWith(): 検索 ===== const message = "Error: Connection failed"; message.includes("Error"); // true message.startsWith("Error"); // true message.endsWith("failed"); // true message.startsWith("Warning"); // false // ===== padStart() / padEnd(): パディング ===== const num = "7"; num.padStart(3, "0"); // "007" num.padEnd(3, "0"); // "700" // 日時のフォーマットに便利 const hour = 9; String(hour).padStart(2, "0"); // "09" // ===== repeat(): 繰り返し ===== "ab".repeat(3); // "ababab" "-".repeat(20); // "--------------------"

🗂️ 4. オブジェクト操作

Node-REDでは msg オブジェクトを頻繁に操作します。オブジェクトの操作方法を覚えましょう。

// ===== Object.keys() / values() / entries() ===== const sensor = { id: "A", temperature: 25, humidity: 60 }; // キー一覧 Object.keys(sensor); // ["id", "temperature", "humidity"] // 値一覧 Object.values(sensor); // ["A", 25, 60] // キーと値のペア Object.entries(sensor); // [["id","A"], ["temperature",25], ["humidity",60]] // 活用例: オブジェクトをループ処理 Object.entries(sensor).forEach(([key, value]) => { node.warn(`${key}: ${value}`); }); // ===== スプレッド構文 {...obj}: オブジェクトのコピー・マージ ===== // シャローコピー(浅いコピー) const copy = { ...sensor }; // プロパティを追加 const extended = { ...sensor, status: "active" }; // {id:"A", temperature:25, humidity:60, status:"active"} // プロパティを上書き const updated = { ...sensor, temperature: 30 }; // {id:"A", temperature:30, humidity:60} // 複数オブジェクトをマージ const defaults = { timeout: 5000, retry: 3 }; const custom = { timeout: 10000 }; const config = { ...defaults, ...custom }; // {timeout: 10000, retry: 3} ← 後から書いた方が優先 // ===== 分割代入: オブジェクトから値を取り出す ===== const data = { name: "センサーA", value: 25, unit: "℃" }; // 従来の書き方 const name1 = data.name; const value1 = data.value; // 分割代入(簡潔) const { name, value, unit } = data; // name="センサーA", value=25, unit="℃" // 別名を付ける const { name: sensorName, value: temp } = data; // sensorName="センサーA", temp=25 // デフォルト値 const { name: n, status = "unknown" } = data; // n="センサーA", status="unknown"(dataにstatusがないため) // ===== msg オブジェクトでの活用例 ===== // 入力 // msg = { payload: { temp: 25, humid: 60 }, topic: "sensor" } // 分割代入で取り出し const { payload, topic } = msg; const { temp, humid } = payload; // 新しいmsgを作成(元のプロパティを維持しつつ更新) msg = { ...msg, payload: { ...msg.payload, status: temp > 30 ? "alert" : "normal" } }; // ===== オプショナルチェーン (?.) : 安全なアクセス ===== const response = { data: { sensors: [{ id: "A", value: 25 }] } }; // 従来(エラーになる可能性) // const val = response.data.sensors[0].value; // 安全なアクセス const val = response?.data?.sensors?.[0]?.value; // 25 // 途中がundefinedでもエラーにならない const missing = response?.unknown?.property; // undefined // ===== Null合体演算子 (??) : デフォルト値 ===== const input = null; const value2 = input ?? "デフォルト"; // "デフォルト" // || との違い const zero = 0; zero || 10; // 10(0はfalsyなので) zero ?? 10; // 0(nullとundefinedのみ置換) // 実用例 const timeout = msg.payload.timeout ?? 5000; const name2 = msg.payload.name ?? "名称未設定";

🔢 5. 数値操作

// ===== Math オブジェクト ===== // 丸め処理 Math.round(4.5); // 5(四捨五入) Math.floor(4.9); // 4(切り捨て) Math.ceil(4.1); // 5(切り上げ) Math.trunc(4.9); // 4(小数部を除去) // 最大・最小 Math.max(1, 5, 3); // 5 Math.min(1, 5, 3); // 1 Math.max(...[1, 5, 3]); // 配列から最大値 // 絶対値 Math.abs(-5); // 5 // べき乗・平方根 Math.pow(2, 3); // 8(2の3乗) Math.sqrt(16); // 4(平方根) // 乱数 Math.random(); // 0以上1未満の乱数 Math.floor(Math.random() * 100); // 0〜99の整数 // ===== 数値変換 ===== Number("123"); // 123 Number("123.45"); // 123.45 Number("abc"); // NaN parseInt("123"); // 123 parseInt("123.45"); // 123(整数部分のみ) parseInt("123abc"); // 123(数値部分まで) parseFloat("123.45"); // 123.45 // ===== 小数点以下の桁数制御 ===== const num = 3.14159; num.toFixed(2); // "3.14"(文字列を返す) Number(num.toFixed(2)); // 3.14(数値に変換) // 注意: toFixedは四捨五入する (2.345).toFixed(2); // "2.35" (2.344).toFixed(2); // "2.34" // ===== NaN / Infinity のチェック ===== isNaN(NaN); // true isNaN("abc"); // true(数値に変換できない) isNaN(123); // false Number.isNaN(NaN); // true(より厳密) Number.isNaN("abc"); // false isFinite(100); // true isFinite(Infinity); // false isFinite(NaN); // false

📅 6. 日付操作

// ===== Date オブジェクト ===== // 現在日時 const now = new Date(); // 特定の日時を作成 const date1 = new Date("2024-01-15"); const date2 = new Date("2024-01-15T10:30:00"); const date3 = new Date(2024, 0, 15, 10, 30, 0); // 月は0始まり // ===== 日時の取得 ===== now.getFullYear(); // 2024 now.getMonth(); // 0〜11(0=1月) now.getDate(); // 1〜31(日) now.getDay(); // 0〜6(0=日曜) now.getHours(); // 0〜23 now.getMinutes(); // 0〜59 now.getSeconds(); // 0〜59 now.getMilliseconds(); // 0〜999 now.getTime(); // UNIXタイムスタンプ(ミリ秒) // ===== 日時のフォーマット ===== // ISO形式 now.toISOString(); // "2024-01-15T01:30:00.000Z" // ローカル形式 now.toLocaleString("ja-JP"); // "2024/1/15 10:30:00" now.toLocaleDateString("ja-JP"); // "2024/1/15" now.toLocaleTimeString("ja-JP"); // "10:30:00" // カスタムフォーマット const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const formatted = `${year}-${month}-${day} ${hours}:${minutes}`; // "2024-01-15 10:30" // ===== タイムスタンプ ===== // 現在のタイムスタンプ(ミリ秒) Date.now(); // 1705285800000 // 秒に変換 Math.floor(Date.now() / 1000); // タイムスタンプから日時を作成 const fromTimestamp = new Date(1705285800000); // ===== 日時の計算 ===== // 1時間後 const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000); // 1日後 const oneDayLater = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 2つの日時の差(ミリ秒) const diff = date2.getTime() - date1.getTime(); const diffHours = diff / (1000 * 60 * 60); // 時間に変換 const diffDays = diff / (1000 * 60 * 60 * 24); // 日数に変換

✨ 7. 便利な構文

🔤 テンプレートリテラル

// テンプレートリテラル: バッククォート(`)で囲む const name = "センサーA"; const temp = 25.5; // 従来の文字列連結 const msg1 = "温度: " + name + " = " + temp + "℃"; // テンプレートリテラル(推奨) const msg2 = `温度: ${name} = ${temp}℃`; // 式も埋め込める const status = `状態: ${temp > 30 ? "高温" : "正常"}`; // 複数行も簡単 const html = `

${name}

温度: ${temp}℃

`; // Node-REDでの活用例 msg.payload = ` センサーID: ${msg.topic} 測定値: ${msg.payload.value} 時刻: ${new Date().toLocaleString("ja-JP")} `.trim();

❓ 三項演算子

// 三項演算子: 条件 ? 真の値 : 偽の値 const temp = 32; // if文で書く場合 let status1; if (temp > 30) { status1 = "高温"; } else { status1 = "正常"; } // 三項演算子で書く場合(1行で済む) const status2 = temp > 30 ? "高温" : "正常"; // ネスト(入れ子)も可能(やりすぎ注意) const level = temp > 35 ? "危険" : temp > 30 ? "警告" : temp > 20 ? "正常" : "低温"; // 実用例: デフォルト値 const timeout = msg.payload.timeout ? msg.payload.timeout : 5000; // より簡潔に(Null合体演算子) const timeout2 = msg.payload.timeout ?? 5000;

🔀 条件分岐の短縮形

// 短絡評価(ショートサーキット) // && : 左がtrueなら右を返す const isActive = true; isActive && console.log("アクティブです"); // 実行される // || : 左がfalsyなら右を返す(デフォルト値に使える) const name = msg.payload.name || "名称未設定"; // ?? : 左がnull/undefinedなら右を返す(より安全) const count = msg.payload.count ?? 0; // 0もそのまま使われる // 早期リターン function processData(data) { // データがなければ早期終了 if (!data) return null; // データがあれば処理を続行 return data.map(d => d * 2); } // Node-REDでの早期リターン if (!msg.payload) { return null; // 処理を中断(次のノードに送らない) } // ここから通常処理 msg.payload = msg.payload.toUpperCase(); return msg;

🛡️ エラーハンドリング(try-catch)

// try-catch: エラーを捕捉して処理 try { // エラーが発生する可能性のある処理 const data = JSON.parse(msg.payload); msg.payload = data; } catch (error) { // エラー発生時の処理 node.error("JSONパースエラー: " + error.message, msg); msg.payload = { error: error.message }; } return msg; // 実用例: 安全なプロパティアクセス let value; try { value = msg.payload.data.sensors[0].value; } catch (e) { value = null; // アクセスできなければnull } // または、オプショナルチェーンを使う(よりシンプル) const value2 = msg?.payload?.data?.sensors?.[0]?.value ?? null;

🎯 8. Node-RED実践例

📊 センサーデータの集計

Inject Function Debug
// センサーデータの集計処理 const sensors = msg.payload; // 入力例: [ // { id: "A", temperature: 25, humidity: 60 }, // { id: "B", temperature: 32, humidity: 55 }, // { id: "C", temperature: 28, humidity: 70 } // ] // 温度の統計を計算 const temps = sensors.map(s => s.temperature); const avgTemp = temps.reduce((a, b) => a + b, 0) / temps.length; const maxTemp = Math.max(...temps); const minTemp = Math.min(...temps); // 高温センサーを抽出 const hotSensors = sensors .filter(s => s.temperature > 30) .map(s => s.id); // 結果を整形 msg.payload = { timestamp: new Date().toISOString(), statistics: { average: Number(avgTemp.toFixed(1)), max: maxTemp, min: minTemp }, alerts: hotSensors, alertCount: hotSensors.length }; return msg;

📝 ログメッセージの整形

// ログメッセージの整形 const { topic, payload } = msg; const timestamp = new Date().toLocaleString("ja-JP"); // センサーIDを抽出(例: "sensors/floor1/temp" → "floor1") const parts = topic.split("/"); const location = parts[1] || "unknown"; // ステータスを判定 const status = payload > 30 ? "ALERT" : "OK"; // 整形されたログメッセージを作成 msg.payload = `[${timestamp}] [${status}] ${location}: ${payload}℃`; // または構造化ログ msg.payload = { timestamp, level: status === "ALERT" ? "warn" : "info", location, value: payload, message: `${location}の温度は${payload}℃です` }; return msg;

🏋️ 9. 実践演習

演習1: 配列のフィルタリングと変換初級

📝 課題:

センサーデータ配列から、温度30度以上のセンサーIDだけを抽出してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

メソッドチェーンで書くと簡潔です:

配列 .filter(条件) .map(変換)
✅ 解答例フロー
[ { "id": "ex1_inject", "type": "inject", "name": "センサーデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "topic": "", "payload": "[{\"id\":\"A\",\"temp\":25},{\"id\":\"B\",\"temp\":35},{\"id\":\"C\",\"temp\":32},{\"id\":\"D\",\"temp\":28}]", "payloadType": "json", "x": 150, "y": 100, "wires": [["ex1_function"]] }, { "id": "ex1_function", "type": "function", "name": "高温センサー抽出", "func": "const sensors = msg.payload;\n\n// filter: 30度以上を抽出\n// map: IDだけを取り出す\nconst hotSensorIds = sensors\n .filter(s => s.temp >= 30)\n .map(s => s.id);\n\nmsg.payload = hotSensorIds;\nreturn msg;", "outputs": 1, "x": 380, "y": 100, "wires": [["ex1_debug"]] }, { "id": "ex1_debug", "type": "debug", "name": "結果", "active": true, "tosidebar": true, "complete": "payload", "targetType": "msg", "x": 570, "y": 100, "wires": [] } ]

演習2: 文字列の解析中級

📝 課題:

MQTTトピック文字列を解析して、構造化データに変換してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

配列の分割代入を使います:

const [a, b, c, d] = "a/b/c/d".split("/");
✅ 解答例フロー
[ { "id": "ex2_inject", "type": "inject", "name": "MQTTトピック", "props": [{"p": "topic"}], "repeat": "", "crontab": "", "once": false, "topic": "factory/floor2/machine_A/temperature", "x": 150, "y": 100, "wires": [["ex2_function"]] }, { "id": "ex2_function", "type": "function", "name": "トピック解析", "func": "const topic = msg.topic;\n\n// スラッシュで分割\nconst parts = topic.split(\"/\");\n\n// 分割代入で各部分を取得\nconst [factory, floor, machine, sensor] = parts;\n\nmsg.payload = {\n factory,\n floor,\n machine,\n sensor\n};\n\nreturn msg;", "outputs": 1, "x": 360, "y": 100, "wires": [["ex2_debug"]] }, { "id": "ex2_debug", "type": "debug", "name": "結果", "active": true, "tosidebar": true, "complete": "payload", "targetType": "msg", "x": 530, "y": 100, "wires": [] } ]

演習3: データの集約中級

📝 課題:

センサーデータの配列から、平均・最大・最小値を計算してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント
// 合計 const sum = arr.reduce((a, b) => a + b, 0); // 配列から最大値 Math.max(...arr);
✅ 解答例フロー
[ { "id": "ex3_inject", "type": "inject", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "topic": "", "payload": "[23, 28, 31, 25, 29, 27]", "payloadType": "json", "x": 140, "y": 100, "wires": [["ex3_function"]] }, { "id": "ex3_function", "type": "function", "name": "統計計算", "func": "const values = msg.payload;\n\n// 合計を計算\nconst sum = values.reduce((a, b) => a + b, 0);\n\n// 統計値を計算\nconst stats = {\n average: Number((sum / values.length).toFixed(1)),\n max: Math.max(...values),\n min: Math.min(...values),\n count: values.length\n};\n\nmsg.payload = stats;\nreturn msg;", "outputs": 1, "x": 340, "y": 100, "wires": [["ex3_debug"]] }, { "id": "ex3_debug", "type": "debug", "name": "結果", "active": true, "tosidebar": true, "complete": "payload", "targetType": "msg", "x": 510, "y": 100, "wires": [] } ]

演習4: データのグループ化上級

📝 課題:

センサーデータをタイプ別にグループ化し、各タイプの平均値を計算してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント
// reduce でグループ化 const grouped = data.reduce((acc, item) => { const key = item.type; if (!acc[key]) acc[key] = []; acc[key].push(item.value); return acc; }, {});
✅ 解答例フロー
[ { "id": "ex4_inject", "type": "inject", "name": "センサーデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "topic": "", "payload": "[{\"type\":\"温度\",\"value\":25},{\"type\":\"湿度\",\"value\":60},{\"type\":\"温度\",\"value\":28},{\"type\":\"湿度\",\"value\":55}]", "payloadType": "json", "x": 150, "y": 100, "wires": [["ex4_function"]] }, { "id": "ex4_function", "type": "function", "name": "グループ化と集計", "func": "const data = msg.payload;\n\n// タイプ別にグループ化\nconst grouped = data.reduce((acc, item) => {\n const key = item.type;\n if (!acc[key]) {\n acc[key] = { values: [] };\n }\n acc[key].values.push(item.value);\n return acc;\n}, {});\n\n// 各グループの平均を計算\nObject.keys(grouped).forEach(key => {\n const values = grouped[key].values;\n const sum = values.reduce((a, b) => a + b, 0);\n grouped[key].average = sum / values.length;\n});\n\nmsg.payload = grouped;\nreturn msg;", "outputs": 1, "x": 380, "y": 100, "wires": [["ex4_debug"]] }, { "id": "ex4_debug", "type": "debug", "name": "結果", "active": true, "tosidebar": true, "complete": "payload", "targetType": "msg", "x": 570, "y": 100, "wires": [] } ]

🎓 10. まとめ

重要ポイント

⚠️ よくある間違い

📚 次のステップ

これらの基礎をマスターしたら、以下も学んでみましょう:

🔧 11. トラブルシューティング

問題 原因 解決方法
Cannot read property of undefined 存在しないプロパティへのアクセス オプショナルチェーン ?. を使用
map is not a function 配列でない値にmap()を使用 Array.isArray() でチェック
配列が変わってしまった sort()などが元配列を変更 [...arr] でコピーしてから操作
NaN が出る 数値変換の失敗 Number() の結果を isNaN() でチェック
日付がずれる タイムゾーンの問題 toISOString() でUTC、toLocaleString() でローカル
0がデフォルト値になる || で0がfalsyとして扱われる ??(Null合体演算子)を使用

🔗 12. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
これらのメソッドを使いこなせば、Functionノードでの処理が格段に楽になります。

🏠