記錄一次螞蟻金服前端電話面試

Va007發表於2019-03-23

座標杭州,18年畢業,算上實習一年半開發經驗。是外派的面試,後面兩面都是阿里的面試官。

本來是給我發的線上測評,但是那邊伺服器出現問題,我一直打不開網頁,最後只好以電話問答的形式。下面我寫的大部分都是測評裡的題目,部分是電話裡新增的題目。。。

  1. 儘量使用盡可能多的方式實現子元素的垂直水平居中

    <div class="father">
        <div class="child">  
        </div>
    </div>
    
    <style>
        .father {
            width: 300px;
            height: 300px;
        }
        .child {
              
        }
    </style>
    複製程式碼

child分很多種情況,大致說一下

  • 行內元素:text-align + line-height
.father{
  text-align: center;
  line-height: 300px;
}
複製程式碼
  • 定寬定高:absolute + margin
.father{
  position: relative;
}
.child{
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -150px 0 0 -150px;
}
/* 或者 */
.child{
  position: absolute;
  left: 0;
  top: 0; 
  right: 0; 
  bottom: 0;
  margin: auto;
}
複製程式碼
  • 不定高:absolute + translate
.father{
  position: relative;
}
.child{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}
複製程式碼
  • 不定高:flex
.father{
  display: flex; 
}
.child{
  margin:auto;
}
/* 或者 */
.child{
  justify-content: center;
  align-items: center;
}
複製程式碼
  • table方式:
.father{
  display: table; 
}
.child{
  display: table-cell;
  text-align: center;
  vertical-align: middle;
}
複製程式碼
  1. 儘可能多的方式實現如下三欄佈局,要求 .main 在中間顯示

<div class="container">
  <div class="main"></div>
  <div class="sub"></div>
  <div class="extra"></div>
</div>
複製程式碼
  • 浮動
  • flex佈局:利用order屬性(排序,這個沒答上來)
  • gird佈局:grid-template-areas
  1. 執行下面的程式碼,輸出結果是什麼?

console.log(+false) // 0, 這裡是一元運算子進行的弱型別轉換,相對應的 +true 則返回 1
console.log('1' + 2 + '3' + 4) // '1234' 遇到字串就會拼接
console.log(4 + 3 + 2 + '1')  // '91' 先加在一起再拼接
複製程式碼
  1. 執行下面的程式碼,輸出什麼?

var x = 3;
var foo = {
  x: 2,
  baz: {
    x: 1,
    bar: function() {
      return this.x;
    }
  }
}
var go = foo.baz.bar;

console.log(go());  // 3
console.log(foo.baz.bar()); // 1
複製程式碼

這題考的是this的指向:

this由呼叫者提供,由呼叫函式的方式來決定。如果是一個物件呼叫的函式,則this指向該物件,比如foo.baz.bar()。如果函式獨立呼叫比如go(),那麼該函式內部的this,則指向undefined。但是在非嚴格模式中,它會被自動指向全域性物件window。

  1. 實現以下函式,使得輸入的字串逆序輸出

function reverse(str) {
  let res = str.split('');
  return res.reverse().join('');
}

reverse('hello world!'); // output: '!dlrow olleh'
複製程式碼

進階問題

'hello world!'.reverse();  // output: '!dlrow olleh'
複製程式碼
  • 這個問題需要給String的原型上新增方法,但是要考慮有一些改造:
String.prototype.reverse = function reverse() {
  let res = this.split('');
  return res.reverse().join('');
}
複製程式碼
  1. 實現sleep 函式

要求用法:

/**
- 使當前執行的非同步操作(promise 或者 async)停止等待若干秒
- 
- @param ms */
(async () => {
  console.log('hello')
  await sleep(2000) // 等待兩秒
  console.log('world')
})()
複製程式碼
const sleep = (ms) => {
  new Promise(resolve, reject) {
    setTimeOut(() => {
      resolve(); // 大致這樣?
    }, ms)
  }
}
複製程式碼
  1. 實現throttle 節流函式

    用法:

    const throFun = () => console.log('hello');
    const thro = throttle(throFun, 300);
    document.body.onscroll = () => {
      thro();  // 呼叫至少間隔 300 毫秒才會觸發一次
    }
    複製程式碼
    /**
    - 頻率控制,返回函式連續呼叫時,action 執行頻率限定為 1次 / delay
    - @param delay  {number}    延遲時間,單位毫秒
    - @param action {function}  請求關聯函式,實際應用需要呼叫的函式
    - @return {function}    返回客戶呼叫函式 */
    function throttle(action, delay) {
      var previous = 0;
      // 使用閉包返回一個函式並且用到閉包函式外面的變數previous
      return function() {
        var _this = this;
        var args = arguments;
        var now = new Date();
        if(now - previous > delay) {
            action.apply(_this, args);
            previous = now;
        }
      }
    }
    複製程式碼
  2. 實現debounce 防抖函式

    用法:

    const throFun = () => console.log('hello');
    const thro = debounce(throFun, 300);
    document.body.onscroll = () => {
      thro();  // 若一直呼叫則不會執行,空閒時間大於 300 毫秒才會執行
    }
    複製程式碼
    /**
    - 空閒控制 返回函式連續呼叫時,空閒時間必須大於或等於 delay,action 才會執行
    - @param delay   {number}    空閒時間,單位毫秒
    - @param action {function}  請求關聯函式,實際應用需要呼叫的函式
    - @return {function}    返回客戶呼叫函式 */
    function debounce(action, delay) {
      var timer; // 維護一個 timer
      return function () {
          var _this = this; // 取debounce執行作用域的this
          var args = arguments;
          if (timer) {
              clearTimeout(timer);
          }
          timer = setTimeout(function () {
              action.apply(_this, args); // 用apply指向呼叫debounce的物件,相當於_this.action(args);
          }, delay);
      };
    }
    複製程式碼
  3. 陣列去重有哪些方法?

