VK的秋招前端奇遇记(四)

嗯,上次写blog已经是几周前的事情了,其实已经积攒了很多小问题需要记录和分享了。但是在8月底,VK我一次经历了了携程、拼多多、腾讯、网易等多轮面试轰炸,忙得不可开交,有喜有忧的同时,还是赶快记录了不足,把一些充满迷惑性的问题继续记录和学习。

JavaScript总是给人以惊喜,学习不止,进步不断,今天继续补充JS容易搞错的几道笔试/面试题,为了秋招继续努力,欢迎一起为秋招努力的小伙伴共勉

系列笔记:

1.VK的秋招前端奇遇记(一)/)

2.VK的秋招前端奇遇记(二)/)

3.VK的秋招前端奇遇记(三)/)

4.VK的秋招前端奇遇记(四)/)

5.番外篇:前端面试&笔试算法 Algorithm


面试&常识题

Q1. forEach 与 map的区别? forEach不支持中断循环?

这是一道巨坑的题目

先看下很多博客、文章总结的一个关于循环的区别是怎么说的

  1. map可以做链式操作、forEach不可以
  2. map有返回值,return、 forEach没有返回值
  3. for循环不用担心兼容性问题,可以break跳出循环,是基础循环
  4. forEach不支持continue和break,是不能退出循环本身的。

上面这些比较,本身没有什么问题,但是当第三点和第四点结合的时候,就很容易让人有个推论:

map是可以跳出循环的,可以提前中断

然而,也的确有些面试官,认为forEach不能break,map是可以的跳出的。 真的是这样的?

首先,众所周知,forEach是不能用break提前中断循环的,如果使用了,会直接报错。比如以下:

1
2
3
4
5
6
var list = [1,2,3,4];
list.forEach(item => {
if(item === 2) {
break;
}
} )

但是,如果真的想要中止? 因为会报错,这也提供给我们一个思路,那就是用try..catch把它保住,捕获错误。 但是,个人认为真是多此一举,应该没人会这么用。就不多做讨论了。

那么map可以用break吗?

很显然,它也不行。所以如果有面试官问你,甚至告诉你”forEach和map的不同,forEach不可以中止”时,你真的可以大胆回击:

  • 想要用Array.prototype.map实现break,也是完全不可能的
  • map中文翻译为映射,所谓映射,当然是把数组中每个值进行映射处理,有何理由中断循环,特殊处理?

Q2. 对象与继承

请问以下程序中,person1person2哪个是Person的实例?其__proto__分别指向谁?

1
2
3
4
5
6
function Person(name) {
this.name = name;
return {name: name}
}
var person1 = new Person('sam');
var person2 = Person('Lily')
Ans
1
2
person1和person2都不是Person的实例
它们的'__proto__'指向Object

这题题目的关键就是理解new的过程,这也是常见的面试题之一了。如果遇到关键字new,那么,函数就不单单是一个函数了:

  • 函数执行,首先隐式建立对象this
  • 执行,绑定this相关的属性,本例中this.name = name
  • 函数最后,隐式的returnthis

当然,这里简化了这个过程,但是核心是这几步。 但是如果在隐式返回this之前,提前返回了一个对象,那么就会退出函数了。 要知道,只有这个this才是实例本身,它的__proto__才指向构造函数,如果不能把this返回出去,那么一切都是徒劳的。因此,这里无论是否new,都返回的是一个新对象{}

Q3. 垃圾回收机制方式及内存管理(爱奇艺2018提前批二面)

这里,自己当时回答的不好,就引用别人博客整理的内容啦.

回收机制方式
  1. 定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。

  2. 原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

  3. 实例如下:

1
2
3
4
5
6
7
8
9
function fn1() {
var obj = {name: 'hanzichi', age: 10};
}
function fn2() {
var obj = {name:'hanzichi', age: 10};
return obj;
}
var a = fn1();
var b = fn2();

fn1中定义的obj为局部变量,而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放。

  1. 垃圾回收策略:标记清除(较为常用)和引用计数。

标记清除:

定义和用法:当变量进入环境时,将变量标记”进入环境”,当变量离开环境时,标记为:”离开环境”。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。

到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

引用计数:

定义和用法:引用计数是跟踪记录每个值被引用的次数。

基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收的对象。

内存管理
  1. 什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。

IE6的垃圾回收是根据内存分配量运行的,当环境中的变量,对象,字符串达到一定数量时触发垃圾回收。垃圾回收器一直处于工作状态,严重影响浏览器性能。

IE7中,垃圾回收器会根据内存分配量与程序占用内存的比例进行动态调整,开始回收工作。

  1. 合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。
  2. GC缺陷:(1)、停止响应其他操作;
  3. GC优化策略:(1)、分代回收(Generation GC);(2)、增量GC

