通过最小化进程、批处理函数和减小有效负载大小,编写更快、更具可缩放性的 Excel 加载项。 本文介绍模式、反模式和代码示例,以帮助你优化常见作。
快速改进
首先应用这些策略,以产生最大的直接影响。
- Batch 加载和写入:组属性
load调用,然后创建单个context.sync()。 - 最小化对象创建:对块区域而不是许多单单元格区域进行作。
- 在数组中写入数据,然后向目标区域分配一次。
- 仅围绕大型更改暂停屏幕更新或计算。
- 避免每次迭代
Excel.run或context.sync()内部循环。 - 重复使用工作表、表和区域对象,而不是在循环内部重新查询。
- 通过在分配之前进行分块或聚合,将有效负载保持在大小限制以下。
重要
建议使用 load 和 sync 调用可以解决许多性能问题。 有关以高效方式使用特定于应用程序的 API 的建议,请参阅 Office 外接程序的资源限制和性能优化 中的“使用特定于应用程序的 API 提高性能”部分。
暂时挂起 Excel 进程
Excel 执行对用户输入和外接程序作做出反应的后台任务。 暂停所选进程可以提高大型作的性能。
暂停计算
如果需要更新大范围 ((例如)赋值,然后重新计算依赖公式) 和不需要临时重新计算结果,请暂停计算,直到下一次 context.sync()。
有关如何使用 suspendApiCalculationUntilNextSync() API 以便捷的方式暂停和重新激活计算的信息,请参阅应用程序对象参考文档。 以下代码演示了如何暂时挂起计算。
await Excel.run(async (context) => {
let app = context.workbook.application;
let sheet = context.workbook.worksheets.getItem("sheet1");
let rangeToSet: Excel.Range;
let rangeToGet: Excel.Range;
app.load("calculationMode");
await context.sync();
// Calculation mode should be "Automatic" by default
console.log(app.calculationMode);
rangeToSet = sheet.getRange("A1:C1");
rangeToSet.values = [[1, 2, "=SUM(A1:B1)"]];
rangeToGet = sheet.getRange("A1:C1");
rangeToGet.load("values");
await context.sync();
// Range value should be [1, 2, 3] now
console.log(rangeToGet.values);
// Suspending recalculation
app.suspendApiCalculationUntilNextSync();
rangeToSet = sheet.getRange("A1:B1");
rangeToSet.values = [[10, 20]];
rangeToGet = sheet.getRange("A1:C1");
rangeToGet.load("values");
app.load("calculationMode");
await context.sync();
// Range value should be [10, 20, 3] when we load the property, because calculation is suspended at that point
console.log(rangeToGet.values);
// Calculation mode should still be "Automatic" even with suspend recalculation
console.log(app.calculationMode);
rangeToGet.load("values");
await context.sync();
// Range value should be [10, 20, 30] when we load the property, because calculation is resumed after last sync
console.log(rangeToGet.values);
});
仅暂停公式计算。 仍会重新生成任何已更改的引用。 例如,重命名工作表仍会更新公式中对该工作表的任何引用。
暂停屏幕更新
Excel 会在发生更改时显示这些更改。 对于大型迭代更新,请取消中间屏幕更新。
Application.suspendScreenUpdatingUntilNextSync() 暂停视觉对象更新,直到 下一个 context.sync() 或结束 Excel.run。 为用户提供反馈,例如状态文本或进度栏,因为 UI 在挂起期间显示为空闲状态。
注意
不要重复调用 suspendScreenUpdatingUntilNextSync (,例如循环) 。 重复调用将导致 Excel 窗口闪烁。
启用和禁用事件
有时可以通过禁用事件来提高性能。 使用事件文章中的代码示例展示了如何启用和禁用事件。
将数据导入表
将大型数据集直接导入 表(例如重复调用 TableRowCollection.add())时,性能可能会降低。 请改用以下方法:
- 使用 将整个 2D 数组写入区域
range.values。 - 在 ()
worksheet.tables.add()的填充区域上创建表。
对于现有表,批量设置值 table.getDataBodyRange() 。 表会自动展开。
下面是此方法的一个示例:
await Excel.run(async (context) => {
let sheet = context.workbook.worksheets.getItem("Sheet1");
// Write the data into the range first.
let range = sheet.getRange("A1:B3");
range.values = [["Key", "Value"], ["A", 1], ["B", 2]];
// Create the table over the range
let table = sheet.tables.add('A1:B3', true);
table.name = "Example";
await context.sync();
// Insert a new row to the table
table.getDataBodyRange().getRowsBelow(1).values = [["C", 3]];
// Change a existing row value
table.getDataBodyRange().getRow(1).values = [["D", 4]];
await context.sync();
});
注意
可以使用 Table.convertToRange() 方法将 Table 对象转换为 Range 对象,此做法非常方便。
有效负载大小限制最佳做法
Excel JavaScript API 对 API 调用具有大小限制。
Excel web 版将请求和响应限制为 5 MB。 如果超出此限制,API 将 RichAPI.Error 返回错误。 在所有平台上,一个范围限制为 500 万个单元格,用于获取作。 大范围通常超过这两个限制。
请求的有效负载大小组合在一起:
- API 调用数。
- 对象数,例如
Range对象。 - 要设置或获取的值的长度。
如果获得 RequestPayloadSizeLimitExceeded,请在拆分作之前应用以下策略来减小大小。
策略 1:将未更改的值移出循环
限制循环内部的进程以提高性能。 在以下代码示例中, context.workbook.worksheets.getActiveWorksheet() 可以移出循环, for 因为它不会在该循环中更改。
// DO NOT USE THIS CODE SAMPLE. This sample shows a poor performance strategy.
async function run() {
await Excel.run(async (context) => {
let ranges = [];
// This sample retrieves the worksheet every time the loop runs, which is bad for performance.
for (let i = 0; i < 7500; i++) {
let rangeByIndex = context.workbook.worksheets.getActiveWorksheet().getRangeByIndexes(i, 1, 1, 1);
}
await context.sync();
});
}
以下代码示例演示了类似的逻辑,但具有改进的策略。 该值 context.workbook.worksheets.getActiveWorksheet() 在循环之前检索,因为它不会更改。 仅应在循环中检索变化的值。
// This code sample shows a good performance strategy.
async function run() {
await Excel.run(async (context) => {
let ranges = [];
// Retrieve the worksheet outside the loop.
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// Only process the necessary values inside the loop.
for (let i = 0; i < 7500; i++) {
let rangeByIndex = worksheet.getRangeByIndexes(i, 1, 1, 1);
}
await context.sync();
});
}
策略 2:创建更少的范围对象
创建较少的范围对象以提高性能并减少有效负载大小。 下面有两种方法。
将每个范围数组拆分为多个数组
创建更少范围对象的一种方法是将每个范围数组拆分为多个数组,然后使用循环和新 context.sync() 调用处理每个新数组。
重要
只有在确认超过有效负载大小限制后,才使用此策略。 多个循环会减小每个有效负载请求的大小,但也会添加额外的 context.sync() 调用,并且可能会降低性能。
下面的代码示例尝试在单个循环中处理大范围数组,然后处理单个 context.sync() 调用。 在一次 context.sync() 调用中处理过多的范围值会导致有效负载请求大小超过 5MB 的限制。
// This code sample does not show a recommended strategy.
// Calling 10,000 rows would likely exceed the 5MB payload size limit in a real-world situation.
async function run() {
await Excel.run(async (context) => {
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// This sample attempts to process too many ranges at once.
for (let row = 1; row < 10000; row++) {
let range = sheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
await context.sync();
});
}
下面的代码示例显示了类似于上述代码示例的逻辑,但策略可避免超过 5MB 有效负载请求大小限制。 在以下代码示例中,区域在两个单独的循环中处理,每个循环后跟一个 context.sync() 调用。
// This code sample shows a strategy for reducing payload request size.
// However, using multiple loops and `context.sync()` calls negatively impacts performance.
// Only use this strategy if you've determined that you're exceeding the payload request limit.
async function run() {
await Excel.run(async (context) => {
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// Split the ranges into two loops, rows 1-5000 and then 5001-10000.
for (let row = 1; row < 5000; row++) {
let range = worksheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
// Sync after each loop.
await context.sync();
for (let row = 5001; row < 10000; row++) {
let range = worksheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
await context.sync();
});
}
在数组中设置范围值
创建更少范围对象的另一种方法是创建数组,使用循环设置该数组中的所有数据,然后将数组值传递到某个区域。 这有利于性能和有效负载大小。 不是对循环中的每个范围调用 range.values , range.values 而是在循环外部调用一次。
下面的代码示例演示如何创建数组,在循环中 for 设置该数组的值,然后将数组值传递到循环外部的区域。
// This code sample shows a good performance strategy.
async function run() {
await Excel.run(async (context) => {
const worksheet = context.workbook.worksheets.getActiveWorksheet();
// Create an array.
const array = new Array(10000);
// Set the values of the array inside the loop.
for (let i = 0; i < 10000; i++) {
array[i] = [1];
}
// Pass the array values to a range outside the loop.
let range = worksheet.getRange("A1:A10000");
range.values = array;
await context.sync();
});
}
后续步骤
- 查看主机级约束 的资源限制和性能优化 。
- 了解如何 使用多个区域 来创建更少的对象。
- 为作持续时间和行计数等数据添加遥测数据,以指导进一步的性能优化。