嗯,上次写blog已经是几周前的事情了,其实已经积攒了很多小问题需要记录和分享了。但是在8月底,VK我一次经历了了携程、拼多多、腾讯、网易等多轮面试轰炸,忙得不可开交,有喜有忧的同时,还是赶快记录了不足,把一些充满迷惑性的问题继续记录和学习。
JavaScript总是给人以惊喜,学习不止,进步不断,今天继续补充JS容易搞错的几道笔试/面试题,为了秋招继续努力,欢迎一起为秋招努力的小伙伴共勉
系列笔记:
面试&常识题
Q1. forEach 与 map的区别? forEach不支持中断循环?
这是一道巨坑的题目
先看下很多博客、文章总结的一个关于循环的区别是怎么说的
- map可以做链式操作、forEach不可以
- map有返回值,return、 forEach没有返回值
- for循环不用担心兼容性问题,可以break跳出循环,是基础循环
- forEach不支持continue和break,是不能退出循环本身的。
上面这些比较,本身没有什么问题,但是当第三点和第四点结合的时候,就很容易让人有个推论:
map是可以跳出循环的,可以提前中断
然而,也的确有些面试官,认为forEach不能break,map是可以的跳出的。 真的是这样的?
首先,众所周知,forEach是不能用break提前中断循环的,如果使用了,会直接报错。比如以下:
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. 对象与继承
请问以下程序中,person1和person2哪个是Person的实例?其__proto__分别指向谁?
function Person(name) {
this.name = name;
return {name: name}
}
var person1 = new Person('sam');
var person2 = Person('Lily')
Ans
person1和person2都不是Person的实例
它们的'__proto__'指向Object
这题题目的关键就是理解new的过程,这也是常见的面试题之一了。如果遇到关键字new,那么,函数就不单单是一个函数了:
- 函数执行,首先隐式建立对象
this - 执行,绑定this相关的属性,本例中
this.name = name - 函数最后,隐式的return
this
当然,这里简化了这个过程,但是核心是这几步。 但是如果在隐式返回this之前,提前返回了一个对象,那么就会退出函数了。 要知道,只有这个this才是实例本身,它的__proto__才指向构造函数,如果不能把this返回出去,那么一切都是徒劳的。因此,这里无论是否new,都返回的是一个新对象{}。
Q3. 垃圾回收机制方式及内存管理(爱奇艺2018提前批二面)
这里,自己当时回答的不好,就引用别人博客整理的内容啦.
回收机制方式
- 定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。
- 原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
- 实例如下:
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所指向,所以该块内存并不会被释放。
- 垃圾回收策略:标记清除(较为常用)和引用计数。
标记清除:
定义和用法:当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。
到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
引用计数:
定义和用法:引用计数是跟踪记录每个值被引用的次数。
基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收的对象。
内存管理
- 什么时候触发垃圾回收?
垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。
IE6的垃圾回收是根据内存分配量运行的,当环境中的变量,对象,字符串达到一定数量时触发垃圾回收。垃圾回收器一直处于工作状态,严重影响浏览器性能。
IE7中,垃圾回收器会根据内存分配量与程序占用内存的比例进行动态调整,开始回收工作。
- 合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。
- GC缺陷:(1)、停止响应其他操作;
- GC优化策略:(1)、分代回收(Generation GC);(2)、增量GC
说实话,这一块,自己没有很好的整理,但是目前准备秋招和毕设,没有更多的经历,只能待有空再深入学习研究了。
编程题
Q1. 表格排序
请将以下表格,按年龄进行排序,使用原生JS,不允许使用任何第三方工具。
<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上。
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:30。111001.. 则表示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进行记录。
根据上面思路,代码如下:
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;
}
以上程序,是跑通了的,虽然有点笨,但是勉强满足要求。