目錄
一、閉包,使用場景
1.閉包的定義
2.閉包的實現原理
3.閉包的應用場景
(1)數據封裝與私有變量
(2)函數柯里化
(3)事件處理與回調
(4)模塊化開發
4.注意事項
二、詞法作用域和動態作用域區別
1.詞法作用域(Lexical Scoping)
2.動態作用域(Dynamic Scoping)
3.核心區別
4.實際影響
三、call、apply和bind方法的區別
1.功能:
2.call 方法
3.apply 方法
4.bind 方法
5.區別:
6.使用場景
示例:借用數組方法
四、什么是高階函數
1.高階函數的定義
2.高階函數的特點
3.高階函數的常見用例
4.示例
5.優勢
五、解釋箭頭函數的特性及其與普通函數的區別
1.特性
2.區別
(1)this綁定
(2)構造函數與prototype
(3)arguments對象
(4)語法靈活性
3.適用場景
六、什么是生成器函數
1.概念
2.基本語法
3.核心特性
4.對象的方法
示例:生成斐波那契數列
5.區別
6.常見應用場景
七、防抖
1.概念
2.基礎實現
3.立即執行版本
4.取消功能擴展
5.應用場景
6.注意事項
八、節流
1.概念
2.時間戳版節流
3.定時器版節流
4.結合時間戳與定時器
5.應用示例
6.注意事項
九、遞歸函數的優缺點
1.優點
2.缺點
3.使用建議
十、什么是純函數
1.定義
2.特性
3.優點
4示例
5.如何編寫
十一、解釋函數重載在JavaScript中的實現方式
1.概念
2.基于參數數量的重載
3.基于參數類型的重載
4.使用對象參數模擬重載
5.結合默認參數與條件判斷
6.注意事項
十二、什么是偏函數
1.概念
2.作用
3.實現方式
4.優點
5.應用場景
6.注意事項
一、閉包,使用場景
1.閉包的定義
閉包(Closure)是指函數與其引用環境(lexical environment)的組合。當一個函數能夠記住并訪問其定義時的作用域,即使該函數在其作用域外執行,依然可以訪問那些變量。閉包的核心特點是:
- 函數嵌套:閉包通常涉及嵌套函數(內部函數引用外部函數的變量)。
- 變量持久化:外部函數的變量不會被垃圾回收,因為內部函數仍持有引用。
2.閉包的實現原理
function outer() {let count = 0;function inner() {count++;console.log(count);}return inner;
}
const closureFunc = outer();
closureFunc(); // 輸出 1
closureFunc(); // 輸出 2
inner
函數引用了outer
的變量count
,形成閉包。- 每次調用
closureFunc
時,count
的值會被保留。
3.閉包的應用場景
(1)數據封裝與私有變量
通過閉包模擬私有變量,避免全局污染:
function createCounter() {let privateCount = 0;return {increment: function() { privateCount++; },getValue: function() { return privateCount; }};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 輸出 1
(2)函數柯里化
將多參數函數轉換為單參數鏈式調用:
function multiply(a) {return function(b) {return a * b;};
}
const double = multiply(2);
console.log(double(5)); // 輸出 10
(3)事件處理與回調
在異步操作中保留上下文:
function setupClickHandler() {let clicked = 0;document.getElementById('btn').addEventListener('click', function() {clicked++;console.log(`按鈕被點擊 ${clicked} 次`);});
}
setupClickHandler();
(4)模塊化開發
實現模塊的隔離作用域:
const module = (function() {let privateData = '秘密';return {getData: function() { return privateData; }};
})();
console.log(module.getData()); // 輸出 "秘密"
4.注意事項
- 內存泄漏:閉包可能導致變量長期駐留內存,需手動解除引用(如
closureFunc = null
)。 - 性能影響:過度使用閉包會增加內存消耗。
二、詞法作用域和動態作用域區別
1.詞法作用域(Lexical Scoping)
詞法作用域由代碼書寫時的結構決定,作用域在代碼編譯階段就已確定。變量的訪問權限取決于其在代碼中的物理位置(即定義時的嵌套關系),與函數調用時的執行上下文無關。
-
特點:
作用域鏈基于函數或塊的定義位置建立,內部函數可訪問外部函數的變量,但外部函數無法訪問內部函數的變量。
大多數現代編程語言(如JavaScript、Python、C++)默認采用詞法作用域。 -
示例:
function outer() {let x = 10;function inner() {console.log(x); // 訪問外部函數的x(詞法作用域)}inner(); } outer(); // 輸出10
2.動態作用域(Dynamic Scoping)
動態作用域由函數調用時的執行上下文決定,作用域鏈在運行時動態生成。變量的訪問權限取決于調用鏈的順序,而非代碼的靜態結構。
-
特點:
函數內部的變量可能因調用方式不同而引用不同的值。
傳統語言如Bash、Perl的局部變量支持動態作用域,但現代語言較少使用。 -
示例:
x=1 function foo() {echo $x # 動態作用域下,x的值取決于調用者 } function bar() {local x=2foo } bar # 輸出2(動態作用域)
3.核心區別
-
確定時機:
- 詞法作用域在代碼編譯時靜態確定。
- 動態作用域在函數運行時動態確定。
-
依賴關系:
- 詞法作用域依賴代碼的物理結構(嵌套關系)。
- 動態作用域依賴函數的調用棧。
-
典型語言:
- 詞法作用域:JavaScript、Python、C。
- 動態作用域:Bash、Emacs Lisp(部分支持)。
-
性能影響:
- 詞法作用域允許更高效的靜態優化。
- 動態作用域需在運行時解析變量,可能降低性能。
4.實際影響
- 閉包(Closure):詞法作用域是閉包的基礎,內部函數可“記住”定義時的環境。
- 調試難度:動態作用域可能導致變量值難以預測,增加調試復雜度。
三、call
、apply
和bind
方法的區別
1.功能:
這三個方法都是 JavaScript 中函數對象的方法,用于顯式綁定函數的 this
值,并執行函數。核心功能是動態改變函數運行時 this
的指向。
2.call 方法
- 語法:
func.call(thisArg, arg1, arg2, ...)
- 作用: 立即調用函數,并指定函數內部的
this
值為thisArg
,同時傳遞參數列表(逐個傳遞)。 - 示例:
function greet(name) {console.log(`Hello, ${name}! I'm ${this.title}.`); } const context = { title: 'Mr' }; greet.call(context, 'Alice'); // 輸出: Hello, Alice! I'm Mr.
3.apply 方法
- 語法:
func.apply(thisArg, [argsArray])
- 作用: 立即調用函數,并指定
this
值為thisArg
,參數通過數組(或類數組對象)傳遞。 - 與 call 的區別: 僅參數傳遞方式不同(數組 vs 逐個參數)。
- 示例:
greet.apply(context, ['Alice']); // 輸出與 call 相同
4.bind 方法
- 語法:
func.bind(thisArg, arg1, arg2, ...)
- 作用: 返回一個新函數,新函數的
this
永久綁定為thisArg
,并可預先綁定部分參數(柯里化)。 - 特點: 不會立即執行函數,需手動調用返回的新函數。
- 示例:
const boundGreet = greet.bind(context, 'Alice'); boundGreet(); // 輸出: Hello, Alice! I'm Mr.
5.區別:
方法 | 調用時機 | 參數形式 | 返回值 |
---|---|---|---|
call | 立即執行 | 逐個參數 | 函數執行結果 |
apply | 立即執行 | 數組或類數組 | 函數執行結果 |
bind | 延遲執行 | 逐個參數 | 綁定后的函數 |
6.使用場景
- call/apply: 需要立即調用函數并明確
this
時,如借用其他對象的方法(如數組方法用于類數組對象)。 - bind: 需要固定
this
或部分參數時(如事件監聽回調、定時器函數)。
示例:借用數組方法
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.push.call(arrayLike, 'c'); // arrayLike 變為 { 0: 'a', 1: 'b', 2: 'c', length: 3 }
四、什么是高階函數
1.高階函數的定義
高階函數是指能夠接收其他函數作為參數,或者將函數作為返回值的函數。這種特性使得代碼更具抽象性和靈活性,常用于函數式編程中。
2.高階函數的特點
(1)接收函數作為參數
高階函數可以接受一個或多個函數作為輸入參數。例如,map
、filter
和 reduce
是常見的高階函數,它們通過傳入的函數對數據進行處理。
(2)返回函數作為結果
高階函數可以生成并返回一個新函數。例如,閉包或裝飾器(在 Python 中)通常會返回一個函數。
3.高階函數的常見用例
(1)函數組合
將多個函數組合成一個新的函數,例如 compose(f, g)(x)
等價于 f(g(x))
。
(2)回調函數
在異步編程中,高階函數常用于處理回調邏輯,例如 setTimeout
或事件監聽器。
(3)裝飾器模式
在不修改原函數代碼的情況下,通過高階函數擴展其功能。例如 Python 的 @decorator
語法。
4.示例
// 接收函數作為參數
const numbers = [1, 2, 3];
const squared = numbers.map(x => x * x); // [1, 4, 9]// 返回函數
function multiplier(factor) {return x => x * factor;
}
const double = multiplier(2);
console.log(double(5)); // 10
5.優勢
- 代碼復用性:通過抽象通用邏輯,減少重復代碼。
- 靈活性:動態調整函數行為,適應不同場景。
- 可讀性:通過函數組合,使代碼更符合聲明式編程風格。
五、解釋箭頭函數的特性及其與普通函數的區別
1.特性
箭頭函數(Arrow Function)是ES6引入的一種簡化函數定義的語法,具有以下核心特性:
-
簡潔語法:省略
function
關鍵字,使用=>
定義函數。單行表達式可省略{}
和return
。const add = (a, b) => a + b;
-
無
this
綁定:箭頭函數不綁定自身的this
,而是繼承外層作用域的this
值。const obj = {value: 10,getValue: () => console.log(this.value) // 輸出undefined(繼承全局作用域) };
-
無
arguments
對象:需使用剩余參數(...args
)替代。const logArgs = (...args) => console.log(args);
-
不可作為構造函數:無法通過
new
調用,且沒有prototype
屬性。const Foo = () => {}; new Foo(); // TypeError: Foo is not a constructor
2.區別
(1)this
綁定
普通函數的this
由調用方式決定(動態綁定),箭頭函數繼承定義時的this
(靜態綁定)。
function regularFunc() { console.log(this); }
const arrowFunc = () => console.log(this);const obj = { method: regularFunc, arrowMethod: arrowFunc };
obj.method(); // 輸出obj(普通函數)
obj.arrowMethod(); // 輸出全局對象(箭頭函數繼承定義時的this)
(2)構造函數與prototype
普通函數可作為構造函數,箭頭函數不能。
(3)arguments
對象
普通函數可通過arguments
訪問參數,箭頭函數需使用剩余參數。
(4)語法靈活性
普通函數支持函數聲明和表達式,箭頭函數僅限表達式形式。
3.適用場景
-
箭頭函數:適合需要綁定外層
this
的場景(如回調函數、數組方法)。[1, 2, 3].map(item => item * 2);
-
普通函數:需要動態
this
、構造函數或arguments
時使用。function Person(name) { this.name = name; }
六、什么是生成器函數
1.概念
生成器函數是 JavaScript 中的一種特殊函數,允許通過 yield
關鍵字暫停和恢復執行。它返回一個生成器對象(Generator),該對象遵循迭代器協議,可以逐步產生值,而非一次性計算所有結果。
2.基本語法
生成器函數通過在 function
后添加星號(*
)定義:
function* generatorFunction() {yield 'value1';yield 'value2';return 'finalValue'; // 可選
}
調用生成器函數時,不會立即執行函數體,而是返回一個生成器對象:
const generator = generatorFunction();
3.核心特性
暫停與恢復
通過 yield
暫停函數執行,并返回當前值。調用 next()
方法恢復執行,直到下一個 yield
或 return
。
惰性求值
值按需生成,節省內存。例如處理大型數據集時,無需預先生成全部結果。
4.對象的方法
next()
:恢復執行,返回{ value: yieldedValue, done: boolean }
。return(value)
:終止生成器,返回{ value: value, done: true }
。throw(error)
:向生成器拋入錯誤,可在函數內通過try-catch
捕獲。
示例:生成斐波那契數列
function* fibonacci() {let [a, b] = [0, 1];while (true) {yield a;[a, b] = [b, a + b];}
}const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
5.區別
特性 | 生成器函數 | 普通函數 |
---|---|---|
執行方式 | 可暫停和恢復 | 一次性執行完畢 |
返回值 | 生成器對象(迭代器) | 直接返回結果 |
內存占用 | 惰性計算,內存效率高 | 可能占用更多內存 |
6.常見應用場景
- 異步編程:與
Promise
結合簡化異步流程(如redux-saga
)。 - 無限序列:按需生成無限序列(如斐波那契數列)。
- 狀態管理:通過
yield
暫停保存中間狀態。
七、防抖
1.概念
防抖(Debounce)是一種優化高頻觸發事件的技術,確保事件在停止觸發后的一段時間內只執行一次。常用于輸入框搜索、窗口大小調整等場景。
2.基礎實現
function debounce(func, delay) {let timer;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);}, delay);};
}
- 核心邏輯:每次觸發時清除舊定時器,重新設置延遲執行。
- 參數說明:
func
:需要防抖的目標函數。delay
:延遲時間(毫秒)。
3.立即執行版本
若需首次觸發立即執行,后續延遲可添加標志位控制:
function debounce(func, delay, immediate) {let timer;return function(...args) {const context = this;if (immediate && !timer) {func.apply(context, args);}clearTimeout(timer);timer = setTimeout(() => {timer = null;if (!immediate) {func.apply(context, args);}}, delay);};
}
immediate
為true
時:首次觸發立即執行,后續停止觸發delay
毫秒后再恢復立即執行能力。
4.取消功能擴展
為防抖函數添加取消方法:
function debounce(func, delay) {let timer;const debounced = function(...args) {clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);}, delay);};debounced.cancel = () => {clearTimeout(timer);timer = null;};return debounced;
}
調用示例:
const debouncedFn = debounce(handler, 500);
debouncedFn.cancel(); // 取消延遲執行
5.應用場景
- 輸入框搜索:用戶停止輸入500毫秒后發起請求。
- 窗口調整:停止調整窗口后計算布局。
- 按鈕防重復點擊:避免用戶快速多次提交。
6.注意事項
- 確保
func
的this
指向正確,需通過apply
或call
綁定上下文。 - 頻繁觸發的場景下(如動畫),防抖可能導致延遲過高,需權衡延遲時間。
八、節流
1.概念
函數節流(Throttle)是一種限制函數執行頻率的技術,確保函數在指定時間間隔內只執行一次。常用于高頻事件(如滾動、輸入、窗口調整)的性能優化。
2.時間戳版節流
通過記錄上一次執行的時間戳,判斷當前時間是否超過間隔:
function throttle(func, delay) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= delay) {func.apply(this, args);lastTime = now;}};
}
特點:立即執行,但最后一次觸發可能不執行。
3.定時器版節流
通過定時器控制執行,確保間隔結束后觸發:
function throttle(func, delay) {let timer = null;return function(...args) {if (!timer) {timer = setTimeout(() => {func.apply(this, args);timer = null;}, delay);}};
}
特點:延遲執行,確保最后一次觸發會執行。
4.結合時間戳與定時器
綜合兩種方案,實現首次立即執行、末次延遲執行的效果:
function throttle(func, delay) {let lastTime = 0, timer = null;return function(...args) {const now = Date.now();const remaining = delay - (now - lastTime);if (remaining <= 0) {if (timer) {clearTimeout(timer);timer = null;}func.apply(this, args);lastTime = now;} else if (!timer) {timer = setTimeout(() => {func.apply(this, args);lastTime = Date.now();timer = null;}, remaining);}};
}
5.應用示例
監聽滾動事件時,限制處理函數的頻率:
const handleScroll = throttle(() => {console.log('Scroll event throttled');
}, 200);window.addEventListener('scroll', handleScroll);
6.注意事項
- 間隔時間:根據實際場景調整
delay
參數,避免卡頓或響應不足。 - 上下文綁定:使用
apply
確保函數內的this
指向正確。 - 取消機制:可擴展功能,支持手動取消未執行的定時器。
九、遞歸函數的優缺點
1.優點
- 代碼簡潔清晰
遞歸能將復雜問題分解為相似的子問題,代碼邏輯更接近數學定義或自然語言描述。例如階乘、斐波那契數列的實現只需幾行代碼。
- 減少顯式循環結構
對于樹形結構(如DOM遍歷、目錄掃描)或分治算法(如快速排序、歸并排序),遞歸無需手動維護棧或循環變量,降低實現復雜度。
- 天然的問題分解能力
適合解決具有自相似性的問題(如漢諾塔、回溯算法),子問題的解能直接組合成原問題的解。
2.缺點
- 棧溢出風險
每次遞歸調用會占用棧幀空間,深度過大時(如未優化的斐波那契數列計算)可能導致棧溢出錯誤。尾遞歸優化可緩解此問題,但并非所有語言支持。
- 性能開銷較高
函數調用涉及壓棧、跳轉等操作,比循環開銷更大。例如計算fib(30)
可能觸發數百萬次遞歸調用,而迭代法只需幾十次循環。
- 調試難度增加
多層遞歸時,調用棧跟蹤復雜,錯誤可能難以定位。需依賴日志或調試工具觀察中間狀態。
- 重復計算問題
某些遞歸(如樸素斐波那契實現)會重復計算相同子問題,需配合備忘錄(Memoization)優化。
# 未優化的斐波那契遞歸
def fib(n):if n <= 1:return nreturn fib(n-1) + fib(n-2) # 存在大量重復計算
3.使用建議
- 適用場景:問題可明確分解為子問題,且子問題與原問題同構(如樹的遍歷)。
- 避免場景:對性能敏感或遞歸深度不可控時(如處理用戶輸入的未知層級數據)。
- 優化手段:尾遞歸改寫(如Scheme)、備忘錄模式(緩存中間結果)、改用迭代實現。
十、什么是純函數
1.定義
純函數是指在相同的輸入下始終返回相同的輸出,并且不會產生任何副作用的函數。這意味著純函數不依賴于外部狀態,也不會修改外部狀態。
2.特性
- 確定性:對于相同的輸入,純函數總是返回相同的輸出。例如:
function add(a, b) {return a + b;
}
無論何時調用 add(2, 3)
,結果始終是 5
。
- 無副作用:純函數不會修改外部變量、全局狀態或傳入的參數。例如:
let counter = 0;
function increment() {counter++; // 副作用:修改了外部變量
}
increment
不是純函數,因為它修改了外部變量 counter
。
3.優點
- 可預測性:由于純函數的行為完全由輸入決定,結果更容易預測和測試。
- 可緩存性:相同的輸入總是對應相同的輸出,因此可以緩存結果以提高性能。
- 易于并行化:純函數不依賴外部狀態,可以在多線程環境下安全運行。
4示例
// 純函數
function square(x) {return x * x;
}// 非純函數
function getRandomNumber() {return Math.random(); // 輸出不確定
}
5.如何編寫
避免修改外部狀態或傳入的參數:
// 不純的函數
function updateUser(user) {user.age = 30; // 直接修改了參數return user;
}// 純函數
function updateUserPure(user) {return { ...user, age: 30 }; // 返回新對象,不修改原參數
}
避免依賴外部變量:
// 不純的函數
let taxRate = 0.1;
function calculateTax(amount) {return amount * taxRate; // 依賴外部變量
}// 純函數
function calculateTaxPure(amount, rate) {return amount * rate; // 所有依賴都通過參數傳入
}
十一、解釋函數重載在JavaScript中的實現方式
1.概念
函數重載允許同一函數名根據參數類型或數量不同執行不同操作。JavaScript本身不支持傳統意義上的函數重載(如Java或C++),但可以通過特定技巧模擬實現。
2.基于參數數量的重載
通過檢查arguments
對象或剩余參數(rest parameters)判斷傳入參數數量,執行不同邏輯:
function example() {if (arguments.length === 1) {console.log("處理單參數邏輯");} else if (arguments.length === 2) {console.log("處理雙參數邏輯");}
}
example("A"); // 輸出: 處理單參數邏輯
example("A", "B"); // 輸出: 處理雙參數邏輯
3.基于參數類型的重載
使用typeof
或instanceof
檢查參數類型,動態調整行為:
function process(input) {if (typeof input === "string") {console.log("處理字符串邏輯");} else if (typeof input === "number") {console.log("處理數字邏輯");}
}
process("text"); // 輸出: 處理字符串邏輯
process(42); // 輸出: 處理數字邏輯
4.使用對象參數模擬重載
通過傳遞配置對象統一參數,避免參數順序問題:
function configure(options) {if (options.mode === "fast") {console.log("啟用快速模式");} else if (options.mode === "safe") {console.log("啟用安全模式");}
}
configure({ mode: "fast" }); // 輸出: 啟用快速模式
5.結合默認參數與條件判斷
利用ES6默認參數簡化邏輯分支:
function load(data, format = "json") {if (format === "json") {console.log("解析JSON數據");} else if (format === "xml") {console.log("解析XML數據");}
}
load({}); // 輸出: 解析JSON數據
load({}, "xml"); // 輸出: 解析XML數據
6.注意事項
- JavaScript函數重載本質是手動模擬,需顯式編寫參數檢查邏輯。
- 過度使用可能導致代碼可讀性下降,建議優先考慮清晰命名的獨立函數。
- TypeScript提供更規范的重載語法支持,適合大型項目。
十二、什么是偏函數
1.概念
偏函數(Partial Function)是一種在函數式編程中常見的概念,指的是通過固定一個函數的部分參數,生成一個新的函數。這種技術允許開發者復用已有函數,同時減少重復代碼。
2.作用
偏函數的主要作用是將一個多參數函數轉換為一個參數較少的函數。通過預先綁定某些參數,可以在后續調用時簡化操作。例如,一個計算乘方的函數可以通過偏函數固定底數,生成一個專門計算平方或立方的函數。
3.實現方式
在Python中,可以使用functools.partial
來實現偏函數。以下是一個示例:
from functools import partialdef power(base, exponent):return base ** exponentsquare = partial(power, exponent=2)
cube = partial(power, exponent=3)print(square(5)) # 輸出25
print(cube(3)) # 輸出27
4.優點
- 代碼復用:通過固定部分參數,避免重復編寫相似的函數。
- 靈活性:可以在運行時動態生成新的函數,適應不同的需求。
- 可讀性:通過賦予偏函數有意義的名稱,提升代碼的可讀性。
5.應用場景
- 回調函數:在事件處理中,可以通過偏函數預先綁定部分參數,簡化回調函數的定義。
- 配置函數:在需要多次調用同一函數但部分參數固定的場景中,使用偏函數可以減少冗余代碼。
- 數學運算:如上述示例所示,通過偏函數可以快速生成特定運算的函數。
6.注意事項
- 參數順序:偏函數綁定的參數順序需與原函數一致,否則可能導致錯誤。
- 不可變參數:偏函數生成的新函數不能修改已綁定的參數值。
- 性能開銷:盡管偏函數的性能開銷通常較小,但在高性能場景中仍需謹慎使用。