1. 程式人生 > >掌握面試——彈出框的實現(一道題中包含佈局/js設計模式)

掌握面試——彈出框的實現(一道題中包含佈局/js設計模式)

這道面試題,當初我面試的時候被問過兩次,因此比較深,此外,我記得還有設計模式的考察,所以,有深刻的體會。

面試題主要考察什麼

面試不是個輕鬆的活,不管是對面試官還是面試者都一樣。對於面試官來說,別的先不管,首先一點技術要過關,對候選人的基本要求就是基礎紮實,有相關專案經驗,有解決問題的能力,思路清晰,易於溝通。而對於面試者來說要技術紮實,知識面要廣,要有技術閃光點,對於各種技術提起來都知道點,但經不起深層次的詢問,這種‘一瓶子水不滿,半瓶子水晃地’現象很不受面試官的喜歡。

雖然,我工作的時間不長,但被面試過,也面試過別人。所以,將這道題被問的情況和自己的體會理解分享給大家。

注:面試公司 去哪兒,360

用 html + css 實現一個彈出框

注:這是按照面試官的預想可能出現的情況設定的,不同的面試者臨場發揮不一樣,面試官可能問的問題也有所變化,但歸根結底,這個系列的問題大致如下。

這裡你在回答前需要問清楚實現有什麼限制沒,如果沒有,你可以以任意的方式來實現;如果有,問清楚限制,一般限制有如下情況,一種定寬定高,另一種不定寬高(補丁寬高一般用js控制),但是使用css3亦可實現,如果你回答出一種,面試官往往會問有沒有其他的實現方法,如果面試管這樣問了,你如果,回答沒有,會被扣分的。面試會進入下一個環節。

彈出框垂直水平居中

html:

<div class="box-default box-wh box-vc-mar">this is a pop-up box</div>

css:
.box-default {
position: fixed;
top: 50%;
left: 50%;
z-index: 99;
padding: 20px;
background-color: white;
border: 1px solid #ccc;
border-radius: 8px;
}

.box-wh {
    width: 200px;
    height: 200px;
}

.box-vc-mar {
    margin-left: -100px;
    margin-top: -100px;
}

如果彈出窗的寬高不定

html:

<div class="box-default">this is a pop-up box</div>

css:

.box-default {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 99;
    padding: 20px;
    background-color: white;
    border: 1px solid #ccc;
    border-radius: 8px;
}

.box-vc-mar {
    margin-left: -100px;
    margin-top: -100px;
}

js:

var $box = $('.box-default'),
    bw = $box.width(),
    bh = $box.height();
$box.css({
  marginLeft: - bw/2 + 'px',
  marginTop: - bh/2 + 'px',
});

有沒有其他方式實現不定寬高的彈出窗

答案:有

html:

<div class="box-default box-vc-trf">this is a pop-up box that only html+css</div>

css:

.box-default {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 99;
    padding: 20px;
    background-color: white;
    border: 1px solid #ccc;
    border-radius: 8px;
}

.box-vc-trf {
    -webkit-transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    -o-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
}

4.如果給彈出窗加一個遮罩層該如何實現

html:

<div class="box-default box-vc-trf">this is a pop-up box that only html+css</div>
<div class="mask"></div>

css:

.box-default {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 99;
  padding: 20px;
  background-color: white;
  border: 1px solid #ccc;
  border-radius: 8px;
}
.box-vc-trf {
  -webkit-transform: translate(-50%, -50%);
  -ms-transform: translate(-50%, -50%);
  -o-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}
.mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 98;
  background-color: #000;
  opacity: 0.75;
  filter: alpha(opacity=75);
}

下面的問題就問的比較深了,牽涉到了一些簡單業務實現

根據是之前實現的 結果,如果在頁面中有一個按鈕,通過觸發鈕如何實現彈出窗以及實現關閉彈出窗

示例程式碼如下:

html:

<div class="box-default box-vc-trf">this is a pop-up box that only html+css</div>
<div class="mask"></div>

<button id="btn">click it</button>

css:

.box-default {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 99;
  display: none;
  padding: 20px;
  background-color: white;
  border: 1px solid #ccc;
  border-radius: 8px;
}

.box-in {
  display: block;
}

.box-vc-trf {
  -webkit-transform: translate(-50%, -50%);
  -ms-transform: translate(-50%, -50%);
  -o-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}

.mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 98;
  display: none;
  background-color: #000;
  opacity: 0.75;
  filter: alpha(opacity=75);
}

