关于JavaScript函数柯里化问题探索

函数柯里化

  关于函数柯里化的问题最初是在《JavaScript忍者秘籍》中讲闭包的部分中看到的,相信很多同学见过这样一道和柯里化有关的面试题:
  实现一个add函数,使得如下断言能够能够通过:

add(1)(2)(3) === 6
add(1)(2,3)(4) === 10

  简单说就是实现一个求值函数,能够将所有参数相加得出结果。
  分析一下:主要有两个要点——1.拿add(1)(2)(3)来说,如果想要在add(1)后再执行(2),那么需要add(1)返回一个函数才能做到,然后这个函数的入参是2,依次类推。2.这种递归什么时候是个头呢,貌似没看到有什么操作能让它停下来,这是个好问题,我们稍后解答。
  根据分析,我们先实现一个add函数:

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    return fun
}
console.log(add(1)(2,3)(4))

这段代码在chrome中输出:

ƒ (){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }

  没错,和我们预期的一样…因为没法跳出递归调用,所以输入了fun函数,而且我们只是把参数存在了数组arr中,但是没有做累加计算。继续改进函数:

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    fun.getValue = function(){
        return arr.reduce(function(total, num){
            return total+num
        }, 0)
    }
    return fun
}
console.log(add(1,2)(2,3)(4).getValue())  //12

  现在可以输出正确的值了,我们给fun函数添加了一个函数getValue,用于将记录参数的数组中的元素求和。但是这样需要在add函数后调用一下getValue,这好像与需求有点差异…

valueOf()和toString()

  这时候,我们想到两个方法valueOf()和toString(),这两个都是定义在Object原型上的方法,他们有什么特别吗?

Object.prototype.valueOf()
用 MDN 的话来说,valueOf() 方法返回指定对象的原始值。
  JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。
  记住上面这句话,下面我们会细说所谓的自动调用是什么意思。
Object.prototype.toString()
toString() 方法返回一个表示该对象的字符串。
  每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。
  这里先记住,valueOf() 和 toString() 在特定的场合下会自行调用。
                  ——摘自ChokCoco《一道面试题引发的对javascript类型转换的思考

  简单来说就是,这两个函数会在特定的场合下被js引擎隐式调用,当然两个函数被调用的条件是不同的,这里不展开分析,可参考《一道面试题引发的对javascript类型转换的思考》。
  我们继续改造add函数:

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    fun.toString = function(){
        console.log(222)
        return arr.reduce(function(total, num){
            return total+num
        }, 0)
    }
    return fun
}
console.log(add(1,2)(2,3)(4))

  先不执行它,我们来分析一下。我们重写了fun函数的toString方法,假设它会被js引擎调用,我们调用了reduce方法来为数组中的元素求和然后return出来,看起来没什么毛病对吧?
  首先在chrome(62.0.3202.94)上执行一下这段代码,看到了什么?

ƒ 12
222
222

  是不是很诡异?我想要的是12,这f 12是什么鬼…222输出了两遍又是什么东东,更诡异的是,先输出了f 12后输出了222…
  chrome上输出f 12,我们可以写一个更简洁的函数来模拟:

var app = function(){}; 
app.toString = function(){ 
    console.log('toString')
    return 12
}; 
function app1(){ 
    return app 
};
console.log(app1())  

  输出结果和上面完全一样,但至于为什么先输出f 12以及为什么输出两遍222,这个需要剖析chrome底层机制了,此处不做讨论。(暂时还没搞明白,后续搞清楚了会更新上来,如果有大神清楚可以在下面评论。)
  再来看FireFox(57.0)中的表现:
这里写图片描述
  无语了吧,直接无视咱写的toString方法,该啥样还啥样…
  再来看看IE(11):
这里写图片描述
  啥也不说了,再瞅瞅node:
这里写图片描述
  一脸懵逼啊…虽然不知道为什么不同环境会输出这些,但可以肯定的一点是——toString方法都没有被正常执行。所以,为了规避这个问题,我们需要让js引擎更明确地知道我们想调用toString,所以,修改一下打印语句:

console.log(''+add(1,2)(2,3)(4))

现在再看看,是不是所有环境输出都正常了:

222
12

  如果你是用valueOf,也会有类似的问题,只需将打印语句改为:

console.log(+add(1,2)(2,3)(4))

console.log()和alert()

  除了上述的解决方法之外,还可以使用alert函数来输出结果,即:

alert(add(1,2)(2,3)(4))

  大家可自行测试,除node外,浏览器中都可以弹出“12”。这是为啥呢?alert和console.log不一样吗?
  还真不一样,console.log可输入任何类型的数据,然而alert只能输出String类型的数据,所以…懂了吧?
  最后建议大家平时自己写代码不要像本例这样,在toString/valueOf函数中做数值运算,而且慎用类型转换。本文作为填坑记录,有不对的地方欢迎指正,文中的问题有大神有见解可以在评论区讨论。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值