【面試】社招中級前端筆試面試題總結-答案及拓展

最近看到有一篇文章總結了一些前端的面試題,面向的對象應該是社招中初、中級的前端,感受有必定的參考價值,所以開一個帖子嘗試解答這些問題,順便當作本身的面試題積累。javascript

原文連接戳這裏css

JavaScript基礎

一、聲明提早類問題

在網上找到一篇文章,裏面有一道面試題,考察了包括變量定義提高、this指針指向、運算符優先級、原型、繼承、全局變量污染、對象屬性及原型屬性優先級等許多知識點,而就其中聲明提早相關的知識,我以爲也十分有參考價值:html

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

// 請寫出如下輸出結果:
Foo.getName();
getName(); // 聲明提早
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
複製代碼

這道題的答案是:二、四、一、一、二、三、3。前端

這裏考察聲明提早的題目在代碼中已經標出,這裏聲明getName方法的兩個語句:vue

var getName = function () { alert (4) };
function getName() { alert (5) }
複製代碼

實際上在解析的時候是這樣的順序:java

function getName() { alert (5) }
var getName;
getName = function () { alert (4) };
複製代碼

若是咱們在代碼中間再加兩個斷點:git

getName(); // 5
var getName = function () { alert (4) };
getName(); // 4
function getName() { alert (5) }
複製代碼

在第一次getName時,function的聲明和var的聲明都被提早到了第一次getName的前面,而getName的賦值操做並不會提早,單純使用var的聲明也不會覆蓋function所定義的變量,所以第一次getName輸出的是function聲明的5; 而第二次getName則是發生在賦值語句的後面,所以輸出的結果是4,因此實際代碼的執行順序是這樣:es6

function getName() { alert (5) }
var getName;
getName(); // 5
getName = function () { alert (4) };
getName(); // 4
複製代碼

二、瀏覽器存儲

localStorage,sessionStorage和cookie的區別

共同點:都是保存在瀏覽器端、僅同源可用的存儲方式github

  1. 數據存儲方面
  • cookie數據始終在同源的http請求中攜帶(即便不須要),即cookie在瀏覽器和服務器間來回傳遞。cookie數據還有路徑(path)的概念,能夠限制cookie只屬於某個路徑下
  • sessionStorage和localStorage不會自動把數據發送給服務器,僅在本地保存。
  1. 存儲數據大小
  • 存儲大小限制也不一樣,cookie數據不能超過4K,同時由於每次http請求都會攜帶cookie、因此cookie只適合保存很小的數據,如會話標識。
  • sessionStorage和localStorage雖然也有存儲大小的限制,但比cookie大得多,能夠達到5M或更大
  1. 數據存儲有效期
  • sessionStorage:僅在當前瀏覽器窗口關閉以前有效;
  • localStorage:始終有效,窗口或瀏覽器關閉也一直保存,本地存儲,所以用做持久數據;
  • cookie:只在設置的cookie過時時間以前有效,即便窗口關閉或瀏覽器關閉
  1. 做用域不一樣
  • sessionStorage不在不一樣的瀏覽器窗口中共享,即便是同一個頁面;
  • localstorage在全部同源窗口中都是共享的;也就是說只要瀏覽器不關閉,數據仍然存在
  • cookie: 也是在全部同源窗口中都是共享的.也就是說只要瀏覽器不關閉,數據仍然存在

三、跨域

不久我寫了一個帖子,對同源策略及各類跨域的方式進行了總結:什麼是跨域,爲何瀏覽器會禁止跨域,及其引發的發散性學習面試

四、Promise的使用及原理

Promise是ES6加入的新特性,用於更合理的解決異步編程問題,關於用法阮一峯老師在ECMAScript 6 入門中做出了詳細的說明,在此就不重複了。

30分鐘,讓你完全明白Promise原理

上面這篇文章則是對Promise的原理進行的詳細的說明,在這裏,我提取最簡單的Promise實現方式來對Promise的原理進行說明:

