相關推薦:
接下來總結一般情況下前端面試中會經歷的以下四個階段和三個決定因素:
作為前端人員,技術的深度廣度是排在第一位的,三年是一個分割線,一定要在這個時候找準自己的定位和方向。
php入門到就業線上直播課:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
其次良好的溝通表達能力、著裝和表現等場外因素能提高面試官對你的認可度。
有的人技術很牛逼,但是面試時讓面試官覺得不爽,覺得你盛氣逼人/形象邋遢/自以為是/表述不清,就直接不要你,那是最得不償失的。
以下是我整個面試準備以及被問到過的問題的一個凝練,因為在面試過程中,和麵試官的交流很大程度並不是簡單的背書,最好是要將知識通過自己的總結和凝練表達出來,再根據面試官的提問臨場發揮將之補足完善。【推薦學習:、】
面試官讓你自我介紹,而且不限定自我介紹的範圍,肯定是面試官想從你的自我介紹中瞭解到你,所以介紹一定要保證簡短和流暢,面對不同的面試官,自我介紹的內容可以是完全一樣的,所以提前準備好說辭很重要,並且一定要注意:不要磕磕巴巴,要自信!流暢的表達和溝通能力,同樣是面試官會對候選人考核點之一。我也曾當過面試官,自信大方的候選人,往往更容易受到青睞。
1、個人介紹(基本情況),主要的簡歷都有了,這方面一定要短
2、個人擅長什麼,包括技術上的和非技術上的。技術上可以瞭解你的轉場,非技術可以瞭解你這個人
3、做過的專案,撿最核心的專案說,不要把所有專案像背書一樣介紹
4、自己的一些想法、興趣或者是觀點,甚至包括自己的職業規劃。這要是給面試官一個感覺:熱衷於"折騰"或者"思考"
範例:
面試官您好,我叫xxx,xx年畢業於xx大學,自畢業以來一直從事著前端開發的相關工作。
我擅長的技術棧是 vue 全家桶,對 vue2 和 vue3 在使用上和原始碼都有一定程度的鑽研;打包工具對 webpack 和 vite 都比較熟悉;有從零到一主導中大型專案落地的經驗和能力。
在上家公司主要是xx產品線負責人的角色,主要職責是。。。。。。
除了開發相關工作,還有一定的技術管理經驗:比如擔任需求評審、UI/UE互動評審評委,負責開發排期、成員共同作業、對成員程式碼進行review、組織例會等等
平常會在自己搭建的部落格上記錄一些學習文章或者學習筆記,也會寫一些原創的技術文章發表到掘金上,獲得過xx獎。
總的來說自我介紹儘量控制在3 - 5分鐘之間,簡明扼要為第一要義,其次是突出自己的能力和長處。
對於普通的技術面試官來說,自我介紹只是習慣性的面試前的開場白,一般簡歷上列舉的基本資訊已經滿足他們對你的基本瞭解了。但是對於主管級別的面試官或者Hr,會看重你的性格、行為習慣、抗壓能力等等綜合能力。所以要讓自己在面試過程儘可能表現的積極向上,愛好廣泛、喜歡持續學習,喜歡團隊合作,可以無條件加班等等。當然也不是說讓你去欺騙,只是在現在這種環境中,這些「側面能力」也是能在一定程度提升自己競爭力的法寶。
在目前這個行情,當你收到面試通知時,有很大概率是因為你的專案經驗和所招聘的崗位比較符合。 所以在專案的準備上要額外上心,比如:
對專案中使用到的技術的深挖
對專案整體設計思路的把控
對專案運作流程的管理
團隊共同作業的能力。
專案的優化點有哪些
這些因人而異就不做贅述了,根據自己的情況好好挖掘即可。
先說個人,當你通過了技術面試,到了主管和hr這一步,不管你當前的技術多牛逼,他們會額外考察你個人的潛力、學習能力、性格與團隊的磨合等軟實力,這裡列出一些很容易被問到的:
直接從個人發展入手錶現出自己的上進心:
一直想去更大的平臺,不但有更好的技術氛圍,而且學到的東西也更多
1、善於規劃和總結,我會對自己經手的專案進行一個全面的分析,一個是業務拆解,對個各模組的業務通過腦圖進行拆解;另一個就是對程式碼模組的拆解,按功能去區分各個程式碼模組。再去進行開發。我覺得這是很多隻會進行盲目業務開發的前端做不到的
2、喜歡專研技術,平常對 vue 的原始碼一直在學習,也有輸出自己的技術文章,像之前寫過一篇逐行精讀 ,花了大約有三十個小時才寫出來的,對每一行原始碼的功能和作用進行了解讀(但是為啥閱讀和點贊這麼低)。
性子比較沉,更偏內向一點,所以我也會嘗試讓自己變得外向一點。
一個是要開各種評審會,作為前端代表需要我去準備各種材料和進行發言。
所以在團隊內做比較多的技術分享,每週主持例會,也讓我敢於去表達和探討。
包依賴管理工具 pnpm(不會重複安裝依賴,非扁平的node_modules結構,符號連結方式新增依賴包)
打包工具 vite (極速的開發環境)
flutter (Google推出並開源的移動應用程式(App)開發框架,主打跨平臺、高保真、高效能)
rust(聽說是js未來的基座)
turbopack,webpack的繼任者,說是比 vite快10倍,webpack快700倍,然後尤雨溪親自驗證其實並沒有比 vite 快10倍
webcomponents
我對個人的規劃是這樣的:
3 - 5 年在提高自己的技術深度的同時,擴寬自己的知識面,就是深度和廣度上都要有提升,主要是在廣度上,充分對大前端有了認知才能更好的做出選擇
5 - 7 年就是當有足夠的知識積累之後再選擇某一個感興趣方向深研下去,爭取成為那個領域的專家
這個因人而異,如實準備即可,因為不同規模團隊的研發模式差別是很大的。
1、最注重的是程式碼的可維護性(變數命名、註釋、函數單一性原則等)
2、擴充套件性:封裝能力(元件、程式碼邏輯是否可複用、可延伸性)
3、ES 新特性(es6+ 、ES2020, ES2021 可選鏈、at)
4、函數使用規範(比如遇到用 map 拿來當 forEach 用的)
5、效能提升,怎樣運用演演算法,寫出更加優雅,效能更好的程式碼
我在上家公司是一個技術管理的角色。
0、落實開發規範,我在公司內部 wiki 上有發過,從命名、最佳實踐到各種工具庫的使用。新人進來前期我會優先跟進他們的程式碼品質
1、團隊分工:每個人單獨負責一個產品的開發,然後公共模組一般我會指定某幾個人開發
2、程式碼品質保證:每週會review他們的程式碼,也會組織交叉 review 程式碼,將修改結果輸出文章放到 wiki中
3、組織例會:每週組織例會同步各自進度和風險,根據各自的進度調配工作任務
4、技術分享:還會組織不定時的技術分享。一開始就是單純的我做分享,比如微前端的體系,ice stark 的原始碼
5、公共需求池:比如webpack5/vite的升級;vue2.7的升級引入setup語法糖;pnpm的使用;拓撲圖效能優化
6、優化專項:在第一版產品出來之後,我還發起過效能優化專項,首屏載入效能,打包體積優化;讓每個人去負責對應的優化項
我覺得加班一般會有兩種情況:
一是專案進度比較緊,那當然以專案進度為先,畢竟大家都靠這個吃飯
二是自身能力問題,對業務不熟啊或者引入一個全新的技術棧,那麼我覺得不僅要加班跟上,還要去利用空閒時間抓緊學習,彌補自己的不足
我平常喜歡閱讀,就是在微信閱讀裡讀一些心理學、時間管理、還有一些演講技巧之類的書
然後是寫文章,因為我發現單純的記筆記很容易就忘了,因為只是記載別人的內容,而寫自己的原創文章,在這個過程中能將知識非常高的比例轉換成自身的東西,所以除了自個發掘金的文章,我也經常會對專案的產出有文章輸出到 wiki 上
其他愛好就是和朋友約著打籃球、唱歌
技術面試一定要注意:簡明扼要,詳略得當,不懂的就說不懂。因為在面試過程中是一個和麵試官面對面交流的過程,沒有面試官會喜歡一個絮絮叨叨半天說不到重點候選人,同時在說話過程中,聽者會被動的忽略自己不感興趣的部分,所以要著重突出某個技術的核心特點,並圍繞著核心適當展開。
大廠基本都會通過演演算法題來篩選候選人,演演算法沒有捷徑,只能一步一步地刷題再刷題,這方面薄弱的要提前規劃進行個學習了。
技術面過程主要會對前端領域相關的技術進行提問,一般面試官會基於你的建立,而更多的是,面試官基於他之前準備好的面試題,或者所在專案組比較熟悉的技術點進行提問,因為都是未知數,所以方方面面都還是要求比較足的。
如果想進入一箇中大型且發展前景不錯的公司,並不是照著別人的面經背一背就能糊弄過去的,這裡作出的總結雖然每一條都很簡短,但都是我對每一個知識點進行全面學習後才提煉出來的部分核心知識點,所以不懼怕面試官的「發散一下思維」。
面試過程一般會涉及到以下八大知識型別的考量:
JS/CSS/TypeScript/框架(Vue、React)/瀏覽器與網路/效能優化/前端工程化/架構/其他
所以面試前的技術準備絕不是一蹴而就,還需要平時的積累,比如可以利用每天十到二十分鐘對其中一個小知識點進行全面的學習,長此以往,無論是幾年的面試,都足夠侃侃而談。
JS的學習梭哈紅包書和冴羽老師的就基本ok了
常見的JS面試題一般會有這些
原型的本質就是一個物件。
當我們在建立一個建構函式之後,這個函數會預設帶上一個prototype
屬性,而這個屬性的值就指向這個函數的原型物件。
這個原型物件是用來為通過該建構函式建立的範例物件提供共用屬性,即用來實現基於原型的繼承和屬性的共用
所以我們通過建構函式建立的範例物件都會從這個函數的原型物件上繼承上面具有的屬性
當讀取範例的屬性時,如果找不到,就會查詢與物件關聯的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止(最頂層就是Object.prototype
的原型,值為null)。
所以通過原型一層層相互關聯的鏈狀結構就稱為原型鏈。
定義:閉包是指參照了其他函數作用域中變數的函數,通常是在巢狀函數中實現的。
從技術角度上所有 js 函數都是閉包。
從實踐角度來看,滿足以下倆個條件的函數算閉包
即使建立它的上下文被銷燬了,它依然存在。(比如從父函數中返回)
在程式碼中參照了自由變數(在函數中使用的既不是函數引數也不是函數區域性變數的變數稱作自由變數)
使用場景:
建立私有變數
vue 中的data,需要是一個閉包,保證每個data中資料唯一,避免多次參照該元件造成的data共用
延長變數的生命週期
一般函數的詞法環境在函數返回後就被銷燬,但是閉包會儲存對建立時所在詞法環境的參照,即便建立時所在的執行上下文被銷燬,但建立時所在詞法環境依然存在,以達到延長變數的生命週期的目的
應用
在絕大多數情況下,函數的呼叫方式決定了 this
的值(執行時繫結)
1、全域性的this非嚴格模式指向window物件,嚴格模式指向 undefined
2、物件的屬性方法中的this 指向物件本身
3、apply、call、bind 可以變更 this 指向為第一個傳參
4、箭頭函數中的this指向它的父級作用域,它自身不存在 this
js 程式碼執行過程中,會建立對應的執行上下文並壓入執行上下文棧中。
如果遇到非同步任務就會將任務掛起,交給其他執行緒去處理非同步任務,當非同步任務處理完後,會將回撥結果加入事件佇列中。
當執行棧中所有任務執行完畢後,就是主執行緒處於閒置狀態時,才會從事件佇列中取出排在首位的事件回撥結果,並把這個回撥加入執行棧中然後執行其中的程式碼,如此反覆,這個過程就被稱為事件迴圈。
事件佇列分為了宏任務佇列和微任務佇列,在當前執行棧為空時,主執行緒回先檢視微任務佇列是否有事件存在,存在則依次執行微任務佇列中的事件回撥,直至微任務佇列為空;不存在再去宏任務佇列中處理。
常見的宏任務有setTimeout()
、setInterval()
、setImmediate()
、I/O、使用者互動操作,UI渲染
常見的微任務有promise.then()
、promise.catch()
、new MutationObserver
、process.nextTick()
宏任務和微任務的本質區別
宏任務有明確的非同步任務需要執行和回撥,需要其他非同步執行緒支援
微任務沒有明確的非同步任務需要執行,只有回撥,不需要其他非同步執行緒支援。
1、基本資料型別大小固定且操作簡單,所以放入棧中儲存
2、參照資料型別大小不確定,所以將它們放入堆記憶體中,讓它們在申請記憶體的時候自己確定大小
3、這樣分開儲存可以使記憶體佔用最小。棧的效率高於堆
4、棧記憶體中變數在執行環境結束後會立即進行垃圾回收,而堆記憶體中需要變數的所有參照都結束才會被回收
1、根據物件的存活時間將記憶體的垃圾回收進行不同的分代,然後對不同分代採用不同的回收演演算法
2、新生代採用空間換時間的 scavenge 演演算法:整個空間分為兩塊,變數僅存在其中一塊,回收的時候將存活變數複製到另一塊空間,不存活的回收掉,周而復始輪流操作
3、老生代使用標記清除和標記整理,標記清除:遍歷所有物件標記標記可以存取到的物件(活著的),然後將不活的當做垃圾進行回收。回收完後避免記憶體的斷層不連續,需要通過標記整理將活著的物件往記憶體一端進行移動,移動完成後再清理邊界記憶體
1、普通function
直接使用()
呼叫並傳參,如:function test(x, y) { return x + y}
,test(3, 4)
2、作為物件的一個屬性方法呼叫,如:const obj = { test: function (val) { return val } }
, obj.test(2)
3、使用call
或apply
呼叫,更改函數 this 指向,也就是更改函數的執行上下文
4、new
可以間接呼叫建構函式生成物件範例
一般情況下,當執行到 script 標籤時會進行下載 + 執行兩步操作,這兩步會阻塞 HTML 的解析;
async 和 defer 能將script的下載階段變成非同步執行(和 html解析同步進行);
async下載完成後會立即執行js,此時會阻塞HTML解析;
defer會等全部HTML解析完成且在DOMContentLoaded 事件之前執行。
DOM 事件流三階段:
捕獲階段:事件最開始由不太具體的節點最早接受事件, 而最具體的節點(觸發節點)最後接受事件。為了讓事件到達最終目標之前攔截事件。
比如點選一個div,則 click 事件會按這種順序觸發: document => <html>
=> <body>
=> <div>
,即由 document 捕獲後沿著 DOM 樹依次向下傳播,並在各節點上觸發捕獲事件,直到到達實際目標元素。
目標階段
當事件到達目標節點的,事件就進入了目標階段。事件在目標節點上被觸發(執行事件對應的函數),然後會逆向迴流,直到傳播至最外層的檔案節點。
冒泡階段
事件在目標元素上觸發後,會繼續隨著 DOM 樹一層層往上冒泡,直到到達最外層的根節點。
所有事件都要經歷捕獲階段和目標階段,但有些事件會跳過冒泡階段,比如元素獲得焦點 focus 和失去焦點 blur 不會冒泡
擴充套件一
e.target 和 e.currentTarget 區別?
e.target
指向觸發事件監聽的物件。e.currentTarget
指向新增監聽事件的物件。例如:
<ul>
<li><span>hello 1</span></li>
</ul>
let ul = document.querySelectorAll('ul')[0]
let aLi = document.querySelectorAll('li')
ul.addEventListener('click',function(e){
let oLi1 = e.target
let oLi2 = e.currentTarget
console.log(oLi1) // 被點選的li
console.log(oLi2) // ul
console.og(oLi1===oLi2) // false
})
登入後複製
給 ul 繫結了事件,點選其中 li 的時候,target 就是被點選的 li, currentTarget 就是被繫結事件的 ul
事件冒泡階段(上述例子),e.currenttarget
和e.target
是不相等的,但是在事件的目標階段,e.currenttarget
和e.target
是相等的
作用:
e.target
可以用來實現,該原理是通過事件冒泡(或者事件捕獲)給父元素新增事件監聽,e.target指向引發觸發事件的元素
擴充套件二
addEventListener 引數
語法:
addEventListener(type, listener);
addEventListener(type, listener, options || useCapture);
登入後複製
type: 監聽事件的型別,如:'click'/'scroll'/'focus'
listener: 必須是一個實現了 介面的物件,或者是一個。當監聽的事件型別被觸發時,會執行
options:指定 listerner 有關的可選引數物件
preventDefault()
AbortSignal
,當它的abort()
方法被呼叫時,監聽器會被移除useCapture:布林值,預設為 false,listener 在事件冒泡階段結束時執行,true 則表示在捕獲階段開始時執行。作用就是更改事件作用的時機,方便攔截/不被攔截。
筆主是主要從事 Vue相關開發的,也做過 react 相關的專案,當然 react 也只是能做專案的水平,所以在簡歷中屬於一筆帶過的那種,框架貴不在多而在精,對Vue原始碼系列的學習讓我對Vue還是十分自信的。學習過程也是如此,如果你能夠對一門框架達到精通原理的掌握程度,學習其他框架不過只是花時間的事情。
1、資料可變性
setState
或者onchange
來實現檢視更新2、寫法
3、diff 演演算法
react
主要使用diff佇列儲存需要更新哪些DOM,得到patch樹,再統一操作批次更新DOM。,需要使用shouldComponentUpdate()
來手動優化react的渲染。擴充套件:瞭解 react hooks嗎
元件類的寫法很重,層級一多很難維護。
函陣列件是純函數,不能包含狀態,也不支援生命週期方法,因此無法取代類。
React Hooks 的設計目的,就是加強版函陣列件,完全不使用"類",就能寫出一個全功能的元件
React Hooks 的意思是,元件儘量寫成純函數,如果需要外部功能和副作用,就用勾點把外部程式碼"鉤"進來。
Vue 在處理更新同型別 vnode 的一組子節點(比如v-for渲染的列表節點)的過程中,為了減少 DOM 頻繁建立和銷燬的效能開銷:
對沒有 key 的子節點陣列更新是通過就地更新的策略。它會通過對比新舊子節點陣列的長度,先以比較短的那部分長度為基準,將新子節點的那一部分直接 patch 上去。然後再判斷,如果是新子節點陣列的長度更長,就直接將新子節點陣列剩餘部分掛載;如果是新子節點陣列更短,就把舊子節點多出來的那部分給解除安裝掉)。所以如果子節點是元件或者有狀態的 DOM 元素,原有的狀態會保留,就會出現渲染不正確的問題。
有 key 的子節點更新是呼叫的patchKeyedChildren
,這個函數就是大家熟悉的實現核心 diff 演演算法的地方,大概流程就是同步頭部節點、同步尾部節點、處理新增和刪除的節點,最後用求解最長遞增子序列的方法區處理未知子序列。是為了最大程度實現對已有節點的複用,減少 DOM 操作的效能開銷,同時避免了就地更新帶來的子節點狀態錯誤的問題。
綜上,如果是用 v-for 去遍歷常數或者子節點是諸如純文字這類沒有」狀態「的節點,是可以使用不加 key 的寫法的。但是實際開發過程中更推薦統一加上 key,能夠實現更廣泛場景的同時,避免了可能發生的狀態更新錯誤,我們一般可以使用 ESlint 設定 key 為 v-for 的必需元素。
想詳細瞭解這個知識點的可以去看看我之前寫的文章:
vue2使用的是Object.defineProperty
去監聽物件屬性值的變化,但是它不能監聽物件屬性的新增和刪除,所以需要使用$set
、$delete
這種語法糖去實現,這其實是一種設計上的不足。
所以 vue3 採用了proxy
去實現響應式監聽物件屬性的增刪查改。
其實從api的原生效能上proxy
是比Object.defineProperty
要差的。
而 vue 做的響應式效能優化主要是在將巢狀層級比較深的物件變成響應式的這一過程。
vue2的做法是在元件初始化的時候就遞迴執行Object.defineProperty
把子物件變成響應式的;
而vue3是在存取到子物件屬性的時候,才會去將它轉換為響應式。這種延時定義子物件響應式會對效能有一定的提升
前提:當同型別的 vnode 的子節點都是一組節點(陣列型別)的時候,
步驟:會走核心 diff 流程
Vue3是快速選擇演演算法
Vue2是雙端比較演演算法
在新舊位元組點的頭尾節點,也就是四個節點之間進行對比,找到可複用的節點,不斷向中間靠攏的過程
diff目的:diff 演演算法的目的就是為了儘可能地複用節點,減少 DOM 頻繁建立和刪除帶來的效能開銷
基於 MVVM 模型,viewModel(業務邏輯層)提供了資料變化後更新檢視和檢視變化後更新資料這樣一個功能,就是傳統意義上的雙向繫結。
Vue2.x 實現雙向繫結核心是通過三個模組:Observer監聽器、Watcher訂閱者和Compile編譯器。
首先監聽器會監聽所有的響應式物件屬性,編譯器會將模板進行編譯,找到裡面動態繫結的響應式資料並初始化檢視;watchr 會去收集這些依賴;當響應式資料發生變更時Observer就會通知 Watcher;watcher接收到監聽器的訊號就會執行更新函數去更新檢視;
vue3的變更是資料劫持部分使用了porxy 替代 Object.defineProperty,收集的依賴使用元件的副作用渲染函數替代watcher
vue2 v-model 原理剖析
V-model 是用來監聽使用者事件然後更新資料的語法糖。
其本質還是單向資料流,內部是通過繫結元素的 value 值向下傳遞資料,然後通過繫結 input 事件,向上接收並處理更新資料。
單向資料流:父元件傳遞給子元件的值子元件不能修改,只能通過emit事件讓父元件自個改。
// 比如
<input v-model="sth" />
// 等價於
<input :value="sth" @input="sth = $event.target.value" />
登入後複製
給元件新增 v-model
屬性時,預設會把value
作為元件的屬性,把 input
作為給元件繫結事件時的事件名:
// 父元件
<my-button v-model="number"></my-button>
// 子元件
<script>
export default {
props: {
value: Number, // 屬性名必須是 value
},
methods: {
add() {
this.$emit('input', this.value + 1) // 事件名必須是 input
},
}
}
</script>
登入後複製
如果想給繫結的 value 屬性和 input 事件換個名稱呢?可以這樣:
在 Vue 2.2 及以上版本,你可以在定義元件時通過 model 選項的方式來客製化 prop/event:
<script>
export default {
model: {
prop: 'num', // 自定義屬性名
event: 'addNum' // 自定義事件名
}
}
登入後複製
vue3 v-model 原理
實現和 vue2 基本一致
<Son v-model="modalValue"/>
登入後複製
等同於
<Son v-model="modalValue"/> <Son :modalValue="modalValue" @update:modalValue="modalUpdate=$event.target.value"/>
登入後複製
自定義 model 引數
<Son v-model:visible="visible"/>
setup(props, ctx){
ctx.emit("update:visible", false)
}
登入後複製
不管vue2 還是 vue3,響應式的核心就是觀察者模式 + 劫持資料的變化,在存取的時候做依賴收集和在修改資料的時候執行收集的依賴並更新資料。具體點就是:
vue2 的話採用的是 Object.definePorperty
劫持物件的 get 和 set 方法,每個元件範例都會在渲染時初始化一個 watcher 範例,它會將元件渲染過程中所接觸的響應式變數記為依賴,並且儲存了元件的更新方法 update。當依賴的 setter 觸發時,會通知 watcher 觸發元件的 update 方法,從而更新檢視。
Vue3 使用的是 ES6 的 proxy,proxy 不僅能夠追蹤屬性的獲取和修改,還可以追蹤物件的增刪,這在 vue2中需要 delete 才能實現。然後就是收集的依賴是用元件的副作用渲染函數替代 watcher 範例。
效能方面,從原生 api 角度,proxy 這個方法的效能是不如 Object.property,但是 vue3 強就強在一個是上面提到的可以追蹤物件的增刪,第二個是對巢狀物件的處理上是存取到具體屬性才會把那個物件屬性給轉換成響應式,而 vue2 是在初始化的時候就遞迴呼叫將整個物件和他的屬性都變成響應式,這部分就差了。
擴充套件一
vue2 通過陣列下標更改陣列檢視為什麼不會更新?
尤大:效能不好
注意:vue3 是沒問題的
why 效能不好?
我們看一下響應式處理:
export class Observer {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 這裡對陣列進行單獨處理
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 對物件遍歷所有鍵值
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
登入後複製
對於物件是通過Object.keys()
遍歷全部的鍵值,對陣列只是observe
監聽已有的元素,所以通過下標更改不會觸發響應式更新。
理由是陣列的鍵相較物件多很多,當陣列資料大的時候效能會很拉胯。所以不開放
Computed 的大體實現和普通的響應式資料是一致的,不過加了延時計算和快取的功能:
在存取computed物件的時候,會觸發 getter ,初始化的時候將 computed 屬性建立的 watcher (vue3是副作用渲染函數)新增到與之相關的響應式資料的依賴收集器中(dep),然後根據裡面一個叫 dirty 的屬性判斷是否要收集依賴,不需要的話直接返回上一次的計算結果,需要的話就執行更新重新渲染檢視。
watchEffect?
watchEffect會自動收集回撥函數中響應式變數的依賴。並在首次自動執行
推薦在大部分時候用 watch
顯式的指定依賴以避免不必要的重複觸發,也避免在後續程式碼修改或重構時不小心引入新的依賴。watchEffect
適用於一些邏輯相對簡單,依賴源和邏輯強相關的場景(或者懶惰的場景 )
vue有個機制,更新 DOM 是非同步執行的,當資料變化會產生一個非同步更行佇列,要等非同步佇列結束後才會統一進行更新檢視,所以改了資料之後立即去拿 dom 還沒有更新就會拿不到最新資料。所以提供了一個 nextTick 函數,它的回撥函數會在DOM 更新後立即執行。
nextTick 本質上是個非同步任務,由於事件迴圈機制,非同步任務的回撥總是在同步任務執行完成後才得到執行。所以原始碼實現就是根據環境建立非同步函數比如 Promise.then(瀏覽器不支援promise就會用MutationObserver,瀏覽器不支援MutationObserver就會用setTimeout),然後呼叫非同步函數執行回撥佇列。
所以專案中不使用$nextTick的話也可以直接使用Promise.then或者SetTimeout實現相同的效果
1、全域性錯誤處理:Vue.config.errorHandler
Vue.config.errorHandler = function(err, vm, info) {};
如果在元件渲染時出現執行錯誤,錯誤將會被傳遞至全域性Vue.config.errorHandler
設定函數 (如果已設定)。
比如前端監控領域的 sentry,就是利用這個勾點函數進行的 vue 相關異常捕捉處理
2、全域性警告處理:Vue.config.warnHandler
Vue.config.warnHandler = function(msg, vm, trace) {};
登入後複製
注意:僅在開發環境生效
像在模板中參照一個沒有定義的變數,它就會有warning
3、單個vue 範例錯誤處理:renderError
const app = new Vue({
el: "#app",
renderError(h, err) {
return h("pre", { style: { color: "red" } }, err.stack);
}
});
登入後複製
和元件相關,只適用於開發環境,這個用處不是很大,不如直接看控制檯
4、子孫元件錯誤處理:errorCaptured
Vue.component("cat", {
template: `<div><slot></slot></div>`,
props: { name: { type: string } },
errorCaptured(err, vm, info) {
console.log(`cat EC: ${err.toString()}\ninfo: ${info}`);
return false;
}
});
登入後複製
注:只能在元件內部使用,用於捕獲子孫元件的錯誤,一般可以用於元件開發過程中的錯誤處理
5、終極錯誤捕捉:window.onerror
window.onerror = function(message, source, line, column, error) {};
登入後複製
它是一個全域性的例外處理函數,可以抓取所有的 JavaScript 異常
Vuex 利用 vue 的mixin 機制,在beforeCreate 勾點前混入了 vuexinit 方法,這個方法實現了將 store 注入 vue 範例當中,並註冊了 store 的參照屬性 store.xxx`去引入vuex中定義的內容。
然後 state 是利用 vue 的 data,通過new Vue({data: {$$state: state}}
將 state 轉換成響應式物件,然後使用 computed 函數實時計算 getter
概念
可以通過全域性方法Vue.use()
註冊外掛,並能阻止多次註冊相同外掛,它需要在new Vue
之前使用。
該方法第一個引數必須是Object
或Function
型別的引數。如果是Object
那麼該Object
需要定義一個install
方法;如果是Function
那麼這個函數就被當做install
方法。
Vue.use()
執行就是執行install
方法,其他傳參會作為install
方法的引數執行。
所以**Vue.use()
本質就是執行需要注入外掛的install
方法**。
原始碼實現
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 避免重複註冊
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 獲取傳入的第一個引數
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
// 如果傳入物件中的install屬性是個函數則直接執行
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
// 如果傳入的是函數,則直接(作為install方法)執行
plugin.apply(null, args)
}
// 將已經註冊的外掛推入全域性installedPlugins中
installedPlugins.push(plugin)
return this
}
}
登入後複製
使用方式
installedPlugins import Vue from 'vue'
import Element from 'element-ui'
Vue.use(Element)
登入後複製
要暴露一個install
方法,第一個引數是Vue
構造器,第二個引數是一個可選的設定項物件
Myplugin.install = function(Vue, options = {}) {
// 1、新增全域性方法或屬性
Vue.myGlobalMethod = function() {}
// 2、新增全域性服務
Vue.directive('my-directive', {
bind(el, binding, vnode, pldVnode) {}
})
// 3、注入元件選項
Vue.mixin({
created: function() {}
})
// 4、新增實體方法
Vue.prototype.$myMethod = function(methodOptions) {}
}
登入後複製
Css直接面試問答的題目相對來說比較少,更多的是需要你能夠當場手敲程式碼實現功能,一般來說備一些常見的佈局,熟練掌握flex基本就沒有什麼問題了。
Block Formatting context,塊級格式上下文
BFC 是一個獨立的渲染區域,相當於一個容器,在這個容器中的樣式佈局不會受到外界的影響。
比如浮動元素、絕對定位、overflow 除 visble 以外的值、display 為 inline/tabel-cells/flex 都能構建 BFC。
常常用於解決
處於同一個 BFC 的元素外邊距會產生重疊(此時需要將它們放在不同 BFC 中);
清除浮動(float),使用 BFC 包裹浮動的元素即可
阻止元素被浮動元素覆蓋,應用於兩列式佈局,左邊寬度固定,右邊內容自適應寬度(左邊float,右邊 overflow)
偽類
偽類即:當元素處於特定狀態時才會運用的特殊類
開頭為冒號的選擇器,用於選擇處於特定狀態的元素。比如:first-child
選擇第一個子元素;:hover
懸浮在元素上會顯示;:focus
用鍵盤選定元素時啟用;:link
+ :visted
點選過的連結的樣式;:not
用於匹配不符合引數選擇器的元素;:fist-child
匹配元素的第一個子元素;:disabled
匹配禁用的表單元素
偽元素
偽元素用於建立一些不在檔案樹中的元素,併為其新增樣式。比如說,我們可以通過::before
來在一個元素前增加一些文字,併為這些文字新增樣式。雖然使用者可以看到這些文字,但是這些文字實際上不在檔案樹中。範例:
::before
在被選元素前插入內容。需要使用 content 屬性來指定要插入的內容。被插入的內容實際上不在檔案樹中
h1:before {
content: "Hello ";
}
登入後複製
::first-line
匹配元素中第一行的文字
href是Hypertext Reference的簡寫,表示超文字參照,指向網路資源所在位置。href 用於在當前檔案和參照資源之間確立聯絡
src是source的簡寫,目的是要把檔案下載到html頁面中去。src 用於替換當前內容
瀏覽器解析方式
當瀏覽器遇到href會並行下載資源並且不會停止對當前檔案的處理。(同時也是為什麼建議使用 link 方式載入 CSS,而不是使用 @import 方式)
當瀏覽器解析到src ,會暫停其他資源的下載和處理,直到將該資源載入或執行完畢。(這也是script標籤為什麼放在底部而不是頭部的原因)
flex
<div class="wrapper flex-center">
<p>horizontal and vertical</p>
</div>
.wrapper {
width: 900px;
height: 300px;
border: 1px solid #ccc;
}
.flex-center { // 注意是父元素
display: flex;
justify-content: center; // 主軸(豎線)上的對齊方式
align-items: center; // 交叉軸(橫軸)上的對齊方式
}
登入後複製
flex + margin
<div class="wrapper">
<p>horizontal and vertical</p>
</div>
.wrapper {
width: 900px;
height: 300px;
border: 1px solid #ccc;
display: flex;
}
.wrapper > p {
margin: auto;
}
登入後複製
Transform + absolute
<div class="wrapper">
<img src="test.png">
</div>
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
position: relative;
}
.wrapper > img {
position: absolute;
left: 50%;
top: 50%;
tansform: translate(-50%, -50%)
}
登入後複製
注:使用該方法只適用於行內元素(a、img、label、br、select等)(寬度隨元素的內容變化而變化),用於塊級元素(獨佔一行)會有問題,left/top 的50%是基於圖片最左側的邊來移動的,tanslate會將多移動的圖片自身的半個長寬移動回去,就實現了水平垂直居中的效果
display: table-cell
<div class="wrapper">
<p>absghjdgalsjdbhaksldjba</p>
</div>
.wrapper {
width: 900px;
height: 300px;
border: 1px solid #ccc;
display: table-cell;
vertical-align: middle;
text-align: center;
}
登入後複製
瀏覽器和網路是八股中最典型的案例了,無論你是幾年經驗,只要是前端,總會有問到你的瀏覽器和網路協定。
最好的學習文章是李兵老師的《瀏覽器工作原理與實踐》
這裡分了同源頁面和不同源頁面的通訊。
不同源頁面可以通過 iframe 作為一個橋樑,因為 iframe 可以指定 origin 來忽略同源限制,所以可以在每個頁面都嵌入同一個 iframe 然後監聽 iframe 中傳遞的 message 就可以了。
同源頁面的通訊大致分為了三類:廣播模式、共用儲存模式和口口相傳模式
第一種廣播模式,就是可以通過 BroadCast Channel、Service Worker 或者 localStorage 作為廣播,然後去監聽廣播事件中訊息的變化,達到頁面通訊的效果。
第二種是共用儲存模式,我們可以通過Shared Worker 或者 IndexedDB,建立全域性共用的資料儲存。然後再通過輪詢去定時獲取這些被儲存的資料是否有變更,達到一個的通訊效果。像常見cookie 也可以作為實現共用儲存達到頁面通訊的一種方式
最後一種是口口相傳模式,這個主要是在使用 window.open 的時候,會返回被開啟頁面的 window 的參照,而在被開啟的頁面可以通過 window.opener 獲取開啟它的頁面的 window 點參照,這樣,多個頁面之間的 window 是能夠相互獲取到的,傳遞訊息的話通過 postMessage 去傳遞再做一個事件監聽就可以了
在瀏覽器第一次發起請求服務的過程中,會根據響應報文中的快取標識決定是否快取結果,是否將快取標識和請求結果存入到瀏覽器快取中。
HTTP 快取分為強制快取和協商快取兩類。
強制快取就是請求的時候瀏覽器向快取查詢這次請求的結果,這裡分了三種情況,沒查詢到直接發起請求(和第一次請求一致);查詢到了並且快取結果還沒有失效就直接使用快取結果;查詢到但是快取結果失效了就會使用協商快取。
強制快取有 Expires 和 Cache-control 兩個快取標識,Expires 是http/1.0 的欄位,是用來指定過期的具體的一個時間(如 Fri, 02 Sep 2022 08:03:35 GMT),當伺服器時間和瀏覽器時間不一致的話,就會出現問題。所以在 http1.1 新增了 cache-control 這個欄位,它的值規定了快取的範圍(public/private/no-cache/no-store),也可以規定快取在xxx時間內失效(max-age=xxx)是個相對值,就能避免了 expires帶來的問題。
協商快取就是強制快取的快取結果失效了,瀏覽器攜帶快取標識向伺服器發起請求,有伺服器通過快取標識決定是否使用快取的過程。
控制協商快取的欄位有 last-modified / if-modified-since 和 Etag / if-none-match,後者優先順序更高。
大致過程就是通過請求報文傳遞 last-modified 或 Etag 的值給伺服器與伺服器中對應值作對比,若和響應報文中的 if-modified-since 或 if-none-match 結果一致,則協商快取有效,使用快取結果,返回304;否則失效,重新請求結果,返回200
使用者輸入一段內容後,瀏覽器會先去判斷這段內容是搜尋內容還是 URL ,是搜尋內容的話就會接合預設的搜尋引擎生成 URL,比如 google 瀏覽器是goole.com/search?xxxx,如果是 URL 會拼接協定,比如 http/https。當頁面沒有監聽 beforeupload 時間或者同意了繼續執行流程,瀏覽器圖示欄會進入載入中的狀態。
接下來瀏覽器程序會通過 IPC 程序間通訊將 URL 請求傳送給網路程序,網路程序會先去快取中查詢該資源,如果有則攔截請求並直接200返回,沒有的話會進入網路請求流程。
網路請求流程是網路程序請求 DNS 伺服器返回域名對應的IP和埠號(如果這些之前有快取也是直接返回快取結果),如果沒有埠號,http預設為80,https預設為443,如果是https還需要建立 TLS 安全連線建立加密的資料通道。
接著就是 TCP 三次握手建立瀏覽器和伺服器連線,然後進行資料傳輸,資料傳輸完成四次揮手斷開連線,如果設定了connection: keep-alive
就可以一直保持連線。
網路程序將通過TCP獲取的封包進行解析,首先是根據響應頭的content-type來判斷資料型別,如果是位元組流或者檔案型別的話,會交給下載管理器進行下載,這時候導航流程就結束了。如果是 text/html 型別,就會通知到瀏覽器程序獲取檔案進行渲染。
瀏覽器程序獲取到渲染的通知,會根據當前頁面和新輸入的頁面判斷是否是同一個站點,是的話就複用之前網頁建立的渲染程序,否則的話會新建立一個單獨的渲染程序。
瀏覽器程序將「提交檔案」的訊息給渲染程序,渲染程序接收到訊息就會和網路程序建立傳輸資料的通道,資料傳輸完成後就返回「確認提交」的資訊給瀏覽器程序。
瀏覽器接收到渲染程序的「確認提交「的訊息後,就會更新瀏覽器的頁面狀態:安全狀態、位址列 URL、前進後退的歷史訊息,並更新 web頁面,此時頁面是空白頁面(白屏)。
頁面渲染過程(重點記憶)
最後是渲染程序對檔案進行頁面解析和子資源載入,渲染程序會將 HTML 轉換成 DOM 樹結構,將 css 轉換成 styleSeets ( CSSOM)。然後複製 DOM 樹過濾掉不顯示的元素建立基本的渲染樹,接著計算每個 DOM 節點的樣式和計算每個節點的位置佈局資訊構建成佈局樹。
具有層疊上下文或者需要要裁剪的地方會獨立建立圖層,這就是分層,最終會形成一個分層樹,渲染程序會給每個圖層生成繪製列表並提交給合成執行緒,合成執行緒將圖層分成圖塊(避免一次性繪製圖層所有內容,可以根據圖塊優先需渲染視口部分),並在光柵化執行緒池中將圖塊轉換成點陣圖。
轉換完畢後合成執行緒傳送繪製圖塊命令 DrawQuard 給瀏覽器程序,瀏覽器根據 DrawQuard 訊息生成頁面,並顯示到瀏覽器上。
速記:
瀏覽器的渲染程序將 html 解析成 dom樹,將 css 解析成 cssom 樹,然後會先複製一份 DOM 樹過濾掉不顯示的元素(比如 display: none),再和 cssom 結合進行計算每個 dom 節點的佈局資訊構建成一個佈局樹。
佈局樹生成完畢就會根據圖層的層疊上下文或者裁剪部分進行分層,形成一個分層樹。
渲染程序再將每個圖層生成繪製列表並提交給合成執行緒,合成執行緒為了避免一次性渲染,就是分塊渲染,會將圖層分成圖塊,並通過光柵化執行緒池將圖塊轉換成點陣圖。
轉換完畢後,合成執行緒將繪製圖塊的命令傳送給瀏覽器進行顯示
UDP 是使用者封包協定(User Dataprogram Protocol),IP 通過 IP 地址資訊把封包傳送給指定電腦後,UDP 可以通過埠號把封包分發給正確的程式。UDP 可以校驗資料是否正確,但沒有重發的機制,只會丟棄錯誤的封包,同時 UDP 在傳送之後無法確認是否到達目的地。UDP 不能保證資料的可靠性,但是傳輸的速度非常快,通常運用於線上視訊、互動遊戲這些不那麼嚴格保證資料完整性的領域。
TCP 是為了解決 UDP 的資料容易丟失,且無法正確組裝封包二引入的傳輸控制協定(Transmission Control Protocol),是一種面向連線的,可靠的,基於位元組流的傳輸層通訊協定。TCP 在處理封包丟失的情況,提供了重傳機制;並且 TCP 引入了封包排序機制,可以將亂序的封包組合成完整的檔案。
TCP 頭除了包含目標埠和本機埠號外,還提供了用於排序的序列號,以便接收端通過序號來重排封包。
一個 TCP 連線的生命週期會經歷連結階段,資料傳輸和斷開連線階段三個階段。
連線階段
用來建立使用者端和伺服器之間的連結,通過三次握手用來確認使用者端、伺服器端相互之間的封包收發能力。
1、使用者端先傳送 SYN 報文用來確認伺服器端能夠發資料,並進入 SYN_SENT 狀態等待伺服器端確認
2、伺服器端收到 SYN 報文,會向用戶端傳送一個 ACK 確認報文,同時伺服器端也會向用戶端傳送 SYN 報文用來確認使用者端是否能夠傳送資料,此時伺服器端進入 SYN_RCVD 狀態
3、使用者端接收到 ACK + SYN 的報文,就會向伺服器端傳送封包並進入 ESTABLISHED 狀態(建立連線);伺服器端接收到使用者端傳送的 ACK 包也會進入 ESTABLISHED 狀態,完成三次握手
傳輸資料階段
該階段,接收端需要對每個包進行確認操作;
所以當傳送端傳送了一個封包之後,在規定時間內沒有接收到接收端反饋的確認訊息,就會判斷為包丟失,從而觸發重發機制;
一個大檔案在傳輸過程中會分為很多個小封包,封包到達接收端後會根據 TCP 頭中的序號為其排序,保證資料的完整。
斷開連線階段
通過四次揮手,來保證雙方的建立的連線能夠斷開
1、使用者端向伺服器發起 FIN 包,並進入 FIN_WAIT_1 狀態
2、伺服器端收到 FIN 包,發出確認包 ACK,並帶上自己的序號,伺服器端進入 CLOSE_WAIT 狀態。這時候使用者端已經沒有資料要發給伺服器端了,但是伺服器端如果有資料要發給使用者端,使用者端還是需要接收。使用者端收到 ACK 後進入 FIN_WAIT_2 狀態
3、伺服器端資料傳送完畢後,向用戶端傳送 FIN 包,此時伺服器進入 LAST_ACK 狀態
4、使用者端收到 FIN 包發出確認包 ACK ,此時使用者端進入 TIME_WAIT 狀態,等待 2 MSL 後進入 CLOSED 狀態;伺服器端接收到使用者端的 ACK 後就進入 CLOSED 狀態了。
對於四次揮手,因為 TCP 是全雙工通訊,在主動關閉方傳送 FIN 包後,接收端可能還要傳送資料,不能立即關閉伺服器端到使用者端的資料通道,所以也就不能將伺服器端的 FIN
包與對使用者端的 ACK
包合併行送,只能先確認 ACK
,然後伺服器待無需傳送資料時再傳送 FIN
包,所以四次揮手時必須是四次封包的互動
Content-length 是 http 訊息長度,用十進位制數位表示的位元組的數目。
如果 content-length > 實際長度,伺服器端/使用者端讀取到訊息隊尾時會繼續等待下一個位元組,會出現無響應超時的情況
如果 content-length < 實際長度,首次請求的訊息會被擷取,然後會導致後續的資料解析混亂。
當不確定content-length的值應該使用Transfer-Encoding: chunked,能夠將需要返回的資料分成多個資料塊,直到返回長度為 0 的終止塊
什麼是跨域?
協定 + 域名 + 埠號均相同時則為同域,任意一個不同則為跨域
解決方案
1、 傳統的jsonp:利用<script>
標籤沒有跨域限制的特點,僅支援 get介面,應該沒有人用這個了
2、 一般使用 cors(跨域資源共用)來解決跨域問題,瀏覽器在請求頭中傳送origin欄位指明請求發起的地址,伺服器端返回Access-control-allow-orign,如果一致的話就可以進行跨域存取
3、 Iframe 解決主域名相同,子域名不同的跨域請求
4、 瀏覽器關閉跨域限制的功能
5、 http-proxy-middleware 代理
預檢
補充:http會在跨域的時候發起一次預檢請求,「需預檢的請求」要求必須首先使用OPTIONS方法發起一個預檢請求到伺服器,以獲知伺服器是否允許該實際請求。「預檢請求」的使用,可以避免跨域請求對伺服器的使用者資料產生未預期的影響。
withCredentials為 true不會產生預請求;content-type為application/json會產生預請求;設定了使用者自定義請求頭會產生預檢請求;delete方法會產生預檢請求;
xss基本概念
Xss (Cross site scripting)跨站指令碼攻擊,為了和 css 區別開來所以叫 xss
Xss 指駭客向 html 或 dom 中注入惡意指令碼,從而在使用者瀏覽頁面的時候利用注入指令碼對使用者實施攻擊的手段
惡意指令碼可以做到:竊取 cookie 資訊、監聽使用者行為(比如表單的輸入)、修改DOM(比如偽造登入介面騙使用者輸入賬號密碼)、在頁面生成浮窗廣告等
惡意指令碼注入方式:
儲存型 xss
駭客利用站點漏洞將惡意 js 程式碼提交到站點伺服器,使用者存取頁面就會導致惡意指令碼獲取使用者的cookie等資訊。
反射性 xss
使用者將一段惡意程式碼請求提交給 web 伺服器,web 伺服器接收到請求後將惡意程式碼反射到瀏覽器端
基於 DOM 的 xss 攻擊
通過網路劫持在頁面傳輸過程中更改 HTML 內容
前兩種屬於伺服器端漏洞,最後一種屬於前端漏洞
防止xss攻擊的策略
1、伺服器對輸入指令碼進行過濾或者轉碼,比如將code:<script>alert('你被xss攻擊了')</script>
轉換成code:<script>alert('你被xss攻擊了')</script>
2、充分利用內容安全策略 CSP(content-security-policy),可以通過 http 頭資訊的 content-security-policy 欄位控制可以載入和執行的外部資源;或者通過html的meta 標籤<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
3、cookie設定為 http-only, cookie 就無法通過 document.cookie
來讀取
csrf基本概念
Csrf(cross site request forgery)跨站請求偽造,指駭客引導使用者存取駭客的網站。
CSRF 是指駭客引誘使用者開啟駭客的網站,在駭客的網站中,利用使用者的登入狀態發起的跨站請求。簡單來講,CSRF 攻擊就是駭客利用了使用者的登入狀態,並通過第三方的站點來做一些壞事。
Csrf 攻擊場景
自動發起 get 請求
比如駭客網站有個圖片:
<img src="https://time.geekbang.org/sendcoin?user=hacker&number=100">
登入後複製
駭客將轉賬的請求介面隱藏在 img 標籤內,欺騙瀏覽器這是一張圖片資源。當該頁面被載入時,瀏覽器會自動發起 img 的資源請求,如果伺服器沒有對該請求做判斷的話,那麼伺服器就會認為該請求是一個轉賬請求,於是使用者賬戶上的 100 極客幣就被轉移到駭客的賬戶上去了。
自動發起 post 請求
駭客在頁面中構建一個隱藏的表單,當使用者點開連結後,表單自動提交
引誘使用者點選連結
比如頁面上放了一張美女圖片,下面放了圖片下載地址,而這個下載地址實際上是駭客用來轉賬的介面,一旦使用者點選了這個連結,那麼他的極客幣就被轉到駭客賬戶上了
防止csrf方法
1、設定 cookie 時帶上SameSite: strict/Lax選項
2、驗證請求的來源站點,通過 origin 和 refere 判斷來源站點資訊
3、csrf token,瀏覽器發起請求伺服器生成csrf token,發起請求前會驗證 csrf token是否合法。第三方網站肯定是拿不到這個token。我們的 csrf token 是前後端約定好後寫死的。
websocket是一種支援雙向通訊的協定,就是伺服器可以主動向使用者端發訊息,使用者端也可以主動向伺服器發訊息。
它是基於 HTTP 協定來建立連線的的,與http協定的相容性很好,所以能通過 HTTP 代理伺服器;沒有同源限制。
WebSocket 是一種事件驅動的協定,這意味著可以將其用於真正的實時通訊。與 HTTP 不同(必須不斷地請求更新),而使用 websockets,更新在可用時就會立即傳送
當連線終止時,WebSockets 不會自動恢復,這是應用開發中需要自己實現的機制,也是存在許多使用者端開源庫的原因之一。
像webpack和vite的devServer就使用了websocket實現熱更新
效能優化是中大型公司非常注重的點,因為和前端人的KPI息息相關,所以自然會作為面試題的常客。
從快取的角度
網路方面比較常用的是靜態資源使用 CDN
打包方面
程式碼層面
首屏速度提升
vue 常見的效能優化方式
可以通過window.performance
獲取各項效能指標資料
完整的前端監控平臺包括:資料採集和上報、資料整理和儲存、資料展示
網頁效能指標:
以上指標可以通過獲取
首屏渲染時間計算:通過MutationObserver
監聽document
物件的屬性變化
首先應該避免直接使用 DOM API 操作 DOM,像 vue react 虛擬 DOM 讓對 DOM 的多次操作合併成了一次。
樣式集中改變,好的方式是使用動態 class
讀寫操作分離,避免讀後寫,寫後又讀
// bad 強制重新整理 觸發四次重排+重繪
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
div.style.right = div.offsetRight + 1 + 'px';
div.style.bottom = div.offsetBottom + 1 + 'px';
// good 快取佈局資訊 相當於讀寫分離 觸發一次重排+重繪
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
var curRight = div.offsetRight;
var curBottom = div.offsetBottom;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
div.style.right = curRight + 1 + 'px';
div.style.bottom = curBottom + 1 + 'px';
登入後複製
原來的操作會導致四次重排,讀寫分離之後實際上只觸發了一次重排,這都得益於瀏覽器的渲染佇列機制:
當我們修改了元素的幾何屬性,導致瀏覽器觸發重排或重繪時。它會把該操作放進渲染佇列,等到佇列中的操作到了一定的數量或者到了一定的時間間隔時,瀏覽器就會批次執行這些操作。
使用display: none
後元素不會存在渲染樹中,這時對它進行各種操作,然後更改 display 顯示即可(範例:向2000個div中插入一個div)
通過 documentFragment 建立 dom 片段,在它上面批次操作 dom ,操作完後再新增到檔案中,這樣只有一次重排(範例:一次性插入2000個div)
複製節點在副本上操作然後替換它
使用 BFC 脫離檔案流,重排開銷小
Css 中的transform
、opacity
、filter
、will-change
能觸發硬體加速
優化請求數
background-url
和backgroun-position
來顯示圖示background-url
來懶載入減小圖片大小
快取
非響應式變數可以定義在created
勾點中使用 this.xxx 賦值
存取區域性變數比全域性變數塊,因為不需要切換作用域
儘可能使用 const
宣告變數,注意陣列和物件
使用 v8 引擎時,執行期間,V8 會將建立的物件與隱藏類關聯起來,以追蹤它們的屬性特徵。能夠共用相同隱藏類的物件效能會更好,v8 會針對這種情況去優化。所以為了貼合」共用隱藏類「,我們要避免」先建立再補充「式的動態屬性複製以及動態刪除屬性(使用delete關鍵字)。即儘量在建構函式/物件中一次性宣告所有屬性。屬性刪除時可以設定為 null,這樣可以保持隱藏類不變和繼續共用。
避免記憶體洩露的方式
避免強制同步,在修改 DOM 之前查詢相關值
避免佈局抖動(一次 JS 執行過程中多次執行強制佈局和抖動操作),儘量不要在修改 DOM 結構時再去查詢一些相關值
合理利用 css 合成動畫,如果能用 css 處理就交給 css。因為合成動畫會由合成執行緒執行,不會佔用主執行緒
避免頻繁的垃圾回收,優化儲存結構,避免小顆粒物件的產生
感興趣的可以看看我之前的一篇效能優化技巧整理的文章
前端工程化是前端er成長路上最必不可少的技能點,一切能夠提高前端開發效率的功能都可以當做前端工程化的一部分。剛入門的可以從零搭建腳手架開始
對於面試而言,考察的最多的還是對打包工具的掌握程度。
webpack 是為現代 JS 應用提供靜態資源打包功能的 bundle。
核心流程有三個階段: 初始化階段、構建階段和生成階段
1、初始化階段,會從組態檔、設定物件和Shell引數中讀取初始化的引數並與預設設定結合成最終的引數,之以及建立 compiler 編譯器物件和初始化它的執行環境
2、構建階段,編譯器會執行它的 run()方法開始編譯的過程,其中會先確認 entry 入口檔案,從入口檔案開始搜尋和入口檔案有直接或者簡介關聯的所有檔案建立依賴物件,之後再根據依賴物件建立 module 物件,這時候會使用 loader 將模組轉換標準的 js 內容,再呼叫 js 的直譯器將內容轉換成 AST 物件,再從 AST 中找到該模組依賴的模組,遞迴本步驟知道所有入口依賴檔案都經過了本步驟的處理。最後完成模組編譯,得到了每個模組被翻譯的內容和他們之間的關係依賴圖(dependency graph),這個依賴圖就是專案所有用到的模組的對映關係。
3、生成階段,將編譯後的 module 組合成 chunk ,再把每個 chunk 轉換成一個單獨的檔案輸出到檔案列表,確定好輸出內容後,根據設定確定輸出路徑和檔名,就把檔案內容寫入檔案系統
loader
webpack只能理解 JS 和 JSON 檔案,loader 本質上就是個轉換器,能將其他型別的檔案轉換成 webpack 識別的東西
loader 會在 webpack 的構建階段將依賴物件建立的 module 轉換成標準的 js 內容的東西。比如 vue-loader 將vue檔案轉換成 js 模組,圖片字型通過 url-loader 轉換成 data URL,這些 webpack 能夠識別的東西。
可以在 module.rules 中設定不同的 loader 解析不同的檔案
plugin
外掛本質是一個帶有 apply 函數的類class myPlugin { apply(compiler) {} }
,這個apply 函數有個引數 compiler 是webpack 初始化階段生成的編譯器物件,可以呼叫編譯器物件中的 hooks 註冊各種勾點的回撥這些 hooks 是貫穿整個編譯的生命週期。所以開發者可以通過勾點回撥在裡面插入特定的程式碼,實現特定的功能。
比如stylelint plugin可以指定 stylelint 的需要檢查檔案型別和檔案範圍;HtmlWebpackPlugin 用來生成打包後的模板檔案;MiniCssExtactPlugin會將所有的css提取成獨立的chunks,stylelintplugin可以在開發階段提供樣式的檢查功能。
MiniCssExtractPlugin 對於瀏覽器來說,一方面期望每次請求頁面資源時,獲得的都是最新的資源;一方面期望在資源沒有發生變化時,能夠複用快取物件。這個時候,使用檔名+檔案雜湊值的方式,就可以實現只要通過檔名,就可以區分資源是否有更新。而webpack就內建了hash計算方法,對生成檔案的可以在輸出檔案中新增hash欄位。
Webpack 內建 hash 有三種
hash: 專案每次構建都會生成一個hash,和整個專案有關,專案任意地方有改變就會改變
hash會更據每次工程的內容進行計算,很容易造成不必要的hash變更,不利於版本管理。 一般來說,沒有什麼機會直接使用hash。
content hash: 和單個檔案的內容相關。指定檔案的內容發生改變,就會改變hash,內容不變hash 值不變
對於css檔案來說,一般會使用MiniCssExtractPlugin將其抽取為一個單獨的css檔案。
此時可以使用contenthash進行標記,確保css檔案內容變化時,可以更新hash。
chunk hash:和webpack打包生成的chunk相關。每一個entry,都會有不同的hash。
一般來說,針對於輸出檔案,我們使用chunkhash。
因為webpack打包後,最終每個entry檔案及其依賴會生成單獨的一個js檔案。
此時使用chunkhash,能夠保證整個打包內容的更新準確性。
擴充套件:file-loader 的 hash 可能有同學會表示有以下疑問。
明明經常看到在處理一些圖片,字型的file-loader的打包時,使用的是[name]_[hash:8].[ext]
但是如果改了其他工程檔案,比如index.js,生成的圖片hash並沒有變化。
這裡需要注意的是,file-loader的hash欄位,這個loader自己定義的預留位置,和webpack的內建hash欄位並不一致。
這裡的hash是使用md4等hash演演算法,對檔案內容進行hash。
所以只要檔案內容不變,hash還是會保持一致。
Vite 主要由兩個部分組成
開發環境
Vite 利用瀏覽器去解析 imports,在伺服器端按需編譯返回,完全跳過了打包這個概念,伺服器隨起隨用(就相當於把我們在開發的檔案轉換成 ESM 格式直接傳送給瀏覽器)
當瀏覽器解析 import HelloWorld from './components/HelloWorld.vue' 時,會向當前域名傳送一個請求獲取對應的資源(ESM支援解析相對路徑),瀏覽器直接下載對應的檔案然後解析成模組記錄(開啟 network 面板可以看到響應資料都是 ESM 型別的 js)。然後範例化為模組分配記憶體,按照匯入匯出語句建立模組和記憶體的對映關係。最後執行程式碼。
vite 會啟動一個 koa 伺服器攔截瀏覽器對 ESM 的請求,通過請求路徑找到目錄下對應的檔案並處理成 ESM 格式返回給使用者端。
vite的熱載入是在使用者端和伺服器端之間建立了 websocket 連線,程式碼修改後伺服器端傳送訊息通知使用者端去請求修改模組的程式碼,完成熱更新,就是改了哪個 view 檔案就重新請求那個檔案,這樣保證了熱更新速度不受專案大小影響。
開發環境會使用 esbuild 對依賴進行個預構建快取,第一次啟動會慢一點,後面的啟動會直接讀取快取
生產環境
使用 rollup 來構建程式碼,提供指令可以用來優化構建過程。缺點就是開發環境和生產環境可能不一致;
Webpack 的熱更新原理簡單來說就是,一旦發生某個依賴(比如 a.js
)改變,就將這個依賴所處的 module
的更新,並將新的 module
傳送給瀏覽器重新執行。每次熱更新都會重新生成 bundle
。試想如果依賴越來越多,就算只修改一個檔案,理論上熱更新的速度也會越來越慢
Vite 利用瀏覽器去解析 imports,在伺服器端按需編譯返回,完全跳過了打包這個概念,伺服器隨起隨用,熱更新是在使用者端和伺服器端之間建立了 websocket 連線,程式碼修改後伺服器端傳送訊息通知使用者端去請求修改模組的程式碼,完成熱更新,就是改了哪個檔案就重新請求那個檔案,這樣保證了熱更新速度不受專案大小影響。
所以vite目前的最大亮點在於開發體驗上,服務啟動快、熱更新快,明顯地優化了開發者體驗,生產環境因為底層是 rollup ,rollup更適合小的程式碼庫,從擴充套件和功能上都是不如 webpack 的,可以使用vite作為一個開發伺服器dev server使用,生產打包用webpack這樣的模式。
0、升級 webpack 版本,3升4,實測是提升了幾十秒的打包速度
1、splitChunksPlugin 抽離共用模組輸出單獨的chunks ,像一些第三方的依賴庫,可以單獨拆分出來,避免單個chunks過大。
2、DllPlugin 作用同上,這個依賴庫相當於從業務程式碼中剝離出來,只有依賴庫自身版本變化才會重新打包,提升打包速度
3、loaders的執行是同步的,同各模組會執行全部的loaders
4、exclude/include 指定匹配範圍;alias指定路徑別名;
5、cache-loader持久化儲存;
6、ESM專案開啟 useExport標記,使用 tree-shaking
7、exclude/include 指定匹配範圍;alias指定路徑別名;cache-loader持久化儲存;
8、terserPlugin 可以提供程式碼壓縮,去除註釋、去除空格的功能;MiniCssExtractPlugin 壓縮 css
0、在 package.json 檔案中可以定義 script 設定項,裡面可以定義執行指令碼的鍵和值
1、在 npm install 的時候,npm 會讀取設定將執行指令碼軟連結到node_modules/.bin
目錄下,同時將./bin
加入當環境變數$PATH
中,所以如果在全域性直接執行該命令會去全域性目錄裡找,可能會找不到該命令就會報錯。比如 npm run start,他是執行的webpack-dev-server帶上引數
2、還有一種情況,就是單純的執行指令碼命令,比如 npm run build,實際執行的是 node build.js,即使用 node 執行 build.js 這個檔案
ES6
CommonJS
代理模式:為物件提供一個代用品或預留位置,以便控制對它的存取
例如實現圖片懶載入的功能,先通過一張loading
圖佔位,然後通過非同步的方式載入圖片,等圖片載入好了再把完成的圖片載入到img
標籤裡面
裝飾者模式的定義:在不改變物件自身的基礎上,在程式執行期間給物件動態地新增方法
通常運用在原有方法維持不變,在原有方法上再掛載其他方法來滿足現有需求。
像 typescript 的裝飾器就是一個典型的裝飾者模式,還有 vue 當中的 mixin
保證一個類僅有一個範例,並提供一個存取它的全域性存取點。實現的方法為先判斷範例存在與否,如果存在則直接返回,如果不存在就建立了再返回,這就確保了一個類只有一個範例物件
比如 ice stark 的子應用,一次只保證渲染一個子應用
1、雖然兩種模式都存在訂閱者和釋出者(具體觀察者可認為是訂閱者、具體目標可認為是釋出者),但是觀察者模式是由具體目標排程的,而釋出/訂閱模式是統一由排程中心調的,所以觀察者模式的訂閱者與釋出者之間是存在依賴的,而釋出/訂閱模式則不會。
2、兩種模式都可以用於鬆散耦合,改程序式碼管理和潛在的複用。
3、在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們只有通過訊息代理進行通訊
4、觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去呼叫觀察者的方法。而釋出-訂閱模式大多數時候是非同步的(使用訊息佇列)
正規表示式是什麼資料型別?
是物件,let re = /ab+c/
等價於let re = new RegExp('ab+c')
正則貪婪模式和非貪婪模式?
量詞
*
:0或多次; ?
:0或1次; +
:1到多次; {m}
:出現m次; {m,}
:出現至少m次; {m, n}
:出現m到n次
貪婪模式
正則中,表示次數的量詞預設是貪婪的,會盡可能匹配最大長度,比如a*
會從第一個匹配到a的時候向後匹配儘可能多的a,直到沒有a為止。
非貪婪模式
在量詞後面加?
就能變成非貪婪模式,非貪婪即找出長度最小且滿足條件的
貪婪& 非貪婪特點
貪婪模式和非貪婪模式,都需要發生回溯才能完成相應的功能
獨佔模式
獨佔模式和貪婪模式很像,獨佔模式會盡可能多地去匹配,如果匹配失敗就結束,不會進行回溯,這樣的話就比較節省時間。
寫法:量詞後面使用+
優缺點:獨佔模式效能好,可以減少匹配的時間和 cpu 資源;但是某些情況下匹配不到,比如:
正則 | 文字 | 結果 | |
---|---|---|---|
貪婪模式 | a{1,3}ab | aaab | 匹配 |
非貪婪模式 | a{1,3}?ab | aaab | 匹配 |
獨佔模式 | a{1,3}+ab | aaab | 不匹配 |
a{1,3}+ab 去匹配 aaab 字串,a{1,3}+ 會把前面三個 a 都用掉,並且不會回溯
常見正則匹配
操作符 | 說明 | 範例 | ||
---|---|---|---|---|
. | 表示任何單個字元 | |||
[ ] | 字元集,對單個字元給出範圍 | [abc] 表示 a、b、c,[a-z]表示 a-z 的單個字元 | ||
[^ ] | 非字元集,對單個字元給出排除範圍 | [^abc] 表示非a或b或c的單個字元 | ||
_ | 前一個字元零次或無限次擴充套件 | abc_ 表示 ab、abc、abcc、abccc 等 | ||
` | ` | 左右表示式的任意一個 | `abc | def`表示 abc、def |
$ | 匹配字串結尾 | abc$ 表示 abc 且在一個字串結尾 | ||
( ) | 分組標記內部只能使用 | (abc) 表示 abc,`(abc | def)`表示 abc、def | |
\D | 非數位 | |||
\d | 數位,等價於0-9 | |||
\S | 可見字元 | |||
\s | 空白字元(空格、換行、製表符等等) | |||
\W | 非單詞字元 | |||
\w | 單詞字元,等價於[a-z0-9A-Z_] | |||
匹配字串開頭 | ^abc 表示 abc 且在一個字串的開頭 | |||
{m,n} | 擴充套件前一個字元 m 到 n 次 | ab{1,2}c 表示 abc、abbc | ||
{m} | 擴充套件前一個字元 m 次 | ab{2}c 表示 abbc | ||
{m,} | 匹配前一個字元至少m 次 | |||
? | 前一個字元 0 次或 1 次擴充套件 | abc? 表示 ab、abc |
概念:前端自動化測試領域的, 用來驗證獨立的程式碼片段能否正常工作
1、可以直接用 Node 中自帶的 assert 模組做斷言:如果當前程式的某種狀態符合 assert 的期望此程式才能正常執行,否則直接退出應用。
function multiple(a, b) {
let result = 0;
for (let i = 0; i < b; ++i)
result += a;
return result;
}
const assert = require('assert');
assert.equal(multiple(1, 2), 3));
登入後複製
2、常見單測工具:Jest,使用範例
const sum = require('./sum');
describe('sum function test', () => {
it('sum(1, 2) === 3', () => {
expect(sum(1, 2)).toBe(3);
});
// 這裡 test 和 it 沒有明顯區別,it 是指: it should xxx, test 是指 test xxx
test('sum(1, 2) === 3', () => {
expect(sum(1, 2)).toBe(3);
});
})
登入後複製
babel 用途
bebel 如何轉換的?
對原始碼字串 parse 生成 AST,然後對 AST 進行增刪改,然後輸出目的碼字串
轉換過程
parse 階段:首先使用 @babel/parser
將原始碼轉換成 AST
transform 階段:接著使用@babel/traverse
遍歷 AST,並呼叫 visitor 函數修改 AST,修改過程中通過@babel/types
來建立、判斷 AST 節點;使用@babel/template
來批次建立 AST
generate 階段:使用@babel/generate
將 AST 列印為目的碼字串,期間遇到程式碼錯誤位置時會用到@babel/code-frame
好的,以上就是我對三年前端經驗通過面試題做的一個總結了,祝大家早日找到心儀的工作~
【推薦學習:、】