代码:
/**
* 排列组合算法
**/
bonusCombination (arr, num, fun) {
if (arr.length < num || num > 10) {
return []
}
let variable = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u']
let replaceStr = '#$#'
let str = 'var arr=arguments[0]; var fun=arguments[1]; var ret=[]; for (var a = 0; a < arr.length; a++) { ' + replaceStr + ' } return ret;'
for (let i = 1; i < num; i++) {
str = str.replace(replaceStr, ' for (var ' + variable[i] + ' =' + variable[i - 1] + '+ 1; ' + variable[i] + ' < arr.length; ' + variable[i] + '++) { ' + replaceStr + ' }')
}
let temp = 'var temp= [];'
for (let i = 0; i < num; i++) {
temp += 'temp.push(arr[' + variable[i] + ']);'
}
if (fun) {
temp += 'ret.push(fun(temp)); '
} else {
temp += 'ret.push(temp);'
}
str = str.replace(replaceStr, temp)
return (new Function(str)).apply(null, [arr, fun])
}
图示:
- 看不懂为什么用字符串做变量,这个方法还有其他的功能吗?
- 一开始为什么定义一个 variable;
- 请一步一步来解释。
一行一行解释源码,估计也不太好理解;我们从另外一个角度来看这个问题。
返回的是动态函数
首先看
bonusCombination
函数的返回语句是:return (new Function(str)).apply(null, [arr, fun])
。这条语句的分解如下::new Function(str)
:含义是利用str
字符串动态构造出函数,我们暂且称这个构造出来的函数名为strFn
(即var strFn = new Function(str)
)strFn.apply(null, [arr, fun])
,转换成我们平时可以理解的形式就是strFn(arr, fun)
从这里我们可以看出,这个 bonusCombination 的目的是动态构建一个函数
strFn
,该函数接受两个入参arr
和fun
—— 而这两个入参就是我们传给bonusCombination
的入参;对应你给的例子,这里的
arr
就是[1,2,3,4]
,而fun
是null
入参 num 的作用,动态创建函数体
我们知道
bonusCombination
还有一个入参,就是num
,它的作用就是 锻造 上述动态函数strFn
的具体内容,利用这个num
入参来影响那一大段字符操作,这样也就影响了动态函数的具体内容:对于阅读这样的函数,要理解它的含义,最好的办法就是枚举。
比如我们让
num
为2
:那么动态构建的函数strFn
结构体如下:可见函数体内包含 2 次循环(因为
num
值为 2),返回的 ret 就是结果值;也就说调用
bonusCombination([1,2,3,4],2)
就相当于调用strFn2([1,2,3,4])
,返回值就是你题中的[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
。同样如果我们让
num
为3
:那么动态构建的函数strFn
结构体如下:函数体内包含
3
次循环(因为 num 值为 3);此时调用bonusCombination([1,2,3,4],3)
就相当于调用strFn3([1,2,3,4])
,返回值是[[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
;你也可以自己试一下让
num
为4
或者1
,写出的函数体内容是怎么样的,你就能彻底明白这个bonusCombination
函数的作用了。回答你的问题
问题1:看不懂为什么用字符串做变量,这个方法还有其他的功能吗?
回答:字符串做变量就是为了动态构造函数体内容,依据
num
的不同值动态生成不同的strFn
函数 —— 从这个角度上来讲bonusCombination
就是一个 工厂函数;该方法还有另外的功能,体现在入参
fun
上,可以对每个结果元素调用fun(temp)
,相当于使用数组的map
功能。问题2:一开始为什么定义一个 variable;
回答:主要是为动态函数体提供足够多的中间变量,没啥具体含义,只要是符合 js 变量名称就可以。这里使用
a
、b
... 这些变量名,估计是为了简单起见,你可以用任何符合 js 变量名称规范的字符串就行。问题3:请一步一步来解释
回答:读懂上面两小节的分析或者参考前一个回答,每一行解释这里就不附上了。
其他
这种利用
Function
动态函数构造出来的功能函数,性能堪忧;你也知道,如果
arr
入参数组越长,num
越大,那么嵌套的循环体也就越多,性能就越差。这样的代码虽然能运行,但质量比较糟糕,还有很大的性能隐患,不能放在线上正式的产品中使用。这段代码比较适合用来学习
Function
的功能,以及 组合数学 的相关知识。这种方式的编程属于 元编程 范畴,可以参考我最近写的文章 《【资源集合】 ES6 元编程(Proxy & Reflect & Symbol)》了解更多。后续有机会,我看能否使用 Proxy & Reflect 来重写这个
bonusCombination
函数。