手機(jī)端網(wǎng)站開發(fā)長(zhǎng)沙網(wǎng)站seo方法
目錄
- 前言
- 一面
- git 常見命令
- 跨窗口通信
- vue 響應(yīng)式原理
- 發(fā)布訂閱模式
- 翻轉(zhuǎn)二叉樹
- Promise.all()
- 扁平化數(shù)組
- 面試官建議
- 二面
- Event Loop 原理
- Promise 相關(guān)
- css 描邊方式
- requestAnimation
- React 18 新特性
- JSX 相關(guān)
- react 輸出兩次
- 函數(shù)式編程
- React 批處理機(jī)制
- http請(qǐng)求頭有哪些
- 本地存儲(chǔ)
- 性能優(yōu)化方面
- webgl 的優(yōu)化手段
- 著色器優(yōu)化相關(guān)
- 緩沖區(qū)和深度測(cè)試
- 異步
- 原型和原型鏈
- 倒序輸出
- 漢語表達(dá)
- 次數(shù)最少字符
- 面試復(fù)盤
前言
即時(shí)設(shè)計(jì) - 可實(shí)時(shí)協(xié)作的專業(yè) UI 設(shè)計(jì)工具
即時(shí)設(shè)計(jì)是一款可在線編輯的專業(yè)UI設(shè)計(jì)工具,支持原型、交互、智能動(dòng)畫、切圖等功能,滿足產(chǎn)品創(chuàng)造和交付的全流程體驗(yàn)。
面試題目源碼:前端面經(jīng)
一面
git 常見命令
初始化倉(cāng)庫:git init
遠(yuǎn)程倉(cāng)庫地址:git clone / git remote add origin
查看關(guān)聯(lián)的遠(yuǎn)程倉(cāng)庫列表 git remote -v
顯示當(dāng)前工作目錄和暫存區(qū)的狀態(tài) git status
添加到暫存區(qū) git add .
提交本地倉(cāng)庫 git commit -m "提交日志"
提交日志信息 git log
推送遠(yuǎn)程倉(cāng)庫 git push origin <本地分支名>
創(chuàng)建分支 git branch 分支名稱
切換分支 git checkout 分支名稱
合并分支 git merge 分支名稱
跨窗口通信
https://juejin.cn/post/7306040473542213644
- 可以借助 web scoket 通過后端這個(gè)載體,進(jìn)行跨頁面通信
- Broadcast Channel:遵循瀏覽器的同源策略,基于發(fā)布-訂閱模式,允許一個(gè)窗口發(fā)送消息,并由其他窗口接收,本質(zhì)上是一個(gè)數(shù)據(jù)共享池子
- SharedWorker:是 HTML5 中提供的一種多線程解決方案,它可以在多個(gè)瀏覽器 TAB 頁面之間共享一個(gè)后臺(tái)線程,從而實(shí)現(xiàn)跨頁面通信
- 本地化存儲(chǔ) API:利用本地存儲(chǔ)實(shí)現(xiàn)同域下的數(shù)據(jù)共享,window 監(jiān)聽 storage 數(shù)據(jù)變化執(zhí)行對(duì)應(yīng)的方法回調(diào)
vue 響應(yīng)式原理
-
vue2 使用 Object.defineProperty實(shí)現(xiàn),是 es6 引入的方法,可以通過配置精確添加或修改對(duì)象的屬性
-
不能利用 get/set 對(duì)額外添加的屬性進(jìn)行監(jiān)聽
-
不能監(jiān)聽自身數(shù)組下標(biāo)修改元素的變化,直接定義數(shù)組可以,但是不常用
-
vue3 使用 Proxy 實(shí)現(xiàn),能夠支持對(duì)象添加或修改屬性的變化
-
通過reactive() 函數(shù)給每一個(gè)對(duì)象都包一層 proxy,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)控
-
支持改變數(shù)組的方法,push、pop、shift、unshift、splice、sort、reverse
-
Reflect 是 es6 引入的新特性,提供一種新的方式操作對(duì)象
-
使用 WeakMap 作為緩存區(qū)防止對(duì)象被重復(fù)代理,主要是避免內(nèi)存泄漏,以及隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)
發(fā)布訂閱模式
class PubSub {constructor() {this.events = {}}subscribe(event, callback) {if (!this.events[event]) this.events[event] = []this.events[event].push(callback)}unSunscribe(event, callback) {if (!this.events[event]) returnthis.events[event] = this.events[event].filter((cb) => cb !== callback)}publish(event, data) {if (!this.events[event]) returnfor (let item of this.events[event]) item(data)}
}const pubsub = new PubSub()
function callback1(data) {console.log('觸發(fā)訂閱事件 1', data)
}function callback2(data) {console.log('觸發(fā)訂閱事件 2', data)
}pubsub.subscribe('myEvent', callback1)
pubsub.subscribe('myEvent', callback2)pubsub.publish('myEvent', '11111')
翻轉(zhuǎn)二叉樹
/*** 實(shí)現(xiàn)翻轉(zhuǎn)二叉樹* 例如* a* / \* b c* 轉(zhuǎn)為* a* / |* c b*/
const invertTree = function (root) {if (root === null) return rootlet temp = root.leftroot.left = invertTree(root.right)root.right = invertTree(temp)return root
}const ensure = (output, expect, message) => {if (JSON.stringify(output) === JSON.stringify(expect)) {console.log(`${message} ok`)} else {console.log(`${message} fail`)}
}const test = function () {const input = {val: 1,left: {val: 2,left: {val: 3,left: null,right: null,},right: {val: 4,left: null,right: null,},},right: {val: 5,left: {val: 6,left: null,right: null,},right: {val: 7,left: null,right: null,},},}const expect = {val: 1,left: {val: 5,left: {val: 7,left: null,right: null,},right: {val: 6,left: null,right: null,},},right: {val: 2,left: {val: 4,left: null,right: null,},right: {val: 3,left: null,right: null,},},}const output = invertTree(input)ensure(output, expect, 'test')
}// 輸出 test ok表示測(cè)試成功
test()
Promise.all()
Promise.myAll = function (promises) {let result = []return new Promise((resolve, reject) => {promises.forEach((item, index) => {Promise.resolve(item).then((res) => {result[index] = resif (Object.keys(result).length === promises.length) resolve(result)}).catch((err) => {reject(err)})})})
}const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {setTimeout(() => {resolve('p2 延時(shí)一秒')}, 3000)
})
const p3 = new Promise((resolve, reject) => {setTimeout(() => {resolve('p3 延時(shí)兩秒')}, 2000)
})const p4 = Promise.reject(4)Promise.myAll([p4, p2, p3]).then((res) => console.log(res)).catch((err) => console.log(err)) // 2秒后打印 [const promise = new Promise((resolve, reject) => {})
console.log(promise)
扁平化數(shù)組
// https://juejin.cn/post/7273693216372916283console.log('\n --- 一:原生方法 --- \n')
let arr = [1, [2, 3], [4, [5, 6, [7, 8]]]]
console.log(arr.flat()) // 默認(rèn)解析一層,可以指定參數(shù)
console.log(arr.flat(Infinity)) // Infinity完全解析console.log('\n--- 二、遞歸解析 ---\n')function flatten(arr, depth) {let result = []for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i]) && depth > 0) {result = result.concat(flatten(arr[i], depth - 1))} else {result.push(arr[i])}}return result
}console.log(flatten(arr, 1))
console.log(flatten(arr, 2))
console.log(flatten(arr, 3))console.log('\n--- 三、reduce解析 ---\n')
function reduceFlatten(arr) {return arr.reduce((acc, cur) => {if (Array.isArray(cur)) {return acc.concat(reduceFlatten(cur))} else {return acc.concat(cur)}}, [])
}console.log(reduceFlatten(arr))
面試官建議
- 面試回答:先總結(jié),然后分點(diǎn)并結(jié)合自己的實(shí)際項(xiàng)目進(jìn)行講解
- 應(yīng)屆生沒有太多的項(xiàng)目/業(yè)務(wù)經(jīng)驗(yàn),面試官可能會(huì)重點(diǎn)問一下基礎(chǔ)、框架原理、數(shù)據(jù)結(jié)構(gòu)與算法、計(jì)算機(jī)網(wǎng)絡(luò)這些知識(shí)點(diǎn)
一面忘記錄音了,有一些問題面試后沒有回想起來
二面
Event Loop 原理
事件循環(huán)(Event Loop)是 JavaScript 中實(shí)現(xiàn)異步編程的一種機(jī)制。
- 執(zhí)行同步任務(wù),放入調(diào)用棧,結(jié)果輸出到控制臺(tái)
- 異步任務(wù)放到web apis,這里分為微任務(wù)、宏任務(wù)
- 同步代碼執(zhí)行完畢(即調(diào)用棧清空)啟動(dòng)事件循環(huán)機(jī)制(Event Loop)
- 依次處理執(zhí)行微任務(wù)、宏任務(wù)隊(duì)列,放入調(diào)用棧執(zhí)行
Promise 相關(guān)
同步:任務(wù)按照次序依次執(zhí)行,每個(gè)任務(wù)必須完成后才能執(zhí)行下一個(gè)任務(wù)。
異步:異步任務(wù)是非阻塞的,可以同時(shí)執(zhí)行多個(gè)任務(wù),不需要等待任務(wù)的完成。
- 執(zhí)行 async 函數(shù),返回的一定是 Promise 對(duì)象
- await 相當(dāng)于 Promise 的 then
- try…catch 可捕獲異常,代替了 Promise 的 catch
- promise.all 接收 promises 數(shù)組,所有為 resolve 才輸出,有一個(gè) reject 就中斷輸出報(bào)
- promise.allSettled 接收 promises 數(shù)組,不論結(jié)果是什么,都會(huì)輸出每一個(gè) promise 的狀態(tài)
css 描邊方式
- border 普通邊框
- outline 不占用空間和影響元素大小
- box-shadow 設(shè)置陰影描邊效果
requestAnimation
- setTimeout:快速滾動(dòng)白屏,原因不同設(shè)備刷新率不同,setTimout 只是一個(gè)固定的時(shí)間間隔。只有主線程執(zhí)行完畢才會(huì)檢查事件隊(duì)列的任務(wù),執(zhí)行時(shí)間會(huì)變晚
- requestAnimationFrame:由系統(tǒng)決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī),不會(huì)引起丟幀現(xiàn)象,1/60 1/75
React 18 新特性
https://juejin.cn/post/7071861718573383716
- 并發(fā)渲染機(jī)制,優(yōu)先處理用戶最關(guān)心的事情
- 批處理優(yōu)化,多個(gè)狀態(tài)合并成一個(gè)批次處理,這樣可以減少不必要的渲染次數(shù)
- 服務(wù)端渲染的改進(jìn),引入新的 api createRoot 創(chuàng)建 root 節(jié)點(diǎn)
- 移除對(duì) IE 支持,繼續(xù)使用需要 17版本
JSX 相關(guān)
- js 是一種動(dòng)態(tài)類型的腳本語言
- jsx 是 react 的擴(kuò)展語法,主要用于定義組件UI的結(jié)構(gòu)和外觀,最后需要通過構(gòu)建工具轉(zhuǎn)換為普通的js代碼
- JSX是React的一個(gè)語法擴(kuò)展,它允許你在JavaScript代碼中寫類似HTML的標(biāo)記。Babel會(huì)將JSX代碼轉(zhuǎn)換成普通的JavaScript代碼,所有的JSX元素都會(huì)被轉(zhuǎn)換成React.createElement函數(shù)調(diào)用
react 輸出兩次
- 組件被開發(fā)模式下的嚴(yán)格模式包裹,組件渲染或副作用函數(shù)會(huì)執(zhí)行兩次,不影響生產(chǎn)模式
- 函數(shù)式編程純函數(shù)的概念,用于幫助開發(fā)者發(fā)現(xiàn)和修復(fù)潛在的問題
函數(shù)式編程
總結(jié)一下: 函數(shù)式編程有兩個(gè)核心概念。
- 數(shù)據(jù)不可變(無副作用): 它要求你所有的數(shù)據(jù)都是不可變的,這意味著如果你想修改一個(gè)對(duì)象,那你應(yīng)該創(chuàng)建一個(gè)新的對(duì)象用來修改,而不是修改已有的對(duì)象。
- 無狀態(tài): 主要是強(qiáng)調(diào)對(duì)于一個(gè)函數(shù),不管你何時(shí)運(yùn)行,它都應(yīng)該像第一次運(yùn)行一樣,給定相同的輸入,給出相同的輸出,完全不依賴外部狀態(tài)的變化。
純函數(shù)帶來的意義。
- 便于測(cè)試和優(yōu)化:這個(gè)意義在實(shí)際項(xiàng)目開發(fā)中意義非常大,由于純函數(shù)對(duì)于相同的輸入永遠(yuǎn)會(huì)返回相同的結(jié)果,因此我們可以輕松斷言函數(shù)的執(zhí)行結(jié)果,同時(shí)也可以保證函數(shù)的優(yōu)化不會(huì)影響其他代碼的執(zhí)行。
- 可緩存性:因?yàn)橄嗤妮斎肟偸强梢苑祷叵嗤妮敵?#xff0c;因此,我們可以提前緩存函數(shù)的執(zhí)行結(jié)果。
- 更少的 Bug:使用純函數(shù)意味著你的函數(shù)中不存在指向不明的 this,不存在對(duì)全局變量的引用,不存在對(duì)參數(shù)的修改,這些共享狀態(tài)往往是絕大多數(shù) bug 的源頭。
React 批處理機(jī)制
- 在事件處理函數(shù)的更新不是一個(gè)個(gè)重新渲染處理的,而是批處理多個(gè)狀態(tài),將更新操作放入一個(gè)隊(duì)列中,下一段時(shí)間一次性執(zhí)行更新操作
- 狀態(tài)更新完成前,一般是無法打印狀態(tài)的最新值的,只能等到下一次
- React 的狀態(tài)更新是異步處理的,可以使用回調(diào)函數(shù)進(jìn)行更新
http請(qǐng)求頭有哪些
常見請(qǐng)求頭:Accept 接受的內(nèi)容、字符集、編碼方式,Content 請(qǐng)求體的類型、長(zhǎng)度,緩存機(jī)制(強(qiáng)制緩存、協(xié)商緩存標(biāo)識(shí))
本地存儲(chǔ)
- Cookie是在客戶端存儲(chǔ)數(shù)據(jù)的機(jī)制,用于存儲(chǔ)少量用戶信息(4KB),可以設(shè)置過期時(shí)間,基于瀏覽器。
- Session是在服務(wù)器端存儲(chǔ)用戶狀態(tài)的機(jī)制,用于存儲(chǔ)用戶的登錄狀態(tài)和其他相關(guān)信息,占用服務(wù)器資源,基于服務(wù)器。
- Token是一種無狀態(tài)的身份驗(yàn)證機(jī)制,用于驗(yàn)證用戶身份,可以包含用戶信息,不占用服務(wù)器資源,可以存儲(chǔ)在客戶端或服務(wù)器。
- IndexedDB 是一個(gè)運(yùn)行在瀏覽器上的非關(guān)系型數(shù)據(jù)庫系統(tǒng),它允許你存儲(chǔ)大量數(shù)據(jù),包括文件和二進(jìn)制數(shù)據(jù)
性能優(yōu)化方面
代碼層面
- 使用防抖和節(jié)流
- 減少頁面的重排和重繪,要統(tǒng)一調(diào)整樣式、多次修改 DOM 先脫離標(biāo)準(zhǔn)流
- 事件觸發(fā)使用事件委托機(jī)制
- 按需加載,如單頁面應(yīng)用的路由懶加載
構(gòu)建方面
- 開啟 gzip 壓縮代碼,vite 使用 vite-plugin-compression 插件,后端開啟 gzip 支持
- 常用的第三庫使用 CDN 服務(wù),就是部署多個(gè)服務(wù)器節(jié)點(diǎn),用戶請(qǐng)求將定位到離用戶最近的節(jié)點(diǎn)
其他
- 優(yōu)先使用緩存減少HTTP請(qǐng)求,將多個(gè)細(xì)小請(qǐng)求可以合并大文件
- 圖?優(yōu)化:對(duì)圖片進(jìn)?壓縮和優(yōu)化,減少圖片的??和請(qǐng)求次數(shù)
- 使?字體圖標(biāo):使?字體圖標(biāo)可以減少圖片的請(qǐng)求,提?頁面性能
- 服務(wù)端渲染
用戶體驗(yàn)
- 增加骨架屏、loading加載動(dòng)畫效果
webgl 的優(yōu)化手段
- 監(jiān)視渲染幀率、控制臺(tái)lighthouse性能報(bào)告
- 良好的編碼規(guī)則,將場(chǎng)景中不需要的對(duì)象通過 remove dispose 進(jìn)行清除廢置
- 使用廉價(jià)的燈光,如環(huán)境光和平行光
- 陰影方面:優(yōu)化陰影貼圖范圍,降低分辨率貼圖尺寸
- 貼圖、模型等資源進(jìn)行壓縮
- 謹(jǐn)慎使用抗鋸齒和后期處理通道
- 著色器指定精度、保持代碼簡(jiǎn)單、使用貼圖紋理表示噪波
- 盡可能在頂點(diǎn)著色器進(jìn)行計(jì)算,并將結(jié)果發(fā)送到片元著色器
著色器優(yōu)化相關(guān)
- 著色器指定精度、保持代碼簡(jiǎn)單、使用貼圖紋理表示噪波
- 盡可能在頂點(diǎn)著色器進(jìn)行計(jì)算,并將結(jié)果發(fā)送到片元著色器
緩沖區(qū)和深度測(cè)試
概述:幾何體由一個(gè)個(gè)頂點(diǎn)粒子構(gòu)成,每個(gè)粒子包含了位置、uv貼圖(3d=>2d坐標(biāo)對(duì)應(yīng))等,三個(gè)頂點(diǎn)形成一個(gè)面,在three.js中。物體由一個(gè)個(gè)三角形面構(gòu)成。
BufferGeometry:是面片、線或點(diǎn)幾何體的有效表述。包括頂點(diǎn)位置,面片索引、法相量、顏色值、UV 坐標(biāo)和自定義緩存屬性值。使用 BufferGeometry 可以有效減少向 GPU 傳輸上述數(shù)據(jù)所需的開銷。目前,three.js的物體都用上了Buffer,可以直接調(diào)用,一些頂點(diǎn)可以使用這個(gè)。
當(dāng)WebGL繪制粒子時(shí),WebGL會(huì)測(cè)試正在繪制的粒子哪個(gè)更靠前,在其后面的粒子不會(huì)被繪制,在其前面的粒子會(huì)被繪制,這被稱為深度測(cè)試,可以通過alphaTest停用depth testing。
異步
setTimeout(function () {console.log(1)
}, 0)new Promise(function (resolve, reject) {console.log(2)for (var i = 0; i < 10000; i++) {if (i === 10) console.log(10)i == 9999 && resolve()}console.log(3)
}).then(function () {console.log(4)
})console.log(5)//2 10 3 5 4 1
- 計(jì)時(shí)器宏任務(wù),放入宏任務(wù)隊(duì)列
- new Promise 立即執(zhí)行函數(shù),輸出 2
- promise 微任務(wù),放入微任務(wù)隊(duì)列
- 進(jìn)入 for 循環(huán),輸出10 3
- 同步任務(wù),輸出 5
- 微任務(wù),輸出 4
- 宏任務(wù),輸出 1
原型和原型鏈
function Foo() {getName = function () {console.log(1)}return this
}Foo.getName = function () {console.log(2)
}
Foo.prototype.getName = function () {console.log(3)
}
var getName = function () {console.log(4)
}
function getName() {console.log(5)
}Foo.getName() //2
getName() //4
Foo().getName() //1
new Foo().getName() //3
- Foo.getName 綁定到類的方法 2
- var 存在變量提升,function fn1(){} 定義的函數(shù)也會(huì)變量提升,結(jié)果如下
var getName
function getName() {console.log(5)
}getName = function () {console.log(4)
}
//node 環(huán)境運(yùn)行報(bào)錯(cuò)、let 聲明不可用
- Foo() 返回 this ,指向window ,但是進(jìn)行 Foo()函數(shù),getName 被重新賦值了,所以是 1,以后也是1
- new Foo() 指向當(dāng)前實(shí)例,內(nèi)部的方法 ,返回3
倒序輸出
// 輸入:1->2->3->4->5->NULL
// 輸出:5->4->3->2->1->NULLfunction traverseStr(str) {const array = str.split('->').slice(0, -1)return array.reverse().join('->') + '->NULL'
}const target = '1->2->3->4->5->NULL'
const result = traverseStr(target)
console.log(result)
漢語表達(dá)
次數(shù)最少字符
const target = 'aaabbbcceeff'
function removeStr(target) {let map = new Map(),result = []for (let ch of target) map.set(ch, (map.get(ch) || 0) + 1)let minCount = Math.min(...map.values())for (let ch of target) {if (map.get(ch) !== minCount) result.push(ch)}return result.join('')
}const result = removeStr(target)
console.log(result)
面試復(fù)盤
- 之前面試的一些八股文、總結(jié)大部分都在自己內(nèi)部的語雀文檔,后續(xù)逐步整理一下發(fā)出來,希望能夠幫助到面試的小伙伴
- 大概是12月12號(hào)一面,第二天的二面,但是第二輪面試掛了。數(shù)字轉(zhuǎn)為漢字那題沒寫出來,只是說了一下大概的思路(后續(xù)源碼整理到算法倉(cāng)庫里面),去除出現(xiàn)最少字符雖然寫出來了,可能代碼邏輯還有待優(yōu)化的地方
- webgl 和著色器優(yōu)化那部分答的一般