最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 写给前端工程师看的函数式编程对话 - 1

    正文概述 掘金(方应杭)   2021-03-11   599

    第一天:重新开始学写代码

    方:那我们开始了。还记得函数式的约定吗?

    学生:记得,数据不可变。

    方:好的,那么我估计你现在应该不会写代码了。

    学生:?

    方:不信?我跟你出道题。请遍历 array = ['a','b','c'] 打印出每一项的值。用 JS 写吧。

    学生:简单啊

    for(let i = 0; i< array.length; i++){
      console.log(array[i])
    }
    

    方:你这么快就忘了约定?数据不可变!

    学生:我没改 array 啊

    方:i++ 改了 i 的值啊

    学生:啊,这也算啊

    方:没错。不写 i++,你再来回答一次

    学生:那我用 for in

    for(let key in array){
      console.log(array[key])
    }
    

    方:有点鸡贼,先不说你遍历的顺序无法保证,这个 key 依然在变化哦,key 一开始等于 0 后来等于 1 最后等于 2。再想想吧。

    学生:我还有一招:

    array.forEach(item => console.log(item))
    

    方:总算摸到边了,这次「你」确实没有改变数据的值

    学生:那就是我答对了吗?

    方:还差一点。array.forEach 是 JS 内置的数组接口,JS 内部的 C++ 实现很有可能还是用到了 for 循环?你能自己写一个 forEach 吗?满足以下用法

    forEach(array, item => console.log(item))
    

    学生:不用数组的 forEach 自己写 forEach 吗……

    方:嗯

    学生:能用 while 吗

    方:不能,while 不也要 i++ 嘛,跟 for 没区别

    学生:那用递归?

    方:试试

    学生:好

    let array = ['a','b','c']
    let forEach = (array, fn) => {
      if(array.length === 0) return
      fn(array[0])
      forEach(array.slice(1), fn)
    }
    forEach(array, item => console.log(item))
    

    方:不错,这次你遵守了约定:「数据不可变」。我想知道为什么你最后才给出这种写法呢?

    学生:以前有人告诉我尽量不要用递归。

    方:我猜猜,那个人是写 JS 或 Java 的?

    学生:嗯,而不止一个人这么说。

    方:他们给出的理由是什么?

    学生:容易「栈溢出」,而且「开销大」「浪费内存」

    方:说得不错,但这只是他们的一面之词。我们先来说说「栈溢出」吧。「栈溢出」的前提是得先有「调用栈 callstack」对吧。

    学生:当然

    方:那你知不道有些语言根本就没有 callstack 呢,比如 Haskell 就没有 callstack,它用图代替了栈。

    学生:闻所未闻……

    方:另外,如果我们把递归改写为尾递归,甚至循环,就基本可以消除「栈溢出」

    学生:可是如果把递归改写成循环,那还不如直接写循环吧?

    方:你很敏锐地发现了我的逻辑漏洞嘛,这里的「尾递归变循环」是「编译器」自动实现的!也就是说程序员专心写代码,编译器专心搞性能优化。

    学生:什么是「尾递归」?

    方:这个我明天再讲,今天我先把你对递归的不信任推翻再说。现在你只需要知道,把递归改写为尾递归,再配合恰当的编译器,「栈溢出」基本不成问题。

    学生:JS 或 Java 的编译器不行吗?

    方:问得好。实际上编译器和程序员的写法是相辅相成的,不是恒定的。我先问问你

    1. 程序员应该记住性能好的代码,就算放弃可读性也值
    2. 程序员以可读性为优先,让编译器想办法优化性能

    这两个观点你支持哪个?

    学生:我不确定,难道不应该尽量挑性能好的代码写吗?

    方:哈哈,你以为性能好的代码就是性能好的吗?

    学生:这是什么意思?

    方:我跟你举个例子,你应该知道 ++i 比 i++ 的性能要好很多吧?

    学生:哦?为什么?

    方:原因不重要,你可以看看这篇文章,也可以不看。假设你已经知道 ++i 比 i++ 性能要好很多,请问,你在写 for 循环时,写 i++ 还是写 ++i。

    学生:我以前一直写 i++,听你这么一说,我是不是应该写 ++i

    方:不用,因为编译器帮你优化了,编译器一旦发现 for 循环的第三个语句是 i++,会自动优化为 ++i,所以不需要程序员自己记住这么多规则,网上这种乱七八糟的优化技巧太多了,你能记住几个

    学生:编译器这么智能!

    方:你也不想想,写编译器的那帮人比写 JS 的人智商高多少。

    学生:也是

    方:甚至有的时候,你为了提高性能而采用的怪异写法,会降低程序的性能,因为它得不到编译器的优化。编译器一般会优化常见的写法。

    学生:原来是这样

    方:所以程序员应该尽量写符合社区习惯的代码,只在性能瓶颈处手动优化性能。但是由于 JS 和 Java 这帮人常年的「教育」,像你这样的新人已经都认为应该尽量不用「递归」,所以 JS 和 Java 的编译器没有必要花时间优化小众写法,优化之后反而会造成安全和debug相关的问题。

    学生:那说递归「开销大」和「浪费内存」是不是也是类似的误区。

    方:是的,如果某个语言特性被程序员用得多,编译器就会想尽办法让它变快。现在,你可以放弃你对递归的偏见了吗?

    学生:可以,那我是不是还得放弃 JS 和 Java 的编译器?

    方:是的,这两门语言的社区文化与函数式不太兼容,而且主流版本的编译器是不支持这些优化的,也许未来的版本有。

    学生:那我再多嘴问一句,「递归」没有缺点吗?

    方:也有,以后会讲,但是瑕不掩瑜,利大于弊。

    学生:好,我先记下来,虽然还没有完全被说服。

    方:我们是从哪聊到这的?哦,是从「你没法第一时间给出递归的写法」聊到这的。那么我给你出第二题,写一个将字符串反转的函数,不能违反「数据不可变」的约定哦。

    学生:递归写法我会

    reverse = (string) => {
      if(string.length <= 1){return string}
      let last = string[string.length-1]
      let head = string.substr(0, string.length - 1)
      return last + reverse(head) // 把最后一个字符移到前面,然后递归
    }
    

    方:写得还挺快

    学生:丢下偏见之后果然写得快很多,不过还是觉得效率会慢、内存会浪费

    方:过几天你就习惯了,我也会教你怎么优化。再来一个,快速排序

    学生:简单

    /*1*/ quickSort = (array) => {
    /*2*/   if(array.length <= 1) {return array}
    /*3*/   let [pivot, ...rest] = array
    /*4*/   let small = rest.filter(i => i<=pivot)
    /*5*/   let big = rest.filter(i => i>pivot)
    /*6*/   return [...quickSort(small), pivot, ...quickSort(big) ]
    /*7*/ }
    

    学生:这个快排写得是真的爽,但我还是担心浪费内存,第 3、4、5、6 行全是内存复制

    方:哼,那是因为 JS 和 Java 的数据可变,所以不能直接复用数据啊。如果数据是不可变的,let [pivot, ...rest] = array 可以直接复用内存啊,rest 和 array 复用同一片内存问题不大

    学生:好像是这么回事,那 rest.filter(i => i<=pivot) 呢

    方:这个内存很难优化。但是你要知道,你用函数式写只用 5 行代码,用指令式写可能要 20 行代码,函数式追求的是逻辑上的简洁,先让逻辑变美,然后等遇到性能瓶颈了再上优化

    学生:好吧

    方:等下,你怎么好像对「数据不可变」还是没有完全接受啊,总想着改原来的内存?

    学生:才第一天嘛,过几天我才能适应

    方:好吧,今天就先到这里,明天继续。


    起源地下载网 » 写给前端工程师看的函数式编程对话 - 1

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元