.mask-in {
  display: block;
}

js:

var $box = $('.box-default'),
    $mask = $('.mask'),
    $btn = $('#btn');

$btn.on('click', function(event) {
  event.preventDefault();
  $box.addClass('box-in');
  $mask.addClass('mask-in');
});

$mask.on('click', function(event) {
  event.preventDefault();
  $(this).removeClass('mask-in');
  $box.removeClass('box-in');
});

這個只是我的實現,每個人的實現都會有所不同。

此外,像這裡如何關閉彈出窗的實現方式,並不一定要通過觸發遮罩層來實現,我們常見的實現是在彈出窗中加一個關閉按鈕。像在這裡這種模稜兩可的地方,往往存在陷阱,我建議在你給出答案前先問清楚這個關閉彈出窗要如何實現,並給出自己的方案,因為在實際的專案開發的過程有些細節問題產品經理並不會面面俱到,會有疏漏的地方,而作為一個前端你要及時的提出來,進行溝通確認,這裡可能會考察你的觀察,判斷和溝通能力。但有些時候你問過後,面試官會對你笑笑,讓你自由發揮,你這時就會明白,這是一處坑。但有時候不是這樣,所以,面試時要主動與面試官溝通,避免因細節問題在面試過程中被降分。

如果在頁面中有多個按鈕,那麼這個彈出窗要如何實現

同上,根據之前的建議在回答問題之前要問清楚問題中模稜兩可的地方。

1.多個按鈕是不是成百上千個,或者就是幾個。

2.是否觸發不同按鈕彈出的視窗現實的內容不同。

同時也可以不問,你只要明白要考察的知識點即可,問只是讓你更清楚的知曉面試官的考察點。

html:

<div class="box-default box-vc-trf">this is a pop-up box that only html+css</div>
<div class="mask"></div>

<button class="btn">click</button>
<button class="btn">click2</button>
<button class="btn">click3</button>
<button class="btn">click4</button>
<button class="btn">click5</button>
<button class="btn">click6</button>

css:

css 同上個示例

js:

