分享JS几个有意思的面试题
Posted Tiger老师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分享JS几个有意思的面试题相关的知识,希望对你有一定的参考价值。
1.斐波那契数列
斐波那契数列都不陌生吧?让大家写马上就可以写出:
// 斐波那契数列
function fib(n) {
if(n < 2) return n
return fib(n - 1) + fib(n - 2)
}
有些朋友可能会想到如果n<0就会出问题然后给它加上:
// 斐波那契数列
function fib(n) {
console.count(\'总计\') // 总计: 13529
if(n < 0) return
if(n < 2) return n
return fib(n - 1) + fib(n - 2)
}
// 这样写会更好一些,抛出一个错误告诉用户
function fib(n) {
console.count(\'总计\') // 总计: 13529
if(n < 0) throw Error(\'传入参数必须大于等于0\')
if(n < 2) return n
return fib(n - 1) + fib(n - 2)
}
到这里可能就结束了,但是如果只是这样的话,那么你传的值如果是20、30甚至90的时候,你的电脑就当场去世了,所以需要给它一个优化让它不要无脑的计算。
于是我们可以给他进一步的优化,然后就有了:
var obj = {},
arr = []
function fib(n) {
console.count(\'总计\') // 总计:39
if(n < 0) throw Error(\'传入参数必须大于等于0\')
if(n < 2) return n
if(obj.hasOwnProperty(n)) return obj[n]
let num = fib(n - 1) + fib(n - 2)
arr.push(num)// ---------------->有展示数组的需求
obj[n] = num
return num
}
// 6765 (20) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
console.log(fib(20), arr);
通过obj来储存数值,然后在调用fib()的时候,通过hasOwnProperty(n)判断里面是否有已经被计算过并储存在里面,如果有就直接从里面拿这样就不用浪费时间、算力去做早就做过的事情了。
通过这样子能够明显减少计算次数,电脑也表示我又行了。但是变量放在外面看起来不够优雅,但是放进去的话这个obj就失去了它的作用,那我们可以尝试在fib函数里面做一个递归函数看看能不能解决这个问题。
进行进一步的优化
// 不在外面声明变量
function fib(n) {
let arr = []
if(n < 0) throw Error(\'传入参数必须大于等于0\')
function computed(n, pre, next) {
console.count(\'总计\') // 总计:21
arr.push(pre)
if(n === 0) return pre
return computed(n - 1, next, pre + next)
}
// 如果只是要求展示其中一个就不需要变量直接return就好了
let obj = {
a: arr,
b: computed(n, 0, 1)
}
return obj
}
// obj: {
// a: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
// b: 6765
// }
console.log(fib(20));
通过在函数内做了一个递归函数,可以不用声明一个变量来储存数值了(arr=[]这个是展示数组用的),这样还省去遍历对象的时间,性能貌似又得到了一丝丝提高,算是一个意外收获。
computed每次被调用的时候都会n-1,越来越小,然后将上一次的next的值给pre;而next的值则将他们两个pre、next相加,即0+1=1,1+1=2这样子。
如果各位还有更好的办法,欢迎指出,让大家一起进步。
2.将数组[1,2,3,[4,5]]转换成以下格式,确保之后往里添加数据也能展示成以下格式
// 为了节省空间只好这样摆了,请见谅!
{
children:[
{ value:1 },
{ value:2 },
{ value:3 },
{ children: [ { value:4 }, { value:5 } ] },
]
}
很多人一看到这里第一想到的应该是用for或者while循环遍历将它们转换成上述的格式吧。
使用while循环
// -----使用while循环-----
function transition(arr) {
var newArr = []
var index = 0
while (index < arr.length) {
if(typeof arr[index] === \'number\') {
newArr.push({
value: arr[index]
})
} else if(Array.isArray(arr[index])) {
newArr.push({
children: [
transition(arr[index])
]
})
}
index++
}
return newArr
}
console.log(transition([1,2,3,[4,5]]));
使用for循环
// -----使用for循环-----
function transition(arr) {
var newArr = []
for(let i = 0; i<arr.length;i++) {
if(typeof arr[i] === \'number\') {
newArr.push({
value: arr[i]
})
} else if(Array.isArray(arr[i])) {
newArr.push({
children: [
transition(arr[i])
]
})
}
}
return newArr
}
console.log(transition([1,2,3,[4,5]]));
这两种方法确实都能无论怎么加,加多少都可以实现,但是这里就有一个问题了。相信大家都知道了,是不是太多行了,为了实现格式转换while用了20行左右、for循环用了18行。那用map试试?
使用map高阶函数
// -----使用map高阶函数-----
function transition(arr) {
return arr.map(i => {
if(typeof i === \'number\') {
return{
value: i
}
} else if(Array.isArray(i)) {
return {
children: [
transition(i)
]
}
}
})
}
console.log(transition([1,2,3,[4,5]]));
只有16行了看起来也比前面两个方法要清晰明了,我们都知道其实map函数也是通过遍历数组只是它是把遍历的过程给隐藏起来不让我们看到,以此减少代码量。
刚才我们是先map遍历数组再进行判断的,那么我们试试看能否先判断再进行map遍历数组?
尝试进一步压缩
// -----尝试进一步压缩-----
function transition(arr) {
if(typeof arr === \'number\') {
return {
value: arr
}
} else if(Array.isArray(arr)) {
return {
children: arr.map(i => {
return transition(i)
})
}
}
}
console.log(transition([1,2,3,[4,5]]));
看起来是可以的,而且还很成功的把代码进一步压缩到15行。鉴于本人技术水平有限,关于如何减少循环次数提升性能方面目前还没有头绪,望各位大佬轻喷。
如果各位还有更好的办法,欢迎在下面评论,让大家一起进步。
3.写一个 setInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 stop,停止上面的 setInterVal
这道题第一次我没使用构造函数,写了个普通函数,调用的时候只要把参数写入就自动执行了。
function mySetInterVal(fn, a, b, end = 2) {
let time = 0 ↓
let handle = null // 这是想要满足除了a,a+b,a+2b以外的时间
function stop () { // 才加的,没要求就不用加它。
console.log(`停止刷新,现在是${a + b * time}`);
clearTimeout(handle)
time = 0
}
function setTime(a, b) {
if(time > end) return stop()
handle = setTimeout(() => {
time++
fn()
console.log(`现在是${a + b*time}`);
setTime(a, b)
}, a + b * time)
}
return setTime(a, b)
}
let test2 = mySetInterVal(() => {console.log(\'我是函数\')}, 1000, 500)
确实满足了要求,但是感觉不是特别好的赶脚。所以把它换成了构造函数看看。
换成构造函数
// -----换成构造函数-----
function mySetInterVal(fn, a, b, end = 2) {
this.num = 0
this.handle = null
this.end = () => {
this.num = 0
clearTimeout(this.handle)
}
this.start = () => {
this.handle = setTimeout(() => {
if(this.num > end) {
console.log(`停止刷新,现在刷新时间为${a+b*this.num}`);
return this.end()// <------------------如果要求要自己调用才停的话,就注释掉就好了
}
this.num++
this.start()
fn()
console.log(`现在是${a}+${b*this.num}`);
}, a + b * this.num)
}
}
let test = new mySetInterVal(() => {console.log(\'我是函数\')}, 1000, 500)
test.start()
虽然好像没啥变化,但是使用了构造函数听起来是不是比普通的函数牛逼一点。让面试官知道你会用构造函数应该也是一个小小的加分项吧(也可能不会有加分hhh,总之能用高级一些的写法尽量使用,总比用最简单的方法实现的印象要好一些)。
或者通过创建一个类class实现也是可以的。
创建一个类class
// -----创建一个类class-----
class mySetInterVal {
constructor(fn, a, b, end=2) {
this.a = a
this.b = b
this.fn = fn
this.time = 0
this.end = end
this.handle = null
}
timeStart() {
if(this.time > this.end) {
return this.timeEnd()
}
this.handle = setTimeout(() => {
console.log(`现在是${this.a}+${this.b*this.time}`);
this.time++
this.timeStart()
}, this.a + this.b * this.time)
}
timeEnd() {
clearTimeout(this.handle)
this.time = 0
}
}
let test = new mySetInterVal(() => {console.log(\'我是函数\')}, 1000, 500)
test.timeStart()
创建一个类来实现的话,看起来应该会比其他人用函数实现要来的耳目一新一些吧。至少让面试官知道你是会用类的人。
如果各位还有其它的方法,请多多指出,大家一起进步呀!
到这里就结束了,如果后面遇到这些类似题目相信各位应该有各种姿势去解决问题了吧!
前端视频方面大家可以关注我的b站,搜索“焖豆不闷”,上面上传了前端入门到精通(1000大合集)、0基础玩转微信小程序、5G时代使用Webview的正确姿势等视频;也可以进前端开发交流群,和大家一块聊天学习哦:点击这进入学习圈
以上是关于分享JS几个有意思的面试题的主要内容,如果未能解决你的问题,请参考以下文章