記錄本人在廣州面試中遇到的一些問題(主要是技術一面),公司中小企業居多,也有大廠(非BAT)。崗位一般寫的中級前端開發或1~3年經驗崗位。問題主要選取一些高頻和基礎的問題。(問題的回答只是本人的理解,非參考答案,有些答案只給個提示,詳解可谷歌百度或在掘金內搜索相關文章)
題目
- 考察頻率指相關問題的考察頻率,並非只是提到的點。
JavaScript基礎
1、聲明提升類問題 (考察頻率:高)
變量聲明和函數聲明都會提升,但函數會提升到變量前。 具體解釋可參考《你不知道的JavaScript(上卷)》
2、js存儲方式(考察頻率:中)
- cookie
- sessionStorage
- localStorage
- indexedDB
3、什么情況下會遇到跨域,怎么解決?(考察頻率:高)
- 同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方資源。若地址里面的協議、域名和端口號均相同則屬於同源。
- jsonp跨域、nginx反向代理、node.js中間件代理跨域、后端設置http header、后端在服務器上設置cors。
4、Promise中的執行順序(考察頻率:高)
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
上面代碼中,Promise 新建后立即執行,所以首先輸出的是Promise。然后,then方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行,所以resolved最后輸出。
5、JavaScript事件循環機制相關問題(考察頻率:高)
- 事件循環機制的概念
關鍵字:單線程非阻塞、執行棧、事件隊列、宏任務(setTimeout()、setInterval())、微任務(new Promise())
可參考: zhuanlan.zhihu.com/p/33058983
- 宏任務、微任務、同步任務的執行順序
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
console.log(4);
// 2
// 4
// 3
// 1
先按順序執行同步任務,Promise新建后立即執行輸出2,接着輸出4,異步任務等同步任務執行完后執行,且同一次事件循環中,微任務永遠在宏任務之前執行。這時候執行棧空了,執行事件隊列,先取出微任務,輸出3,最后取出一個宏任務,輸出1。
6、for循環中的作用域問題(考察頻率:高)
寫出以下代碼輸出值,嘗試用es5和es6的方式進行改進輸出循環中的i值。
for (var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
- 輸出5個6,因為回調函數在for循環之后執行,所有函數共享一個i的引用。
- es5:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j*1000);
})(i);
}
- es6:
for (let i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
7、閉包的作用(考察頻率:中)
閉包的目的是外部函數可以訪問內部函數的作用域(局部作用域)。比如訪問到內部作用域的變量。
8、原型及原型鏈(考察頻率:中)
原型的理解
- 所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性(null除外)
- 所有的引用類型(數組、對象、函數),都有一個__proto__屬性,屬性值是一個普通的對象
- 所有的函數,都有一個prototype屬性,屬性值也是一個普通的對象
- 所有的引用類型(數組、對象、函數),__proto__屬性值指向它的構造函數的prototype屬性值
原型鏈的理解
一段代碼如下:
// 構造函數
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 創建示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 測試
f.printName()
f.alertName()
f.toString()
因為f本身沒有toString(),並且f.proto(即Foo.prototype)中也沒有toString。當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的__proto__(即它的構造函數的prototype)中尋找。
如果在f.__proto__中沒有找到toString,那么就繼續去f.proto.__proto__中尋找,因為f.__proto__就是一個普通的對象而已嘛!
f.__proto__即Foo.prototype,沒有找到toString,繼續往上找 f.proto.__proto__即Foo.prototype.proto。Foo.prototype就是一個普通的對象,因此Foo.prototype.__proto__就是Object.prototype,在這里可以找到toString。 因此f.toString最終對應到了Object.prototype.toString 這樣一直往上找,你會發現是一個鏈式的結構,所以叫做“原型鏈”。如果一直找到最上層都沒有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.proto === null
9、重繪和回流(考察頻率:中)
- 重繪:當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素並重新繪制它,這個過程稱為重繪。
- 回流:當Render Tree(DOM)中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文檔的過程稱為回流。
- 回流要比重繪消耗性能開支更大。
- 回流必將引起重繪,重繪不一定會引起回流。
- 參考:juejin.im/post/684490…
10、實現一個深拷貝(思路)(考察頻率:中)
對象中可能又存在對象,所以需要深拷貝。首先需要知道這是一個遞歸調用,然后要判斷一些特殊類型(數組,正則對象,函數)進行具體的操作,可以通過Object.prototype.toString.call(obj)進行判斷。
11、js浮點數運算精度問題(0.1+0.2!==0.3)
比如在 JavaScript 中計算 0.1 + 0.2時,到底發生了什么呢?
首先,十進制的0.1和0.2都會被轉換成二進制,但由於浮點數用二進制表達時是無窮的,例如。
JavaScript 代碼:
0.1 -> 0.0001100110011001...(無限)
0.2 -> 0.0011001100110011...(無限)
IEEE 754 標准的 64 位雙精度浮點數的小數部分最多支持 53 位二進制位,所以兩者相加之后得到二進制為:
JavaScript 代碼: 0.0100110011001100110011001100110011001100110011001100
因浮點數小數位的限制而截斷的二進制數字,再轉換為十進制,就成了 0.30000000000000004。所以在進行算術計算時會產生誤差。
瀏覽器相關
12、瀏覽器從加載到渲染的過程,比如輸入一個網址到顯示頁面的過程。 (考察頻率:高)
加載過程:
- 瀏覽器根據 DNS 服務器解析得到域名的 IP 地址
- 向這個 IP 的機器發送 HTTP 請求
- 服務器收到、處理並返回 HTTP 請求
- 瀏覽器得到返回內容
渲染過程:
- 根據 HTML 結構生成 DOM 樹
- 根據 CSS 生成 CSSOM
- 將 DOM 和 CSSOM 整合形成 RenderTree
- 根據 RenderTree 開始渲染和展示
- 遇到