JS编程题
函数组合基础
首先,我们拥有了一个基本的字符串处理函数toUpperCase
,用于将输入的字符串转换为大写形式。此外,还有一个字符串拼接函数hellow
,它接受一个字符串参数并返回"Hello World!"加上该字符串的结果。
js
var toUpperCase = function(x) {
return x.toUpperCase();
}
var hellow = function(x) {
return "Hello World!"+x;
}
简单组合
思考如何组合toUpperCase
和hellow
实现greet
函数,能将输入的字符大写并且拼接上"Hello World!",这一步是不难想到的。
js
var greet = function(x) {
return hellow(toUpperCase(x));
}
如何又出现一个toLowerCase
函数,有着将字符转为小写的功能,我们是不是要向下面这样再写一次?
js
var toLowerCase = function(x) {
return x.toLowerCase();
}
var greet2=function(x){
return hellow(toLowerCase(x));
}
这样我们才能将新的功能耦合起来,思考思考是不是有些许麻烦,我们能不能做到封装一个函数,它能够 组合抽象函数 C=A(B(x)),有了思路,代码自然就形成了。
手写compose 组合函数
js
var compose = function(f,g) {
// 闭包
return function(x) {
return f(g(x));
}
}
var greet = compose(hellow,toUpperCase);
console.log(greet('kevin'));
通过 compose
函数,你可以轻松地将多个函数组合成一个新的函数,并按照从右到左的顺序依次应用这些函数。这种方式不仅提高了代码的可读性和复用性,还使得复杂的操作可以通过简单的函数组合来实现。
完成这一步,你是不是觉得面试题已经解完了呢?但是,若你深入思考,我们只解决了,两个函数的组合,如果存在三个四个,乃至更多,我们该如何解决呢?
最终成品
js
var compose = function() {
var args = arguments; // 获取所有传入的函数
var start = args.length - 1; // 最右边的函数索引,即最先执行的函数
return function(x) { // 返回一个新函数,该函数接受一个参数 x
var i = start;
var result = args[start].call(this, x); // 先执行最右边的函数
while (i--) { // 从右向左依次调用其他函数
result = args[i].call(this, result); // 将上一个结果作为参数传递给下一个函数
}
return result; // 返回最终的结果
}
}
var greet = compose(hellow, toUpperCase); // 组合 hellow 和 toUpperCase 函数
console.log(greet('kevin')); // 输出: Hello World!KEVIN
-
定义
compose
函数:compose
函数不直接接受两个函数作为参数,而是使用arguments
对象来接收任意数量的函数。args
变量保存了所有传入的函数。start
变量表示最右边(即最先执行)的函数在args
数组中的索引。
-
返回一个新的匿名函数:
- 这个匿名函数接受一个参数
x
,它将作为第一个输入值传递给最右边的函数。 i = start
初始化循环变量i
,使其指向最右边的函数。result = args[start].call(this, x)
首先调用最右边的函数,并将结果存储在result
中。
- 这个匿名函数接受一个参数
-
循环调用其他函数:
- 使用
while (i--)
循环从右向左依次调用每个函数。 - 在每次迭代中,当前的
result
作为参数传递给下一个函数(即左边的函数),并将新的结果重新赋值给result
。 args[i].call(this, result)
确保每个函数都能正确访问当前的执行上下文(即this
值)。
- 使用
若不了解call 的用法可以看看除了 call,JS 还有哪些强大的函数绑定方式?探索 JavaScript 函数绑定的多样世界 在我深入研究 setT - 掘金
this 在此处的作用:
- 如果这些函数是普通函数(即没有依赖于
this
),那么this
的值不会影响函数的行为。 - 如果这些函数是对象的方法,并且它们内部依赖于
this
,那么通过call(this, ...)
可以确保this
正确指向调用上下文中的对象。
前序遍历二叉树的又一写法
补充点与抽象组合函数无关的面试题,想要编写二叉树的前序遍历,你第一想到的肯定是递归,代码也很简洁:
js
function preorderTraversal(root) {
if(!root) return
console.log(root.val)
preorderTraversal(root.left)
preorderTraversal(root.right)
}
如果面试官,让你不能用递归,使用迭代的写法,你是否还会觉得简单呢? 想到迭代实现二叉树,能否想到一个数据结构,栈的用法,先进后出,来模拟二叉树的遍历
js
function preOrderTraversal(root){
if(!root) return
// 栈模拟递归
var stack = []
stack.push(root)
while(stack.length){
var node = stack.pop()
console.log(node.val)
// 注意:先压入右子节点,再压入左子节点,以保证左子节点先被处理
if(node.right) stack.push(node.right)
if(node.left) stack.push(node.left)
}
}
重点:
看到该题首先你有没有想到使用栈去实现,其次是:根据栈的先进后出,有没有先压入右子树,再压入左子树,为关键点。