引子:
今天遇到一个问题,问的是如何实现sum(2,3)和sum(2)(3)都输出5,由此导出了这篇博客的内容。其实这个问题的核心思想是闭包。具体做法是先在函数内定义一个变量,同时用闭包的机制缓存,不断把新的变量加给它。如下:
function add(){
var sum = arguments[0];
console.log(arguments)
if(arguments.length === 1){
return function (sec){
return sum + sec;
}
} else {
num = 0;
for(var i=0;i<arguments.length;i++){
sum = sum + arguments[i];
}
return function (sec){
return sum + sec;
}
}
}
console.log(add(2,3));
console.log(add(2)(3));
但是这个方法有一个问题在于它只能处理有限次调用,如果要调用任意个括号就无能为力了。因此我们想到可以用递归的思想来处理。如下:
function add() {
let sum = [...arguments].reduce((x, y) => x + y)
let helper = function() {
sum += [...arguments].reduce((x, y) => x + y)
return helper
}
return helper
}
它的主要思想也是通过闭包缓存变量,最后加总(注意这里arguments是一个类数组,无法直接使用reduce方法,所以需要先解构)。但是有一个问题在于如何把最后的结果输出出来。这就引出了今天的主题,类型转换。
类型转换:
首先我先把解决办法写出来。如下:
function add() {
let sum = [...arguments].reduce((x, y) => x + y)
let helper = function() {
sum += [...arguments].reduce((x, y) => x + y)
return helper
}
helper.toString = function() {
return sum
}
return helper
}
可以看到只是在函数里改写了toString方法。至于原因,我们下面一一解释。
先简单了解两个方法:valueOf 和 toString
valueOf() 和 toString() 在特定的场合下会自行调用。
在不同的类型转换下会有不同的调用顺序。
1. String类型转换
在某个操作或者运算需要字符串而该对象又不是字符串的时候,会触发该对象的 String 转换,会将非字符串的类型尝试自动转为 String 类型。系统内部会自动调用 toString 函数。
- 如果 toString 方法存在并且返回原始类型,返回 toString 的结果。
- 如果 toString 方法不存在或者返回的不是原始类型,调用 valueOf 方法,如果 valueOf 方法存在,并且返回原始类型数据,返回 valueOf 的结果。
- 其他情况,抛出错误。
2. Number类型转换
- 如果 valueOf 存在,且返回原始类型数据,返回 valueOf 的结果。
- 如果 toString 存在,且返回原始类型数据,返回 toString 的结果。
- 其他情况,抛出错误。
3. Function类型转换
这个类型比较特殊,是我们开头所提到的。
当你只写出函数,而不调用的时候,会自动调用函数的toString方法和valueOf方法。
function test() {
var a = 1;
console.log(1);
}
test.toString= function() {
console.log('调用 toString 方法');
return 3;
}
test;
// 输出如下:
// 调用 toString 方法
// 3
通过这个特性,我们就可以输出开头提到问题的结果。
注1:这里有个规律,如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。
注2:事实上,在我进行尝试的时候,valueOf的改写在Chrome浏览器中并没有起到作用,只有toString的改写有效果。而在Node.js中两个方法都没起到效果。这可能与不同引擎的规范有关。暂时留个坑。
参考文章: