您的位置:首页 > 职场人生

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

2017-03-28 14:43 302 查看
转载自: 伯乐在线专栏作者
- chokcoco

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

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

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

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

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

function add() {
var args = Array.prototype.slice.call(arguments);

return function() {
var arg2 = Array.prototype.slice.call(arguments);
return args.concat(arg2).reduce(function(a, b){
return a + b;
});
}
}

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

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方法进行一个累加操作, 最后就能得到正确的结果了.

验证了一下,发现错了:

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

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

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

function add () {
var args = Array.prototype.slice.call(arguments);

var fn = function () {
var arg_fn = Array.prototype.slice.call(arguments);
return add.apply(null, args.concat(arg_fn));
}

fn.valueOf = function () {
return args.reduce(function(a, b) {
return a + b;
})
}

return fn;
}

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

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

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

function转换
我们定义一个函数如下:

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

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



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



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



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

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

function add () {
console.log('进入add');
var args = Array.prototype.slice.call(arguments);

var fn = function () {
var arg_fn = Array.prototype.slice.call(arguments);
console.log('调用fn');
return add.apply(null, args.concat(arg_fn));
}

fn.valueOf = function () {
console.log('调用valueOf');
return args.reduce(function(a, b) {
return a + b;
})
}

return fn;
}

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



其实也就是相当于:



当链式调用两次的时候:



当链式调用三次的时候:



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

这里有个规律,如果只改写
valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: