Luckysheet源码分析之3-函数实现

返回目录

1.functionImplementation.js

1.1. 以SUM为例

1.1.1. 参数 arguments

1.1.1.1. SUM(A1,B1,C1) 形式

arguments 是一个数组 ,形式如下:
在这里插入图片描述
每一个 argument有一个data
在这里插入图片描述

1.1.1.2.SUM(A1:C1)形式

arguments 参数只有一条记录,coll为范围内单元格的个数
在这里插入图片描述
在这里插入图片描述

data里面有一个数组,包括了相应的单元格信息
在这里插入图片描述

1.1.2. 逻辑

1.1.2.1. 判断 arguments 的长度是否越界
        if (arguments.length < this.m[0] || arguments.length > this.m[1]) {
            return formula.error.na;
        }

搜索 funcitonlist.js 关键字 “SUM” 可以看到这个函数的 m为 1到255,所以可知 SUM最多可以支持到255个单元格
在这里插入图片描述

1.1.2.2. 参数类型错误检测
        for (var i = 0; i < arguments.length; i++) {
            var p = formula.errorParamCheck(this.p, arguments[i], i);
            console.log("p["+i+"]="); // [true],[success]
            console.table(p);
            if (!p[0]) {
                return formula.error.v;
            }
        }

搜索 funcitonlist.js 关键字 “SUM” this.p require=m或者o, type=rangeall

  "p": [{
      "name": "value1",
      "detail": "The first number or range to add together.",
      "example": "A2:A100",
      "require": "m",  // <--
      "repeat": "n",
      "type": "rangeall"   // <--
  }, {
      "name": "value2",
      "detail": "Additional numbers or ranges to add to `value1`.",
      "example": "2",
      "require": "o",    // <--
      "repeat": "y",
      "type": "rangeall"   // <--
  }]

继续看 formula.js , data是 object 或者array就可以通过检测

...
        if(require == "o" && (data == null || data == "")){
            return [true, locale_formulaMore.tipSuccessText];
        }
...
        if(type.indexOf("range") > -1 && (getObjType(data) == "object" || getObjType(data) == "array")){
            return [true, locale_formulaMore.tipSuccessText];  // <-- [true, success]
        }
1.1.2.3. 生成dataArr数组

这一部分不太复杂,根据不同类型的 data,把单元格里面的数字 push 到dataArr

1.1.2.4. 循环处理dataArr累加到sum并返回

这一块也比较简单,一个dataArr的循环累加

1.2. 继续看AVERAGE/COUNT/COUNTA/MAX/MIN

大量的冗余代码,看着很不舒服,感觉 构造数组dataArr(包含) 之前的部分都应该合并到一个方法里面实现,避免代码重复

1.3. 身份证相关

SEX_BY_IDCARD/BIRTHDAY_BY_IDCARD/PROVINCE_BY_IDCARD/
CITY_BY_IDCARD/STAR_BY_IDCARD/ANIMAL_BY_IDCARD,
看名称分别是根据身份证号码返回客户的性别、出生日期、省份、城市、星座、属相,同样参数错误的检测应该放到某一个方法里面处理,获取单元格数据数组第一个值可以看一下。
ISIDCARD判断是否身份证,正则表达式可以学习一下。

1.3.1. func_methods.getFirstValue

func_methods.js 这个函数有点复杂,根据data不同类型,获取单个单元格数据,数组第一个值

1.3.2. 身份证正则表达式

...
var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
if(reg.test(idcard))
...

1.4. 关键字提取

DM_TEXT_CUTWORD/DM_TEXT_TFIDF/DM_TEXT_TEXTRANK,三个函数对应的算法不同,都需要通过POST方法,调用后端的服务,因为后端服务没有

1.5. 股票相关

DATA_CN_STOCK_CLOSE/DATA_CN_STOCK_OPEN/DATA_CN_STOCK_MAX/DATA_CN_STOCK_MIN/DATA_CN_STOCK_VOLUMN/DATA_CN_STOCK_AMOUNT,根据股票代码和日期,返回A股对应股票收盘价、开盘价、最高价、最低价、成交量、成交额,这几个函数也是对接到若干个后端接口,代码冗余也很厉害,应该整合归并重复的代码。

1.6. 判断是否日期

ISDATE 单元格是否为日期时间格式

1.6.1. 日期正则表达式