$(function(){
    var $box = $('.box-default'),
        $mask = $('.mask');

    $mask.on('click', function(event) {
      event.preventDefault();
      hideMask();
    });

    $('.btns').forEach(function(el, i) {
        el.on('click', function(event) {
          event.preventDefault();
          var content = getText($(this));
          showMask(content);
        }
    });

    function getText(obj) {
        return obj.text()
    }

    function showMask(content) {
      $box.addClass('box-in').html(content);
      $mask.addClass('mask-in');
    }

    function hideMask(obj) {
      $mask.removeClass('mask-in');
      $box.removeClass('box-in');
    }
});

這裡主要考察了兩點:

1.js基礎是否紮實

  • 表現和行為的分離
  • 可維護性
  • 可擴充套件性

2.是否使用了事件代理

程式碼修改如下:

$(function(){
    ...

    $(document.body).on('click', '.btn', function(event) {
      event.preventDefault();
      var content = getText($(this));
      showMask(content);
    });

    ...
});

有時候面試官會讓你用原生js來實現事件代理,那麼我們應該如何回答呢?

由於事件委託可以實現目標物件的隱藏,在開發這對於我們保護一些核心的物件非常有用,不過實話來說,在JavaScript中就是call,apply的使用,因為js中只有這兩個方法提供了改變當前函式內部this作用域的功能。當然這只是物件的委託,而要實現對類的委託則要相對複雜些,對類的實現大家有興趣的可以參考以下Prototype.js或Jquery中的相應實現。

事件代理就要改變偵聽器的位置,或者說改變事件繫結的物件。得益於js的事件傳播機制,實現起來非常容易。

下面這個例子大家都可能見過:

html:

<ul id="nav">
    <li><a href="http://blog.csdn.net/weixin_41559723?ref=toolbar/">CSDN</a></li>
    <li><a href="https://segmentfault.com/u/birenyangguangcanlan">Segmentdefault</a></li>
    <li><a href="https://github.com/lvzhenbang">github</a></li>
    <li><a href="https://juejin.im/user/58b83c66128fe100642f5297/">稀土掘金</a></li>
    <li><a href="https://home.cnblogs.com/u/1309794/">部落格園</a></li>
</ul>

js:

window.onload = function(){
  var nav = document.getElementById("nav");
  nav.onclick = function () {
    var e = arguments[0] || window.event,
        target = e.srcElement ? e.srcElement : e.target;
    if (target.nodeName.toLowerCase() === 'a')
        alert(target.innerHTML);
    return false;
  }
}
var addEvent = (function () {
    if (document.addEventListener) {
      return function (el, type, fn) {
        el.addEventListener(type, fn, false);
      };
    } else if (document.attachEvent) {
      return function (el, type, fn) {
        el.attachEvent('on' + type, fn);
      }
    } else {
        el['on' + type] = fn
    }
  })();

上面這個事件監聽的相容性程式碼片段,是面試官問我是否對jQuery原始碼有所瞭解,我當然說有,就讓我說一下如何實現。

好了,關於事件代理的問題就不深入討論了,我們接著瞭解關於彈出窗的其他問題。

遮罩層的共用問題

同一個頁面我們可以考慮,直接在頁面中加一個表示遮罩層的div,
但是對於不同的頁面我們最好用js來控制。

問題:這裡面試管會問,如何實現遮罩層的共享?

我們通常會寫下面的一個程式碼片段:

function createMask() {
    return $(document.body).append($('<div>').addClass('mask'));
}

雖然,這樣新增一個遮罩層,在隱藏的時候可以使用remove()移除它,但顯然這樣在頁面中頻繁的新增,刪除dom元素不合理。

問題:有沒有什麼方法可以對它進行優化?

我們可能會考慮對它進行優化,先在全域性建立一個這個遮罩層div,用一個變數來引用它。程式碼如下:

var mask = $(document.body).append($('<div>').addClass('mask'));

這樣頁面就只會建立一個遮罩層div,但是可能出現,我們在使用的過程中不會用到這個div,這樣我們就會造成資源的浪費,dom的節點就會平白多出一個無用的div。

這時我們可以藉助一個變數來判斷是否建立過div。程式碼如下:

var mask;
function createMask() {
    if (mask) {
        return mask;
    } else {
        mask = $('<div/>').addClass('mask').appendTo($(document.body));
        return mask;
    }
}

細心的你是否發現這是一個單例?

當你在面試前對這個問題有研究,你說用一個單例模式來解決,面試官會讓你先實現,然後讓你說說單例模式的理解,最後詢問相關的問題。如:什麼是單例模式,單例模式有何優缺點,如何使用單例模式。

但是也有時候面試官會讓你直接使用一個設計模式來對它進行優化?

在這裡,面試官的詢問方向跟面試官的知識面和掌控度有關。所以,面試大廠或者面試中/高階工程師的童鞋還是把自己的所學知識技能系統化比較好。

你發現上面那段程式碼實現有哪裡不妥嗎?對了,你在函式體內改變了函式外的變數mask的引用,在多人協作的專案中,你寫的createMask是一個不安全的函式,此外我們開發中應儘量避免像mask這樣的全域性變數使用,用一個區域性變數如何來解決這個問題呢,我想很多同學會想到閉包,修改後的程式碼如下:

function createMask() {
    var mask;
    return function() {
        if (mask) {
            return mask;
        } else {
            mask = $('<div/>').addClass('mask').appendTo($(document.body));
            return mask;
        }
    }           
}

好吧,到了這裡你可能在心裡想‘這下總算完了吧’,是的,我想說的是這道題你答成這個樣子,對於中級工程師來說已經過關了,但是對於高階工程師來說,你還需要對這個程式碼進行優化,如果經常研究原始碼的童鞋會見到這樣寫的程式碼片段:

function createMask() {
    var mask;
    return function() {
        return mask || mask = $('<div/>').addClass('mask').appendTo($(document.body));   
    }           
}

對於那些開發框架,常用庫或外掛的大牛來說,同樣的功能,同樣的效能,不會多浪費一個字元。

如果是面試高階工程師,可能還會被問到,如何多個功能都要用到單例模式,該如何解決?

function singleton(fn) {
    var res;
    return function() {
        return res || (res = fn.apply(this, arguments))
    }
}

var createMask = singleton(function () {
    return $('<div/>').addClass('mask').appendTo($(document.body));
});

其實,這裡又用到了另一種設計模式——橋接模式。

面試到這裡一般就算完了。通過上面的程式碼我們發現,設計模式也不是什麼洪水猛獸,只不過是設計模式的使用靈活多變,但要想真正完全掌握設計模式,不是看兩篇文章就行的最重要的還是要多想,多實踐。