Web前端開發(fā)工程師_JavaScript進階面試題
事件機制
DOM事件觸發(fā)順序是:捕獲階段,目標階段,冒泡階段
捕獲階段:就是某個頁面元素觸發(fā)事件后,瀏覽器會從頂級標簽依次向下檢查元素是否綁定了相同事件,且設置了捕獲,如果有則觸發(fā),一直檢查到觸發(fā)事件的元素。
冒泡階段:瀏覽器會從觸發(fā)事件的元素開始向頂級標簽依次檢查,看元素是否有相同的事件,且未設置捕獲,如果有則觸發(fā)。
例如:
<!DOCTYPE html>
<html id='html'>
????<style>
????????.box {
????????????width: 150px;
????????????height: 150px;
????????????background-color: #f00;
????????}
????????.inner {
????????????width: 100px;
????????????height: 100px;
????????????background-color: #0f0;
????????}
????????.insert {
????????????width: 50px;
????????????height: 50px;
????????????background-color: #00f;
????????}
????</style>
????<body id='body'>
????????<div id='box'>
????????????<div id='inner'>
????????????????<div id='insert'></div>
????????????</div>
????????</div>
????????<script>
????????????html.addEventListener('click', clickHandle, true);
????????????body.addEventListener('click', clickHandle, true);
????????????box.addEventListener('click', clickHandle, true);
????????????inner.addEventListener('click', clickHandle, true);
????????????html.onclick = body.onclick = box.onclick = inner.onclick = insert.onclick = clickHandle;
????????????function clickHandle(){
????????????????console.log(this.id);
????????????}
????????</script>
????</body>
</html>
當我們點擊 insert 元素時,在控制臺輸出如下內(nèi)容:
html
body
box
inner
insert
inner
box
body
Html
事件委托是什么?為什么要使用它?
簡介:利用瀏覽器事件冒泡的機制以及事件對象,將子節(jié)點的監(jiān)聽函數(shù)定義在父節(jié)點上,由父節(jié)點的監(jiān)聽函數(shù)統(tǒng)一處理多個子元素的事件,這種方式稱為事件委托。
優(yōu)點:
不用為每一個子元素都綁定一個監(jiān)聽事件,減少了內(nèi)存上的消耗。
實現(xiàn)了事件的動態(tài)綁定,比如說新增了一個子節(jié)點,不需要單獨地為它添加一個監(jiān)聽事件,它所發(fā)生的事件會交給父元素中的監(jiān)聽函數(shù)來處理。
頁面通訊
BroadcastChannel
它可以幫我們創(chuàng)建一個用于廣播的通訊頻道。當所有頁面都監(jiān)聽同一頻道的消息時,其中一個頁面通過它發(fā)送的消息就會被其它所有頁面收到。
使用:
// 創(chuàng)建廣播頻道
var bc = new BroadcastChannel("bufan");
// 通過 `onmessage` 方法來接受廣播消息:
bc.onmessage = function (e){
????console.log(`接收到的消息為:`, e.data);
}
// 監(jiān)聽錯誤
bc.onmessageerror = function(e){
????console.warn("error:", e);
}
// 發(fā)送消息
bc.postMessage(mydata);
// 關(guān)閉監(jiān)聽
bc.close();
雖然好用,但是兼容性不太樂觀

LocalStorage 作為前端最常用的本地存儲,大家應該已經(jīng)非常熟悉了;但StorageEvent這個與它相關(guān)的事件有些同學可能會比較陌生。
當 LocalStorage 變化時,會觸發(fā)storage事件。利用這個特性,我們可以在發(fā)送消息時,把消息寫入到某個 LocalStorage 中;然后在各個頁面內(nèi),通過監(jiān)聽storage事件即可收到通知。
window.addEventListener("storage", function(e){
????if(e.key == "ctc-msg"){
????????var data = JSON.parse(e.newValue);
????????console.log("監(jiān)聽到存儲改變:", e.newValue);
????}
})
需要注意的是:如果某個頁面設置存儲內(nèi)容的值與第一次相同,那么不會觸發(fā) storage 事件??梢酝ㄟ^每次改變添加時間戳來讓每次修改值都觸發(fā)事件。
瀏覽器本地存儲
sessionStorage:用于本地存儲一個會話(session)中的數(shù)據(jù),這些數(shù)據(jù)只有在同一個會話中的頁面才能訪問并且當會話結(jié)束后數(shù)據(jù)也隨之銷毀。
localStorage:用于持久化的本地存儲,除非主動刪除數(shù)據(jù),否則數(shù)據(jù)是永遠不會過期的。
二者區(qū)別:
存儲周期 – 上述介紹
共享范圍:localStorage 同源頁面共享存儲的值;sessionStorage 僅在當前頁面共享存儲的值;
二者擁有相同的屬性和方法:
setItem(key, value):設置存儲的鍵值對
getItem(key):獲取對應鍵名存儲的內(nèi)容
removeItem(key):刪除對應鍵名存儲的內(nèi)容
clear():清空存儲u內(nèi)容
key(n):根據(jù)下標獲取對應存儲的鍵名
注意:存儲對象或者數(shù)組時,需要將其更換為JSON字符串,然后再進行存儲。
判斷數(shù)據(jù)類型的方式
typeof
使用方式:typeof 要判斷類型的內(nèi)容
返回一個表示數(shù)據(jù)類型的字符串,返回結(jié)果包括:number、boolean、string、object、undefined、function,不能具體區(qū)分出 null、array、object
instanceof
使用方式:A instanceof 類型構(gòu)造函數(shù)
返回一個布爾值,用來判斷A是否為某類型構(gòu)造函數(shù)的實例。如果返回 true,則證明是對應類型
Object.prototype.toString.call(要判斷類型的內(nèi)容):
返回一個字符串[object 數(shù)據(jù)類型對應構(gòu)造函數(shù)名],是當前判斷類型最準確且最常用的方式
作用域
定義:作用域是在運行時代碼中的某些特定部分中變量,函數(shù)和對象的可訪問性。說人話就是:指變量的取值范圍。
常見的的作用域分為兩類:
全局作用域
塊級作用域(個人認為函數(shù)作用域也可以歸類于塊兒級作用域,所以這里寫兩個)
全局作用域代表的是瀏覽器環(huán)境,即在 window 上可以訪問的屬性,塊級作用域指由 {} 包裹的內(nèi)部空間。
由于作用域可以進行嵌套,屬于內(nèi)部的作用域可以訪問它外部的作用域中聲明變量。
將作用域可訪問變量值的查找路徑列出來,就是作用域鏈,目的是指導變量值的查找順序。
函數(shù)作用域:詞法作用域
函數(shù)作用域在聲明時就確定了,不會隨著調(diào)用環(huán)境的更改而更改。
var scope = "window";
function fn(){
????console.log(scope);
}
function gn(){
????var scope = "gn";
????fn();
}
gn(); // window ??因為fn只能訪問自己作用域中聲明的變量和全局的
call、apply、bind
call 和 apply 會執(zhí)行函數(shù),改變函數(shù)中的 this 為第一個接受的參數(shù)對象。作用一樣,只是傳參方式不同;
bind 方法用于創(chuàng)建固定 this 指向的函數(shù)或者固定部分參數(shù)的函數(shù);
閉包
定義:
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
作用:
局部變量無法共享和長久的保存,而全局變量可能造成變量污染,所以我們希望有一種機制既可以長久的保存變量又不會造成全局污染。
特性:
1、函數(shù)嵌套函數(shù)
2、函數(shù)內(nèi)部可以引用外部的參數(shù)和變量
3、參數(shù)和變量不會被垃圾回收機制回收
嚴格模式
在JS代碼第一行或者函數(shù)體第一行添加 use strict; 即可開啟嚴格模式。
優(yōu)點:
消除JS語法的一些不合理、不嚴謹之處,減少一點怪異行為;
消除代碼運行的不安全之處,保證代碼安全運行;
提高編譯器效率,增加運行速度
禁用了在ECMAScript未來版本中可能會用到的一些語法單詞(比如class、export等),為未來新版本的JS做好鋪墊
缺點:
嚴格模式在 IE10 以上版本的瀏覽器中才會被支持
當前網(wǎng)站的 JS 都會進行壓縮,一些文件用了嚴格模式,而另一些沒有。這時這些本來是嚴格模式的文件,被 merge后,這個串就到了文件的中間,不僅沒有指示嚴格模式,反而在壓縮后浪費了字節(jié)。
原型與原型鏈
JS中函數(shù)對象都有 prototype 屬性指向函數(shù)對應原型對象;
通過 new 函數(shù)() 的方式創(chuàng)建的對象稱為該函數(shù)的實例,實例具有 __proto__ 屬性指向函數(shù)的原型對象;
對象可以讀取原型上的屬性;
通過更改對象的 __proto__ 屬性可以設置對象的原型,讓原型形成遞進關(guān)系,由此形成的鏈式結(jié)構(gòu)稱為原型鏈。原型鏈提供了對象屬性的查找方向–從當前元素開始,返回原型鏈中第一個查找到的屬性值。
原型鏈的終點是 null,由(Object.prototype.__proto__)得到
一般而言創(chuàng)建一個實例對象,它的原型鏈為:
實例對象 ?--__proto__——> ?構(gòu)造函數(shù)的原型 ?--__proto__——> ?Object的原型 ?--__proto__——> ?null
完整原型圖:

