【長坑慎入】2014阿里前端線上筆試題解答-1

於明昊發表於2013-10-02

2014年阿里的前端線上筆試題目區分度十分的高,感覺十分有意義。所以在這裡做一些分析,也希望能豐富一下自己。這裡已經有一個帖子,但是因為其中作者有一部分也沒有解釋,而且文章的版式比較混亂,所以決定自己動手再分析一下。

No. 1

題目:

下圖綠色區域的寬度為100%,其中有三個矩形,第一個矩形的寬度是200px,第二個和第三個矩形的寬度相等。請使用CSS3中的功能實現它們的佈局。

第一題

已知HTML結構是:

<div class="box">
    <div class="item">column 1</div>
    <div class="item">column 2</div>
    <div class="item">column 3</div>
</div>

解答:


【2013/10/16 更新】

十分感謝 @酷劍 的指正。回去仔細查了一下MDN,發現本題一開始的解答有誤。回頭查了查原帖,發現原帖正確的使用 box-flex 的屬性,而我的解答並不規範。更新解答如下:

.box {
    background-color: #619B99;
    display: -webkit-box;
    height: 40px;
    padding: 10px;
    padding-right: 0px;
    width: 100%;
}
.box>div {
    height: 40px;
    background-color: #EEEEEE;
    margin-right: 10px;
    -webkit-box-flex: 1;
}
.box div:first-of-type {
    width: 200px;
    -webkit-box-flex: 0;
}

這裡其實 box-flex 的含義在於規定框是否具有可伸縮尺寸。我個人總結的規則如下:

  • 當box-flex: 0時,框的空間固定而不伸縮
  • 當 box-flex: n時,框的空間大約會按照剩餘寬度的 n/m 伸縮,m為所有設定了box-flex值的和。
  • 當給有flex的box設定了大小時,該box將擁有設定的空間與flex分配的空間之和

例如父元素有500的空間,其中五個元素設定flex為1,則五個元素的寬度均為100;一個設定為flex:0、寬度180,則剩下的元素均分320的寬度,也即每個80;一個設定flex:6,則該元素分的6/10的寬度即300,剩餘的元素各分1/10的寬度即50;一個元素設定為flex:1,同時設定寬度100,則該元素分得100+(500-100)/5=180的寬度,剩餘的元素分的(500-100)/5=80的寬度。

(實測發現這裡並不是很準確,準確的說當設定了flex的元素內部沒有其他元素時,框的寬度會按照box-flex設定的那樣按比例填充,但如果有元素和邊距等情況,這個比例就僅僅是個大約的值了。)

實際上box-flex是個特別有用的工具,其一勞永逸的解決了當一個box中有元素有固定值,而又要求剩下的元素按比例浮動的情況。像這種問題之前都是使用絕對定位,將固定的元素採取 absolute 定位的方式,然後剩下的元素使用margin留出浮動元素留下的位子。但是當固定的元素過多且混雜出現時,使用絕對定位的方法將不能優雅的解決這個問題,那麼flex模型就能展現它的力量了。

No.2

題目:

有兩個盒子 A、B,B 在 A 盒子中,它們的 CSS 是這麼定義的:

.A {
    position: relative;
    width: 500px;
    height: 500px;
    background-color: green;
}

.B {
    position: absolute;
    max-width: 300px;
    max-height: 300px;
    background-color: blue;
}

如何實現 B 在 A 中水平方向和垂直方向居中?

解答:

作者在原文裡表示可以使用CSS3的 box-alignbox-pack 的方法(W3C連結如下box-alignbox-pack),但是在實際測試中發現,如果B的定位不是 absolute (而是 relative 或者 static)那麼如下追加的程式碼是有效的:

.A {
    display: -webkit-box;
    -webkit-box-align: center;
    -webkit-box-pack: center;
} 

但是當B的定位是絕對定位時,使用CSS3的box進行定位就起不到應有的效果。這裡我們可以簡單的看以下CSS的四種定位的原理(可參見mdn的解釋:css position 以及quicksmode的這篇博文:the position declaration)。

  • static :預設定位,不可使用 top right bottom left 四種屬性(以下簡稱TRBL);
  • relative :相對定位,指的是針對預設定位的位置而產生偏移,可以使用TRBL調整定位的偏移量,但是元素本身還在文件流中佔據位置;
  • absolute :絕對定位,指標對上一級非預設定位的位置(即上一級非 static 定位的父元素的位置)而產生的偏移,可用TRBL調整偏移量,與相對定位不同的是,元素本身漂出文件流,不再佔據位置;
  • fix :固定定位,錨定瀏覽器視窗的一種定位方式,可用TRBL調整偏移量。

