前言
大家好,我是抹茶。
昨天看到一道很有意思的题目,拆解下实现思路和涉及到的底层原理。 题目如下:
console.log("sum=" + add(1)); // sum=1
console.log("sum=" + add(1)(2)); // sum=3
console.log("sum=" + add(1)(2)(3)); // sum=6
拆解实现思路
- 首先,从整体去看,
"sum="是一个字符串,后面是个+号,所以这里会用到字符串的拼接 - 其次,形如
add(1)(2)其内部要用函数柯里化实现,要接收第二个参数,第一次函数调用应该要返回一个函数。 - 看输出结果,是等于所有参数的和,所以需要有将所有参数累加的逻辑,前置的条件就是收集记录所有的参数。
- 回到最外层,字符串拼接的时候,
add(1)(2)返回的对象,需要调用toString()方法将其转化为字符串。
一步步,编码实现
首先,定义add函数,它需要接收参数,并返回一个函数
function add(...arg){
const fn = function (){
//...
}
return fn;
}
add(1)(2)表明需要每次调用都返回一个函数,以接收下一次传入的参数
function add(...arg){
const fn = function (){
//...
return fn;//函数的内部也需要返回函数
}
return fn;
}
输出的结果是所有参数的累加和,所以每次函数调用,都需要把入参收集起来。
function add(...arg){
let args = arg;// 收集入参
const fn = function (...rest){
args = args.concat(rest);
return fn;//函数的内部也需要返回函数
}
return fn;
}
"sum=" +会调用后面的函数返回的对象的toString()方法(涉及JS引擎底层逻辑,当我们尝试把一个对象当作字符串,JS引擎会调用其toString()方法,如果对象没有toString()方法,就会往它的原型链上找),所以我们需要自己实现这个方法,这个方法要实现的功能是把所有的入参相加,并返回。
function add(...arg){
let args = arg;// 收集入参
const fn = function (...rest){
args = args.concat(rest);
return fn;//函数的内部也需要返回函数
}
fn.toString = function(){
const sum = args.reduce((total,cur)=> total + cur,0);
return sum;
}
return fn;
}
我们运行测试一下
结果是正确的。
下面是扩展思考部分,如果我们没有在fn上增加toString()方法,会如何?
如果对象没有toString方法,就会往它的原型链上找,fn的原型链指向的是Function.prototype,而Function.prototype.toString()默认返回一个表示该函数源码的字符串。
现在我们的toString()方法返回的数据类型是数字,而JS规范中,toString()方法返回的是一个字符串。而且,如果我们没有返回字符串,在与"sum="做字符串拼接的时候,JS引擎内部也会将其转换成字符串再做拼接。
function add(...arg){
let args = arg;// 收集入参
const fn = function (...rest){
args = args.concat(rest);
return fn;//函数的内部也需要返回函数
}
fn.toString = function(){
const sum = args.reduce((total,cur)=> total + cur,0);
return sum + ''; //toString()方法按JS底层逻辑应该要返回字符串
}
return fn;
}
虽然我们的toString()方法返回字符串底层还是JS引擎的类型转换,但是相比于在"sum="+的时候再进行类型转换,遵循toString()方法返回字符串的会更符合业界的代码风格规范。
总结
本文研究了"sum=" + add(1)(2)(3)如何分析设计逻辑,以及背后涉及到的函数柯里化,对象用作字符串时,JavaScript引擎调用其toString()方法,以及toString()应遵循的代码规范。
完整代码
function add (...arg) {
let args = arg;// 收集入参
const fn = function(...rest) {
args = args.concat(rest);
return fn;//函数的内部也需要返回函数
}
fn.toString = function() {
const sum = args.reduce((total, cur) => total + cur, 0);
return sum +'';
}
return fn;
}
console.log("sum=" + add(1)); // sum=1
console.log("sum=" + add(1)(2)); // sum=3
console.log("sum=" + add(1)(2)(3)); // sum=6