文章目录
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