刷《一年半經驗,百度、有贊、阿裡面試總結》·手記

来源:https://www.cnblogs.com/geyouneihan/archive/2018/11/29/10041412.html
-Advertisement-
Play Games

在掘金上看到了一位大佬發了一篇很詳細的面試記錄文章 "《一年半經驗,百度、有贊、阿裡面試總結》" ,為了查漏補缺,抽空就詳細做了下。( 估計只有我這麼無聊了哈哈哈 ) 有給出的或者有些不完善的答案,也儘力給出/完善了(可能有錯,大家自行辨別)。有些很困難的題目(例如實現 ),附帶相關鏈接(懶癌患者福 ...


在掘金上看到了一位大佬發了一篇很詳細的面試記錄文章-《一年半經驗,百度、有贊、阿裡面試總結》,為了查漏補缺,抽空就詳細做了下。(估計只有我這麼無聊了哈哈哈

有給出的或者有些不完善的答案,也儘力給出/完善了(可能有錯,大家自行辨別)。有些很困難的題目(例如實現Promise),附帶相關鏈接(懶癌患者福利)。

總的來說,將這些題目分成了“Javascript”、“CSS”、“瀏覽器/協議”、“演算法”和“Web工程化”5個部分進行回答和代碼實現。

最後,歡迎來我的博客和我扯犢子:godbmw.com。直接戳本篇原文的地址:刷《一年半經驗,百度、有贊、阿裡面試總結》·手記

1. Javascript相關

1.1 迴文字元串

題目:實現一個函數,判斷是不是迴文字元串

原文的思路是將字元串轉化成數組=>反轉數組=>拼接成字元串。這種做法充分利用了js的BIF,但性能有所損耗

function run(input) {
  if (typeof input !== 'string') return false;
  return input.split('').reverse().join('') === input;
}

其實正常思路也很簡單就能實現,性能更高,但是沒有利用js的特性

// 迴文字元串
const palindrome = (str) => {
  // 類型判斷
  if(typeof str !== 'string') {
    return false;
  }

  let len = str.length;
  for(let i = 0; i < len / 2; ++i){
    if(str[i] !== str[len - i - 1]){
      return false;
    }
  }
  return true;
}

1.2 實現Storage

題目:實現Storage,使得該對象為單例,並對localStorage進行封裝設置值setItem(key,value)和getItem(key)

題目重點是單例模式,需要註意的是藉助localStorage,不是讓自己手動實現!

const Storage = () => {}

Storage.prototype.getInstance = (() => {
  let instance = null
  return () => {
    if(!instance){
      instance = new Storage()
    }
    return instance
  }
})()

Storage.prototype.setItem = (key, value) => {
  return localStorage.setItem(key, value)
}

Storage.prototype.getItem = (key) => {
  return localStorage.getItem(key)
}

1.3 JS事件流

題目:說說事件流吧

事件流分為冒泡和捕獲。

事件冒泡:子元素的觸發事件會一直向父節點傳遞,一直到根結點停止。此過程中,可以在每個節點捕捉到相關事件。可以通過stopPropagation方法終止冒泡。

事件捕獲:和“事件冒泡”相反,從根節點開始執行,一直向子節點傳遞,直到目標節點。印象中只有少數瀏覽器的老舊版本才是這種事件流,可以忽略。

1.4 實現函數繼承

題目:現在有一個函數A和函數B,請你實現B繼承A。並且說明他們優缺點。

方法一:綁定構造函數

優點:可以實現多繼承

缺點:不能繼承父類原型方法/屬性

function Animal(){
  this.species = "動物";
}

function Cat(){
  Animal.apply(this, arguments); // 父對象的構造函數綁定到子節點上
}

var cat = new Cat()
console.log(cat.species) // 輸出:動物

方法二:原型鏈繼承

優點:能夠繼承父類原型和實例方法/屬性,並且可以捕獲父類的原型鏈改動

缺點:無法實現多繼承,會浪費一些記憶體(Cat.prototype.constructor = Cat)。除此之外,需要註意應該將Cat.prototype.constructor重新指向本身。

js中交換原型鏈,均需要修複prototype.constructor指向問題。

function Animal(){
  this.species = "動物";
}
Animal.prototype.func = function(){
  console.log("heel")
}

function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat

var cat = new Cat()
console.log(cat.func, cat.species)

方法3:結合上面2種方法

function Animal(){
  this.species = "動物";
}
Animal.prototype.func = function(){
  console.log("heel")
}

function Cat(){
  Animal.apply(this, arguments)
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat;

var cat = new Cat()
console.log(cat.func, cat.species)

1.5 ES5對象 vs ES6對象

題目:es6 class 的new實例和es5的new實例有什麼區別?

ES6中(和ES5相比),classnew實例有以下特點:

  • class的構造參數必須是new來調用,不可以將其作為普通函數執行
  • es6class不存在變數提升
  • 最重要的是:es6內部方法不可以枚舉。es5的prototype上的方法可以枚舉。

為此我做了以下測試代碼進行驗證:

console.log(ES5Class()) // es5:可以直接作為函數運行
// console.log(new ES6Class()) // 會報錯:不存在變數提升

function ES5Class(){
  console.log("hello")
}

ES5Class.prototype.func = function(){ console.log("Hello world") }

class ES6Class{
  constructor(){}
  func(){
    console.log("Hello world")
  }
}

let es5 = new ES5Class()
let es6 = new ES6Class()

console.log("ES5 :")
for(let _ in es5){
  console.log(_)
}

// es6:不可枚舉
console.log("ES6 :")
for(let _ in es6){
  console.log(_)
}

這篇《JavaScript創建對象—從es5到es6》對這個問題的深入解釋很好,推薦觀看!

1.6 實現MVVM

題目:請簡單實現雙向數據綁定mvvm

vuejs是利用Object.defineProperty來實現的MVVM,採用的是訂閱發佈模式。每個data中都有set和get屬性,這種點對點的效率,比Angular實現MVVM的方式的效率更高。

<body>
  <input type="text">
  <script>
    const input = document.querySelector('input')
    const obj = {}

    Object.defineProperty(obj, 'data', {
      enumerable: false,  // 不可枚舉
      configurable: false, // 不可刪除
      set(value){
        input.value = value
        _value = value
        // console.log(input.value)
      },
      get(){
        return _value
      }
    }) 
    obj.data = '123'
    input.onchange = e => {
      obj.data = e.target.value
    }
  </script>
</body>

1.7 實現Promise

這是一位大佬實現的Promise版本:過了Promie/A+標準的測試!!!網上能搜到的基本都是從這篇文章變形而來或者直接照搬!!!原文地址,直接戳:剖析Promise內部結構,一步一步實現一個完整的、能通過所有Test case的Promise類

下麵附上一種近乎完美的實現:可能無法和其他Promise庫的實現無縫對接。但是,上面的原文實現了全部的,歡迎Mark!

function MyPromise(executor){
  var that = this
  this.status = 'pending' // 當前狀態
  this.data = undefined
  this.onResolvedCallback = [] // Promise resolve時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面
  this.onRejectedCallback = [] // Promise reject時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面

  // 更改狀態 => 綁定數據 => 執行回調函數集
  function resolve(value){
    if(that.status === 'pending'){
      that.status = 'resolved'
      that.data = value
      for(var i = 0; i < that.onResolvedCallback.length; ++i){
        that.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason){
    if(that.status === 'pending'){
      that.status = 'rejected'
      that.data = reason
      for(var i = 0; i < that.onResolvedCallback.length; ++i){
        that.onRejectedCallback[i](reason)
      }
    }
  }

  try{ 
    executor(resolve, reject) // resolve, reject兩個函數可以在外部傳入的函數(executor)中調用
  } catch(e) { // 考慮到執行過程可能有錯
    reject(e)
  }
}

// 標準是沒有catch方法的,實現了then,就實現了catch
// then/catch 均要返回一個新的Promise實例

MyPromise.prototype.then = function(onResolved, onRejected){
  var that = this
  var promise2

  // 值穿透
  onResolved = typeof onResolved === 'function' ? onResolved : function(v){ return v }
  onRejected = typeof onRejected === 'function' ? onRejected : function(r){ return r }

  if(that.status === 'resolved'){
    return promise2 = new MyPromise(function(resolve, reject){
      try{
        var x = onResolved(that.data)
        if(x instanceof MyPromise){ // 如果onResolved的返回值是一個Promise對象,直接取它的結果做為promise2的結果
          x.then(resolve, reject)
        }
        resolve(x) // 否則,以它的返回值做為promise2的結果 
      } catch(e) {
        reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
      }
    })
  }

  if(that.status === 'rejected'){
    return promise2 = new MyPromise(function(resolve, reject){
      try{
        var x = onRejected(that.data)
        if(x instanceof MyPromise){
          x.then(resolve, reject)
        }
      } catch(e) {
        reject(e)
      }
    })
  }

  if(that.status === 'pending'){
    return promise2 = new MyPromise(function(resolve, reject){
      self.onResolvedCallback.push(function(reason){
        try{
          var x = onResolved(that.data)
          if(x instanceof MyPromise){
            x.then(resolve, reject)
          }
        } catch(e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(value){
        try{
          var x = onRejected(that.data)
          if(x instanceof MyPromise){
            x.then(resolve, reject)
          }
        } catch(e) {
          reject(e)
        }
      })
    })
  }
}

MyPromise.prototype.catch = function(onRejected){
  return this.then(null, onRejected)
}

// 以下是簡單的測試樣例:
new MyPromise(resolve => resolve(8)).then(value => {
  console.log(value)
})

1.8 Event Loop

題目:說一下JS的EventLoop

其實阮一峰老師這篇《JavaScript 運行機制詳解:再談Event Loop》已經講的很清晰了(手動贊)!

這裡簡單總結下:

  1. JS是單線程的,其上面的所有任務都是在兩個地方執行:執行棧和任務隊列。前者是存放同步任務;後者是非同步任務有結果後,就在其中放入一個事件。
  2. 當執行棧的任務都執行完了(棧空),js會讀取任務隊列,並將可以執行的任務從任務隊列丟到執行棧中執行。
  3. 這個過程是迴圈進行,所以稱作Loop

2. CSS相關

2.1 水平垂直居中

題目: 兩種以上方式實現已知或者未知寬度的垂直水平居中

第一種方法就是利用CSS3translate進行偏移定位,註意:兩個參數的百分比都是針對元素本身計算的。

.wrap {
  position: relative;
  width: 100vw;
  height: 100vh;
}

.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

第二種方法是利用CSS3flex佈局,父元素diplay屬性設置為flex,並且定義元素在兩條軸線的佈局方式均為center

.wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}

.wrap .box {
  width: 100px;
  height: 100px;
}

第三種方法是利用margin負值來進行元素偏移,優點是瀏覽器相容好,缺點是不夠靈活(要自行計算margin的值):

.wrap {
  position: relative;
  width: 100vw;
  height: 100vh;
}

.box {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 100px;
  margin: -50px 0 0 -50px;
}

2.2 “點擊”改變樣式

題目:實現效果,點擊容器內的圖標,圖標邊框變成border 1px solid red,點擊空白處重置。

利用event.target可以判斷是否是指定元素本身(判斷“空白處”),除此之外,註意禁止冒泡(題目指明瞭“容器內”)。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
  #app {
    min-width: 100vw;
    min-height: 100vh;
  }
  #app .icon{
    display: inline-block;
    cursor: pointer;
  }
  </style>
</head>
<body>
  <div id="app">
    <span class="icon">123456</span>
  </div>
  <script>
  const app = document.querySelector("#app")
  const icon = document.querySelector(".icon")

  app.addEventListener("click", e => {
    if(e.target === icon){
      return;
    }
    // 非空白處才去除 border 
    icon.style.border = "none";
  })

  icon.addEventListener("click", e => {
    // 禁止冒泡
    e.stopPropagation()
    // 更改樣式
    icon.style.border = "1px solid red";
  })
  </script>
</body>
</html>

3. 瀏覽器/協議相關

3.1 緩存機制

題目:說一下瀏覽器的緩存機制。

瀏覽器緩存分為強緩存和協商緩存。緩存的作用是提高客戶端速度、節省網路流量、降低伺服器壓力

強緩存:瀏覽器請求資源,如果header中的Cache-ControlExpires沒有過期,直接從緩存(本地)讀取資源,不需要再向伺服器請求資源。

協商緩存:瀏覽器請求的資源如果是過期的,那麼會向伺服器發送請求,header中帶有Etag欄位。伺服器再進行判斷,如果ETag匹配,則返回給客戶端300系列狀態碼,客戶端繼續使用本地緩存;否則,客戶端會重新獲取數據資源。

關於過程中詳細的欄位,可以參考這篇《http協商緩存VS強緩存

3.2 從URL到頁面生成

題目:輸入URL到看到頁面發生的全過程,越詳細越好

  1. DNS解析
  2. 建立TCP連接(3次握手)
  3. 發送HTTP請求,從伺服器下載相關內容
  4. 瀏覽器構建DOM樹和CSS樹,然後生成渲染樹。這個一個漸進式過程,引擎會力求最快將內容呈現給用戶。
  5. 在第四步的過程中,<script>的位置和載入方式會影響響應速度。
  6. 搞定了,關閉TCP連接(4次握手)

3.3 TCP握手

題目:解釋TCP建立的時候的3次握手和關閉時候的4次握手

看這題的時候,我也是突然懵(手動捂臉)。推薦翻一下電腦網路的相關書籍,對於FINACK等欄位的講解很贊!

3.4 CSS和JS位置

題目:CSS和JS的位置會影響頁面效率,為什麼?

先說CSS。CSS的位置不會影響載入速度,但是CSS一般放在<head>標簽中。前面有說DOM樹和CSS樹共同生成渲染樹,CSS位置太靠後的話,在CSS載入之前,可能會出現閃屏、樣式混亂、白屏等情況。

再說JS。JS是阻塞載入,預設的<script>標簽會載入並且立即執行腳本,如果腳本很複雜或者網路不好,會出現很久的白屏。所以,JS標簽一般放到<body>標簽最後。

現在,也可以為<script>標簽設置async或者defer屬性。前者是js腳本的載入和執行將與後續文檔的載入和渲染同步執行。後者是js腳本的載入將與後續文檔的載入和渲染同步執行,當所有元素解析完,再執行js腳本。

4. 演算法相關

4.1 數組全排列

題目:現在有一個數組[1,2,3,4],請實現演算法,得到這個數組的全排列的數組,如[2,1,3,4],[2,1,4,3]。。。。你這個演算法的時間複雜度是多少

實現思路:從“開始元素”起,每個元素都和開始元素進行交換;不斷縮小範圍,最後輸出這種排列。暴力法的時間複雜度是 \(O(N_N)\),遞歸實現的時間複雜度是 \(O(N!)\)

如何去重?去重的全排列就是從第一個數字起每個數分別與它後面非重覆出現的數字交換。對於有重覆元素的數組,例如:[1, 2, 2],應該剔除重覆的情況。每次只需要檢查arr[start, i)中是不是有和arr[i]相同的元素,有的話,說明之前已經輸出過了,不需要考慮。

代碼實現:

const swap = (arr, i, j) => {
  let tmp = arr[i]
  arr[i] = arr[j]
  arr[j] = tmp
}

const permutation = arr => {
  const _permutation = (arr, start) => {
    if(start === arr.length){
      console.log(arr)
      return
    }
    for(let i = start; i < arr.length; ++i){
      // 全排列:去重操作
      if(arr.slice(start, i).indexOf(arr[i]) !== -1){
        continue
      }
      swap(arr, i, start) // 和開始元素進行交換
      _permutation(arr, start + 1)
      swap(arr, i, start) // 恢複數組
    }
    return 
  }
  return _permutation(arr, 0)
}

permutation([1, 2, 2])
console.log("**********")
permutation([1, 2, 3, 4])

4.2 背包問題

題目:我現在有一個背包,容量為m,然後有n個貨物,重量分別為w1,w2,w3...wn,每個貨物的價值是v1,v2,v3...vn,w和v沒有任何關係,請求背包能裝下的最大價值。

這個還在學習中,背包問題博大精深。。。

4.3 圖的連通分量

題目:我現在有一個canvas,上面隨機布著一些黑塊,請實現方法,計算canvas上有多少個黑塊。

這一題可以轉化成圖的聯通分量問題。通過getImageData獲得像素數組,從頭到尾遍歷一遍,就可以判斷每個像素是否是黑色。同時,準備一個width * height大小的二維數組,這個數組的每個元素是1/0。如果是黑色,二維數組對應元素就置1;否則置0。

然後問題就被轉換成了圖的連通分量問題。可以通過深度優先遍歷或者並查集來實現。之前我用C++實現了,這裡不再冗贅:

5. Web工程化

5.1 Dialog組件思路

題目:現在要你完成一個Dialog組件,說說你設計的思路?它應該有什麼功能?

  • 可以指定寬度、高度和位置
  • 需要一個遮蓋層,遮住底層內容
  • 由頭部、尾部和正文構成
  • 需要監聽事件和自定義事件,非單向數據流:例如點擊組件右上角,修改父組件的visible屬性,關閉組件。

關於工程化組件封裝,可以去試試ElementUI。這個是ElementUI的Dialog組件:Element-Dialog

5.2 React的Diff演算法和虛擬DOM

題目: react 的虛擬dom是怎麼實現的

原答案寫的挺好滴,這裡直接貼了。

首先說說為什麼要使用Virturl DOM,因為操作真實DOM的耗費的性能代價太高,所以react內部使用js實現了一套dom結構。
在每次操作在和真實dom之前,使用實現好的diff演算法,對虛擬dom進行比較,遞歸找出有變化的dom節點,然後對其進行更新操作。
為了實現虛擬DOM,我們需要把每一種節點類型抽象成對象,每一種節點類型有自己的屬性,也就是prop,每次進行diff的時候,react會先比較該節點類型:
假如節點類型不一樣,那麼react會直接刪除該節點,然後直接創建新的節點插入到其中;
假如節點類型一樣,那麼會比較prop是否有更新,假如有prop不一樣,那麼react會判定該節點有更新,那麼重渲染該節點,然後在對其子節點進行比較,一層一層往下,直到沒有子節點。

參考鏈接:React源碼之Diff演算法

最後,歡迎來我的博客和我扯犢子:godbmw.com。直接戳本篇原文的地址:刷《一年半經驗,百度、有贊、阿裡面試總結》·手記


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡介: Application和Activity、Service一樣,都是Android框架的一個系統組件,每一個應用都有一個Application,Application的生命周期也就是整個app的生命周期。 特點: 實例的創建方式:單例模式 每一個app運行是會首先會創建Application類 ...
  • XKZoomingView.h XKZoomingView.m USE ELSE ...
  • 使用jQuery增加或刪除元素(內容):一、jQuery添加元素或內容:1,append() 方法:在被選元素的結尾插入元素或內容 2,prepend() 方法:被選元素的開頭插入元素或內容。 3,after() 方法:在被選元素之後插入內容。 4,before() 方法:在被選元素之前插入內容。註 ...
  • 百度智能小程式自定義彈窗組件wcPop|百度小程式model對話框|智能小程式彈窗界面模板 最近百度也推出了自己的智能小程式,如是就趕緊去試了下,官方提供的api還不是狠完整。而且官方提供的彈窗組件也不能滿足一些複雜展示場景,所以就自己動手封裝了個智能小程式彈窗wcPop自定義模板插件。 百度智能小 ...
  • 支付寶小程式自定義彈窗組件wcPop|小程式自定義對話框|actionSheet彈窗模板 支付寶小程式官方提供的alert提示框、dialog對話框、model彈窗功能比較有限,有些都不能隨意自定義修改的。如是自己就捯飭著封裝了個支付寶小程式自定義彈窗插件wcPop,多種展示場景,隨意修改調用。 自 ...
  • With Custom Elements, web developers can create new HTML tags, beef-up existing HTML tags, or extend the components other developers have authored. ...
  • 1. 2. 3.(類似法2) ...
  • js如何判斷值是否是數字 1. isNaN()方法2. 正則表達式var re = /^[0-9]+.?[0-9]*$/; //判斷字元串是否為數字 //判斷正整數 /^[1-9]+[0-9]*]*$/3. 利用parseFloat的返回值 isNaN(inputData)不能判斷空串或一個空格;如 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...