☰
Jspreadsheet 表格公式实战:在 JavaScript 数据表格中引入 Excel 风格公式、第三方公式库与自定义公式函数
2026/9/29 8:55:09 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ce

Jspreadsheet is a lightweight JavaScript data grid component for creating interactive data grids with advanced spreadsheet controls.

项目地址:https://gitcode.com/gh_mirrors/ce/ce
点击查看免费下载

Jspreadsheet(前身为 Jexcel)是一个轻量级 JavaScript 数据表格组件,支持在网格单元格中直接书写以=开头的 Excel 风格公式。本文基于仓库中 v2 示例文档 展开,完整覆盖三类实战用法:内置公式计算、借助第三方公式库(underscore.string、moment、jstat、numeric、numeral 等)扩展统计与日期计算,以及自定义 JavaScript 公式函数;并结合当前仓库源码(internal.js、data.js、package.json)深入讲解公式的解析执行、循环保护与依赖链自动更新原理。读完本文,你可以直接在自己的页面中实现"公式单元格 + 自动联动 + 自定义函数"的完整电子表格能力。

背景:Jexcel v2 时代的公式能力

本文关联文档属于docs/jspreadsheet/v2/系列,对应 Jspreadsheet 的 jQuery 版本(CDN 上以jquery.jexcel.js发布,版本 2.1.0)。在该版本中,公式的引入方式非常轻量:

  1. 引入公式解析引擎excel-formula.min.js(可选地配合formulas.js扩展库);
  2. 在数据数组的任意单元格写入以=开头的表达式字符串;
  3. 表格初始化后自动计算并展示结果,单元格数据变化时联动更新。

这套"以=开头即公式"的约定一直延续到当前仓库的 v5 实现:在 internal.js 中,isFormula函数判断一个值是否为公式:

export const isFormula = function (value) { const v = ('' + value)[0]; return v == '=' || v == '#' ? true : false; };

即单元格值以=或#开头即被视为公式参与计算。后续所有示例都遵循这一规则。

第一个实战:Excel 风格公式

以下代码是 原 v2 示例 的第一个完整实现:一张设备清单表格,包含数量(B 列)、单价(C 列)、总价(D 列,=B*C)以及是否超过 5000 的判断列(E 列,=IF(...)),并在末行使用SUM求和。

<html> <script src="https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script> <script src="http://cdn.bossanova.uk/js/excel-formula.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jexcel.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jexcel.min.css" type="text/css" /> <div id="my"></div> <script> let data = [ ['Furnace',1,10000,'=B1*C1','=IF(C1 > 5000, true, false)'], ['Tower',2,6000,'=B2*C2','=IF(C2 > 5000, true, false)'], ['Drum',3,5000,'=B3*C3','=IF(C3 > 5000, true, false)'], ['Pump',4,4000,'=B4*C4','=IF(C4 > 5000, true, false)'], ['Total','=SUM(B1:B4)','=(C1+C2+C3+C4)','=SUM(D1:D4)',''] ] $('#my').jexcel({ data:data, columns: [ { type:'text' }, { type:'numeric' }, { type:'numeric' }, { type:'numeric' }, ], colHeaders: ['Equipment','Quantity', 'Price', 'Total', '>5000?'], colWidths: [ 200, 80, 100, 100, 100 ] }); $('#my').jexcel('updateSettings', { table: function (instance, cell, col, row, val, id) { // Format numbers if (col == 2 || col == 3) { // Get text txt = $(cell).text(); // Format text txt = numeral(txt).format('0,0.00'); // Update cell value $(cell).html(' $ ' + txt); } // Bold the total row if ($(cell).text() == 'Total') { $('.r' + row).css('font-weight', 'bold'); $('.r' + row).css('background-color', '#fffaa3'); } } }); </script> </html>

要点拆解:

  • 公式即数据:公式直接写在数据数组的字符串里,如'=B1*C1'、'=IF(C1 > 5000, true, false)',初始化时即被解析。D 列四个单元格分别引用 B、C 列对应行做乘法,E 列使用条件函数IF。
  • 区间引用:第 5 行(Total)使用了SUM(B1:B4)、=(C1+C2+C3+C4)与SUM(D1:D4)。其中(C1+C2+C3+C4)演示了不带函数的纯算术表达式同样可用。
  • 列类型声明:columns中将第 2、3、4 列声明为numeric,用于规范数值列行为;colHeaders与colWidths分别设置表头与列宽。
  • 格式化与样式联动:通过updateSettings的table回调,用 numeral.js 将价格列格式化为两位小数(0,0.00)并加$前缀;当单元格文本为Total时,将整行加粗并设置浅黄背景色#fffaa3。这是"公式计算结果 + 展示格式化"分离的典型做法——公式引擎算的是裸数值,呈现层负责美化。

