嗯,上次寫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;
}
複製程式碼
以上程式,是跑通了的,雖然有點笨,但是勉強滿足要求。