由淺入深的前端面試題 和矯情的“浪漫主義”詩句

LucasHC發表於2017-03-06

好吧,我承認太標題黨了,這篇文章是通過一道前端面試題引出的純技術討論。我先要矯情無比的從中外詩歌說起。

傳統的佛學經典裡,被世人熟知的有這樣一句話:“一花一世界,一葉一菩提,一木一浮生,一草一天堂,一砂一極樂,一方一淨土,一笑一塵緣,一念一清靜。”

昔時佛祖拈花,惟迦葉微笑,既而步往極樂。從一朵花中便能悟出整個世界,得昇天堂,佛祖就是佛祖,誰人能有這樣的境界。

同時,早在18世紀,英國偉大的浪漫主義詩人Black名為《天真的暗示》的詩中,也類似寫道:"To see a world in a grain of sand, and a heaven in a wild flower",一顆沙裡一個世界,一朵野花一座天堂。

轉念,雖卑為碼農,我們寫出的程式碼,卻彰顯了功力:菜鳥和大神之間的差距,往往工程線上卑微的幾行程式碼,便有天壤之差。

一道系列面試題,在JS知識體系中雖滄海一粟,但考察點充分評判面試者的能力。
管中窺豹,期待讀者有不同想法與我討論。

題目背景

題目是我在《effective javascript》一書中提取的。這一星期陸陸續續面試了不少於10個人,其中不乏工作履歷突出的候選者。
但是很遺憾沒有能完全在較短時間內有較高質量的回答。

題目前身

這道題可以分為前後兩個部分,第一部分很簡單,一般有一定JS OOP基礎的候選者應該都可以答好:

一個社交網路有一組成員(member),每個成員有一個自己的名字,和儲存其朋友資訊的列表。請實現這樣一個Member構造器。

正確答案不難理解:

function Member (name) {
    this.name = name;
    this.friends = [];
}複製程式碼

是不是非常簡單。它的典型錯誤包括但是不限於:

function Member (name) {
    this.name = name;
}
Member.prototype.friends = [];複製程式碼

關於方法和屬性是應該放在原型上,還是建構函式中,如果您不明白的話,是時候補一補原型原型鏈的知識了。推薦給大家看一下我的同事顏海鏡早在3年前的一篇文章。

同樣,這道題上我會順便考察一下面試者對JS中變數的儲存方式,包括堆疊儲存的不同情況和引用賦值的掌握情況。

題目變身

以上是對JS基礎的考察,但是在這道題目的基礎上,我進行了更深一步提問。希望對候選者的臨場思維、JS基礎甚至一些設計能力,又更進一步認識。

我要實現一個帶環社交網路(社交圈):

var a = new Member('Alice');
var b = new Member('Bob');
var c = new Member('Carol');
var d = new Member('Dieter');
var e = new Member('Eli');
var f = new Member('Fatima');

a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d, f);複製程式碼

這種情況下,需要實現一個inNetwork方法,判斷某目標成員是否在另一個物件成員的社交圈中。規定:順著社交鏈能找到目標成員,就認為在社交圈中。否則,不在其社交圈。

解題思路

如果剛接觸到這樣的題目,尤其是在面試現場,作為面試者很可能會慌亂一下。這時候,需要做的就是先準確分析題目。
根據題目,畫出符合上述題目程式碼的例項化網路:

由淺入深的前端面試題 和矯情的“浪漫主義”詩句
例項社交圈圖示

接下來思考,搜尋意味著需要遍歷整個社交網路。我們應該:

1)以單個根節點開始,

2)然後新增發現的節點,

3)移除訪問過的節點,防止死環

最終實現:

Member.prototype.inNetwork = function (target) {
    var visited = {};
    var worklist = [this]; // 用於存放社交鏈上的個體資訊,初始時以“自己”作為根節點

    while (worklist.length > 0) {
        // 將worklist裡的最後一項成員刪除並取出
        var member = worklist.pop();
        // 如果存在環的情況,需要避免重複訪問
        if (member.name in visited) {
             continue;
        }
        visited[member.name] = member;
        if (member === target) {
            return true;
        }
        // 將當前成員的朋友列表加入worklist當中,他們都在根節點的社交鏈上
        member.friends.forEach(function(friend) {
            worklist.push(friend);
        })
    }
    return false;
}複製程式碼

我在程式碼中加上了註釋,如果您還不明白也沒有關係。建議去跑一下程式,進行debugger和console,嘗試去理解。

測試:

a.inNetwork(f) // true
f.inNetwork(a) //false複製程式碼

哈哈,果然Alice能通過朋友圈查詢到Fatima,而Fatima卻不能反向找到Alice!當然,這樣我認為是違反人類社會常識的。但是,誰讓他是題目呢?

一道簡單的題卻覆蓋了很多知識點,比如:while迴圈中的流程控制(continue),陣列的基本方法(pop,forEach,push),for...in等等。

它的典型錯誤包括但是不限於:使用物件承載worklist,然後用for...in迴圈遍歷worklist。

這樣做的問題在於:for...in迴圈並沒有要求列舉物件的修改與當前迴圈保持一致。事實上,標準規範規定了:

“如果被列舉物件在列舉期間新增了新的屬性,那麼列舉期間並不能保證新新增的屬效能夠訪問”。

總結

這道題考察了面試者包括JS OOP在內的多項基礎,尤其是後半道考察了候選者的思維能力、反應能力。如果讀者不能理解,歡迎留言問我。或者去查閱:《effective javascript》一書。

扯回原詩句,談一下感悟,在天體的轉動和歲月的輪迴中,我們分明的感受到每一個個體所擁有生命週期的單薄無力,在巨集大的宇宙觀中恐怕渺小不及滄海一粟。詩句的後半句拿出來共勉:“Hold infinity in the palm of your hand, and eternity in an hour 把無限放在你的手上,永恆在一剎那裡收藏”。

在前端快速迭代發展的學習中,作為初學者,往往面對浩瀚的知識海洋望洋興嘆,此時基礎便是那能夠收藏“永恆和無限”的潘多拉魔盒。


本文對你有幫助?歡迎掃碼加入前端學習小組微信群:

由淺入深的前端面試題 和矯情的“浪漫主義”詩句

相關文章