前言
今天給大家分享一個位元組跳動系公司——石墨文件的面經吧!廢話不多說,先看題目!
題目
一面
- 1、['10', '10', '10', '10', '10'].map(parseInt) 的輸出值是什麼?
- 2、你們現在的技術棧是什麼?為什麼要使用ts?
- 3、setTimeout的執行過程(事件迴圈,同步、非同步)?
- 4、對Promise的理解,與async、await的區別,async、await是怎麼實現的?
- 5、解釋 requestAnimationFrame/requestIdleCallback,分別有什麼用?
- 6、react效能優化?
- 7、說說對flex的理解?
- 8、迴流、重繪是什麼?如何減少迴流和重繪?
- 9、怎麼尋找react頁面卡頓的原因?
10、程式設計題:實現一個物件的 flatten 方法,如下:
const obj = { a: { b: 1, c: 2, d: { e: 5 } }, b: [1, 3, {a: 2, b: 3}], c: 3 }
flatten(obj){} 結果返回如下:
// { // 'a.b': 1, // 'a.c': 2, // 'a.d.e': 5, // 'b[0]': 1, // 'b[1]': 3, // 'b[2].a': 2, // 'b[2].b': 3 // c: 3 // }
二面
- 1、說說對web worker的理解
- 2、service worker和強快取相比,有哪些優勢?
- 3、說說對堆疊溢位的理解
- 4、position中的sticky是什麼,還有哪些其他的?
- 5、ts中,any和unknown分別是什麼意思?泛型怎麼使用?
- 6、bind有什麼用?連續多個bind,最後this指向是什麼?
- 7、webpack的plugin怎麼實現?
- 8、程式設計題:
現已知一個字串是由正整數和加減乘除四個運算子(+ - /)組成。 例如存在字串 const str = '11+2-34+5/24+10/5',現在需要將高優先順序運算,用小括號包裹起來,例如結果為 '11+2-(34)+(5/2*4)+(10/5)'。注意可能會出現連續的乘除運算,需要包裹到一起。 請用 javascript 實現這一過程
三面
1、手寫體:使用TypeScript 實現一個 get 函式來獲取它的屬性值
const data = { name: 'tom', age: 18, address: 'xxx' }
- 2、ts中的 any 、 unknown 的區別
- 3、有用過ts中的 keyof 嗎?
- 4、for in/for of的區別?
- 5、Promise值穿透?
解答
一面
1、['10', '10', '10', '10', '10'].map(parseInt) 的輸出值是什麼?
可轉化為:
['10', '10', '10', '10', '10'].map((num, index) => parseInt(num, index))
// [10, NaN, 2, 3, 4]
'10' 0 -> 10: 進製為0,則預設10進位制
'10' 1 -> NaN: 1進位制不存在
'10' 2 -> 2: 2 * 1 + 2 * 0
'10' 3 -> 3: 3 * 1 + 3 * 0
'10' 4 -> 4: 4 * 1 + 4 * 0
2、你們現在的技術棧是什麼?為什麼要使用ts?
typescript
是 JavaScript
的超集,它本質其實是是在 JavaScript
上新增了 可選靜態型別
和 基於類的物件導向程式設計
typescript 的特點
- 可以在編譯期間發現並糾正錯誤
- 提高可維護性
- 提高協同開發的效率
- 支援強型別、介面、泛型、模組
3、setTimeout的執行過程(事件迴圈,同步、非同步)?
事件迴圈
- 1、執行同步程式碼
- 2、
1
中生成的微任務
先執行 - 3、
1
中生產的巨集任務
再執行
同步
簡單來說就是:排隊。程式碼有前後順序,必須按照順序去執行
非同步
非同步任務可以不阻塞後面的程式碼執行,而是可以同時進行,並且執行完後會有一個非同步的回撥。
想起一個故事可以很好的解釋 同步
和 非同步
- 同步:你打電話去書店借書,老闆接電話時讓你等著,他去找書,你只能守著電話乾等著
- 非同步:你打電話去書店借書,老闆接電話後說等他找到書再打回給你,然後掛電話了,這段找書的時間你可以自由活動
4、對Promise的理解,與async、await的區別,async、await是怎麼實現的?
Promise的理解
顧名思義, Promise
就是 承諾
的意思,表現在了Promise的狀態一旦改變則不會再變了,如果狀態為 fulfilled
則執行 then
,如果狀態為 rejected
則執行 catch
,Promise 也支援 鏈式呼叫
。我覺得Promise最大的用處就是 解決了回撥地獄,提高了程式碼的可讀性
。常用的方法有 resolve、reject、then、catch、race、all、allSettled、any、finally
async await async/await
的作用是 用同步的方式執行非同步的操作
,它的實現原理,我個人理解就是利用了 Promise
的不斷巢狀,再加上 generator函式
的步驟控制,實現了按順序執行非同步操作的效果
補充:async函式返回的是一個Promise
5、解釋 requestAnimationFrame/requestIdleCallback,分別有什麼用?
requestAnimationFrame:
- 一般間隔是
16ms
,因為大部分電腦都是 每秒60幀 ,所以1000 / 60 ≈ 16ms
- 會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中完成,且時間間隔緊緊跟隨瀏覽器的重新整理頻率
- 如果有隱藏或不可見的元素,將不會進行重繪或迴流,減少了cpu、gpu的記憶體使用量
- 如需取消則使用
cancelAnimationFrame
- 一般間隔是
- requestIdleCallback:我的理解就是找瀏覽器空閒時間去執行傳入的回撥,具體也沒在專案中使用過
6、react效能優化?
7、說說對flex的理解?
彈性佈局
,設定了 display: flex
的盒子為 彈性盒子
,子元素會自動變成 彈性專案
,盒子有一根主軸,預設是水平,並且有一個交叉軸(跟主軸垂直)。
彈性盒子的樣式:
- flex-direction:定義主軸方向
- flex-wrap:是否允許換行
- flex-flow:flex-direction 和 flex-wrap的簡寫
- justify-content:主軸方向上的對齊方式
- align-items:交叉軸方向的對齊方式
- align-content:多根軸線的對齊方式
彈性專案的樣式:
- order:定義專案的排列順序,數值越小排列越靠前,預設0
- flex-grow:定義專案的放大比例,預設為
0
- flex-shrink:定義專案的縮小比例,預設為1
- flex-basis:定義了在分配多餘空間之前,專案佔據的主軸空間,預設auto
- flex:flex-grow、flex-shrink、flex-basis的簡寫
align-self:允許單個專案設定不同的交叉軸對齊方式
8、迴流、重繪是什麼?如何減少迴流和重繪?
重繪迴流
- 迴流:尺寸、佈局改變時,引起頁面重新構建
- 重繪:元素外觀、風格改變時,不影響佈局,則為重繪
- 區別:迴流一定引起重繪,重繪不一定引起迴流
- 瀏覽器幫忙:瀏覽器維護一個佇列,把所有引起迴流、重繪的操作放入這個佇列,等佇列到了一定數量或者到了一定的時間間隔,瀏覽器就會清空佇列,進行批量處理。
避免重繪、迴流
- 1、批量修改DOM或者樣式
- 2、複雜動畫使用絕對定位讓它脫離文件流,不然會印日分元素或後續元素的頻繁迴流
3、GPU加速:transform、opacity、filters、will-change等樣式
9、判斷一個物件是陣列的方法?
- Object.prototype.toString.call(xxx)
- Array.isArray(xxx)
xxx instaceOf Array
10、怎麼尋找react頁面卡頓的原因?
11、程式設計題:實現一個物件的 flatten 方法,如下:
const obj = { a: { b: 1, c: 2, d: { e: 5 } }, b: [1, 3, {a: 2, b: 3}], c: 3 }
flatten(obj){} 結果返回如下:
// { // 'a.b': 1, // 'a.c': 2, // 'a.d.e': 5, // 'b[0]': 1, // 'b[1]': 3, // 'b[2].a': 2, // 'b[2].b': 3 // c: 3 // }
解題
const isObject = (target) => { return typeof target === 'object' && target !== null } const flatten = (obj) => { if (!isObject) return const res = {} const dfs = (cur, prefix) => { if (isObject(cur)) { if (Array.isArray(cur)) { cur.forEach((item, index) => dfs(item, `${prefix}[${index}]`)) } else { for(let key in cur) { dfs(cur[key], `${prefix}${prefix ? '.' : ''}${key}`) } } } else { res[prefix] = cur } } dfs(obj, '') return res }
二面
1、說說對web worker的理解
- 1、開啟一個子執行緒,並在此子執行緒進行一些大資料處理或者耗時的操作
- 2、使用
postMessage
和onmessage
,實現主執行緒和子執行緒之間的通訊 - 3、使用
onerror
監聽子執行緒掛了沒 - 4、
web worker
並沒有改變JavaScript單執行緒的事實
2、service worker和強快取相比,有哪些優勢?
service快取沒用過。。
3、說說對堆疊溢位的理解?
常見的情況發生在 大數量遞迴
或 死迴圈
時,就會造成 棧溢位
,因為每次執行程式碼都需要分配一定空間的記憶體,以上兩種情況都會使執行空間超出最大限度,從而報錯
4、position中的sticky是什麼,還有哪些其他的?
- static:預設
- relative:相對定位,相對於自身定位
- absolute:絕對定位,相對於非static的第一個祖宗元素定位
- fixed:相對於瀏覽器視窗進行定位
- inherit:規定應該從父元素繼承 position 屬性的值
- sticky:吸頂定位
5、ts中,any和unknown分別是什麼意思?泛型怎麼使用?
- any:變數如果是 any 型別,繞過所有型別檢查,直接可使用
- unknown:變數如果是 unknow 型別,需要判斷完是什麼型別之後才能使用
6、bind有什麼用?連續多個bind,最後this指向是什麼?
bind
的作用是改變函式執行的指向,且不會立即執行,而是返回一個新的函式,可以自主呼叫這個函式的執行(此函式不可當做建構函式)
連續多個bind之後this指向始終指向第一個
7、webpack的plugin怎麼實現?
一個plugin就是一個類,類裡有一個 apply方法
,每次打包時都會呼叫這個apply,而這個apply方法接受一個引數物件,其中有一個 plugin
方法,此方法中有許多 鉤子函式
,且可以決定靜態檔案的生成,修改等等
8、程式設計題:
現已知一個字串是由正整數和加減乘除四個運算子(+ - /)組成。 例如存在字串 const str = '11+2-34+5/24+10/5',現在需要將高優先順序運算,用小括號包裹起來,例如結果為 '11+2-(34)+(5/2*4)+(10/5)'。注意可能會出現連續的乘除運算,需要包裹到一起。 請用 javascript 實現這一過程
解答
我比較菜,用的方法也是臨時想出來的,沒有優化,大家將就著看吧:
const checkType = (str) => {
if (['*', '/'].includes(str)) return 'high'
if (['+', '-'].includes(str)) return 'low'
return 'number'
}
const addBrackets = (formula) => {
const strs = formula.split('')
let i = 0, j = 1, high = false, res = []
while(j < strs.length) {
const jType = checkType(strs[j])
if (jType === 'low' && !high) {
i = ++j
j++
}else if (jType === 'low' && high) {
res.push(j++)
i = j++
high = false
}else if (jType === 'high') {
j++
!high && res.push(i)
high = true
}else {
j++
}
}
if (high) res.push(strs.length)
let add = 0
for(let i = 0; i < res.length; i++) {
const index = res[i]
strs.splice(index + add, 0, add % 2 ? ')' : '(')
add++
}
return strs.join('')
}
三面
1、手寫體:使用TypeScript 實現一個 get 函式來獲取它的屬性值
const data = { name: 'tom', age: 18, address: 'xxx' }
解答:
const get = <T extends object, K extends keyof T>(obj: T, key: K): T[K] => {
return obj[key]
}
2、ts中的 any 、 unknown 的區別?
- any:變數如果是 any 型別,繞過所有型別檢查,直接可使用
unknown:變數如果是 unknow 型別,需要判斷完是什麼型別之後才能使用
3、有用過ts中的 keyof 嗎?
將一個interface的所有key,匯聚成一個聯合型別,可以用來對傳入key的限制,比如:
interface Target { name: string, age: number } const fn = (obj: Target, key: keyof Target) => {} const obj: Target = { name: 'sunshine', age: 18 } fn(obj, name) // 成功 fn(obj, age) // 成功 fn(obj, height) // 報錯
4、for in/for of的區別?
- for in:遍歷物件的key或者陣列的索引
for of:遍歷可迭代物件的值,如陣列、Set
5、Promise值穿透
then或catch沒有傳入函式的話,會發生值穿透,原理是Promise內部檢測如果傳入的是非函式,則會拿上一次的結果包裝成一個返回Promise的函式,達到穿透效果
例如:
Promise.resolve('foo')
.then(Promise.resolve('bar'))
.then(function(result){
console.log(result) // foo
})
但是如果傳入的是函式的話:
Promise.resolve('foo')
.then(() => Promise.resolve('bar'))
.then(function(result){
console.log(result) // bar
})
結語
由於本人React太菜,所以不敢答題有關React的題目
我是林三心,一個熱心的前端菜鳥程式設計師。如果你上進,喜歡前端,想學習前端,那我們們可以交朋友,一起摸魚哈哈,摸魚群,加我請備註【思否】