這裡B元素使用了絕對定位,也就是其偏移是針對A元素而計算的,可以使用TRBL進行調整。所以說莫非這裡出題人是想讓我們採用原始的div居中方法?

在CSS2的時代,要將div水平垂直居中也算是個挺讓人頭疼的問題,其大致的方法有如下幾種:

  1. 外層元素寬度不固定,內層元素寬度固定:使用自動外邊距或負值外邊距兩種方式;

    • 自動外邊距,這裡要求內層元素不能採取絕對定位:

      .B {
          width: 100px;
          margin: 0 auto;
      }
      
    • 負值外邊距,用到了TRBL屬性,要求內層元素採取絕對定位:

      .B { width: 200px; position: absolute; left: 50%; margin-left: -100px; }

  2. 外層元素寬度固定,內層元素寬度不固定,採用表格法,要求內部元素不能採取絕對定位:

        A. {
            display: table;
        }
        .B {
            display: table-cell;
            vertical-margin: center;
        }
    
  3. 增添HTML元素、使用Hack、使用JS等,不詳述之。

然而觀察上面的方法,題目中的限定條件是:外部元素寬度固定、內部元素寬度變動且絕對定位,這樣就導致上述的方法沒有一個可以滿足所需要求的。我認為如果不改變題目中的限定,可以使用負值外邊距的方法,用JS設定內層元素的margin值;或者直接在後面的CSS中修改內層元素的定位(設定為非絕對定位),然後使用CSS3的 box-alignbox-pack 來實現,而這也是目前我認為實現效果最好,實現程式碼最為簡潔的實現方式。

其中如果附加JS,程式碼如下:

<script>
    var b = document.getElementsByClassName('B')[0]
    ,   l = b.offsetWidth
    ,   h = b.offsetHeight

    b.style.marginLeft = -l/2 + 'px';
    b.style.marginTop = -h/2 + 'px';
</script>

No.8

題目:

現有程式碼如下:

var foo = 1;
function main(){
    console.log(foo);
    var foo = 2;
    console.log(this.foo)
    this.foo = 3;
}
  1. 請給出以下兩種方式呼叫函式時,輸出的結果,並說明原因。

    var m1 = main();
    var m2 = new main();
    
  2. 如果想要 var m1 = main() 產生的m1和前面的m2完全一致,又該如何改造main函式?

解答:

看到全域性變數與函式內區域性變數重名時,一定要先想到 hoisting (變數宣告提升規則)!

在第一種呼叫方式裡,函式內部宣告瞭區域性變數 foo ,因此其宣告被提升至最前。因此該段程式碼等價於:

function main(){
    var foo;
    console.log(foo);
    foo = 2;
    console.log(this.foo)
    this.foo = 3;
}

在呼叫的時候,第一次輸出為undefined,因為區域性變數 foo 還沒有被賦值,第二次輸出為1,因為此時在非嚴格模式下 this 指代 window 物件。this.foo 即為全域性變數的 foo

在第二種呼叫方式裡,使用了原型鏈繼承的方法,可以參見我的這篇文章:JavaScript原型繼承工作原理,其中 new 運算子的作用等價於如下函式:

function New (f) {
    var n = { '__proto__': f.prototype };
    return function () {
        f.apply(n, arguments);
        return n;
    };
}

則在進行 new 運算時,函式中的 this 實際上指代的是運算子後面所跟的建構函式的原型鏈。因為在 new 運算的時候,實際上執行建構函式的是這一步: f.apply(n, arguments); 也就是說在建構函式中的 this 實際上指向的是變數 n ,而 n 是一個除了原形鏈指向 f 的原形鏈之外一無所有的空物件,所以這裡的 this 實際上是指向了建構函式 f 的原形鏈。

所以這裡的輸出也就不難理解:因為建構函式 main() 本身的原形鏈為空,所以輸出 this.foo 的結果為 undefined。

然後是第二問:使m1與m2的結果完全一致。我們知道m2所得到的是類 main 的一個例項化物件,這裡要使得達到題目中的要求,就只能手工編寫原形鏈繼承,使 main 函式的返回結果為一個繼承自其本身的物件。這裡可以參考上面程式碼中 new 運算子的具體實現,編寫 main 函式如下:

function main(){
    console.log(foo);
    var foo = 2;
    console.log(this.foo)
    this.foo = 3;

    //返回一個main的例項化物件
    var n = {'__proto__': main.prototype};
    //main.apply(n, arguments);
    n.foo = 3;
    return n;
}

這裡因為在 main 函式裡有 this.foo = 3 這一項,為原型新增了成員變數,但如果在這裡使用 apply 的方式為n新增這一屬性的話,將進行無限遞迴導致棧溢位。而採取其他的方式都不可避免的會帶來一些問題,所以直接給返回值n的屬性foo進行了賦值,也起到了同樣的效果。

相關文章