一道有趣的面试题和相关知识补充

转载自: 伯乐在线专栏作者 - chokcoco

文章地址: http://mp.weixin.qq.com/s/0QwvOHpTiAg7Io_bdUQo7g


最近在网上看到这么一道面试题: 

使用JS实现一个哈数, 运算结果可以满足如下要求:

  1. add(1)(2) // 3
  2. add(1, 2, 3)(10) // 16
  3. add(1)(2)(3)(4)(5) // 15

然后在一片博文中看到这样的解法:

  1. function add() {
  2. var args = Array.prototype.slice.call(arguments);
  3. return function() {
  4. var arg2 = Array.prototype.slice.call(arguments);
  5. return args.concat(arg2).reduce(function(a, b){
  6. return a + b;
  7. });
  8. }
  9. }

上面代码中的解题思路如下:

1. 首先将每次调用的参数通过arguments获取出来, 通过slice方法将其转换成数组

2. 将每次转换出来的数组通过concat拼合成一个完整的数组

3. 调用reduce方法, 将之前得到的完整的数组 将数组中的数字进行一个累加的操作.


这个方法里面包含了几个知识点

(1)arguments本身并不是一个数组, 而是一个类数组, 因此在这个解题思路里面我们首先做的第一步是要将其转换为数组. 通过调用Array类的方法slice, 然后将arguments传递进去, 返回的对象就是一个包含arguments里面的内容的真正的数组. slice这个方法用于获取数组中的某些数组元素, 可以传两个参数, 第一个参数是从第几位(下标)开始, 第二个参数是获取从这一位开始获取多少个数组项. 最后返回的结果是一个数组类型的.
(2)使用数组方法concat用于将两个数组拼接成一个完整的数组

(3)数组方法reduce, 用于遍历数组中的每一项, 然后将前一项和后一项可以做一个运算操作, a代表前一项, b代表后一项


当我们调用这个方法add(1, 2, 3, 55)(24); 这样子的时候, 首先获得变量args = [1, 2, 3, 55]. 然后返回一个匿名函数, 接着执行下一段(24), 也就是执行这个返回的匿名函数, 将24作为参数传递进去. 又通过闭包的原理, 这个返回的匿名函数是可以获取刚刚生成的数组args, 然后将这两个数组通过concat进行拼接生成一个完整的数组[1, 2, 3, 55, 24], 最后通过reduce方法进行一个累加操作, 最后就能得到正确的结果了.


验证了一下,发现错了:

  1. add(1)(2) // 3
  2. add(1, 2)(3) // 6
  3. add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)

上面的解法,只有在 add()() 情形下是正确的。而当链式操作的参数多于两个或者少于两个的时候,无法返回结果。

而这个也是这题的一个难点所在,add()的时候,如何既返回一个值又返回一个函数以供后续继续调用?后来经过高人指点,通过重写函数的 valueOf 方法或者 toString 方法,可以得到其中一种解法:

  1. function add () {
  2. var args = Array.prototype.slice.call(arguments);
  3. var fn = function () {
  4. var arg_fn = Array.prototype.slice.call(arguments);
  5. return add.apply(null, args.concat(arg_fn));
  6. }
  7. fn.valueOf = function () {
  8. return args.reduce(function(a, b) {
  9. return a + b;
  10. })
  11. }
  12. return fn;
  13. }

嗯?第一眼看到这个解法的时候,我是懵逼的。因为我感觉 fn.valueOf() 从头到尾都没有被调用过,但是验证了下结果:

  1. add(1) // 1
  2. add(1,2)(3) //6
  3. add(1)(2)(3)(4)(5) // 15

神奇的对了!那么玄机必然是在上面的 fn.valueOf = function() {} 内了。为何会是这样呢?这个方法是在函数的什么时刻执行的?且听我一步一步道来。

function转换

我们定义一个函数如下:

  1. function test() {
  2. var a = 1;
  3. console.log(1);
  4. }

如果我们仅仅是调用 test 而不是 test() ,看看会发生什么?


可以看到,这里把我们定义的 test 函数的重新打印了一遍,其实,这里自行调用了函数的 valueOf 方法:


我们改写一下 test 函数的 valueOf 方法。


与 Number 转换类似,如果函数的 valueOf 方法返回的不是一个原始类型,会继续找到它的 toString 方法:


那么回到刚刚那道面试题, 正是运用了函数会自行调用 valueOf 方法这个技巧,并改写了该方法。我们稍作改变,变形如下:

  1. function add () {
  2. console.log('进入add');
  3. var args = Array.prototype.slice.call(arguments);
  4. var fn = function () {
  5. var arg_fn = Array.prototype.slice.call(arguments);
  6. console.log('调用fn');
  7. return add.apply(null, args.concat(arg_fn));
  8. }
  9. fn.valueOf = function () {
  10. console.log('调用valueOf');
  11. return args.reduce(function(a, b) {
  12. return a + b;
  13. })
  14. }
  15. return fn;
  16. }

当调用一次 add 的时候,实际是是返回 fn 这个 function,实际是也就是返回 fn.valueOf();


其实也就是相当于:


当链式调用两次的时候:


当链式调用三次的时候:


可以看到,这里其实有一种循环。只有最后一次调用才真正调用到 valueOf,而之前的操作都是合并参数,递归调用本身,由于最后一次调用返回的是一个 fn 函数,所以最终调用了函数的 fn.valueOf,并且利用了 reduce 方法对所有参数求和。

这里有个规律,如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值