function Promise(fn) {
    var value = null,
        callbacks = [];  // callbacks爲數組,由於可能同時有不少個回調

    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}
複製代碼

首先,then裏面聲明的單個或多個函數,將被推入callbacks列表,在Promise實例調用resolve方法時遍歷調用,並傳入resolve方法中傳入的參數值。

如下,使用一個簡單的例子來對Promise的執行流程進行分析:

functionm func () {
	return new Promise(function (resolve) {
		setTimeout(function () {
			resolve('complete')
		}, 3000);
	})
}

func().then(function (res) {
	console.log(res); // complete
})
複製代碼

func函數的定義是返回了一個Promise實例,聲明實例時傳入的回調函數加入了一個resolve參數(這個resolve參數在Promise中的fn(resolve)定義中獲取resolve的函數實體),回調中執行了一個異步操做,在異步操做完成的回調中執行了resolve函數。

再看執行步驟,func函數返回了一個Promise實例,實例則能夠執行Promise構造函數中定義的then方法,then方法中傳入的回調則會在resolve(即異步操做完成後)執行,由此實現了經過then方法執行異步操做完成後回調的功能。

五、JavaScript事件循環機制

原文中貼出的文章具備很大參考價值,先貼個連接:詳解JavaScript中的Event Loop(事件循環)機制

JavaScript是一種單線程、非阻塞的語言,這是因爲它當初的設計就是用於和瀏覽器交互的:

  • 單線程:JavaScript設計爲單線程的緣由是,最開始它最大的做用就是和DOM進行交互,試想一下,若是JavaScript是多線程的,那麼當兩個線程同時對DOM進行一項操做,例如一個向其添加事件,而另外一個刪除了這個DOM,此時該如何處理呢?所以,爲了保證不會 發生相似於這個例子中的情景,JavaScript選擇只用一個主線程來執行代碼,這樣就保證了程序執行的一致性。
  • 非阻塞:當代碼須要進行一項異步任務(沒法馬上返回結果,須要花必定時間才能返回的任務,如I/O事件)的時候,主線程會掛起(pending)這個任務,而後在異步任務返回結果的時候再根據必定規則去執行相應的回調。而JavaScript實現異步操做的方法就是使用Event Loop。
setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})
複製代碼

下面經過一段代碼來分析這個問題,首先setTimeoutPromise中的then回調都是異步方法,而new Promise則是一個同步操做,因此這段代碼應該首先會當即輸出2JavaScript將異步方法分爲了marco task(宏任務:包括setTimeoutsetInterval等)和micro task(微任務:包括new Promise等),在JavaScript的執行棧中,若是同時存在到期的宏任務和微任務,則會將微任務先所有執行,再執行第一個宏任務,所以,兩個異步操做中then的回調會率先執行,而後才執行setTimeout的回調,所以會依次輸出三、1,因此最終輸出的結果就是二、三、1。

六、ES6做用域及let和var的區別

這個問題阮一峯老師在ECMAScript 6 入門中的let 和 const 命令章節對這個問題做出了詳細的說明,下面提取一些我認爲關鍵的點進行講解。

ES6引入了使用{}包裹的代碼區域做爲塊級做用域的聲明方式,其效果與ES5中function聲明的函數所生成的函數做用域具備相同的效果,做用域外部不能訪問做用域內部聲明的函數或變量,這樣的聲明在ES6中對於包括for () {}if () {}等大括號包裹的代碼塊中都會生效,生成一個單獨的做用域。

ES6新增的let聲明變量的方式相比var具備如下幾個重要特色:

  • let聲明的變量只在做用域內有效,以下方代碼,if聲明生成了一個塊級做用域,在這個做用域內聲明的變量在做用域外部沒法訪問,假如訪問會產生錯誤:
if (true) {
	let me = 'handsome boy';
}
console.log(me); // ReferenceError
複製代碼
  • let聲明的變量與var不一樣,不會產生變量提高,以下方代碼,在聲明以前輸出代碼,會產生錯誤:
// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的狀況
console.log(bar); // ReferenceError
let bar = 2;
複製代碼
  • let的聲明方式不容許重複聲明,如重複聲明會報錯,而var聲明變量時,後聲明的語句會對先聲明的語句進行覆蓋:
// 報錯
function func() {
  let a = 10;
  var a = 1;
}
// 報錯
function func() {
  let a = 10;
  let a = 1;
}
複製代碼
  • 只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響,這個特性稱爲暫時性死區
var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
複製代碼

七、閉包

待補充

八、原型及原型鏈

待補充

九、瀏覽器的迴流與重繪 (Reflow & Repaint)

參考:juejin.im/post/5a9923…

瀏覽器在接收到 htmlcss 後,渲染的步驟是:html 通過渲染生成 DOM 樹, css 通過渲染生成 css 渲染樹,二者再通過結合,生成 render tree,瀏覽器就能夠根據 render tree 進行畫面繪製。

若是瀏覽器從服務器接收到了新的 css ,須要更新頁面時,須要通過什麼操做呢?這就是迴流 reflow 與重繪 repaint。因爲瀏覽器在從新渲染頁面時會先進行 reflow 再進行 repaint,所以,迴流必將引發重繪,而重繪不必定會引發迴流。

重繪:當前元素的樣式(背景顏色、字體顏色等)發生改變的時候,咱們只須要把改變的元素從新的渲染一下便可,重繪對瀏覽器的性能影響較小。發生重繪的情形:改變容器的外觀風格等,好比 background:black 等。改變外觀,不改變佈局,不影響其餘的 DOM。    迴流:是指瀏覽器爲了從新渲染部分或者所有的文檔而從新計算文檔中元素的位置和幾何構造的過程。

由於迴流可能致使整個 DOM 樹的從新構造,因此是性能的一大殺手,一個元素的迴流致使了其全部子元素以及 DOM 中緊隨其後的祖先元素的隨後的迴流。下面貼出會觸發瀏覽器 reflow 的變化:

  • 頁面首次渲染
  • 瀏覽器窗口大小發生改變
  • 元素尺寸或位置發生改變
  • 元素內容變化(文字數量或圖片大小等等)
  • 元素字體大小變化
  • 添加或者刪除可見的DOM元素
  • 激活CSS僞類(例如::hover)
  • 查詢某些屬性或調用某些方法

優化方案:

CSS

  • 避免使用table佈局。
  • 儘量在DOM樹的最末端改變class
  • 避免設置多層內聯樣式。
  • 將動畫效果應用到position屬性爲absolutefixed的元素上。
  • 避免使用CSS表達式(例如:calc())。

JavaScript

  • 避免頻繁操做樣式,最好一次性重寫style屬性,或者將樣式列表定義爲class並一次性更改class屬性。
  • 避免頻繁操做DOM,建立一個documentFragment,在它上面應用全部DOM操做,最後再把它添加到文檔中。
  • 也能夠先爲元素設置display: none,操做結束後再把它顯示出來。由於在display屬性爲none的元素上進行的DOM操做不會引起迴流和重繪。
  • 避免頻繁讀取會引起迴流/重繪的屬性,若是確實須要屢次使用,就用一個變量緩存起來。
  • 對具備複雜動畫的元素使用絕對定位,使它脫離文檔流,不然會引發父元素及後續元素頻繁迴流。

十、JS對象的深複製

通常的思路就是遞歸解決,對不一樣的數據類型作不一樣的處理:

function deepCopy (obj) {
  let result = {}
  for (let key in obj) {
    if (obj[key] instanceof Object || obj[key] instanceof Array) {
      result[key] = deepCopy(obj[key])
    } else {
      result[key] = obj[key]
    }
  }
  return result
}
複製代碼

