Js 刷题记录

Posted by Youzi Blog on March 5, 2019

一些JS实现的面试题

数组扁平化

  1. 使用ES6的Array.prototype.flat()实现,参数设置为Infinity
  2. 使用ES6的扩展运算符,思路:判断数组中每个元素是否为数组,若为数组,调用一次语句array = [].concat(...array),每次调用这条语句都会对数组进行一次一维的扁平化;
  3. 递归方法,思路:判断数组每个元素是否为数组,若为数组,递归调用函数,不是数组就push()并返回;
  4. 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
  • 前两个语句没什么好解释的,定义对象foobar都指向{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))