说实话,这一块,自己没有很好的整理,但是目前准备秋招和毕设,没有更多的经历,只能待有空再深入学习研究了。

编程题

Q1. 表格排序

请将以下表格,按年龄进行排序,使用原生JS,不允许使用任何第三方工具。

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
<table>
<thead>
<tr>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>17</td>
</tr>
<tr>
<td>李四</td>
<td>43</td>
</tr>
<tr>
<td>王五</td>
<td>22</td>
</tr>
<tr>
<td>小刘</td>
<td>9</td>
</tr>
<tr>
<td>黄三</td>
<td>20</td>
</tr>
</tbody>
</table>
Ans:

这个其实很easy了,只是我在写的时候,还是调试了好几次,这里主要两点:

  • 通过document.getElementsBytagNames选择的对象,在dom的映射机制下,是双双绑定的;
  • 使用dom.appendChild()方法,要保证参数是node节点。
  • Array.sort()方法,注意,只能用在Array上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var sortByAge = function () {
var tbody = document.getElementsByTagName('tbody')[0];
var items = tbody.getElementsByTagName('tr');
let arrayI = Array(...items); //将类数组转化为数组,使用sort方法
arrayI.sort((a,b)=> {
let ageA = a.getElementsByTagName('td')[1].innerText;
let ageB = b.getElementsByTagName('td')[1].innerText;
return ageA - ageB;
})
for(let i = 0; i<items.length; i++) {
tbody.appendChild(arrayI[i]); //依次插入,这里arrayI的每一个元素都是原来的dom映射过来的实例。所以并不是“创造”出了复制品,而是重新排序了
}
}
sortByAge();

Q2.时间’0101’转换为正常时间值(杭州有赞2018秋招二面在线编程)

一天24小时,我们将其折为每30分钟为一段,这样一天共有48段。我们用1表示这段时间有效,0表示无效,比如10... 表示开始时间为00:00 持续了半个小时,技术时间为00:30111001.. 则表示00:00~01:30 02:30~03:00两个时间段。

要求写一个函数,对时间码进行转换:

输入:110100000000000000000000000000000000000000000000

输出: ["00:30~01:30", "01:00~02:00"]

Ans

这个是我视频面试时的一道编程题,由于时间紧,面对面试官有点小压力,所以就用了比较笨的方法实现了,后来也没有重新思考和优化,有好的思路和简单的方法的小伙伴欢迎交流。

以下思路:

  • 遍历输入字符串
  • 当为1,判断是否为第一次,如果是第一次,根据index,记录startTime;如果不是第一次,进行time累加
  • 当为0,判断之前是否为1,如果之前是1,则根据time值进行累加,计算endTime,并将时间段push到数组
  • 判断是否为第一次、判断之前是否为1,主要通过一个flag进行记录。

根据上面思路,代码如下:

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
32
33
34
35
36
37
38
39
40
function timeTransfer(str) {
var flag = false, //flag,判断是否第一次为1
time = 0, //时间记录值
n = str.length,
startTime, //开始时间
startHour, //开始的小时
startMin, //开始的分钟
endTime, //结束时间
endHour, //结束的小时
endMin, //结束的分钟
retTime = []; //最终返回的数组
for(let i = 0 ; i < n; i++) {
if(str[i] === '1') {
if(flag === false) { //判断是否为第一个1,如果是,根据i设定开始时间
startHour = parseInt(i/2);
startMin = i % 2 ? 0:30;
startTime = `${startHour > 9? startHour: '0' + startHour}:${startMin > 9 ? startMin : '0' + startMin}`;
flag = true; //置位 flag
}
time += 0.5; //时间累加
if(i === n-1) { //如果已经遍历到最后,那么计算结束时间(当str的末尾为1时,需要处理)
endHour = startHour + parseInt(time);
endMin = startMin + (time % 2 ? 0 : 30);
endTime = `${endHour > 9 ? endHour : '0' + endHour}:${endMin > 9 ? endMin : '0' + endMin}`;
retTime.push(`${startTime}~${endTime}`);
}
} else if (str[i] === '0') {
if(flag === false) { //如果为flase,代表前面不是1,继续下次循环
continue;
} else { //否则,计算结束时间
flag = false;
endHour = startHour + parseInt(time);
endMin = startMin + (time % 2 ? 0 : 30);
endTime = `${endHour > 9 ? endHour : '0' + endHour}:${endMin > 9 ? endMin : '0' + endMin}`;
retTime.push(`${startTime}~${endTime}`);
}
}
}
return retTime;
}

以上程序,是跑通了的,虽然有点笨,但是勉强满足要求。

参考资料

  1. csdn wdlhao的博客
0%