1、手撕防抖與節流、樹與對象的轉換、遞歸調用,鏈表頭插法
1.1、防抖
防抖函數用于延遲執行某個函數,直到過了一定的間隔時間(例如等待用戶停止輸入)后再執行。
即后一次點擊事件發生時間距離一次點擊事件至少間隔一定時間。
function debounce(fn, wait) {let timer = nullreturn function () {if (timer) {clearTimeout(timer)timer = null}timer = setTimeout(() => {fn.call(this, arguments)}, wait)}
}
1.2、節流
節流函數用于限制函數的執行頻率,確保一定時間內只執行一次。
//時間戳版
function throttle(fn, wait) {let date = Date.now()return function () {let now = Date.now()if (now - date > wait) {fn.call(this, arguments)date = now}}
}
//定時器版
function throttle(fn, wait) {let timer = nullreturn function () {if (!timer) {timer = setTimeout(() => {fn.call(this, arguments)timer = null}, wait)}}
}
?1.3、樹與對象的轉換
參考作者之前的文章
2、水平垂直居中方法
①flex布局,父元素display:flex; justify-content:center; align-items:center;
②父元素position:relative; 子元素position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
③父元素position:relative; 子元素position:absolute; left:0; top:0; bottom:0; right:0; margin:auto;
④文字的話, text-align:center; line-height 和 height 相等
3、?手寫ajax(使用promise封裝)
function getJSON(url) {let promise = new Promise((resolve, reject) => {let xhr = new XMLHttpRequest()xhr.open("GET", url, true)xhr.onreadystatechange = function () {if (this.readyState !== 4) returnif (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.onerror = function () {reject(new Error(this.statusText))}xhr.responseType = "json"xhr.setRequestHeader("Accept", "application/json")xhr.send(null)})return promise
}
4、扁平數組
const flatten = (arr) => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
};arr1 = [1, [2, 3], [4, [5, [6, 7], 8]]];
5、setTimeout實現setInterval
function myInterval(func, time) {let ids = [];function fn() {let id = setTimeout(() => {func();fn();}, time);ids.push(id);}fn();return ids;
}let id = myInterval(() => {console.log("Hello World");
}, 500);function clearMyInterval(idList) {idList.forEach((id) => {clearTimeout(id);});
}setTimeout(() => {clearMyInterval(id);
}, 3000);
6、輸入url發生了什么?
-
URL 解析: 瀏覽器解析輸入的 URL。
-
DNS 解析: 如果域名需要解析,進行 DNS 解析。如果已經有緩存的 DNS 記錄,可以跳過此步驟。
-
檢查緩存: 瀏覽器檢查緩存,看是否已經有了之前請求過的資源的副本。這包括檢查瀏覽器緩存和可能存在的代理服務器緩存。
-
有緩存: 如果資源已經存在于緩存中,并且沒有過期,瀏覽器可以跳過后續的步驟,直接使用緩存中的資源渲染頁面。
-
無緩存或緩存過期: 如果資源不存在于緩存中,或者緩存已經過期,瀏覽器將按照正常的流程發起網絡請求,
-
TCP 連接:拿到IP地址后,三次握手建立TCP連接,https的話還需要進行TLS加密協議的握手過程
-
發送請求,獲取響應:連接建立成功之后,瀏覽器會構建請求行、cookie等數據附加到請求頭中,發給服務器,服務器接受請求并解析,如果沒有對應的資源就404;否則檢查HTTP請求頭有沒有包含協商緩存信息(前面命中強緩存且已過期的話就會走這個步驟),如果驗證緩存沒有更新,過期的緩存依然可以使用,就返回304和空響應體;如果沒有緩存或者資源更新了,就讀取完整請求并準備http響應,進行查詢數據庫等操作,返回200和查詢到的資源
-
TCP 連接:??瀏覽器接收到響應數據之后,如果是http1.1以下則直接關閉連接,否則雙方都可以根據情況選擇關閉TCP連接或者保留重用,現在瀏覽器默認都會保持連接(keep-alive)
-
瀏覽器渲染: 瀏覽器使用獲取到的資源渲染頁面。
緩存是一種重要的性能優化手段,可以減少網絡請求,加快頁面加載速度。緩存策略通常由服務器端和瀏覽器端一起決定,可以通過 HTTP 頭部信息來進行配置。例如,使用 Cache-Control
頭部可以控制緩存的行為,而 ETag
和 Last-Modified
頭部可以用于驗證緩存是否過期。
7、加載js和css會不會阻塞頁面渲染
css用link和@import的情況
link標簽引入css資源時在火狐瀏覽器中是異步加載的,在谷歌瀏覽器中是同步加載的。
但如果是通過style標簽引入樣式,則不論何種瀏覽器,均為同步加載。
@import是在網頁完全載入后才加載,在關鍵路徑上創造了更多的網絡請求,阻塞渲染時間,影響瀏覽器的并行下載,多個@import導致下載順序紊亂。
8、重排重繪
重排:當渲染樹的一部分必須更新并且節點的尺寸發生了變化,瀏覽器會使渲染樹中受到影響的部分失效,并重新構造渲染樹。
①添加、刪除可見的dom②元素的位置改變
③元素的尺寸改變(外邊距、內邊距、邊框厚度、寬高等幾何屬性)
④頁面渲染初始化
⑤瀏覽器窗口尺寸改變
重繪:是在一個元素的外觀被改變所觸發的瀏覽器行為,瀏覽器會根據元素的新屬性重新繪制,使元素呈現新的外觀。
如何減少reflow、repaint?
①不要一條一條的修改DOM的樣式,可以先定義好css的class,然后修改DOM的className。
②不要把DOM結點的屬性值放在一個循環里當成循環里的變量。
③為動畫的HTML元件適用fixed或absolute的position,那么修改他們的css是不會reflow
9、深淺拷貝
淺拷貝:基本數據類型、擴展運算符()、slice()、concat()、Object.assign()深拷貝:JSON.parse(JSON.stringify())、手寫深拷貝、lodash
手撕深拷貝
let obj = {lili: { name: "lili", person: ["lisan", "zhangsan"] },arr: [1, 2, 3, 4],fruit: "apple",
};
function deepclone(obj) {// 檢查是否是基本數據類型,如果是則直接返回if (obj === null || typeof obj !== "object") {return obj;}if (Array.isArray(obj)) {let newArray = [];for (let i = 0; i < obj.length; i++) {newArray[i] = deepclone(obj[i]);}return newArray;} else {let newObj = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = deepclone(obj[key]);}}return newObj;}
}
console.log(deepclone(obj));
10、手撕合并函數
function deepMerge(target, source) {// 檢查參數類型if (typeof target !== "object" || typeof source !== "object") {throw new Error("Both target and source must be objects");}// 遍歷source對象的屬性for (const key in source) {if (source.hasOwnProperty(key)) {// 如果屬性是對象且存在于target中,遞歸深度合并if (typeof source[key] === "object" &&source[key] !== null &&target.hasOwnProperty(key) &&typeof target[key] === "object" &&target[key] !== null) {if (Array.isArray(target[key])) {target[key] = [].concat(target[key], source[key]);} else {target[key] = deepMerge(target[key], source[key]);}} else {// 否則直接賦值// target[key] = source[key];target[key] = [].concat(target[key], source[key]);}}}return target;
}// 使用例子
const targetObject = {name: "John",age: 30,address: {city: "New York",zip: "10001",people: { class: "1班" },},hobbies: ["shopping"],
};const sourceObject = {age: 31,address: {zip: "10002",},hobbies: ["reading", "traveling"],
};const resultObject = deepMerge(targetObject, sourceObject);
console.log(resultObject);
11、Object和map
共同點:鍵值對的動態集合,支持增刪
不同點:
①構造方式不同
//map const?map?=?new?Map() const?map1?=?new?Map([['a',1],['b',2]]) //obj const?obj?=?new?Object() const?obj1?=?Object.create()
②object鍵的類型必須是String或者Symbol、map鍵的類型可以是任意類型
③object中key是無序的,map中可以是有序的,按照插入的順序返回
④object只能通過Object.key()方法或for in統計數量,map有map.size
⑤object可以通過點或中括號訪問屬性,map用map.get()
⑥object不具備Iterator特性,不能for of遍歷,map的keys()、values()、entries()都具有迭代器
⑦object可以用JSON.stringify()進行序列化,map只能轉化成JSON,不能被parse解析
⑧應用場景:object做數據存儲,需要序列化時使用;map頻繁更新鍵值對,key類型未知時使用
12、http和https的區別
①https協議需要到CA申請證書,一般免費證書較少,因而需要一定費用。
②http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl/tls加密傳輸協議。
③http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
④http的連接很簡單,是無狀態的;HTTPS協議是由SSL/TLS+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
?
13、堆和棧
13.1、區別
①堆棧空間分配區別(操作系統):
棧由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧;
堆 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似于鏈表。
②堆棧緩存方式區別:
棧使用的是一級緩存, 他們通常都是被調用時處于存儲空間中,調用完畢立即釋放;
堆是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(并不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
③堆棧數據結構區別:
棧是一種先進后出(FILO)的數據結構;
堆可以被看成是一棵樹,如:堆排序。
13.2、最大堆和最小堆?
最大堆:根結點的鍵值是所有堆結點鍵值中最大者,且每個結點的值都比其孩子的值大。
最小堆:根結點的鍵值是所有堆結點鍵值中最小者,且每個結點的值都比其孩子的值小。
13.3、 堆棧溢出
- JavaScript 的函數調用棧有一定大小限制,當函數調用的嵌套層數過多時,會導致棧溢出錯誤。
function recursive() {// 遞歸出現棧溢出recursive();
}
recursive();
- JavaScript 的堆也有大小限制。堆是用來存儲變量和對象等數據的一段內存空間,當我們創建了大量數據或者數據太大而超過了堆的容量時,就會觸發堆溢出錯誤。
let arr = [];
while (true) {// 堆溢出arr.push('a');
}
14、進程和線程
進程:一個在內存中運行的應用程序。每個進程都有自己獨立的一塊內存空間,一個進程可以有多個線程。
線程:進程中的一個執行任務(控制單元),負責當前進程中程序的執行。一個進程至少有一個線程,一個進程可以運行多個線程,多個線程可共享數據。
進程和線程的區別:
①進程是資源分配的最小單位,線程是程序執行的最小單位(資源調度的最小單位)
②進程有自己的獨立地址空間,每啟動一個進程,系統就會為它分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,這種操作非常昂貴。
而線程是共享進程中的數據的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創建一個線程的開銷也比進程要小很多。
③線程之間的通信更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC) 進行。
④但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉并不會對另外一個進程造成影響,因為進程有自己獨立的地址空間。
15、promise.all,promise.any,promise.race,promise allsettled
Promise.allSettled
不會在其中一個 Promise 失敗時立即 reject,而是等待所有 Promise 完成后再返回結果。
const promise1 = Promise.resolve(42);
const promise2 = Promise.reject("Oops!");
const promise3 = new Promise((resolve) => setTimeout(() => resolve("Done!"), 1000));Promise.allSettled([promise1, promise2, promise3]).then((results) => {console.log(results);// results 包含了每個 Promise 的狀態和結果// [{ status: 'fulfilled', value: 42 }, { status: 'rejected', reason: 'Oops!' }, { status: 'fulfilled', value: 'Done!' }]}).catch((error) => {console.error("Error:", error);});
Promise.all
在所有 Promise 全部成功(resolved)時才會成功,但只要有一個 Promise 失敗(rejected),整個 Promise.all
就會立即失敗。這種行為被稱為“一敗俱敗”。
傳遞給 Promise.all
的數組為空: 如果傳遞給 Promise.all
的 Promise 數組為空,返回的 Promise 會立即被解決為一個空數組。
如果在某一個promise中reject后,那一個函數捕捉catch,就不會報錯。不然就會走all的catch
16、場景題:100000條數據渲染
const renderList = async () => {const list = await getList()const total = list.lengthconst page = 0const limit = 200const totalPage = Math.ceil(total / limit)const render = (page) => {if (page >= totalPage) returnsetTimeout(() => {for (let i = page * limit; i < page * limit + limit; i++) {const item = list[i]const div = document.createElement('div')div.className = 'sunshine'div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`container.appendChild(div)}render(page + 1)}, 0)}render(page)
17、你做的項目如何部署到服務器
function myNew(constructor, ...args) {// 步驟 1:創建一個新的空對象const obj = {};// 步驟 2:將新對象的 __proto__ 指向構造函數的 prototype 屬性obj.__proto__ = constructor.prototype;// 步驟 3:將構造函數的上下文傳遞給構造函數,并執行構造函數const result = constructor.apply(obj, args);// 步驟 4:如果構造函數返回一個對象,則返回該對象;否則,返回新創建的對象return result instanceof Object ? result : obj;
}
18、路由鑒權
18.1、請求數據
-
用戶登錄: 用戶在登錄時,通過用戶名和密碼等方式向后端發起登錄請求。后端驗證用戶身份,并在登錄成功后生成一個 Token。
-
Token 存儲: 將生成的 Token 存儲在前端,通常是通過 localStorage。axios請求攔截,如果存在token,在每次請求時自動附加到請求頭。
-
請求時攜帶 Token: 在每次請求后端受保護資源時,將 Token 攜帶在請求頭中,通常是通過 Authorization 頭或自定義頭。
-
后端驗證 Token: 后端接收到請求后,通過解析 Token 驗證用戶身份和權限。如果 Token 有效且權限足夠,則返回相應資源;否則,返回錯誤狀態。
18.2、路由守衛
根據權限決定跳轉: 如果用戶擁有訪問權限,正常放行,讓用戶訪問受保護頁面。如果用戶沒有權限,可以將其重定向到登錄頁或其他提示頁面,或者顯示相應的提示信息。
// 路由表
const routes = [{ path: '/', component: Home, meta: { requiresAuth: true } },{ path: '/admin', component: Admin, meta: { requiresAuth: true, requiresAdmin: true } },{ path: '/login', component: Login }
];// 創建路由實例
const router = new VueRouter({routes
});// 路由守衛
router.beforeEach((to, from, next) => {const isAuthenticated = /* 根據用戶身份信息判斷用戶是否已登錄 */;const isAdmin = /* 根據用戶身份信息判斷用戶是否是管理員 */;if (to.meta.requiresAuth && !isAuthenticated) {// 用戶未登錄,重定向到登錄頁next('/login');} else if (to.meta.requiresAdmin && !isAdmin) {// 用戶不是管理員,可以根據需要進行處理,例如重定向到首頁next('/');} else {// 用戶有權限,放行next();}
});// 在 Vue 實例中使用路由
new Vue({el: '#app',router,render: h => h(App)
});
18.3、不同等級的權限控制
-
獲取用戶權限信息: 在用戶登錄成功后,獲取token與用戶的權限信息,存儲在Vuex或者localStorage
-
定義菜單權限配置: 在前端定義一個菜單權限配置,包含不同權限下允許訪問的菜單項。這可以是一個簡單的 JSON 對象或數組,其中每個菜單項都包含一個權限屬性,表示需要的用戶權限。
-
根據用戶權限過濾菜單項: 根據用戶擁有的權限,從菜單權限配置中篩選出符合條件的菜單項。
-
動態生成菜單: 使用框架或庫的路由功能,根據過濾后的菜單項動態生成菜單。這可以在頁面加載時或用戶登錄成功后執行。
// 菜單權限配置
const menuConfig = [{ path: '/dashboard', name: 'Dashboard', permission: 'view_dashboard' },{ path: '/users', name: 'Users', permission: 'manage_users' },// 其他菜單項
];// 獲取用戶權限信息(模擬)
const userPermissions = ['view_dashboard', 'manage_users']; // 實際中應該根據登錄成功后的用戶信息獲取權限// 根據用戶權限過濾菜單項
const userMenu = menuConfig.filter(item => userPermissions.includes(item.permission));// 在 Vue 實例中使用路由
const router = new VueRouter({routes: userMenu,
});// 示例組件中動態生成菜單
Vue.component('Sidebar', {template: `<div><router-link v-for="item in userMenu" :to="item.path" :key="item.path">{{ item.name }}</router-link></div>`,data() {return {userMenu,};},
});// 在 Vue 實例中使用路由和菜單組件
new Vue({el: '#app',router,render: h => h(App),
});