重學前端
一、前端基礎
JS:
1、Promise、Async有什麽區別
- Async 更簡潔,不需要用 then 連接
-
Promise 中不能自定義使用 try/catch 進行錯誤捕獲,但是在 Async/await 中可以像處理同步代碼處理錯誤
- 調試更方便
- Generator 函數可以暫停執行和恢復執行,這是它能封裝異步任務的根本原因
2、介紹service worker
-
一個獨立的 worker 線程,獨立於當前網頁進程,有自己獨立的 worker context。
-
一旦被 install,就永遠存在,除非被 uninstall
-
需要的時候可以直接喚醒,不需要的時候自動睡眠(有效利用資源,此處有坑)
-
可編程攔截代理請求和返回,緩存文件,緩存的文件可以被網頁進程取到(包括網絡離線狀態)
-
能向客戶端推送消息
-
不能直接操作 DOM,但是service worker可以通過postMessage與頁面之間通信,把消息通知給頁面,如果需要的話,讓頁面自己去操作DOM。
-
出於安全的考慮,必須在 HTTPS 環境下才能工作
-
異步實現,內部大都是通過 Promise 實現
3、列舉Es6,常用的一些新特性
-
let, const:新增塊級作用域,分別定義變量和常量
-
class, extends, super:用
class
定義了一個“類”,可以看到裏面有一個constructor
this
關鍵字則代表實例對象;Class之間可以通過extends
關鍵字實現繼承;super
關鍵字,它指代父類的實例(即父類的this對象)30分鐘掌握ES6/ES2015核心內容(上) -
箭頭函數:函數體內的this對象,就是定義時所在的對象,而不是運行時時所在的對象。
-
模板字符串(template string)
-
解構(Destructuring)
-
ES6模塊:(import export):ES6模塊的設計思想,是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。CommonJS和AMD模塊,都只能在運行時確定這些東西。30分鐘掌握ES6/ES2015核心內容(下)
// 假設我們有兩個js文件: index.js和content.js,現在我們想要在index.js中使用content.js返回的結果,我們要怎麽做呢? //content.js define(‘content.js‘, function(){ return ‘A cat‘; }) // AMD //index.js require([‘./content.js‘], function(animal){ console.log(animal); //A cat }) // CMD //index.js var animal = require(‘./content.js‘) //content.js module.exports = ‘A cat‘ // ES6 //index.js import animal from ‘./content‘ //content.js export default ‘A cat‘
- API:實例感受-es6的常用語法和優越性
keys:獲取對象鍵名,返回一個數組
values:獲取對象值,返回一個數組
entries:把對象鍵值對轉換成一個數組,返回一個包含鍵值對數組的新數組
includes:用於某個數組或字符串是否包含給定的值,返回一個布爾值。如果沒找到符合條件的成員就返回 underfind
參考資料如下:
ECMAScript 6 入門
ES6 Promise 用法講解
4、說下自己對模塊化開發的理解,以及模塊化開發的好處
- 提高開發效率,有利團隊協同開發,
- 避免全局變量汙染,命名沖突,
- 方便代碼的復用維護等。
5、瀏覽器事件流向
- “DOM2級事件”規定事件流包括三個階段,事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的事件捕獲,為截獲事件提供了機會,然後是實際的目標接收了事件,最後一個階段是冒泡階段
- 註意處於目標階段的時候,既不是冒泡階段、也不是捕獲階段,事件處理程序被調用的順序是註冊的順序
6、類型轉換規則
[] == ![]
- 邏輯非運算符使 ![] 轉換為布爾值 false
- 如果有一方是布爾值,就把布爾值轉換成 number 0 再比較 [] == 0
- 如果一方是 object ,且另一方為 string 或 number, object就先調用 valueOf [].valueOf() 返回 [] 不是基本類型,再調用 toString 返回 ‘‘ ,最後將 ‘‘ 轉換為 0
- 轉換順序:object / 布爾值 -> string -> number
7、如何實現深拷貝?
- 很多人認為
Object.assign
是用來深拷貝的,其實並不是,Object.assign
只會拷貝所有的屬性值到新的對象中,如果屬性值是對象的話,拷貝的是地址,所以並不是深拷貝。 - 展開運算符 ... 和
Object.assign()
行為一致, 執行的都是淺拷貝(只遍歷一層) - JSON.parse(JSON.stringify(object)) 可以用來深拷貝,但它會會忽略掉函數和
undefined
- lodash 的深拷貝函數:
function deepClone(obj) { function isObject(o) { return (typeof o === ‘object‘ || typeof o === ‘function‘) && o !== null } if (!isObject(obj)) { throw new Error(‘非對象‘) } let isArray = Array.isArray(obj) // 擴展運算符只拷貝一層 let newObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj }
8、如何理解原型、原型鏈?
Object
是所有對象的爸爸,所有對象都可以通過__proto__
找到它Function
是所有函數的爸爸,所有函數都可以通過__proto__
找到它- 函數的
prototype
是一個對象 - 對象的
__proto__
屬性指向原型,__proto__
將對象和原型連接起來組成了原型鏈
9、基本類型判斷,如怎麽區分數組和對象?
- 用typeof運算符來判斷
- 用Object的toString方法判斷 :兼容性最好的方法
- 用Array對象的isArray方法判斷 :最便捷安全的方法
if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === ‘[object Array]‘; }; }
9、寫一個通用的事件偵聽器函數
// 視能力分別使用dom0||dom2||IE方式 來綁定事件 // 參數: 操作的元素,事件名稱 ,事件處理程序 addEvent : function(element, type, handler) { if (element.addEventListener) { //事件類型、需要執行的函數、是否捕捉 element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent(‘on‘ + type, function() { handler.call(element); }); } else { element[‘on‘ + type] = handler; } }, // 移除事件 removeEvent : function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.datachEvent) { element.detachEvent(‘on‘ + type, handler); } else { element[‘on‘ + type] = null; } }, // 阻止事件 (主要是事件冒泡,因為IE不支持事件捕獲) stopPropagation : function(ev) { if (ev.stopPropagation) { ev.stopPropagation(); } else { ev.cancelBubble = true; } }, // 取消事件的默認行為 preventDefault : function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }, // 獲取事件目標 getTarget : function(event) { return event.target || event.srcElement; }, // 獲取event對象的引用,取到事件的所有信息,確保隨時能使用event; getEvent : function(e) { var ev = e || window.event; if (!ev) { var c = this.getEvent.caller; while (c) { ev = c.arguments[0]; if (ev && Event == ev.constructor) { break; } c = c.caller; } } return ev; }
10、用js實現千位分隔符
/(\d)(?=(/d{3})+\.)/g
num.toString() .replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) { return $1 + ","; });
CSS:
1、介紹css3中position: sticky
absolute 生成絕對定位的元素,相對於值不為 static的第一個父元素進行定位。
fixed (老IE不支持) 生成絕對定位的元素,相對於瀏覽器窗口進行定位。
relative 生成相對定位的元素,相對於其正常位置進行定位。
static 默認值。沒有定位,元素出現在正常的流中(忽略 top, bottom, left, right z-index 聲明)。
inherit 規定從父元素繼承 position 屬性的值。
- sticky 相對於最近的塊級父元素定位
- 父級元素不能有任何
overflow:visible
以為的overflow設置,否則沒有粘滯效果。因為改變了滾動容器(即使沒有出現滾動條)。因此,如果你的position:sticky
無效,看看是不是某一個祖先元素設置了overflow:hidden
,移除之即可。 - 同一個父容器中的sticky元素,如果定位值相等,則會重疊;如果屬於不同父元素,則會鳩占鵲巢,擠開原來的元素,形成依次占位的效果
2、瀏覽器的兼容性
png24位的圖片在iE6瀏覽器上出現背景,解決方案是做成PNG8
瀏覽器默認的margin和padding不同。解決方案是加一個全局的*{margin:0;padding:0;}來統一
IE下,even對象有x,y屬性,但是沒有pageX,pageY屬性; Firefox下,event對象有pageX,pageY屬性,但是沒有x,y屬性
- 一些新特性如 flex ,需要加前綴兼容不同的瀏覽器
- even對象 Chrome 等瀏覽器用 e.target、而 IE 用 e.srcElement
- 給元素添加監聽事件時,Chrome 等瀏覽器用的 addEventListener, 而IE 用 attachEvent
3、簡述一下你對HTML語義化的理解?
用正確的標簽做正確的事情。
html語義化讓頁面的內容結構化,結構更清晰,便於對瀏覽器、搜索引擎解析;
即使在沒有樣式CSS情況下也以一種文檔格式顯示,並且是容易閱讀的;
搜索引擎的爬蟲也依賴於HTML標記來確定上下文和各個關鍵字的權重,利於SEO;
便於閱讀維護理解。
4、請描述一下 cookies,sessionStorage 和 localStorage 的區別?
是否攜帶:cookie是網站為了標示用戶身份而儲存在用戶本地終端(Client Side)上的數據(通常經過加密),會在 http 請求中攜帶(即使不需要)
存儲大小: cookie數據大小不能超過4k。 sessionStorage和localStorage 雖然也有存儲大小的限制,但比cookie大得多,可以達到5M或更大。
有期時間: localStorage 存儲持久數據,瀏覽器關閉後數據不丟失除非主動刪除數據; sessionStorage 數據在當前瀏覽器窗口關閉後自動刪除。 cookie 設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉
5、frame有那些缺點?
*iframe會阻塞主頁面的加載(Onload事件)
*不利於搜索引擎優化;
如果需要使用iframe,最好是通過javascript 動態給iframe添加src屬性值,這樣可以繞開以上兩個問題。
6、如何實現瀏覽器內多個標簽頁之間的通信? (阿裏)
WebSocket、SharedWorker;
也可以調用localstorge、cookies等本地存儲方式;
localstorge另一個瀏覽上下文裏被添加、修改或刪除時,它都會觸發一個事件 storage
7、CSS3有哪些新特性?
圓角 (border-radius:8px)
陰影 (Shadow\Reflect)
文字特效 (text-shadow、)
線性漸變 (gradient)
旋轉 (transform) 縮放,定位,動畫等,例如:transform:\scale(0.85,0.90)\ translate(0px,-30px)\ skew(-9deg,0deg)\Animation:
二、構建工具
1、Webpack熱更新實現原理
- 修改頁面代碼後,Webpack 監聽到文件修改後,開始編譯,編譯完成後,發送消息給客戶端
- 客戶端獲取到hash,成功後客戶端構造hot-update.js script鏈接,然後插入主文檔
- hot-update.js 插入成功後,執行hotAPI 的 createRecord 和 reload方法,獲取到 Vue 組件的 render方法,重新 render 組件, 繼而實現 UI 無刷新更新
三、框架原理
1、描述一下React 生命周期
渲染過程調用到的生命周期函數,主要幾個要知道;
* constructor?
* getInitialState?
* getDefaultProps?
* componentWillMount?
* render?
* componentDidMount?
更新過程
* componentWillReceiveProps?
* shouldComponentUpdate?
* componentWillUpdate?
* render?
* componentDidUpdate?
卸載過程
componentWillUnmount
2、實現組件有哪些方式?
React.createClass 使用API來定義組件 React ES6 class component 用 ES6 的class 來定義組件 Functional stateless component 通過函數定義無狀態組件
3、應該在React生命周期的什麽階段發出ajax請求,為什麽?
AJAX請求應在 componentDidMount函數 進行請求。因為此時組件已經掛載安裝,保證了數據更新後對應的組件也能更新
4、當組件的setState函數被調用之後,發生了什麽?
React 會將傳入的參數與組件當前的狀態合並,生成新的狀態 一> 根據新的狀態構建 React 元素樹 一> 計算出新的樹與老樹的節點差異,進行最小化重渲染
5、為什麽循環產生的組件中要利用上key這個特殊的prop?
Keys負責幫助React跟蹤列表中哪些元素被改變/添加/移除。React利用子元素的key在比較新舊元素樹的時候,快速得知一個元素是新的還是剛剛被移除。
6、介紹redux,主要解決什麽問題
- 是管理應用程序狀態的庫,解決數據管理和數據通信的問題
- view 發出一個 action —> 派發器接收 action —> 讓 store 進行數據更新:reducer 進行 state 操作 —> view 通過 store 提供的 getState 獲取最新的數據
7、redux 有什麽缺點
-
一個組件所需要的數據,必須由父組件傳過來,而不能像 flux 中直接從 store 取。
-
當一個組件相關數據更新時,即使父組件不需要用到這個組件,父組件還是會重新 render,可能會有效率影響,或者需要寫復雜的 shouldComponentUpdate 進行判斷。
8、為什麽虛擬 dom 會提高性能?(必考)
- 虛擬 dom 相當於在 js 和真實 dom 中間加了一個緩存,利用 dom diff 算法避免了沒有必要的 dom 操作,從而提高性能。
9、react diff 原理(常考,大廠必考)
-
把樹形結構按照層級分解,只比較同級元素。
-
給列表結構的每個單元添加唯一的 key 屬性,方便比較。
-
合並操作,調用 component 的 setState 方法的時候, React 將其標記為 dirty.到每一個事件循環結束, React 檢查所有標記 dirty 的 component 重新繪制.
-
選擇性子樹渲染。開發人員可以重寫 shouldComponentUpdate 提高 diff 的性能
8、介紹react優化
用 shouldComponentUpdate 避免資源浪費
復雜的頁面不要在一個組件裏面寫完。
- 請盡量使用
const element
。 - map裏面添加key,並且key不要使用index(可變的)。具體可參考使用Perf工具研究React Key對渲染的影響
- 盡量少用
setTimeOut
或不可控的refs、DOM操作。 props
和state
的數據盡可能簡單明了,扁平化。- 使用
return null
而不是CSS的display:none
來控制節點的顯示隱藏。保證同一時間頁面的DOM節點盡可能的少
四、性能優化
0、在開發項目上,知道那些優化的方式
- 性能優化:減少 http 請求數、避免跳轉重定向、壓縮代碼圖片、
減少DOM操作、避免全局變量、
將樣式表放在頂部,將腳本放在底部、
小圖片(<10kb)轉換base64編碼、資源按需加載等。 - 代碼優化:有意義的命名,適當的註釋,代碼邏輯清晰,避免巨大函數,避免對象強耦合、使用事件代理、函數節流和防抖、禁止全局變量防止內存泄漏等。
1、函數節流和防抖
-
debounce:把觸發非常頻繁的事件(比如按鍵搜索)合並成一次執行。
-
throttle:保證每 X 毫秒恒定的執行次數,比如每200ms檢查下滾動位置,並觸發 CSS 動畫或請求數據。
-
requestAnimationFrame:可替代 throttle ,函數需要重新計算和渲染屏幕上的元素時,想保證動畫或變化的平滑性,可以用它。註意:IE9 不支持。
requestAnimationFrame優點
- 動畫保持 60fps(每一幀 16 ms),瀏覽器內部決定渲染的最佳時機
- 簡潔標準的 API,後期維護成本低
- 瀏覽器標簽未激活時,一切都不會執行
查看示例
2、介紹事件代理以及優缺點
在js中,當我們移除某個元素但沒有將元素和監聽函數進行解綁時,事件處理函數依舊會留在內存中,無法被當成垃圾回收。
- 減少事件註冊,節省內存
- 減少了dom節點更新的操作,處理邏輯只需在委托元素上進行
- 缺點:事件委托基於冒泡,對於onfoucs和onblur等事件不支持;層級過多,冒泡過程中,可能會被某層阻止掉
3、手寫原生js實現事件代理,並要求兼容瀏覽器
- 其實就是考核對事件對象e的了解程度,以及在IE下對應的屬性名
- addEventListener、attachEvent
- e.target、e.srcElement
function delegateEvent(interfaceEle, selector, type, fn) { if(interfaceEle.addEventListener){ interfaceEle.addEventListener(type, eventfn); }else{ interfaceEle.attachEvent("on"+type, eventfn); } function eventfn(e){ var e = e || window.event; var target = e.target || e.srcElement; //如果目標元素與選擇器匹配則執行函數 if (matchSelector(target, selector)) { if(fn) { //將fn內部的this指向target(在此之前this都是指向的綁定事件的元素即interfaceEle) fn.call(target, e); } } } }
4、React組件事件代理的原理、事件處理過程
- 並非 #child 和 #parent 的事件分別代理到 document 上,而是 React 在 document 上綁定了一個 dispatchEvent 函數(事件),在執行 dispatchEvent 的過程中,其內部會依次執行 #child 和 #parent 上綁定的事件。
- React 合成事件對象的
e.stopPropagation
,只能阻止 React 模擬的事件冒泡,並不能阻止真實的 DOM 事件冒泡,更加不能阻止已經觸發元素的多個事件的依次執行。在這種情況下,只有原生事件對象的stopImmediatePropagation
能做到。 - 事件處理過程:
- 示例:
5、什麽是內存泄漏?
本質上,內存泄漏可以定義為:應用程序不再需要占用內存的時候,由於某些原因,內存沒有被操作系統或可用內存池回收
所以,確保用完以後把它設置為 null 或者重新定義
三種類型的常見 JavaScript 內存泄漏
- 意外的全局變量
- 被遺忘的計時器或回調函數
- 脫離 DOM 的引用
- 閉包裏多余的沒有被引用的變量
-
實例:使用 Chrome 發現內存泄漏
6、PWA:Workbox
五、算法設計模式
前端架構主要解決的是高復用性,架構能力提升方向主要是組件庫開發、前端框架實現
1、觀察者和訂閱-發布的區別,各自用在哪裏
-
在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發布訂閱模式中,發布者和訂閱者不知道對方的存在。它們只有通過消息代理進行通信。
-
在發布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。
-
觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而發布-訂閱模式大多數時候是異步的(使用消息隊列)。
- 觀察者設計模式定義了對象間的一種一對多的組合關系,以便一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新
- 示例
六、網絡安全、通信協議
1、HTTP2.0和HTTP1.X相比的新特性
- 新的二進制格式,相對文本來說有更強的健壯性
- 多路復用(MultiPlexing),即連接共享。一個連接上可以有多個request,一個request對應一個id 。
- header壓縮:HTTP1.x的header帶有大量信息,而且每次都要重復發送,HTTP2.0使用encoder來減少需要傳輸的header大小,通訊雙方各自cache一份header fields表,既避免了重復header的傳輸,又減小了需要傳輸的大小。
- 服務端推送(server push)
2、通過什麽做到並發請求
對請求並發數進行限制,並且使用排隊機制讓請求有序的發送出去
3、http1.1時如何復用tcp連接
使用 keep-alive
4、三次握手
- 客戶端向服務器發送一個 SYN(synchronize:同步) 包,請求建立連接
- 服務器收到後會發一個對SYN包的確認包ACK (Acknowledgement)即是確認字符回去
- 客戶端發送一個確認包(ACK),通知服務器連接已建立
5、什麽是 XSS 攻擊?如何防範 XSS 攻擊?什麽是 CSP?
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼註入攻擊。攻擊者通過註入惡意腳本,使之在用戶的瀏覽器上運行
解決方法:
- 轉義字符
- CSP 本質上就是建立白名單,開發者明確告訴瀏覽器哪些外部資源可以加載和執行
什麽是 CSP(Content-Security-Policy) ?
內容安全策略 (CSP) 本質上就是建立白名單,開發者明確告訴瀏覽器哪些外部資源可以加載和執行
通常可以通過兩種方式來開啟 CSP:
- 設置 HTTP Header 中的
Content-Security-Policy
- 設置
meta
標簽的方式<meta http-equiv="Content-Security-Policy">
示例:
-
只允許加載本站資源
Content-Security-Policy: default-src ‘self’
-
只允許加載 HTTPS 協議圖片
Content-Security-Policy: img-src https://*
- 【XSS】 美團
6、什麽是 CSRF 攻擊?如何防範 CSRF 攻擊?
CSRF(Cross-site request forgery)跨站請求偽造:冒充用戶執行某項操作的目的
- CSRF自動防禦策略:同源檢測(Origin 和 Referer 驗證)。
- CSRF主動防禦措施:Token驗證 或者 雙重Cookie驗證 以及配合Samesite Cookie。
- 保證頁面的冪等性,後端接口不要在GET頁面中做用戶操作。
示例:
2007年Gmail的CSRF漏洞:點開一個黑客的鏈接,所有郵件都被竊取(攻擊者誘導用戶進入某個頁面,在頁面中通過表單提交 POST
請求,也說明了表單是可以進行跨域攻擊的)
2008年YouTube上幾乎所有用戶可以操作的動作都存在CSRF漏洞
2012年WordPress發現了一個操作用戶賬戶的CSRF漏洞:攻擊者引導用戶先進入目標的WordPress,然後點擊其釣魚站點上的某個按鈕,該按鈕實際上是表單提交按鈕,會添加某個具有管理員權限的用戶
7、什麽是點擊劫持?如何防範點擊劫持?
點擊劫持是一種視覺欺騙的攻擊手段。攻擊者將需要攻擊的網站通過
iframe
嵌套的方式嵌入自己的網頁中,並將iframe
設置為透明,在頁面中透出一個按鈕誘導用戶點擊。
X-FRAME-OPTIONS
是一個 HTTP 響應頭,在現代瀏覽器有一個很好的支持。這個 HTTP 響應頭 就是為了防禦用 iframe
嵌套的點擊劫持攻擊。
DENY
,表示頁面不允許通過iframe
的方式展示SAMEORIGIN
,表示頁面可以在相同域名下通過iframe
的方式展示ALLOW-FROM
,表示頁面可以在指定來源的iframe
中展示
8、什麽是中間人攻擊?如何防範中間人攻擊?
中間人攻擊是攻擊方同時與服務端和客戶端建立起了連接,並讓對方認為連接是安全的,但是實際上整個通信過程都被攻擊者控制了。攻擊者不僅能獲得雙方的通信信息,還能修改通信信息。如:不安全的公共 WIFI
9、URI 與 URL 的區別
- URI:統一資源標誌符
- URL:統一資源定位符
- URN:URN 是另一種形式的 URI,它通過特定命名空間中的唯一名稱來標識資源,比如 ISBN 版本號,
urn:isbn:9780141036144
- URI 包括很多協議,如 HTTPS、data、URN 等等,所以 URL 和 URN 都是 URI 的子集
重學前端