const arr = [1,2,3,4,4,3,2,1];
// 方法一:new Set ES6
return [...new Set(arr)]; // 這裡又問到我...的用法

// 方法二:雙層for迴圈 (然後說這樣效能不好,讓我只用一層for迴圈的方法)
function unique(arr){
  var res=[];
  for (var i = 0; i < arr.length; i++) {
    for (var j = i+1; j < arr.length; j++) {
      if (arr[i] === arr[j]) {
        ++ i;
        j = i;
      }
    }
    res.push(arr[i]);
  }
  return res;
}

// 方法三:單層for迴圈 + indexOf
function unique(array){
    var res = [];
    for(var i = 0; i < array.length; i++) {
        //如果當前陣列的第i項在當前陣列中第一次出現的位置是i,才存入陣列;否則代表是重複的
        if (array.indexOf(array[i]) === i) {
            res.push(array[i])
        }
    }
    return res;
}
// 方法三點三:或者這樣
function unique(array){
    let res = [];
    for(var i = 0; i < array.length; i++) {
        if (res.indexOf(array[i]) === -1) {
            res.push(array[i]);
        }
    }
    return res;
}

// 方法四:面試官說如果可以容忍改變原有陣列的情況下,怎麼改進效能更好
function unique(array){
    // 注意這裡一定要倒敘for迴圈,否則會出現問題
    for(var i = array.length - 1; i > 0; i--) { 
        if (array.indexOf(array[i]) !== i) {
            array.splice(i, 1);
        }
    }
    // 因為少宣告一個變數,節省了記憶體空間(雖然可以忽略不計,但是面試嘛~)
    return array;
}
複製程式碼
  1. 場景題,考察事件代理,事件監聽,DOM自定義屬性等

題目描述:

  • 網頁中有一個元素A,它有個data-href屬性,裡面存放一個連結地址?
  • 實現一個函式,當任意點選時,如果點選的元素就是A或其上層節點中找到A,則進行連結跳轉

實現思路:

  • 首先給window新增點選事件的監聽,然後先宣告一箇中間變數targetNode記錄節點
  • 然後迴圈判斷當前節點targetNode是否存在data-href屬性
  • 如果存在則進行跳轉,break。不存在則將該節點的父節點賦值給targetNode
  • 一直迴圈到判斷targetNode是最外層節點為止

面試過後整理的程式碼:

window.addEventListener('click', (e) => {
    let targetNode = e.target
    while (targetNode !== document) { // 只要當前節點不是最外層document
        console.log(targetNode)
        if (targetNode.getAttribute('data-href')) { //  其實用hasAttribute更合適
            window.open(targetNode.dataset.href)
            break
        } else { // 沒找到就繼續往上找
            targetNode = targetNode.parentNode
        }
    }
})
複製程式碼

總結:

  • 感覺這道題出的很好,回答過程中面試官也一直引導我的思路,比如最開始我想到判斷節點是否為上下級關係的方法用DOM的contains()方法,然後面試官考慮效能,最好用原生api。然後我就說用parentNode。後來我查了一下MDN,發現contains這個方法就是原生的api,IE5以上就支援了:
    contains相容性
    但是這也不是重點,重點是用contains這個方法不能實現這個場景,因為你需要父節點(上層節點)是已知的。但這個場景是通過已知子節點(點選事件返回的target)來向上尋找節點。
  • 我說到用dataset獲取自定義屬性的時候,面試官說考慮到相容性,有沒有別的方法。我回答getAttribute()
  • 在說怎麼一層一層的往上找節點的時候,最開始我想到了用遞迴呼叫函式,面試官當即打斷我說沒有必要,而且浪費效能。然後提示我說有什麼迴圈的方法。最後想到了while
  1. 其他問題

  • ES6陣列有哪些方法
  • React使用中用過哪些狀態管理工具,緊接著問了redux和mobx的區別,面試遇到三四次了,故總結一下:

1.Redux 鼓勵一個應用只用一個 Store,Mobx 鼓勵使用多個 Store; 2.Redux 是函數語言程式設計,而Mobx的模式是物件導向的; 3.Redux 使用“拉”的方式使用資料,這一點和 React是一致的,但 Mobx 使用“推”的方式使用資料; 4.Redux 鼓勵資料正規化化,減少冗餘,Mobx 容許資料冗餘,但同樣能保持資料一致。 5.Redux更嚴格,必須呼叫reducer觸發action來改變state, Mobx 最初的一個賣點就是直接修改資料,但是實踐中大家還是發現這樣無組織無紀律不好,所以後來 Mobx 還是提供了 action 的概念。和 Redux 的 action 有點不同,Mobx 中的 action 其實就是一個函式,不需要做 dispatch。如果想強制要求使用 action,禁止直接修改 observable 資料,使用 Mobx 的 configure,如下:

import {configure} from 'mobx';

configure({enforceActions: true});
複製程式碼
  • 讓講了一下react的虛擬DOM,簡單講一下diff演算法原理
  • React中key的作用

相關文章