[TOC]
### 1. 什么是閉包,形成的條件,如何解決
~~~
什么是閉包
1. 閉包是指函數可以訪問其詞法作用域之外的變量,即使在其外部函數執行完畢后仍然可以訪問這些變量
~~~
~~~
形成的條件 (函數嵌套,內部函數對變量引用,外部函數返回內部函數的引用)
1. 內部函數引用外部函數的變量:內部函數必須引用外部函數中的變量,也就是說內部函數在詞法作用域中引用了外部函數的變量。
2. 外部函數執行完畢后仍然存在對內部函數的引用:在外部函數執行完畢后,如果仍然存在對內部函數的引用(例如將內部函數作為返回值返回或將其賦值給其他變量),則形成了閉包。
~~~
~~~
如何解決
對函數引用設置為空,inner = null,inner = undefined
~~~
~~~
以下是一個閉包的例子:
function outerFunction() {
var outerVariable = 'Hello';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
var inner = outerFunction();
inner(); // 輸出: Hello
在這個例子中,`outerFunction` 是外部函數,它包含一個內部函數 `innerFunction`。`outerFunction` 內部定義了一個變量 `outerVariable`,然后將 `innerFunction` 返回。我們將返回的函數賦值給變量 `inner`。當我們調用 `inner()` 時,它會訪問 `outerVariable` 的值,并將其打印到控制臺上。
在這個例子中,雖然 `outerFunction` 已經執行完畢,但是 `innerFunction` 仍然可以訪問 `outerVariable`。這是因為 `innerFunction` 形成了一個閉包,它可以記住 `outerFunction` 的詞法環境,包括其中定義的變量。
閉包可以用于許多場景,例如私有變量、模塊化等。它的優點包括可以隱藏變量和函數,并且使代碼更加模塊化和可維護。但是需要注意的是,閉包可能導致內存泄漏,因為嵌套函數中的變量無法被垃圾回收。因此,在使用閉包時需要注意內存管理。
~~~
### 2. 什么是防抖,什么是截流
防抖和節流都是用于控制函數執行頻率的技術,以提高性能和優化用戶體驗。
防抖(Debounce)是指在觸發事件后,等待一定時間(如1000毫秒),如果在這個時間內沒有再次觸發相同事件,則執行該事件。如果在這個時間內再次觸發了相同事件,則重新計時。
防抖常用于處理頻繁觸發的事件,例如輸入框的輸入事件。當用戶連續輸入時,防抖可以確保只在用戶停止輸入后才執行相應的操作,以避免過多的計算或請求。
以下是一個防抖的示例代碼:
~~~
1function debounce(func, delay) {
2 let timeoutId;
3
4 return function() {
5 clearTimeout(timeoutId);
6 timeoutId = setTimeout(func, delay);
7 };
8}
9
10// 使用防抖函數來處理輸入框的輸入事件
11const input = document.querySelector('input');
12input.addEventListener('input', debounce(function() {
13 // 執行相應的操作
14}, 1000));
~~~
節流(Throttle)是指在一定時間間隔內只執行一次函數。當觸發事件后,函數會立即執行,然后在設定的時間間隔內忽略后續的事件觸發。
節流常用于處理高頻率觸發的事件,例如滾動事件和窗口大小調整事件。通過節流可以限制函數的執行頻率,避免過度的計算或操作。
以下是一個節流的示例代碼:
~~~
1function throttle(func, delay) {
2 let timeoutId;
3 let lastExecTime = 0;
4
5 return function() {
6 const currentTime = Date.now();
7 const remainingTime = delay - (currentTime - lastExecTime);
8
9 clearTimeout(timeoutId);
10
11 if (remainingTime <= 0) {
12 func();
13 lastExecTime = currentTime;
14 } else {
15 timeoutId = setTimeout(func, remainingTime);
16 }
17 };
18}
19
20// 使用節流函數來處理滾動事件
21window.addEventListener('scroll', throttle(function() {
22 // 執行相應的操作
23}, 1000));
~~~
在使用防抖和節流時,需要根據實際場景和需求選擇合適的技術。防抖適用于需要等待一段時間后再執行的情況,而節流適用于需要限制函數執行頻率的情況。
#### 總結:防抖截流都是使用setTimeout實現,區別是在一定時間內再此觸發重新計算時間,一段時間內重復觸發忽略不計的是截流
### useMemo、useCallback和 React.memo比較
`useMemo`、`useCallback` 和 `React.memo` 都是 React 中用于性能優化的機制,但它們的作用和用法有所不同。
1. `useMemo`: `useMemo` 是用來緩存計算結果的鉤子函數。它接收一個函數和依賴數組,并返回函數的計算結果。當依賴數組中的值發生變化時,`useMemo` 會重新執行函數并返回新的結果,否則會返回上一次的緩存結果。用于避免不必要的計算和重新渲染。
~~~
1const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
~~~
2. `useCallback`: `useCallback` 是用來緩存函數引用的鉤子函數。它接收一個函數和依賴數組,并返回一個經過緩存的函數引用。當依賴數組中的值發生變化時,`useCallback` 會返回新的函數引用,否則會返回上一次緩存的函數引用。用于避免不必要的函數重新創建,并在傳遞給子組件時優化性能。
~~~
1const memoizedCallback = useCallback(() => { /* 處理函數邏輯 */ }, [dependency1, dependency2]);
~~~
3. `React.memo`: `React.memo` 是一個高階組件(Higher Order Component),用于包裝函數組件。它會對組件的輸入的 `props` 進行淺比較,如果 `props` 沒有發生變化,就會直接使用上一次的渲染結果,避免不必要的重渲染。
~~~
1const MemoizedComponent = React.memo(Component);
~~~
`useMemo` 和 `useCallback` 都是在函數組件內部使用的鉤子函數,用于優化計算和函數引用,而 `React.memo` 是用于優化組件的輸出。它們可以結合使用,以進一步提高性能和避免不必要的計算和渲染。
### 受控組件和非受控組件
~~~
受控組件和非受控組件是React中兩種不同的組件狀態管理方式。
受控組件是指組件的狀態(比如input的值)由React來管理。當用戶輸入時,React會更新組件的狀態,并且通過props將新的狀態傳遞給組件。受控組件需要在組件中定義一個事件處理函數來更新狀態,并且在render方法中通過props將更新后的狀態傳遞給組件。
非受控組件是指組件的狀態由組件自身來管理。當用戶輸入時,組件直接更新自身的狀態,而不是通過React來管理。非受控組件不需要在組件中定義事件處理函數來更新狀態,而是通過ref來獲取輸入框的值。
受控組件的優點是可以精確控制組件的狀態,可以對用戶的輸入進行驗證和處理。非受控組件的優點是簡單且易于使用。
在實際開發中,可以根據具體的需求選擇使用受控組件或非受控組件。如果需要對用戶輸入進行復雜的驗證和處理,或者需要將組件的狀態在不同組件之間共享,可以使用受控組件。如果只是簡單地獲取用戶輸入的值,或者需要處理大量的輸入框,可以使用非受控組件。
~~~
### Node.js的事件循環機制
~~~
Node.js的事件循環機制是其運行時環境的核心之一。它使用單線程來處理所有的I/O操作,并利用事件循環機制來實現非阻塞的異步操作。
下面是Node.js事件循環的基本流程:
1. 執行全局代碼:Node.js在啟動時會執行全局代碼,并初始化一些內置模塊和變量。
2. 執行事件循環(Event Loop):事件循環是一個無限循環,它會不斷地從事件隊列中取出事件并執行對應的回調函數。
3. 執行階段劃分:事件循環將事件的執行分為不同的階段,每個階段都有對應的任務隊列。常見的階段包括定時器(Timers)、I/O回調(I/O Callbacks)、閑置(Idle)、輪詢(Polling)、檢查(Check)和關閉回調(Close Callbacks)。
4. 執行任務隊列:在每個階段中,事件循環會檢查對應的任務隊列是否有待執行的任務,如果有,則按照順序執行它們的回調函數。
5. 事件循環一直執行,直到沒有事件可處理或達到了退出條件。
值得注意的是,事件循環機制中的每個階段都有一個特定的順序和執行時間,不同的階段之間可能會存在優先級關系。在執行完一個階段的所有任務之后,事件循環會檢查是否需要觸發下一個階段,如果需要則繼續執行,否則就會等待新的事件進入事件隊列。
Node.js的事件循環機制使得它能夠高效地處理大量的并發請求,同時也能夠避免阻塞導致的性能問題。通過合理地利用回調函數和事件驅動的思想,Node.js可以實現非阻塞的異步編程模型。
~~~
### Chair 和 Koa 區別
~~~
Chair 和 Koa 是兩個不同的 Node.js 框架,它們有以下區別:
1. 生態系統:Koa 是一個非常流行的 Node.js 框架,擁有龐大的社區和廣泛的生態系統。它有很多可用的中間件和插件,可以方便地擴展和定制。而 Chair 是一個相對較新的框架,生態系統相對較小,可用的中間件和插件也較少。
2. 設計哲學:Koa 的設計哲學是極簡主義,它提供了一套簡潔而靈活的 API 集合,允許開發者自由組合中間件來構建自己想要的功能。而 Chair 是一個面向企業級應用的框架,它提供了更多的開箱即用的功能和工具,以便于開發者快速搭建和部署應用。
3. 異步支持:Koa 是基于 async/await 的異步編程模型,通過使用 async 函數來處理中間件。這使得編寫異步代碼變得更加簡單和直觀。而 Chair 目前仍然使用傳統的回調函數和 Promise 鏈式調用來處理異步操作,不直接支持 async/await。
4. 框架大小和性能:由于 Koa 的設計理念是極簡主義,它的核心代碼體積較小,相對而言性能較高。而 Chair 的設計目標是提供更多的功能和工具,可能會導致相對較大的代碼體積,并且在某些場景下性能可能稍遜于 Koa。
綜上所述,Koa 是一個成熟、流行且靈活的 Node.js 框架,適用于構建中小型應用和個人項目。而 Chair 則更適合處理復雜的企業級應用,提供了更多的功能和工具,但生態系統相對較小。選擇使用哪個框架取決于項目需求以及開發者個人偏好。
~~~
### 瀏覽器緩存
~~~
瀏覽器緩存是瀏覽器在本地存儲和重復使用已經獲取的資源的一種機制。它可以提高網頁加載速度和減輕服務器負載,同時減少用戶的數據使用量。
瀏覽器緩存的工作原理如下:
1. 當用戶第一次訪問一個網頁時,瀏覽器會請求服務器獲取網頁的內容,并將內容存儲在本地的緩存中。
2. 在用戶再次訪問相同網頁時,瀏覽器會首先檢查緩存,如果有緩存的副本,并且副本尚未過期,瀏覽器會直接從緩存中加載資源,而不是向服務器發送請求。
3. 如果緩存的副本已過期或者用戶強制刷新頁面,瀏覽器會向服務器發送請求,服務器會返回最新的資源,并更新緩存。
瀏覽器緩存可以分為兩種類型:強緩存和協商緩存。
* 強緩存:當瀏覽器訪問一個資源時,如果該資源的緩存策略設置了強緩存,并且緩存未過期,瀏覽器會直接從緩存中加載資源,而不會發送請求到服務器。常用的強緩存策略有設置`Expires`響應頭和`Cache-Control`響應頭(比如`max-age`)。
* 協商緩存:當瀏覽器訪問一個資源時,如果該資源的緩存策略設置了協商緩存,瀏覽器會發送請求到服務器,服務器會通過比較資源的標識(比如`ETag`和`Last-Modified`響應頭)來決定是否返回新的資源。如果資源未變化,服務器會返回`304 Not Modified`狀態碼,瀏覽器會從緩存中加載資源,否則服務器會返回新的資源。
開發者可以通過設置響應頭來控制瀏覽器緩存行為,以提高網頁性能和用戶體驗。但需要注意,應該正確地設置緩存策略,避免在資源更新時用戶仍然加載舊的資源。
~~~
### 瀏覽器本身的緩存用什么做
~~~
瀏覽器本身的緩存通常使用硬盤上的文件系統來存儲緩存的資源。具體來說,瀏覽器緩存使用以下幾種方式來存儲緩存資源:
1. 磁盤緩存:瀏覽器將資源保存在磁盤上的緩存文件夾中。這些文件可以是普通的文件,也可以是特定格式的文件,例如WebP格式的圖片。
2. 內存緩存:瀏覽器可以將一部分資源保存在內存中的緩存中,以提高資源的讀取速度。內存緩存更快,但容量較小,適用于頻繁訪問的資源。
3. HTTP 緩存控制:瀏覽器通過 HTTP 頭部中的緩存控制字段(如 Cache-Control 和 Expires)來判斷是否使用緩存。這些字段指示瀏覽器緩存資源的時間和有效期。
4. 數據庫緩存:某些瀏覽器使用數據庫存儲來緩存資源。這種方式通常用于存儲用戶的表單數據、Cookie 信息等。
需要注意的是,瀏覽器緩存是有限的,當緩存達到一定大小或者存儲時間過長時,瀏覽器可能會自動清理緩存。用戶也可以手動清除瀏覽器緩存,以釋放磁盤空間或者清除過時的緩存數據。
瀏覽器緩存不僅可以提高網頁的加載速度,還可以減少對服務器的請求,節省網絡資源和流量。同時,合理配置緩存策略也能夠提高網站的性能和用戶體驗。
~~~
### 304原理
~~~
304狀態碼是HTTP協議中的一種響應狀態碼,表示資源未修改,客戶端可以使用緩存的副本。當客戶端發送一個GET請求獲取某個資源時,服務器會根據請求頭中的`If-None-Match`和`If-Modified-Since`字段檢查資源的標識和最后修改時間,然后做出相應的處理。
下面是304狀態碼的工作原理:
1. 客戶端發送GET請求到服務器,并在請求頭中包含`If-None-Match`和`If-Modified-Since`字段,分別表示資源的標識和最后修改時間。
2. 服務器接收到請求后,會根據請求頭中的`If-None-Match`和`If-Modified-Since`字段來判斷資源是否未修改。如果資源未修改,則服務器會返回304狀態碼。
3. 客戶端接收到304狀態碼后,表示服務器的資源未修改,客戶端可以直接從本地緩存中獲取資源。
4. 如果客戶端本地沒有緩存或者緩存已過期,客戶端會發送新的請求到服務器,服務器會正常返回最新的資源。
通過使用304狀態碼,可以減少不必要的數據傳輸和服務器負載,提高網頁的加載速度和用戶體驗。服務器可以在響應頭中設置`ETag`和`Last-Modified`字段,用于標識資源和記錄最后修改時間,以便客戶端下次請求時進行比較。如果資源未修改,服務器可以直接返回304狀態碼,而不用返回整個資源內容。這樣可以減少網絡傳輸量和服務器的響應時間。
~~~
### etag 防重
~~~
ETag(實體標簽)是HTTP協議中用于標識資源的一種機制,它可以用于防止重放攻擊(replay attack)。
防重放攻擊是指攻擊者通過截獲和重放合法用戶的請求來進行欺騙或偽造身份。為了防止這種攻擊,服務器可以使用ETag來標識每個資源,并將ETag值隨著響應一起發送到客戶端。
客戶端在發送請求時,會在請求頭中包含`If-None-Match`字段,該字段的值為上次請求中服務器返回的ETag值。服務器在接收到請求后,會將請求頭中的`If-None-Match`值與當前資源的ETag值進行對比。
如果兩個值相同,表示客戶端請求的資源與服務器上的資源一致,服務器會返回304狀態碼,告訴客戶端直接使用緩存的資源。
如果兩個值不同,表示客戶端請求的資源與服務器上的資源不一致,服務器會返回200狀態碼,并返回最新的資源。
通過使用ETag來進行資源比對,可以避免客戶端重復請求相同的資源,節省網絡帶寬和服務器資源。同時,攻擊者無法重放以前的ETag值來獲取資源,因為只有與當前資源一致的ETag才能通過對比。這有效地防止了重放攻擊。
~~~
### 登錄原理
~~~
登錄原理是指用戶在訪問應用程序或網站時,通過提供憑證(如用戶名和密碼)來驗證身份,并獲取訪問權限的過程。下面是一個基本的登錄原理:
1. 用戶訪問應用程序或網站的登錄頁面,并輸入用戶名和密碼。
2. 應用程序或網站將用戶提供的用戶名和密碼發送到服務器進行驗證。
3. 服務器接收到用戶名和密碼后,首先會對用戶名進行檢查,確認用戶是否存在。
4. 如果用戶存在,服務器會通過加密算法將用戶輸入的密碼與數據庫中存儲的密碼進行比對。如果密碼匹配,則驗證成功。
5. 服務器會生成一個令牌(Token)或Session ID,作為用戶的身份標識,并將令牌返回給客戶端。
6. 客戶端(如瀏覽器)將令牌存儲在本地,通常以Cookie或者本地存儲的形式保存。
7. 在用戶繼續訪問應用程序或網站的其他頁面時,客戶端會將令牌作為請求頭或參數發送到服務器。
8. 服務器接收到請求后,會驗證令牌的有效性。如果令牌有效,則表示用戶已經登錄,并根據用戶的權限和角色進行相應的處理。
登錄原理的關鍵在于用戶提供的憑證與服務器存儲的憑證進行比對,并生成有效的身份標識。這種身份標識在后續的請求中用于驗證用戶的身份,以確保用戶擁有相應的權限和訪問權限。同時,為了保證安全性,密碼通常會進行加密存儲,并使用SSL/TLS等加密協議來保護通信過程中的敏感信息。
~~~
### hooks和生命周期區別
~~~
Hooks 是 React 提供的一種函數式編程方式,用于在函數組件中添加和管理狀態以及其他 React 功能。而生命周期是指組件在不同階段的運行過程中,會觸發一系列的方法。
下面是 Hooks 和生命周期之間的區別:
1. 代碼組織方式:生命周期方法是通過在類組件中聲明和定義的,而 Hooks 是通過在函數組件中使用特定的 Hook 函數來添加和管理狀態以及其他 React 功能。Hooks 支持將邏輯相關的代碼進行組合,使得組件代碼更加清晰、易于理解和維護。
2. 使用方式:生命周期方法是在特定的時間點被自動觸發的,而 Hooks 則是在組件的頂層進行調用。Hooks 是基于函數調用的方式,可以在組件內部的任意位置調用,而不需要遵循特定的聲明順序。
3. 邏輯復用:Hooks 提供了一系列的 Hook 函數,如 useState、useEffect、useContext 等,可以方便地復用組件邏輯。而生命周期方法在類組件中是分散在不同的生命周期階段中的,邏輯復用相對較為復雜。
4. 性能優化:Hooks 的設計可以更好地優化組件的性能。例如,使用 useEffect 可以避免在每次渲染時都重復執行副作用代碼,而只在依賴項變化時執行。這樣可以更精細地控制組件的更新。
總的來說,Hooks 是一種更現代、更靈活的編程方式,可以使函數組件具備類組件的狀態管理和其他功能。它提供了更好的代碼組織方式、邏輯復用和性能優化。生命周期方法則是在類組件中使用的一套方法,用于管理組件的生命周期。在使用 React 開發時,可以根據實際情況選擇使用 Hooks 或生命周期方法
~~~
### 如何保障項目安全上線,監控
~~~
確保項目安全上線和監控是一個重要的任務,以下是一些常見的實踐和建議:
1. 安全審計和代碼審查:在項目上線之前,進行全面的安全審計和代碼審查,以發現和修復潛在的安全漏洞和代碼問題。可以使用靜態代碼分析工具、漏洞掃描工具等進行檢查。
2. 訪問控制和權限管理:確保項目的訪問控制機制和權限管理系統健全有效,只有授權的用戶才能訪問和操作敏感數據和功能。
3. 強化身份驗證:使用適當的身份驗證機制,如多因素身份驗證、單點登錄(SSO)等,確保用戶的身份安全。
4. 數據加密和保護:對敏感數據進行加密存儲和傳輸,使用合適的加密算法和密鑰管理機制,以防止數據泄露。
5. 定期更新和修補漏洞:保持項目的軟件和依賴庫等組件的最新版本,并及時修補已知的安全漏洞。
6. 安全培訓和意識提升:對項目團隊進行安全培訓,提高安全意識和技能,確保他們了解和遵守安全最佳實踐。
7. 實施安全監控和日志記錄:建立完善的安全監控和日志記錄系統,監測項目的運行狀況和安全事件,及時發現和應對潛在的安全威脅。
8. 漏洞管理和應急響應:建立漏洞管理和應急響應計劃,制定相應的流程和措施,以應對可能的安全事件和漏洞。
9. 定期安全評估和滲透測試:定期進行安全評估和滲透測試,以發現潛在的安全弱點和漏洞,并及時修復。
10. 合規和法規遵循:確保項目符合適用的法律法規和合規要求,例如數據保護、隱私保護等。
通過綜合以上措施和實踐,可以提高項目的安全性和穩定性,并及時響應和應對潛在的安全風險和威脅。
~~~
### `bind`、`call`和`apply`
~~~
`bind`、`call`和`apply`都是JavaScript中用于改變函數執行上下文(即函數內部的`this`值)的方法,它們的區別如下:
1. `bind`方法:`bind`方法會創建一個新函數,并將指定的對象綁定為新函數內部的`this`值。它不會立即執行函數,而是返回一個綁定了`this`值的函數。我們可以稍后調用該函數來執行,并傳遞參數。
2. `call`方法:`call`方法會立即調用函數,并將指定的對象綁定為函數內部的`this`值。我們可以通過逗號分隔的方式,將參數依次傳遞給函數。
3. `apply`方法:`apply`方法也會立即調用函數,并將指定的對象綁定為函數內部的`this`值。不同之處在于,`apply`方法接受一個參數數組,而不是逗號分隔的參數列表。
以下是關于這些方法的示例:
~~~
1const person = {
2 name: "John",
3 age: 30,
4};
5
6function sayHello(greeting) {
7 console.log(greeting + ", " + this.name);
8}
9
10const boundFunction = sayHello.bind(person); // 創建一個新函數并綁定person對象
11boundFunction("Hello"); // 輸出: Hello, John
12
13sayHello.call(person, "Hi"); // 直接調用函數,綁定person對象和傳遞參數
14// 輸出: Hi, John
15
16sayHello.apply(person, ["Welcome"]); // 直接調用函數,綁定person對象和傳遞參數數組
17// 輸出: Welcome, John
~~~
在上述示例中,我們定義了一個`person`對象和一個`sayHello`函數。通過使用`bind`、`call`和`apply`方法,我們可以將`person`對象作為函數`sayHello`的`this`值,并調用該函數。
需要注意的是,`bind`方法返回的是一個新函數,而`call`和`apply`方法會立即執行函數。另外,`call`和`apply`方法在參數的傳遞方式上有所不同,`call`方法使用逗號分隔的參數列表,而`apply`方法接受一個參數數組。
這些方法在實際開發中經常用于確定函數的執行上下文,并傳遞參數。通過選擇正確的方法,我們可以更靈活地控制函數的執行方式。
~~~
### 手寫bind call apply
~~~
當你需要手動實現`bind`、`call`和`apply`方法時,可以按照以下方式進行:
1. 實現`bind`方法:
~~~
1Function.prototype.myBind = function(context, ...args) {
2 const fn = this;
3 return function(...innerArgs) {
4 return fn.apply(context, args.concat(innerArgs));
5 };
6};
~~~
2. 實現`call`方法:
~~~
1Function.prototype.myCall = function(context, ...args) {
2 const fn = this;
3 context = context || window;
4 context.fn = fn;
5 const result = context.fn(...args);
6 delete context.fn;
7 return result;
8};
~~~
3. 實現`apply`方法:
~~~
1Function.prototype.myApply = function(context, args) {
2 const fn = this;
3 context = context || window;
4 context.fn = fn;
5 const result = context.fn(...args);
6 delete context.fn;
7 return result;
8};
~~~
在上述代碼中,我們使用了原型鏈的方式,將這些方法添加到`Function`對象的原型上,以使所有的函數實例都可以調用它們。然后,我們在每個方法的實現中,通過`this`關鍵字來引用調用方法的函數。我們使用剩余參數(`...args`)來接收傳入的參數,然后在適當的時候調用`apply`方法傳遞參數。
需要注意的是,在`call`和`apply`方法中,我們對未提供上下文(`context`)的情況做了處理,將上下文設置為全局對象(`window`)。
這些手寫的實現并不是完全準確,僅供參考。在實際開發中,建議使用原生的`bind`、`call`和`apply`方法,因為它們已經經過優化和測試,能夠更好地處理各種情況和邊界條件。
~~~