如果你剛開始用 Apps Script,getValue() 大概是第一個讓你覺得「就這?」的 API。可是真正上戰場,才知道坑點滿滿:格式化跟原始值差在哪?日期為什麼少一天?合併儲存格怎麼老是讀不到?
別急,這篇會用很白話的方式帶你走一次實戰流程:先懂概念,再看範例,最後整理一份錯誤清單。
你不需要背語法,只要知道做報表、寄信、做自動化時要注意哪裡,getValue() 就會從小螺絲,變成你最順手的扳手。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼是 getValue()?
在 Apps Script 操作試算表時,你最常見的任務就是「讀取一個儲存格的值」。Range.getValue() 正是做這件事的直球 API。看似簡單,但牽涉到資料型態(文字/數字/日期/布林/公式)、時區、效能(單次 vs 批次呼叫)、與常見錯誤(空值、合併儲存格、保護範圍等)。理解它,等於打通資料讀寫的第一哩路。
getValue() 是什麼?它回傳什麼?
定義:
在 Apps Script 中,Range.getValue() 會回傳目前範圍左上角那一格的原生值(非字串呈現),資料型態會依儲存格內容自動對應:
數字:number(含小數)
文字:string
日期/時間:Date 物件(JS Date)
勾選方塊:true/false(布林)
空白:""(空字串)或 null(依情境,詳見雷點)
公式儲存格:
回傳公式「計算後的值」,不是字串的公式本身(要拿公式請用 getFormula())
對比:
getValues():回傳二維陣列(可批次讀取多格,效能更好)
getDisplayValue():回傳「使用者看到的格式化文字」(例如貨幣、日期格式)
getDisplayValues():回傳格式化文字的二維陣列
getRichTextValue():回傳 RichTextValue(保留超連結/粗體等樣式)
getValue() = 左上角那一格的「原始值」;如果你要一次讀很多格,請優先用 getValues()。
開工前準備:專案與授權
1. 打開你的 Google 試算表 → 上方選單 Extensions(擴充功能)→ Apps Script。
2. 第一次使用會要求授權,依提示選取帳號並允許。
3. 在編輯器中建立 Code.gs,以下範例皆為 Apps Script(JavaScript),這是操作試算表的官方腳本語言;雖然你偏好 Kotlin,但此處屬必要語言(GAS 僅支援 JS/TS)。
提醒:專案層級時區可在 Apps Script 專案 → Project Settings 設定,影響日期讀取與寫入的行為。
最小可用範例
範例 1:讀取 A1 的值
function readSingleCell() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getActiveSheet(); // 目前工作表
const value = sh.getRange('A1').getValue();
Logger.log(value); // 可能是字串、數字、Date、布林或空字串
}
範例 2:用列列號取得
function readByRowCol() {
const sh = SpreadsheetApp.getActiveSheet();
// getRange(row, column, numRows?, numColumns?)
const value = sh.getRange(2, 3).getValue(); // 讀取 C2
Logger.log(value);
}
範例 3:讀取命名範圍
function readNamedRange() {
const sh = SpreadsheetApp.getActiveSheet();
const value = sh.getRange('myTotalCell').getValue(); // 命名範圍
Logger.log(value);
}
資料型態與格式的眉角
文字 vs 數字
儲存格中 123 可能被視為 number,若你要「以字串」讀取(保留前導零等),請在試算表先設為文字格式,或改用 getDisplayValue()。
例:SKU 00123,getValue() 會掉前導零;getDisplayValue() 保留為 "00123"。
日期/時間(Date 物件)
getValue() 回傳 Date,請以 Utilities.formatDate() 或 toLocaleString() 格式化。
時區很關鍵:試算表檔案時區、Apps Script 專案時區、帳號時區不一致會造成日差。
function readDate() {
const sh = SpreadsheetApp.getActiveSheet();
const date = sh.getRange('B2').getValue(); // Date 物件
const tz = Session.getScriptTimeZone();
const text = Utilities.formatDate(date, tz, 'yyyy-MM-dd HH:mm:ss');
Logger.log(text);
}
公式儲存格
getValue() 取得「公式計算後的值」。若需要公式本身:getRange().getFormula()。
若公式需花時間計算,建議 SpreadsheetApp.flush() 之後再讀,避免讀到舊值。
勾選方塊
Checkbox 讀到的是 true/false。若使用者自訂勾選/未勾選的輸出文字,則不再是布林,可能變字串。
空白儲存格
多數情況回傳 ""(空字串);個別函式計算失敗可能回 null。判斷時請以「寬鬆邏輯」處理:
const v = sh.getRange('A10').getValue();
if (v === "" || v === null) {
// 視為空
}
getValue() vs getValues():效能與規模
何時用 getValue()
單一格、偶發性讀取。
事件觸發中只要一格參數。
何時用 getValues()
要讀很多格或整塊範圍(例如 1000×10)。
一次 I/O 拿回二維陣列,在記憶體迭代,效能遠勝逐格 getValue() 迴圈。
function preferBatch() {
const sh = SpreadsheetApp.getActiveSheet();
const range = sh.getRange(2, 1, sh.getLastRow()-1, 5); // A2:E(last)
const values = range.getValues(); // [[...],[...],...]
// 在記憶體中跑邏輯
const ids = values.map(row => row[0]).filter(x => x !== "");
Logger.log(ids.length);
}
原則:能批次就不要逐格。這是 GAS 大量資料處理的基本功。
情境實作:常見讀取套路
讀取表頭索引,找欄位值
function readByHeader(headerName) {
const sh = SpreadsheetApp.getActiveSheet();
const header = sh.getRange(1, 1, 1, sh.getLastColumn()).getValues()[0];
const idx = header.indexOf(headerName);
if (idx === -1) throw new Error('找不到欄位:' + headerName);
const value = sh.getRange(2, idx+1).getValue(); // 第二列該欄
return value;
}
讀取動態最後一列某欄
function readLastRowValue(colA1) {
const sh = SpreadsheetApp.getActiveSheet();
const lastRow = sh.getLastRow();
if (lastRow < 1) return null;
const col = sh.getRange(colA1 + lastRow).getColumn(); // 例如 'B' → B(lastRow)
return sh.getRange(lastRow, col).getValue();
}
讀取多工作表:設定表(Config)+ 主表(Data)
function readConfigThenData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const cfg = ss.getSheetByName('Config');
const data = ss.getSheetByName('Data');
const days = cfg.getRange('B2').getValue(); // 報表天數
const start = new Date(Date.now() - days*24*3600*1000);
const tz = Session.getScriptTimeZone();
const values = data.getRange(2,1,data.getLastRow()-1,5).getValues();
const filtered = values.filter(r => r[0] instanceof Date && r[0] >= start);
Logger.log(`近 ${days} 天筆數:` + filtered.length);
}
讀取 Checkbox 以決定流程
function toggleByCheckbox() {
const sh = SpreadsheetApp.getActiveSheet();
const on = sh.getRange('D2').getValue() === true;
if (on) {
// do something
} else {
// do another thing
}
}
大量讀取但仍需單格語意
先 getValues() 取回陣列,再在記憶體模擬「單格讀」的語意,避免千次 I/O。
function readManyAsSingle() {
const sh = SpreadsheetApp.getActiveSheet();
const range = sh.getRange(2,1,100,3);
const values = range.getValues();
// 假裝逐格
values.forEach((row, i) => {
const a = row[0]; // A
const b = row[1]; // B
const c = row[2]; // C
// ...
});
}
與顯示格式共舞:何時用 getDisplayValue()
報表輸出、寄信、合約號碼、金額文字化(含千分位/幣別)等情境,請使用 getDisplayValue() 取得「看得見的樣子」。
混用技巧:同一儲存格先用 getValue() 得到原始數值做運算,再用 getDisplayValue() 做輸出。
function valueVsDisplay() {
const sh = SpreadsheetApp.getActiveSheet();
const v = sh.getRange('C5').getValue(); // 1000.5
const d = sh.getRange('C5').getDisplayValue(); // $1,000.50
Logger.log({ raw: v, show: d });
}
與公式同步:flush() 與計算時序
當你剛寫入公式或資料、立刻讀取結果時,可能遇到尚未重新計算的狀態。解法:
function writeThenRead() {
const sh = SpreadsheetApp.getActiveSheet();
sh.getRange('E2').setFormula('=SUM(A2:D2)');
SpreadsheetApp.flush(); // 強制將排隊動作與計算刷新
const v = sh.getRange('E2').getValue(); // 比較可靠
Logger.log(v);
}
錯誤處理與例外情境
常見錯誤訊息與狀況
| 狀況 | 症狀 | 可能原因 | 解法 |
|---|---|---|---|
| 讀到空字串 | "" |
儲存格真的空、或合併格非左上角 |
確認是否合併;改讀左上角;或以
getDisplayValue() 檢查
|
讀到 null |
null |
公式錯誤或外掛來源還未可用 |
flush()、加重試;或以
getDisplayValue() 觀察畫面值
|
讀到 #N/A 字樣 |
字串 | 公式錯誤值會以字串回傳 | 視為錯誤狀態處理,不要當數值用 |
| 日期少一天 | Date 物件顯示日差 | 時區不一致 | 專案與試算表時區對齊;格式化時帶入相同 tz |
| 極慢 | 執行逾時 | 逐格 getValue() 迴圈 |
用 getValues() 批次化 |
| 權限例外 | Exception | 無存取權或 Web App 身分不同 | 正確共享、以執行者身分部署 |
getValue() 的 12 條黃金守則
1. 只讀一格才用 getValue();多格一定用 getValues()。
2. 要格式化樣式(幣別/日期字串)就用 getDisplayValue()。
3. 讀日期時,處理好時區與格式化。
4. 讀公式值要 flush(),避免舊值。
5. Checkbox 讀回 true/false,自訂文字就不是布林。
6. 空值判斷同時檢查 "" 與 null。
7. 避免在資料區使用合併儲存格。
8. 欄位定位請用表頭索引,不要硬編欄號。
9. 讀大量資料→記憶體運算→一次寫回。
10. 與觸發器並用時,盡量用事件物件 e 的值,少碰即時 I/O。
11. 專案、檔案、帳號時區要一致。
12. 錯誤字串(#N/A 等)要當錯誤處理,不要當數值。
常見錯誤與雷點
值怪怪的?
看看是 getValue() 還是 getDisplayValue()。
公式剛寫入就讀?→ 加 SpreadsheetApp.flush()。
合併儲存格?→ 改讀左上角或取消合併。
日期亂跳一天?
專案與試算表時區是否一致?
Utilities.formatDate(date, tz, 'yyyy-MM-dd') 明確指定 tz。
大量資料慢到爆?
把所有 getValue() 迴圈改為一次 getValues()。
只在記憶體運算,最後 setValues() 一次性寫回。
讀到 #N/A 字樣?
把它視為錯誤訊號,不是數字。
追本溯源修正公式或資料。
觸發器偶爾讀不到最新資料?
flush();或從 e.values 拿當次提交值;必要時加 100~500ms 微延遲(Utilities.sleep())
問題集
Q1:getValue() 回傳的日期能直接做加減嗎?
可以。它是 Date 物件。用 getTime() 取毫秒加減,再建立新 Date。
Q2:我只想把儲存格「看起來的樣子」寄信出去?
用 getDisplayValue()(單格)或 getDisplayValues()(多格),寄文字即可。
Q3:為什麼同一格有時回 "" 有時 null?
空白多半是 ""。但若來源是函式錯誤或外部資料連結中斷,偶爾會見到 null。撐開判斷就好。
Q4:我能一次讀「不連續」的多個單格嗎?
原生 getRangeList(['A1','D5','H10']) 可取得 RangeList,但仍建議合併成最小矩形用 getValues(),再用陣列索引取值,效能更佳。