公式是如何被解析执行的:源码级原理

v2 示例依赖外置的excel-formula.min.js引擎;而在当前仓库(jspreadsheet-ce v5)中,公式引擎被封装为 npm 依赖@jspreadsheet/formula(见 package.json),解析入口集中在 src/utils/internal.js 的executeFormula。

从数据到计算结果的调用链

单元格值进入表格后,parseValue 会先判断值是否以=开头:

if (('' + value).substr(0, 1) == '=' && obj.parent.config.parseFormulas != false) { value = executeFormula.call(obj, value, i, j); }

也就是说,parseFormulas配置为false时公式不会被计算(该配置项同样见于 v3 快速参考 与 v4 快速参考)。随后executeFormula完成以下关键步骤:

  1. 循环保护:维护一个formulaLoopProtection表,若某个单元格在计算链中被再次访问,则判定Reference loop detected并返回#ERROR(internal.js)。
  2. 自引用保护:解析出表达式中的所有单元格 token(如B1),若其中包含公式自身所在单元格,则判定Self Reference detected并返回#ERROR(internal.js)。
  3. 范围展开:将A1:B2这类区间 token 按行列坐标展开成逗号分隔的单元格列表(internal.js),因此SUM(B1:B4)会被展开为SUM(B1,B2,B3,B4)后交给引擎。
  4. 类型转换:对于被引用的单元格,空值转为null;可转为数字的值(受autoCasting配置约束)转为Number,否则包装成字符串字面量"..."(internal.js)。
  5. 公式转 JavaScript 并执行:调用formula(expression.substr(1), formulaExpressions, x, y, obj)完成计算;执行异常时返回#ERROR,若debugFormulas为true则在控制台输出诊断信息(internal.js)。

onbeforeformula 事件

在正式交给引擎计算之前,executeFormula会派发onbeforeformula事件,允许开发者拦截或改写表达式(internal.js):

const ret = dispatch.call(obj, 'onbeforeformula', obj, expression, x, y); if (ret === false) { return expression; } else if (ret) { expression = ret; }

该事件的签名在 v5 公式文档 中有明确记载:onbeforeformula(worksheet, expression, x, y) => String,返回false表示跳过计算,返回字符串则替换原表达式。典型用途包括:动态改写公式、记录审计日志、或在计算前做权限校验。

公式链与自动联动更新

电子表格公式的核心体验是"改一个单元格,依赖它的所有公式自动重算"。这一机制在源码中有完整实现:

  • 当executeFormula解析表达式时,会把"当前公式单元格 → 被引用单元格"的依赖关系登记到obj.formula[cellId]链表中(internal.js)。
  • 用户通过setValue/setValueFromCoords修改任意单元格时,updateCell更新目标值后立即调用updateFormulaChain(data.js、data.js),找到所有依赖该单元格的公式并强制重算(internal.js)。
  • 重算过程同样带chainLoopProtection防环保护:若发现循环依赖,则把相关单元格显示为#ERROR并清空该链(internal.js)。

因此,v2 示例中的=B1*C1等公式在用户编辑 B1 或 C1 后会自动更新结果,这正是公式链机制带来的能力。

第二个实战:引入第三方公式实现

excel-formula.min.js只提供基础函数;若要使用更丰富的统计、文本、日期函数,可以叠加第三方公式库。原文档给出的第二个示例引入了一组知名的 JavaScript 库,并配合formulas.js(bossanova 提供的公式扩展),直接用AVERAGE、STDEVA、MAX、COUNT等统计函数:

<html> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.0/underscore-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jstat/1.7.1/jstat.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script> <script src="https://bossanova.uk/components/jexcel/dist/js/excel-formula.min.js"></script> <script src="https://bossanova.uk/components/jexcel/dist/js/formulas.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jexcel.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jexcel.min.css" type="text/css" /> <div id="my1"></div> <script> let data1 = [ ['Furnace', 10, 'AVERAGE', '=AVERAGE(B1:B4)'], ['Tower', 9, 'STDEVA', '=STDEVA(B1:B4)'], ['Drum', 44, 'MAX', '=MAX(B1,B2,B3,B4)'], ['Pump', 12, 'COUNT', '=COUNT(B1:B4)'], ]; $('#my1').jexcel({ data:data1, colWidths: [ 400, 80, 100, 200 ], }); </script> </html>

