一道有趣的面试题和相关知识补充
2017-03-28 14:43
369 查看
转载自: 伯乐在线专栏作者
- 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() 方法。
- 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() 方法。
相关文章推荐
- 一道有趣的面试题 - 设计模式的运用
- 一道有趣java面试题
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)
- 一道有趣的面试题
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)
- 今天碰到一道比较有趣的面试题,大家来探讨一下。
- IIS 相关知识补充
- 看到的一道面试题,很有趣,与大家分享zt
- 封装相关知识(不断补充中)
- 基于ArcGIS10.0和Oracle10g的空间数据管理平台(C#开发)ArcGIS相关知识补充学习
- 一道有趣的C#面试题
- 一道有趣的面试题
- 一道有趣的面试题——将int型数组强制转换为char*,再求strlen,涉及大小端
- 封装相关知识(不断补充中)
- 封装相关知识(不断补充中)