datecontroll.js 的 checkDateTime(),这里有日期的正则表达式如下,但是貌似没有非润年2月29日的校验

        var reg1 = /^(\d{4})-(\d{1,2})-(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/;
        var reg2 = /^(\d{4})\/(\d{1,2})\/(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/;

1.7. 范围中符合指定条件的值求和

SUMIF 这个函数可以有2个或者三个参数,第一个参数是条件检测范围,第二个参数是条件,第三个参数是求和的范围

1.7.1. 条件表达式解析

看 func.js luckysheet_parseData(),这里用到的没有太特别的逻辑,直接返回表达式,如果输入参数是object类型,就稍稍复杂了。

var criteria = luckysheet_parseData(arguments[1]);

1.7.2. 二维数组转一维

formula.js 双重循环把二维的数组push 到 rangeNow

rangeData = formula.getRangeArray(rangeData)[0];

1.7.3. 两个参数的计算逻辑

两个参数相对比较简单,formula.js acompareb() 的核心逻辑:
formula.isCompareOperator()判断是否为比较预算夫
func.luckysheet_compareWith() 比较运算

formula.acompareb(v, criteria)

1.7.4. 三个参数的计算逻辑

如果有三个参数,那么情况稍微有点复杂,需要构造出一个 sumRangeData 数组, func.js luckysheet_getcelldata() 函数 解析表达式: sheetName + “!” +rangeStart + “:” + rangeEnd,
最后把range里面的 data 复制到 sumRangeData

sumRangeData = luckysheet_getcelldata(realSumRange).data;

接下来, sumRangeData变为 一维数组,其他的操作方式与 两个参数的逻辑一致, 遍历匹配项这里又有大量的重复代码,看着不舒服。

[20201104_17:48 Line.1782]

1.7.5. 数学函数相关

这一部分的函数没有太多复杂的逻辑,值得注意的是 es5的Math提供了很多的数学相关函数实现方法,开箱即用
TAN(角度正切): Math.tan()
TANH(双曲正切): Math.exp()
CEILING(向上取整): Math.ceil()
ATAN(反正切): Math.atan()
ASINH(反双曲正弦): Math.log()
ABS(绝对值): Math.abs()
ACOS(反余弦值): Math.acos()
ACOSH(反双曲余弦值): Math.log()
MULTINOMIAL(参数和的阶乘除以各参数阶乘的乘积): 这里用到 func_methods.factorial() 阶乘的递归实现算法
ATANH(反双曲正切值): Math.log()
ATAN2(连线的夹角): Math.atan2()
COSH(双曲余弦值): Math.exp()
INT(向下取整): Math.floor()
ISEVEN(是否为偶数): Math.abs(number) & 1 这个判断方式比较有意思,和二进制 0x0001 做一个与操作,然后判断结果
ISODD(是否为奇数): 与ISEVEN逻辑一致
LCM(最小公倍数): 双重循环加工Array
LN(欧拉数对数): Math.log()
LOG(指定底数对数): Math.log()
LOG10(底数10对数): Math.log()
MOD(余数): Math.abs()
MROUND(整数最接近的整数倍): Math.round() 先除再乘
ODD(向上取整最接近的奇整数): Math.ceil 向上取整,然后奇数操作
SUMSQ(一组数平方和): 先转一维数组,然后平方求和
COMBIN(集合选择对象的组合数): 也是用到递归的阶乘func_methods.factorial()
SUBTOTAL(): 这个函数比较复杂,相当于把函数作为参数带入计算公式,可以学习一下function里面嵌套function的用法。另外看源代码,好像没有实现id=100+的函数的隐藏列特殊处理。arr.shift()相当于pop()操作。
ASIN(反正弦值): Math.asin()
RADIANS(角度值转换为弧度): Math.PI/180
RAND(随机数): 0-1之间的随机数
DEGREES(弧度转换为度): 180 / Math.PI
ERFC(互补高斯误差函数): 听名字很高端 jStat.erfc(number)
EVEN(向上取整最接近的偶数);
EXP(欧拉数指定次幂): Math.exp()
FACT(阶乘): func_methods.factorial(number)
FACTDOUBLE(双阶乘): 递归func_methods.factorialDouble(number)
PI(14位小数的圆周率): Math.PI
FLOOR(向下取整指定因数最接近的整数倍):
GCD(整数的最大公约数): 双层循环实现
RANDBETWEEN(两整数之间的随机数): 基于Math.random()放大倍数
ROUND(四舍五入): Math.round()
ROUNDDOWN(向下舍入): Math.floor
ROUNDUP(向上舍入): Math.ceil
SERIESSUM(幂级数的和):
SIGN(正负零判断): 三重判断条件
SIN(正弦值): Math.sin()
SINH(实数的双曲正弦值): Math.exp()
SQRT(正数的正平方根): Math.sqrt(number)
SQRTPI( PI 与给定正数乘积的正平方根):Math.sqrt(number * Math.PI)
GAMMALN(伽玛函数的以 e(欧拉数)为底的对数): jStat.gammaln()
COS(余弦值): Math.cos()
TRUNC(数据有效位):
QUOTIENT(除数): 整除部分不含余数
POWER(指定次幂):Math.pow()

1.7.6. 单元格操作相关

COUNTBLANK(空单元格数): validate.isRealNull() 这个是判断单元格空的函数
COUNTIF(满足条件的单元格数量): 这个值得学习,通过function的嵌套,实现了条件表达式的动态计算
COUNTUNIQUE(不重复数值的个数): 首先是变为一维表(这一个方法之前好几个函数都用到了应该抽象出来),然后用到window.luckysheet_function.UNIQUE.f()拿唯一值
SUMIFS(多条件范围之和): 双重循环生成单元格是否条件的results[]数组,对应True的单元格就参与sum计算
COUNTIFS(多条件范围单元格数量): 双重循环生成单元格是否条件的results[]数组,对应True的单元格就累加1
PRODUCT(一组数相乘): 又见到了转一维数组的重复代码,然后循环把数组相乘
HARMEAN(调和平均值): 转一维数组然后循环处理
HYPGEOMDIST(超几何分布): 又是一个很有科幻色彩的名称,逻辑有点复杂

。。。

[20201105_15:13]复制粘贴太枯燥了,常用的函数方法来来去去就这几个,基本都看清楚了,接下来研究一下,这些function实现是怎么与其他模块交互的。

2. functionlist.js

这个文件的作用是所有function的目录索引

2.1. 函数分类

包括了15类函数:
{“0”:“数学”,“1”:“统计”,“2”:“查找”,“3”:“Luckysheet内置”,“4”:“数据挖掘”,“5”:“数据源”,“6”:“日期”,“7”:“过滤器”,“8”:“财务”,“9”:“工程计算”,“10”:“逻辑”,“11”:“运算符”,“12”:“文本”,“13”:“转换工具”,“14”:“数组”}

2.2. 函数定义结构

每一个function包括:

n-名称;
t-函数大类;
d-描述;
a-描述;
m-参数可选数量;
p-参数描述,

继续分解p-参数描述,

name-名称;
detail-描述;
example-案例;
require-是否必须(m-Yes/ o-No) ;
repeat-是否可以设置多个 ;
type-类型 (rangenumber/rangeall/range)

2.3. 相关逻辑

2.3.1. 提取中文或者英文参数

let functionListOrigin = Store.lang === 'zh' ? functionlist_zh : functionlist_en;

目前只支持中文和英文,如果再来10种外语类型,这样处理就不太妥当了。

2.3.2. 增加函数实现

    for (let i = 0; i < functionListOrigin.length; i++) {
        let func = functionListOrigin[i];
        func.f = functionImplementation[func.n];
    }

增加一个f属性,指向函数的具体实现脚本。

2.3.3. 构造function数组

    const luckysheet_function = {};
    for (let i = 0; i < functionListOrigin.length; i++) {
        let func = functionListOrigin[i];
        luckysheet_function[func.n] = func;
    }

这里用functionlist的name作为数组变量,生成带有funciton实现的函数列表

2.3.4. 最后赋值function数组

    window.luckysheet_function = luckysheet_function; 
    //Mount window for eval() calculation formula
    Store.luckysheet_function = luckysheet_function;

这里有一个问题是window和Store分别是什么用途?
key: ./Store/index.js 里面定义了 Store.luckysheet_function,消费端集中
./controllers/insertFormula.js 公式参数框用到 function的p标签
./global/formula.js 这里就用到了function的 f标签,这个js的信息量非常大,后面再具体分析。
getValueByFuncData(): 获得函数里某个参数的值
functionParser1():
functionParser():
isFunctionRange1():
isFunctionRange():
checkSpecialFunctionRange():
execfunction1(): 运行function得到结果

3. func.js

这部分是函数的底层实现部分,看到了excel操作熟悉报错信息,如#VALUE!(错误的参数或运算符) 以及 #NAME?(公式名称错误)等。

3.1. luckysheet_compareWith()

这个函数很庞大,1500多行,包含了比较、加减乘除幂余运算等,比较复杂的部分是二维数组和一维数组的处理逻辑,review代码,总感觉冗余度很高,好多反复出现的代码段。

3.2. luckysheet_getarraydata()

这个函数的解释很明确,一维数组变二维
{1,2,3;2,3,4} —> [[1,2,3],[2,3,4]]

3.3. luckysheet_getcelldata()

解析 ![sheetName]:[startRow]:[endRow] 字符串生成下面 格式:
{ “sheetName”: sheettxt,
“startCell”: rangetxt,
“rowl”: rowl,
“coll”: coll,
“data”: ret
};

3.4. luckysheet_parseData

解析单元格取得的值,包括下面几种情况:

3.4.1. 字符串或数字

直接返回

3.4.2. 比较表达式

formula.isCompareOperator(value).flag判断是比较表达式,调用 formula.genarate() 生成value结果。genarate的实现逻辑,待研究。

3.4.3. value是object

这种比较复杂如果value本身是一个Array,那么就返回 formula.genarate(value[0]), 否则,单元格带格式的情况,返回value.data.v

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值