几个值得注意的实战细节:

  • 库的分工:underscore与underscore.string提供通用的集合与字符串处理能力;moment处理日期时间;jstat提供统计学函数(标准差、分布等);numeric提供数值与矩阵运算;numeral负责数字格式化。这些库为formulas.js中的高级公式函数提供底层支撑。
  • 函数名大小写:示例中的函数名(AVERAGE、STDEVA、MAX、COUNT)全部大写。v5 官方文档也明确要求"所有公式名(包括自定义的)都应大写,以符合安全与标准化要求"(见 docs/jspreadsheet/docs/formulas.md)。
  • 三种引用方式并存:AVERAGE(B1:B4)与STDEVA(B1:B4)使用区间引用,MAX(B1,B2,B3,B4)使用逗号分隔的逐单元格引用,两种写法在引擎中都得到支持。

关于当前仓库的能力边界:jspreadsheet-ce 的 公式文档 说明,CE 版本提供 SUM、AVERAGE 及基础算术等公式;更完整的 Excel 兼容函数集(VLOOKUP、SUMIF、财务函数、跨工作表计算、动态区间A:A等)属于 Pro 扩展能力,这一点在选择第三方库方案时可以作为参考。

第三个实战:自定义 JavaScript 公式

当内置与第三方函数都不满足需求时,可以直接在页面中定义普通 JavaScript 函数,公式引擎会自动按函数名调用它们。原文档第三个示例定义了CUSTOM、CONSOLE、CONCAT_COLUMNS、AVG四个自定义函数:

<html> <script src="http://cdn.bossanova.uk/js/excel-formula.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jexcel.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jexcel.min.css" type="text/css" /> <div id="my2"></div> <script> let data2 = [ ['Furnace','=CUSTOM(A1)', 10], ['Tower','=CONSOLE(A2)', 9], ['Drum','=CONCAT_COLUMNS(A1,A3)', 3], ['Pump','=CONSOLE(A4)', 7], ['Total','', '=AVG(C1:C4)'], ] $('#my2').jexcel({ data:data1, colWidths: [ 400, 200 ], }); function CUSTOM(cell) { return 'alert: ' + cell; } function CONSOLE(cell) { console.log(cell); return cell; } function CONCAT_COLUMNS(a, b) { return a + ', ' + b; } function AVG(v) { var sum = v.reduce(function(a, b) { return a + b; }); var avg = sum / v.length; return avg; } </script> </html>

自定义函数的编写要点:

  • 函数名即公式名:页面全局作用域中定义function CUSTOM(cell) {...},表格里写=CUSTOM(A1)即可调用。参数可以是单个单元格引用、多个单元格引用(如CONCAT_COLUMNS(A1,A3))或区间引用。
  • 区间参数以数组传入:AVG(C1:C4)中的区间C1:C4会被展开为一个数组,因此AVG内部使用v.reduce(...)求和再取平均,这正是范围展开机制(前述源码第 3 步)对外暴露的调用形态。
  • 返回值即单元格结果:函数返回的字符串或数值直接作为公式结果渲染;CONSOLE演示了"副作用 + 原样返回"的模式(把值打印到控制台再返回)。
  • 注意示例中的一个易错点:该示例jexcel({...})传入的data是data1而非data2,属于示例代码的笔误。正确做法是传入data: data2,否则自定义公式行不会被渲染。这也是复制示例到生产环境时需要自行修正的地方。

自定义公式的实现原理在 v5 源码中同样清晰:executeFormula最终把表达式交给@jspreadsheet/formula引擎执行,引擎在受限的 JavaScript 求值环境中按函数名查找并调用全局函数(internal.js)。若自定义函数抛出异常,结果会统一收敛为#ERROR。

公式相关的配置项与安全边界

综合 v5 公式文档 与源码实现,与公式直接相关的配置、事件、方法整理如下:

类别名称说明默认值
配置parseFormulas是否解析执行=开头的公式;设为false则公式原样显示不计算(internal.js)true
配置secureFormulas公式安全开关:开启后将公式内字母统一转为大写(小写字符串字面量除外),以防范不安全的公式构造(internal.js、internal.js)true
配置debugFormulas公式计算抛异常时是否在控制台输出表达式与参数详情(internal.js)false
配置autoCasting引用单元格数值的自动类型转换开关(value == Number(value)时转数字),影响公式运算精度(internal.js)由实例配置决定
事件onbeforeformula公式执行前拦截,可返回false跳过或返回新表达式替换(internal.js)—
方法executeFormula(expression, x, y)手动触发一次公式计算(internal.js)—

此外需要注意两个安全细节:

  1. =开头的值会被当公式执行,因此从外部(URL、接口、用户输入)带入表格的字符串若以=开头,会被当作公式求值。secureFormulas默认开启即为缓解此类注入风险的手段之一。
  2. #开头同样被isFormula判为公式(internal.js),设计表格数据时应避免以#开头存放普通文本。

从 v2 到 v5:公式能力的演进脉络

本文主体来自 v2 时代的 jQuery 插件示例(引入jquery.jexcel.js并调用$('#my').jexcel({...}))。当前仓库已演进为 jspreadsheet-ce v5(见 package.json),公式相关的两个关键变化值得了解:

  • 引擎内化:v2 需要在页面手动引入excel-formula.min.js;v5 将公式引擎作为@jspreadsheet/formula依赖随包发布(package.json),开发者无需再关心引擎脚本的引入顺序。
  • 多工作表与配置精细化:v5 中公式与worksheets结构结合,secureFormulas、parseFormulas、debugFormulas、onbeforeformula等配置/事件在 公式官方文档 中成为一等公民;跨工作表计算(如Sheet2!A1)在 CE 中属于 Pro 扩展能力。

若你使用的是 v5 API,参考 公式用法示例 即可体验同样的"数据中写=公式、自动计算联动"能力:

<script src="https://bossanova.uk/jspreadsheet/v5/jspreadsheet.js"></script> <script src="https://jsuites.net/v5/jsuites.js"></script> <link rel="stylesheet" href="https://jsuites.net/v5/jsuites.css" type="text/css" /> <link rel="stylesheet" href="https://bossanova.uk/jspreadsheet/v5/jspreadsheet.css" type="text/css" /> <div id="spreadsheet"></div> <script> jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [{ data: [ [ 'Crayons Crayola only (No Rose Art)', 2, 5.01, 0.01, '=B1*C1*(1-D1)' ], [ 'Colored Pencils Crayola only', 2, 4.41, 0.02, '=B2*C2*(1-D2)' ], [ 'Total', '=SUM(B1:B8)', '=ROUND(SUM(C1:C8), 2)', '', '=SUM(E1:E8)' ], ], columns: [ { type: 'text', title:'Product', width:'300' }, { type: 'number', title: 'Total', width: '100px', format: 'US #.##0,00' }, ] }] }); </script>

小结

本文以 v2 官方示例为骨架,覆盖了 Jspreadsheet 公式能力的完整路径:在数据中以=开头书写 Excel 风格公式 → 借助第三方库扩展统计/日期/文本函数 → 用普通 JavaScript 函数实现自定义公式。同时,通过当前仓库源码可以确认这些能力背后的工程保障:executeFormula的循环/自引用保护、范围 token 展开与类型转换(internal.js)、updateFormulaChain的依赖链自动重算(internal.js)、secureFormulas的安全兜底(internal.js),以及parseFormulas、debugFormulas、onbeforeformula等精细化控制开关。

进一步阅读可参考:

  • v2 示例索引:查看更多列类型、事件处理、表格样式等示例;
  • v2 快速参考:jexcel 方法与初始化参数速查表;
  • v5 公式文档:当前版本公式配置、事件与方法的官方说明;
  • 自定义公式文档:CE 自定义公式的进阶用法;
  • 公式核心实现:executeFormula、updateFormulaChain、secureFormula等函数源码。
  • 前端
  • UI组件

【免费下载链接】ce

Jspreadsheet is a lightweight JavaScript data grid component for creating interactive data grids with advanced spreadsheet controls.

项目地址:https://gitcode.com/gh_mirrors/ce/ce
点击查看免费下载

相关推荐

上一篇:Kafka-Docker终极安全加固指南:认证授权与数据加密配置
下一篇:彻底解决Redisson解码异常:从ClassNotFound到数据错乱的实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询