上圖中還有兩條隱藏的線,只有聰明的同學才能看見:
Function 可以通過 new 生成 Object構(gòu)造函數(shù)
Object.__proto__ 指向Function的原型
JS中的雞和蛋:對象是由構(gòu)造函數(shù)產(chǎn)生的,函數(shù)本身也是對象,那么是先有的函數(shù)?還是先有的對象?
new操作符做了什么
創(chuàng)建空對象
將給對象的原型屬性(__proto__)指向構(gòu)造函數(shù)的原型
將構(gòu)造函數(shù)中 this 綁定的屬性賦予對象
返回該對象
function Person(name){
????this.name = name;
}
Person.prototype.age = 23;
// var libai = new Person("libai");
// 相當于如下操作
var libai = {}
libai.__proto__ = Person.prototype;
Person.call(libai, "libai");
繼承
寄生式組合繼承:使用借用構(gòu)造函數(shù)繼承讓子類繼承父類構(gòu)造函數(shù)定義的實例屬性,使用寄生式繼承讓子類繼承父類原型定義的共享屬性
function Animal(type){
????this.type = type;
}
Animal.prototype.run = function(){
????console.log("running");
}
function Cat(name, type){
????// 借用構(gòu)造函數(shù)繼承父類實例屬性
????Animal.call(this,type);
????this.name = name;
}
// 寄生式繼承父類原型屬性
/*
????function Temp(){}
????Temp.prototype = Animal.prototype;
????Cat.prototype = new Temp();
*/
Cat.prototype.__proto__ = Animal.prototype;
// 或者
Cat.prototype = Object.create(Animal.prototype);
var cat = new Cat();
cat.run();
瀏覽器輸入網(wǎng)址到顯示的過程
詳細版:
瀏覽器會開啟一個線程來處理這個請求,對 URL 分析判斷如果是 http 協(xié)議就按照 Web 方式來處理;
調(diào)用瀏覽器內(nèi)核中的對應方法,比如 WebView 中的 loadUrl 方法;
通過DNS解析獲取網(wǎng)址的IP地址,設置 UA 等信息發(fā)出第二個GET請求;
進行HTTP協(xié)議會話,客戶端發(fā)送報頭(請求報頭);
進入到web服務器上的 Web Server,如 Apache、Tomcat、Node.JS 等服務器;
進入部署好的后端應用,如 PHP、Java、JavaScript、Python 等,找到對應的請求處理;
處理結(jié)束回饋報頭,此處如果瀏覽器訪問過,緩存上有對應資源,會與服務器最后修改時間對比,一致則返回304;
瀏覽器開始下載html文檔(響應報頭,狀態(tài)碼200),同時使用緩存;
文檔樹建立,根據(jù)標記請求所需指定MIME類型的文件(比如css、js),同時設置了cookie;
頁面開始渲染DOM,JS根據(jù)DOM API操作DOM,執(zhí)行事件綁定等,頁面顯示完成。
簡潔版:
瀏覽器根據(jù)請求的URL交給DNS域名解析,找到真實IP,向服務器發(fā)起請求;
服務器交給后臺處理完成后返回數(shù)據(jù),瀏覽器接收文件(HTML、JS、CSS、圖象等);
瀏覽器對加載到的資源(HTML、JS、CSS等)進行語法解析,建立相應的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如HTML的DOM);
載入解析到的資源文件,渲染頁面,完成。
防抖和節(jié)流
防抖 – 在一定時間內(nèi)觸發(fā)事件就執(zhí)行一次定義好的行為,如果在這段時間內(nèi)再次觸發(fā)事件,則時間重新計算,且不執(zhí)行定義好的行為。
// 防抖
function debounce(func, wait) {
????var timer = null;
????return function() {
????????if(timer){
???????????clearTimeout(timer);
????????}
????????timer = setTimeout(function() {
????????????func();
????????????timer = null;
????????}, wait)
????}
}
// 立即執(zhí)行的防抖
function debounce(func, wait) {
????var timer = null;
????return function() {
????????timer ? clearInterval(timer) : func();
????????timer = setTimeout(function() {
????????????timer = null;
????????}, wait)
????}
}
節(jié)流 – 在一定時間內(nèi)觸發(fā)事件就執(zhí)行一次定義好的行為,如果在這段時間內(nèi)再次觸發(fā)事件,不重新計算時間,且不執(zhí)行定義好的行為
// 節(jié)流
// 時間戳版,先執(zhí)行
function throttle(func, wait) {
????var startTime = Date.now();
????return function() {
????????var now = Date.now();
????????if(now - startTime > wait){
????????????func();
????????????startTime = now;
????????}
????}
}
// 定時器版
function throttleImmediately(func, wait) {
????var timer = null;
????return function() {
????????if(!timer){
????????????// func(); // 先執(zhí)行,再等待
????????????timer = setTimeout(function() {
????????????????func(); // 先等待,再執(zhí)行
????????????????timer = null;
????????????}, wait)
????????}
????}
}
AJAX請求封裝
function ajax(params){
??// if(!params.url){
??// ??alert("請傳入url地址");
??// }
??// 對象賦默認值
??params = Object.assign({
????url: location.href,
????type: "GET",
????dataType: "json",
????timeout: 10000,
????contentType: "application/x-www-form-urlencoded",
????data: {},
????success: function(){},
????error: function(){}
??}, params);
??// 1. 聲明核心對象
??var xhr = new XMLHttpRequest();
??// 2. 設置預期返回值類型
??xhr.responseType = params.dataType;
??// 3. 設置各種監(jiān)聽
??xhr.timeout = params.timeout;
??xhr.onloadstart = params.beforeSend;
??xhr.onload = function (){
????params.success(xhr.response);
??}
??xhr.onerror = function (){
????params.error(xhr.status);
??};
??xhr.onloadend = params.complete;
??// 將對象形式參數(shù)改成form形式
??// name:value => name=value
??var str = new URLSearchParams(params.data).toString();
??if(params.type.toUpperCase() === "GET"){
????// 4. 設置請求相關(guān) ??
????xhr.open("GET", params.url + (str ? "?"+str : ""));
????// 5. 發(fā)送請求
????xhr.send(null);
??}else {
????xhr.open("POST", params.url);
????xhr.setRequestHeader("Content-Type", params.contentType);
????if(/json/.test(params.contentType)){
??????xhr.send(JSON.stringify(params.data));
????}else if(/www/.test(params.contentType)){
??????xhr.send(str);
????}else {
??????xhr.send(params.data);
????}
??}
}
TCP三次握手和四次揮手
通俗來講,三次握手主要是讓客戶端與服務端確認自己是否具有收發(fā)消息的能力。
將其抽象化:
由客戶端發(fā)起
Client:喂?收到消息請回復 ???------1------> ???Server
第一次:服務端確認了自己接收消息的能力
Client ??<-----2------- ??Server:我收到你發(fā)來的消息了,你收到我消息請回復
第二次:客戶端確認了自己可以收/發(fā)消息的能力
Client:OK,我也能收到你發(fā)來的消息 ?------3------> ?Server
第三次:服務端確認自己具有發(fā)送消息的能力
四次揮手讓雙方都知道對方要斷開連接:
由客戶端發(fā)起
Client:我要斷開連接了 ??----1---> ?Server
第一次:客戶端關(guān)閉給服務端發(fā)送數(shù)據(jù)的通道
Client ?<------2------- ?Server:好的
第二次:服務端知道客戶端不會發(fā)數(shù)據(jù)了,關(guān)閉接受數(shù)據(jù)的通道
Client ?<------3------- ?Server:我也要斷開連接了
第三次:服務端關(guān)閉發(fā)送數(shù)據(jù)的通道
Client:好的 ?----4---> ?Server
第四次:客戶端知道服務端不會發(fā)消息了,關(guān)閉接受數(shù)據(jù)的通道
注意:上述對話內(nèi)容是通過報文發(fā)送的,不發(fā)送的是正常連接時傳輸?shù)臄?shù)據(jù)(非報文)。
為什么是四次揮手:握手的時候,C 和 S 打個招呼,S 可以直接把自己發(fā)送的信息和對 C 的回應信息一起帶上,但是揮手的時候,C 說我要斷開了,S 可能還有沒發(fā)完最后的數(shù)據(jù),因此需要先回應一下 C,我收到你的斷開的請求了,但是你要等我把最后的內(nèi)容給你,所以這里分開了 2 步: (1)回應 C; (2)發(fā)送自己的最后一個數(shù)據(jù)
JS中的同步和異步任務
同步任務:前一個任務結(jié)束后再執(zhí)行后一個任務,程序的執(zhí)行順序與任務的排列順序是一致的、同步的。比如做飯的同步做法:我們要燒水煮飯,等水開了(10分鐘之后),再去切菜,炒菜。
異步任務:并行處理多件任務,在做這一個任務的同時,你還可以去處理其他任務。比如做飯的異步做法,我們在燒水的同時,利用這10分鐘,去切菜,炒菜。
JavaScript 本身是單線程的,即它在同一個時間只能做一件事。所有任務需要排隊,前一個任務結(jié)束,才會執(zhí)行后一個任務。這樣所導致的問題是: 如果某一段 JS 執(zhí)行的時間過長,這樣就會造成后面的代碼執(zhí)行不連貫,導致頁面渲染加載阻塞的感覺。
為了解決這個問題,HTML5 提出 Web Worker 標準,允許 JavaScript 腳本創(chuàng)建多個線程。于是,JS 中出現(xiàn)了同步和異步。
JS中的異步任務一般分為以下三種類型:
1,DOM事件:如 click、resize 等
2,資源加載:如 load、error 等
3,定時器:包括 setInterval、setTimeout 等
4,網(wǎng)絡資源請求:Ajax、Fetch 等
JS 在執(zhí)行代碼時,會按照代碼的書寫順序,將每一句代碼壓入調(diào)用堆棧中執(zhí)行,如果遇到異步代碼,它們會被添加到瀏覽器提供的異步線程中,等到它們的觸發(fā)條件滿足,被壓入任務隊列等候執(zhí)行。當頁面代碼執(zhí)行完畢,事件輪循(event-loop)就會按次序讀取任務隊列中等候執(zhí)行的異步任務,將它們推入調(diào)用堆棧(call stack)中執(zhí)行。
GET和POST的區(qū)別
語義上:GET 用與獲取數(shù)據(jù),POST 用于提交數(shù)據(jù)
GET 請求的參數(shù)會在地址欄顯示,而 POST 請求會把請求的數(shù)據(jù)放在HTTP請求體中,不在地址欄顯示,相對安全
GET 參數(shù)有長度限制(受限于URL長度,具體的數(shù)值取決于瀏覽器和服務器的限制,最長2048字節(jié)),而 POST 無限制
GET 請求參數(shù)只能以 name=value&... 形式,POST 參數(shù)有更多的數(shù)據(jù)格式
GET 請求會被瀏覽器主動緩存
網(wǎng)絡安全
XSS攻擊:注入惡意代碼攻擊。比如input框里面別人如果輸入一段惡意的腳本代碼直接解析,就有可能造成數(shù)據(jù)的不安全(解決方法:1.cookie 設置 httpOnly; 2.轉(zhuǎn)義頁面上的輸入內(nèi)容和輸出內(nèi)容)
CSRF:跨站點請求偽造。(解決辦法:1.get 不修改數(shù)據(jù);2.不被第三方網(wǎng)站訪問到用戶的 cookie; 3. 設置白名單,不被第三方網(wǎng)站請求;4.請求加校驗)
內(nèi)容有些多,大家可以先點贊收藏,但一定要去看。當你把大廠面試題知識點都看完并且理解了,以后就沒有你過不了的前端面試。資源都幫你整理好了,還不學就有些說不過去了。
不凡學院最新前端學習路線圖

2022年不凡學院前端全套教程?
?