這個只能複製內部有數組、對象或其餘基礎數據類型的對象,假若有一些像RegExpDate這樣的複雜對象複製的結果就是一個{},沒法正確進行復制,由於沒有對這些特殊對象進行單獨的處理。若要參考對複雜對象進行復制,能夠參考lodash中數組深複製方法_.cloneDeep()的實現方案,下面這篇文章對數組深複製的方法進行了詳細的解析,有必定參考價值: jerryzou.com/posts/dive-…

另外若是要複製的對象數據結構較爲簡單,沒有複雜對象的數據,那麼能夠用最簡便的方法:

let cloneResult = JSON.parse(JSON.stringify(targetObj))
複製代碼

十一、JS運算精度丟失

此前轉載了一篇文章,對JavaScript運算精度丟失的緣由及解決方案都有比較詳細的說明: blog.csdn.net/qq_35271556…

瀏覽器相關

一、瀏覽器從加載到渲染的過程,好比輸入一個網址到顯示頁面的過程

加載過程:

  • 瀏覽器根據 DNS 服務器解析獲得域名的 IP 地址
  • 向這個 IP 的機器發送 HTTP 請求
  • 服務器收到、處理並返回 HTTP 請求
  • 瀏覽器獲得返回內容

渲染過程:

  • 根據 HTML 結構生成 DOM 樹
  • 根據 CSS 生成 CSSOM
  • 將 DOM 和 CSSOM 整合造成 RenderTree
  • 根據 RenderTree 開始渲染和展現
  • 遇到<script>時,會執行並阻塞渲染

二、瀏覽器緩存機制

參考文章:segmentfault.com/a/119000001…

三、性能優化

參考文章:blog.csdn.net/na_sama/art…

Vue

一、組件間通訊方式

Vue的官方文檔對組件間的通訊方式作了詳細的說明:cn.vuejs.org

父組件向子組件傳輸

  • 最經常使用的方式是在子組件標籤上傳入數據,在子組件內部用props接收:
// 父組件
<template>
  <children name="boy"></children>
</template>
<script> // 子組件:children export default { props: { name: String } } </script>
複製代碼
  • 還能夠在子組件中用 this.$parent 訪問父組件的實例,不過官方文檔有這樣一段文字,很好的說明了 $parent 的意義:節制地使用 $parent$children —— 它們的主要目的是做爲訪問組件的應急方法。更推薦用 propsevents 實現父子組件通訊。

子組件向父組件傳輸

  • 通常在子組件中使用 this.$emit('eventName', 'data'),而後在父組件中的子組件標籤上監聽 eventName 事件,並在參數中獲取傳過來的值。
// 子組件
export default {
  mounted () {
    this.$emit('mounted', 'Children is mounted.')
  }
}
複製代碼
<template>
  <children @mounted="mountedHandle"></children>
</template>
<script> // 父組件 export default { methods: { mountedHandle (data) { console.log(data) // Children is mounted. } } } </script>
複製代碼
  • $parent 同樣,在父組件中能夠經過訪問 this.$children 來訪問組件的全部子組件實例。

非父子組件之間的數據傳遞

  • 對於非父子組件間,且具備複雜組件層級關係的狀況,能夠經過 Vuex 進行組件間數據傳遞: vuex.vuejs.org/zh/

  • Vue 1.0 中經常使用的 event bus 方式進行的全局數據傳遞,在 Vue 2.0 中已經被移除,官方文檔中有說明:$dispatch$broadcast 已經被棄用。請使用更多簡明清晰的組件間通訊和更好的狀態管理方案,如:Vuex

二、雙向綁定原理

blog.seosiwei.com/detail/35 blog.seosiwei.com/detail/36 blog.seosiwei.com/detail/37

三、路由導航鉤子

router.vuejs.org/zh-cn/advan…

做者信息

做者其餘文章

相關文章
相關標籤/搜索