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

来源:互联网 发布:mac怎么下载idea 编辑:程序博客网 时间:2024/06/04 08:42

函数柯里化

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

add(1)(2)(3) === 6add(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)上执行一下这段代码,看到了什么?

ƒ 12222222

  是不是很诡异?我想要的是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))

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

22212

  如果你是用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函数中做数值运算,而且慎用类型转换。本文作为填坑记录,有不对的地方欢迎指正,文中的问题有大神有见解可以在评论区讨论。

原创粉丝点击