一些JS实现的面试题
数组扁平化
- 使用ES6的
Array.prototype.flat()
实现,参数设置为Infinity
; - 使用ES6的扩展运算符,思路:判断数组中每个元素是否为数组,若为数组,调用一次语句
array = [].concat(...array)
,每次调用这条语句都会对数组进行一次一维的扁平化; - 递归方法,思路:判断数组每个元素是否为数组,若为数组,递归调用函数,不是数组就
push()
并返回; -
reduce
函数实现;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
方法1省略; 方法2: function flatArray(arr){ while(arr.some( item => Array.isArray(item) )){ arr = [].concat(...arr); } return arr; } 方法3: function flatArray(arr){ var result = []; for(let i of arr){ if(Array.isArray(i)){ result = result.concat(flatArray(i)); }else{ result.push(i); } } return result; } 方法4: // 和递归方法思路差不多 function flatArray4(arr){ return arr.reduce((result, item) => { return result.concat(Array.isArray(item) ? flatArray4(item) : item); }, []); }
实现new
思路:封装成一个函数,第一个参数是构造函数,第二个参数是构造函数的参数。
- 创建空对象,空对象继承构造函数的原型对象;
- 执行一次构造函数,绑定this到新创建的空对象;
-
返回这个对象。
1 2 3 4 5 6
function _new(Constructor, ...args) { let tmpObj = Object.create(Constructor.prototype) // tmpObj.__proto__ == Constructor.prototype Constructor.apply(tmpObj, args) return tmpObj }
变量提升
最近面试遇到这样一个题:
1
2
3
4
5
6
7
8
9
10
function f(ary) {
for (i = 1; i <= ary.length; i++) {
console.log(i);
}
}
var array = [4,5,6]
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
f(array)
}
这题考点主要是变量提升和作用域。函数f
里的变量i
没有声明,而是直接使用的,所以这个i
会提升为全局变量(成为window
的属性);而JS里的作用域对于var
而言,只有函数会产生作用域,for if
等语句不会产生对于var
有用的作用域。
所以这题最后的输出应该是:
1
2
3
4
4
1
2
3
下面来分步解释执行过程:
- 首先调用
f
函数前,变量i
不会提升到顶部; - 在外层
for
循环中,用var
声明了i
,由于for
循环体对var
没有作用域的约束,相当于window.i == 0
; - 进入循环体后,先输出
array[0] == 4
,然后调用f(array)
,这是第一次外层循环; - 进入函数体后,执行
for
循环,在函数体内没有i
这个变量的声明,所以i
会通过作用域链找到window
对象,将window.i = 0
,执行三次for
循环后全局变量window.i == 4
,函数调用结束后返回; - 进入到外层的
for
循环中,执行外层for
循环的i++
语句,此时window.i == 5
,然后执行判断i < array.length
,结果为false
,就跳出了循环。 - 最终结果是上述的结果。
这里给出一些测试的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function f(ary) {
for (i = 1; i <= ary.length; i++) {
console.log(i);
}
}
var array = [4, 5, 6]
// console.log('1',i); // i是undefined
// function x() {
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
f(array)
debugger
console.log(i);
}
// }
// x()
console.log(i);
当把外部循环放在一个函数里时,就可以完整的打印出9个值了,因为函数作用域对var
有约束,这里读取到的变量始终是函数体内部的i
。
运算符优先级
问题:foo.x的值是什么?
1
2
3
4
var foo = {n: 1};
var bar = foo;
foo.x = foo = {n: 2};
// foo.x == undefined
- 前两个语句没什么好解释的,定义对象
foo
和bar
都指向{n: 1}
; - 重点是第三句的连等,在MDN运算符优先级的表格中https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table,成员访问运算符
.
的优先级比赋值运算符=
的优先级要高; - 所以会先计算
foo.x
的值,先给foo
指向的对象添加一个属性x
,然后返回这个引用(可以说是内存地址),我暂时将这个引用称为reference
,也就相当于给bar
添加一个属性x
,因为此时这俩变量指向同一个地址,bar.x, foo.x, reference
三个值是一样的; - 再执行后面半句的赋值语句:
foo = {n: 2}
(因为赋值语句是右结合的,和编译原理的语法分析有关),这里的foo
会指向新的对象{n: 2}
,注意此时foo
指向的内存地址已经变了; - 最后执行前半句赋值语句:
reference = foo
,让reference
重新指向新的foo
对象,这条赋值语句其实等同于bar.x = foo
;而foo
此时指向的是{n: 2}
,并没有x
属性,所以访问foo.x
会得到undefined
。 - 如果此时访问
bar
对象的话,就会发现bar == {n: 1, x: {n: 2}}
。
sleep函数
问题:实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现
思路:题目里描述的等待,个人感觉不是很清晰,如果要求同步等待,那么程序会卡在sleep
函数中,这就要求后续的操作都放在setTimeout
函数中;如果可以异步等待,那只需要让部分操作放在超时函数里就行。
实现:
1
2
3
4
5
6
7
8
9
10
function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, ms);
})
}
sleep(1000).then(() => {
// do something
})
出现次数最多的字符
问题:编写一个js函数,传入一个非空字符串,计算出现次数最多的字符,返回该字符及 出现次数,结果可能包含多个字符。如传入“xyzzyxyz”,则返回:{y:3, z:3}
思路:没啥思路,循环找呗。主要就是要返回一个对象;想到用map或object数据结构去解决,在map里维护每个字符出现的次数,在object里维护出现最多的字符,最后返回object即可。
实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function findLongestCharacter(str) {
let ary = str.split('')
let map = new Map()
let maxLength = 1
let obj = {}
ary.forEach(el => {
if (map.has(el)) {
map.set(el, map.get(el) + 1)
// map.get(el) > maxLength ? maxLength = map.get(el) : maxLength
} else {
map.set(el, 1)
}
let length = map.get(el)
if (length > maxLength) {
obj = {}
maxLength = length
obj[el] = maxLength
} else if (length == maxLength) {
// maxLength = length
obj[el] = maxLength
}
});
/* for (let [key, val] of map) {
if (val == maxLength) {
console.log(`${key}: ${val}`);
}
} */
return obj
}
let string = 'abcdefgxyzabcdxzyxzyxzy'
console.log(findLongestCharacter(string))