资讯专栏INFORMATION COLUMN

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

vboy1010 / 2482人阅读

摘要:函数柯里化关于函数柯里化的问题最初是在忍者秘籍中讲闭包的部分中看到的,相信很多同学见过这样一道和柯里化有关的面试题实现一个函数,使得如下断言能够能够通过简单说就是实现一个求值函数,能够将所有参数相加得出结果。方法返回一个表示该对象的字符串。

函数柯里化

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90066.html

相关文章

  • JavaScript中高阶函数的魅力

    摘要:上传进度下面通过高阶函数的方式我们来实现函数节流节流函数计时器是否是第一次调用首次调用直接放行存在计时器就拦截设置使用节流分时函数节流函数为我们提供了一种限制函数被频繁调用的解决方案。 高阶函数是指至少满足下列条件之一的函数 1:函数可以作为参数被传递 2:函数可以作为返回值输出 JavaScript语言中的函数显然的是满足了高阶函数的条件,下面我们一起来探寻JavaScript种高阶...

    Tony_Zby 评论0 收藏0
  • 全本 | iKcamp翻译 | 《JavaScript 轻量级函数式编程》|《你不知道的JS》姊妹篇

    摘要:本书主要探索函数式编程的核心思想。我们在中应用的仅仅是一套基本的函数式编程概念的子集。我称之为轻量级函数式编程。通常来说,关于函数式编程的书籍都热衷于拓展阅读者的知识面,并企图覆盖更多的知识点。,本书统称为函数式编程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 译者团队(排名不分先后)...

    paney129 评论0 收藏0
  • 翻译连载 | 附录 C:函数式编程函数库-《JavaScript轻量级函数式编程》 |《你不知道的J

    摘要:为了尽可能提升互通性,已经成为函数式编程库遵循的实际标准。与轻量级函数式编程的概念相反,它以火力全开的姿态进军的函数式编程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是 CSS 里最闪耀的一瞥;总结,...

    Miracle 评论0 收藏0
  • ES6函数与Lambda演算

    摘要:高阶函数函数式编程中,接受函数作为参数,或者返回一个函数作为结果的函数通常就被称为高阶函数。均属于高阶函数,高阶函数并不神秘,我们日常编程也会用到。参考演算函数式编程指南入门康托尔哥德尔图灵永恒的金色对角线原文函数与演算 缘起 造了一个轮子,根据GitHub项目地址,生成项目目录树,直观的展现项目结构,以便于介绍项目。欢迎Star。 repository-tree 技术栈: ES6 ...

    fasss 评论0 收藏0
  • 掌握JavaScript函数柯里

    摘要:原文链接和都支持函数的柯里化函数的柯里化还与的函数编程有很大的联系如果你感兴趣的话可以在这些方面多下功夫了解相信收获一定很多看本篇文章需要知道的一些知识点函数部分的闭包高阶函数不完全函数文章后面有对这些知识的简单解释大家可以看看什么是柯里化 原文链接 Haskell和scala都支持函数的柯里化,JavaScript函数的柯里化还与JavaScript的函数编程有很大的联系,如果你感兴...

    DTeam 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<