目标:彻底吃透函数的声明/调用/作用域/闭包/this,能在真实业务中写出可维护、无坑的函数代码。
1. 函数基础与声明方式 1.1 函数声明(Function Declaration) 1.1.1 基本语法与特性
语法 :function foo() { ... }
提升规则 :整体被提升(hoisting),函数体可在声明前调用。
作用域 :函数声明会创建一个绑定在当前作用域的标识符。
适用场景 :公共工具函数、需在文件顶部就能调用的函数、需要递归调用的函数。
1.1.2 提升机制详解 函数声明的提升是完整的提升 ,包括函数名和函数体:
1 2 3 4 5 6 7 8 9 10 11 12 say (); function say ( ) { console .log ('hi' ); } function say ( ) { console .log ('hi' ); } say ();
1.1.3 函数声明的条件提升 函数声明在块级作用域中的行为(ES6+):
1 2 3 4 5 6 7 8 9 10 11 12 if (true ) { function foo ( ) { console .log ('foo' ); } } foo (); let foo;if (true ) { foo = function ( ) { console .log ('foo' ); }; } foo ();
1.1.4 函数声明的重复声明 1 2 3 4 5 6 7 8 function foo ( ) { return 1 ; }function foo ( ) { return 2 ; } console .log (foo ()); "use strict" ;function bar ( ) { return 1 ; }
1.1.5 函数声明的实际应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function formatDate (date ) { return new Intl .DateTimeFormat ('zh-CN' ).format (date); } function formatCurrency (amount ) { return new Intl .NumberFormat ('zh-CN' , { style : 'currency' , currency : 'CNY' }).format (amount); } function factorial (n ) { if (n <= 1 ) return 1 ; return n * factorial (n - 1 ); }
1.2 函数表达式(Function Expression) 1.2.1 基本语法与特性
语法 :const foo = function() { ... };
提升规则 :只提升变量声明(var 提升为 undefined,let/const 进入暂时性死区),函数值不会提升。
适用场景 :需要控制创建时机、避免过度提升导致的调用混乱、条件性创建函数。
1.2.2 变量提升 vs 函数提升 1 2 3 4 5 6 7 8 9 10 11 12 13 14 console .log (foo); var foo = function ( ) { console .log ('later created' ); }; foo (); const bar = function ( ) { console .log ('later created' ); }; bar ();
1.2.3 函数表达式的优势 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let handler;if (condition) { handler = function ( ) { console .log ('A' ); }; } else { handler = function ( ) { console .log ('B' ); }; } (function ( ) { const localFunction = function ( ) { }; })(); const createValidator = (rule ) => { return function (value ) { return rule.test (value); }; };
1.2.4 函数表达式的性能考虑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const handlers = [];for (let i = 0 ; i < 1000 ; i++) { handlers.push (function ( ) { return i; }); } function createHandler (i ) { return function ( ) { return i; }; } const optimizedHandlers = [];for (let i = 0 ; i < 1000 ; i++) { optimizedHandlers.push (createHandler (i)); }
1.3 命名函数表达式(Named Function Expression, NFE) 1.3.1 基本语法
语法 :const foo = function bar() { ... };
作用 :bar 仅在函数内部可见,便于递归或调试堆栈。
调试优势 :在调用栈中显示有意义的函数名。
1.3.2 内部名称的作用域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const foo = function bar ( ) { console .log (typeof bar); }; console .log (typeof bar); foo (); const factorial = function fact (n ) { if (n <= 1 ) return 1 ; return n * fact (n - 1 ); }; console .log (factorial (5 ));
1.3.3 调试堆栈中的表现 1 2 3 4 5 6 7 8 9 10 11 const anonymous = function ( ) { throw new Error ('test' ); }; const named = function myFunction ( ) { throw new Error ('test' ); };
1.3.4 命名函数表达式的陷阱 1 2 3 4 5 6 7 8 9 10 11 "use strict" ;const foo = function foo ( ) { }; const factorial = function fact (n ) { if (n <= 1 ) return 1 ; return n * fact (n - 1 ); };
1.4 参数默认值与剩余参数 1.4.1 默认值的基本规则 默认值只在实参为 undefined 时触发(null、0、false、'' 等都不触发):
1 2 3 4 5 6 7 8 9 10 function f (x = 1 ) { return x; } f (undefined ); f (null ); f (0 ); f (false ); f ('' ); f ();
1.4.2 默认值的惰性求值 默认值是惰性求值 :每次调用都会重新计算表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function logTime (x = Date .now() ) { console .log (x); } logTime (); setTimeout (() => logTime (), 1000 ); function processItems (items = [] ) { items.push ('new' ); return items; } const result1 = processItems (); const result2 = processItems ();
1.4.3 默认值可以引用前面的参数 1 2 3 4 5 6 7 function createUser (name, displayName = name.toUpperCase() ) { return { name, displayName }; } createUser ('alice' );
1.4.4 解构结合默认值 防止解构 undefined 报错,同时为解构的属性提供默认值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function createUser ({ name = '匿名' , role = 'guest' } = {} ) { return { name, role }; } createUser (); createUser ({}); createUser ({ name : 'Alice' }); function processConfig ({ api: { baseURL = 'https://api.example.com' , timeout = 5000 } = {}, cache = true } = {} ) { return { baseURL, timeout, cache }; } function fetchData ({ url, method = 'GET' , headers = {}, body = null , timeout = 5000 } = {} ) { if (!url) throw new Error ('URL is required' ); }
1.4.5 剩余参数(Rest Parameters) 剩余参数 ...rest 必须在形参末尾,得到一个真数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function sum (base, ...nums ) { return nums.reduce ((a, b ) => a + b, base); } sum (10 , 1 , 2 , 3 ); function logArgs (...args ) { console .log (Array .isArray (args)); console .log (args.map (x => x * 2 )); } function compose (...fns ) { return function (value ) { return fns.reduceRight ((acc, fn ) => fn (acc), value); }; } const add1 = x => x + 1 ;const multiply2 = x => x * 2 ;const add1ThenMultiply2 = compose (multiply2, add1);console .log (add1ThenMultiply2 (5 ));
1.4.6 默认值与剩余参数的组合 1 2 3 4 5 6 7 8 9 10 function process (...items = [] ) { } function apiCall (url, { method = 'GET' , headers = {}, body = null } = {} ) { }
1.5 箭头函数 vs 普通函数 1.5.1 箭头函数的完整特性列表 箭头函数特点:
**没有自己的 this**:沿用定义处的外层 this(词法绑定),无法通过 call/apply/bind 改变。
**没有 arguments**:需用剩余参数 ...args。
**不能 new**:没有 prototype,也不能做构造函数。
**不能用 yield**:不能是生成器函数。
**没有 super**:不能用于类方法(除非是静态方法)。
**没有 new.target**:无法判断是否通过 new 调用。
1.5.2 this 绑定的差异 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const obj = { name : 'Object' , regular : function ( ) { console .log (this .name ); }, arrow : () => { console .log (this .name ); } }; obj.regular (); obj.arrow (); function Timer ( ) { this .seconds = 0 ; setInterval (function ( ) { this .seconds ++; }, 1000 ); setInterval (() => { this .seconds ++; }, 1000 ); }
1.5.3 arguments 的差异 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function regular ( ) { console .log (arguments ); console .log (arguments [0 ]); } const arrow = ( ) => { }; const arrowWithRest = (...args ) => { console .log (args); console .log (args[0 ]); };
1.5.4 构造函数能力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Person (name ) { this .name = name; } const person = new Person ('Alice' ); const ArrowPerson = (name ) => { this .name = name; }; console .log (Person .prototype ); console .log (ArrowPerson .prototype );
1.5.5 适用场景详解 箭头函数适合:
回调函数(需要捕获外层 this)
高阶函数的参数
简短的工具函数
函数式编程风格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const numbers = [1 , 2 , 3 , 4 , 5 ];const doubled = numbers.map (n => n * 2 ); class Component { constructor ( ) { this .value = 0 ; this .handleClick = () => { console .log (this .value ); }; } } const pipe = (...fns ) => (value ) => fns.reduce ((acc, fn ) => fn (acc), value);
箭头函数不适合:
对象方法(需要动态 this)
构造函数
需要 arguments 的函数
需要 prototype 的函数
生成器函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const obj = { value : 1 , bad : () => { return this .value ; }, good : function ( ) { return this .value ; } }; document .addEventListener ('click' , function (event ) { console .log (this ); }); document .addEventListener ('click' , (event ) => { console .log (this ); });
1.5.6 返回对象字面量的语法 1 2 3 4 5 6 7 8 9 10 11 12 const bad = ( ) => { value : 1 }; const good = ( ) => ({ value : 1 }); const createConfig = ( ) => ({ api : 'https://api.example.com' , timeout : 5000 , retries : 3 });
1.5.7 箭头函数的性能考虑 1 2 3 4 5 6 7 8 9 10 11 function heavyComputation (x ) { return x * 2 ; } const lightComputation = (x ) => x * 2 ;
1.6 小结与易错点 1.6.1 选择指南
场景
推荐
原因
需要提升
函数声明
整体提升,可在声明前调用
需要延迟创建
函数表达式
控制创建时机
需要递归
函数声明或命名函数表达式
函数名在内部可用
需要动态 this
普通函数
箭头函数无法改变 this
需要捕获外层 this
箭头函数
词法绑定
需要 arguments
普通函数
箭头函数没有 arguments
需要作为构造函数
普通函数
箭头函数不能 new
1.6.2 常见易错点 错误1:混淆提升行为
1 2 3 4 5 6 foo (); const foo = function ( ) {};
错误2:默认值判断
1 2 3 4 5 6 function f (x = 1 ) {}f (null ); f (undefined );
错误3:箭头函数作为对象方法
1 2 3 4 5 6 7 8 9 10 11 const obj = { value : 1 , getValue : () => this .value }; const obj = { value : 1 , getValue ( ) { return this .value ; } };
错误4:剩余参数位置
1 2 3 4 5 function bad (first, ...rest, last ) {} function good (first, ...rest ) {}
2. 高阶函数、回调与递归 2.1 高阶函数是什么 2.1.1 定义与分类 定义 :接收函数作为参数,或返回一个函数,或两者兼有的函数。
分类 :
接收函数作为参数 :如 map、filter、forEach
返回函数 :如 bind、函数工厂
两者兼有 :如 compose、curry
2.1.2 高阶函数的价值
复用控制逻辑 :将通用模式抽象出来
抽象操作 :隐藏实现细节,关注做什么而非怎么做
组合行为 :通过组合小函数构建复杂功能
提高可读性 :声明式编程风格
2.1.3 实际应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function withRetry (fn, maxRetries = 3 ) { return async function (...args ) { let lastError; for (let i = 0 ; i < maxRetries; i++) { try { return await fn (...args); } catch (error) { lastError = error; if (i < maxRetries - 1 ) { await new Promise (resolve => setTimeout (resolve, 1000 * (i + 1 ))); } } } throw lastError; }; } function withErrorHandling (fn, errorHandler ) { return function (...args ) { try { return fn (...args); } catch (error) { return errorHandler (error, args); } }; } function withPerformanceMonitor (fn, label ) { return function (...args ) { const start = performance.now (); const result = fn (...args); const end = performance.now (); console .log (`${label} took ${end - start} ms` ); return result; }; }
2.2 常见内置高阶函数 2.2.1 Array.prototype.map 逐个变换,返回新数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const numbers = [1 , 2 , 3 , 4 ];const doubled = numbers.map (n => n * 2 ); const indexed = numbers.map ((n, i ) => `${i} : ${n} ` ); const users = [ { name : 'Alice' , age : 25 }, { name : 'Bob' , age : 30 } ]; const names = users.map (user => user.name ); const result = numbers .map (n => n * 2 ) .map (n => n + 1 ) .filter (n => n > 5 );
2.2.2 Array.prototype.filter 按条件保留,返回新数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const numbers = [1 , 2 , 3 , 4 , 5 , 6 ];const evens = numbers.filter (n => n % 2 === 0 ); const users = [ { name : 'Alice' , age : 25 , active : true }, { name : 'Bob' , age : 30 , active : false }, { name : 'Charlie' , age : 20 , active : true } ]; const activeUsers = users.filter (user => user.active );const mixed = [0 , 1 , false , 2 , '' , 3 , null , undefined ];const truthy = mixed.filter (Boolean );
2.2.3 Array.prototype.reduce 累积器模式,可做求和、分组、统计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const numbers = [1 , 2 , 3 , 4 , 5 ];const sum = numbers.reduce ((acc, n ) => acc + n, 0 ); const users = [ { name : 'Alice' , role : 'admin' }, { name : 'Bob' , role : 'user' }, { name : 'Charlie' , role : 'admin' } ]; const grouped = users.reduce ((acc, user ) => { if (!acc[user.role ]) acc[user.role ] = []; acc[user.role ].push (user); return acc; }, {}); const words = ['apple' , 'banana' , 'apple' , 'cherry' , 'banana' ];const count = words.reduce ((acc, word ) => { acc[word] = (acc[word] || 0 ) + 1 ; return acc; }, {}); const nested = [[1 , 2 ], [3 , 4 ], [5 , 6 ]];const flat = nested.reduce ((acc, arr ) => acc.concat (arr), []); const pipe = (...fns ) => (value ) => fns.reduce ((acc, fn ) => fn (acc), value);
2.2.4 其他常见高阶函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const numbers = [1 , 2 , 3 ];numbers.forEach (n => console .log (n)); const users = [{ id : 1 , name : 'Alice' }, { id : 2 , name : 'Bob' }];const user = users.find (u => u.id === 2 ); const hasEven = numbers.some (n => n % 2 === 0 ); const allPositive = numbers.every (n => n > 0 ); const unsorted = [3 , 1 , 4 , 1 , 5 ];const sorted = [...unsorted].sort ((a, b ) => a - b);
2.2.5 手写高阶函数示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 function withLog (fn ) { return function (...args ) { console .log ('start' , args); const res = fn.apply (this , args); console .log ('end' , res); return res; }; } const add = (a, b ) => a + b;const addWithLog = withLog (add);addWithLog (1 , 2 ); function withCache (fn, keyGenerator = JSON .stringify ) { const cache = new Map (); return function (...args ) { const key = keyGenerator (args); if (cache.has (key)) { console .log ('Cache hit' ); return cache.get (key); } const result = fn.apply (this , args); cache.set (key, result); return result; }; } function withRetry (fn, maxRetries = 3 ) { return async function (...args ) { let lastError; for (let i = 0 ; i < maxRetries; i++) { try { return await fn.apply (this , args); } catch (error) { lastError = error; if (i < maxRetries - 1 ) { await new Promise (resolve => setTimeout (resolve, 1000 )); } } } throw lastError; }; } function compose (...fns ) { return function (value ) { return fns.reduceRight ((acc, fn ) => fn (acc), value); }; } const add1 = x => x + 1 ;const multiply2 = x => x * 2 ;const add1ThenMultiply2 = compose (multiply2, add1);console .log (add1ThenMultiply2 (5 ));
2.3 递归函数 2.3.1 递归的三要素
终止条件 (不写会栈溢出)
缩小问题规模 (向终止条件逼近)
递归调用自身
2.3.2 基础递归示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function factorial (n ) { if (n <= 1 ) return 1 ; return n * factorial (n - 1 ); } console .log (factorial (5 )); function fibonacci (n ) { if (n <= 1 ) return n; return fibonacci (n - 1 ) + fibonacci (n - 2 ); } function sumArray (arr ) { if (arr.length === 0 ) return 0 ; return arr[0 ] + sumArray (arr.slice (1 )); } console .log (sumArray ([1 , 2 , 3 , 4 ]));
2.3.3 尾递归优化 1 2 3 4 5 6 7 8 9 10 11 12 13 function factorial (n ) { if (n <= 1 ) return 1 ; return n * factorial (n - 1 ); } function factorialTail (n, acc = 1 ) { if (n <= 1 ) return acc; return factorialTail (n - 1 , n * acc); }
2.3.4 递归转迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function traverseRecursive (node, callback ) { callback (node); if (node.children ) { node.children .forEach (child => { traverseRecursive (child, callback); }); } } function traverseIterative (root, callback ) { const stack = [root]; while (stack.length ) { const node = stack.pop (); callback (node); if (node.children ) { for (let i = node.children .length - 1 ; i >= 0 ; i--) { stack.push (node.children [i]); } } } } function traverseBFS (root, callback ) { const queue = [root]; while (queue.length ) { const node = queue.shift (); callback (node); if (node.children ) { queue.push (...node.children ); } } }
2.3.5 递归的常见模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function quickSort (arr ) { if (arr.length <= 1 ) return arr; const pivot = arr[0 ]; const left = arr.slice (1 ).filter (x => x < pivot); const right = arr.slice (1 ).filter (x => x >= pivot); return [...quickSort (left), pivot, ...quickSort (right)]; } function findPath (maze, start, end ) { const visited = new Set (); function dfs (x, y, path ) { if (x === end.x && y === end.y ) return path; if (visited.has (`${x} ,${y} ` ) || maze[x][y] === 1 ) return null ; visited.add (`${x} ,${y} ` ); const directions = [[0 , 1 ], [1 , 0 ], [0 , -1 ], [-1 , 0 ]]; for (const [dx, dy] of directions) { const newPath = dfs (x + dx, y + dy, [...path, [x + dx, y + dy]]); if (newPath) return newPath; } visited.delete (`${x} ,${y} ` ); return null ; } return dfs (start.x , start.y , [[start.x , start.y ]]); } function treeMap (node, fn ) { const newNode = fn (node); if (node.children ) { newNode.children = node.children .map (child => treeMap (child, fn)); } return newNode; }
2.3.6 递归的性能优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function fibonacci (n ) { if (n <= 1 ) return n; return fibonacci (n - 1 ) + fibonacci (n - 2 ); } function fibonacciMemo (n, memo = {} ) { if (n in memo) return memo[n]; if (n <= 1 ) return n; memo[n] = fibonacciMemo (n - 1 , memo) + fibonacciMemo (n - 2 , memo); return memo[n]; } function fibonacciIterative (n ) { if (n <= 1 ) return n; let a = 0 , b = 1 ; for (let i = 2 ; i <= n; i++) { [a, b] = [b, a + b]; } return b; }
2.3.7 递归的注意事项 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function infiniteRecursion ( ) { return infiniteRecursion (); } function badFactorial (n ) { return n * badFactorial (n - 1 ); } function safeFactorial (n ) { if (typeof n !== 'number' || n < 0 || !Number .isInteger (n)) { throw new Error ('Invalid input' ); } if (n <= 1 ) return 1 ; return n * safeFactorial (n - 1 ); }
3. 作用域、执行上下文与闭包 3.1 作用域类型 3.1.1 全局作用域 在任意地方可见(浏览器全局是 window,Node.js 是 global):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var globalVar = 'global' ;let globalLet = 'global' ;const globalConst = 'global' ;function test ( ) { console .log (globalVar); console .log (globalLet); console .log (globalConst); } console .log (window .globalVar );
3.1.2 函数作用域 var 声明的变量仅在函数内可见:
1 2 3 4 5 6 7 8 9 10 function test ( ) { var functionScoped = 'inside function' ; if (true ) { var alsoFunctionScoped = 'also inside function' ; } console .log (functionScoped); console .log (alsoFunctionScoped); }
3.1.3 块级作用域 let/const 声明在最近的花括号 {} 内有效;有暂时性死区:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { let blockScoped = 'inside block' ; const alsoBlockScoped = 'also inside block' ; console .log (blockScoped); console .log (alsoBlockScoped); } let tdz = 'value' ;for (let i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 0 ); } for (var i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 0 ); }
3.1.4 作用域链 查找变量时,先看当前上下文的作用域,再沿着作用域链 向外层父作用域查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const global = 'global' ;function outer ( ) { const outerVar = 'outer' ; function inner ( ) { const innerVar = 'inner' ; console .log (innerVar); console .log (outerVar); console .log (global ); } inner (); } outer ();
3.2 执行上下文与调用栈 3.2.1 执行上下文的概念 每次调用函数都会创建执行上下文 (Execution Context),包含:
变量环境 (Variable Environment):var 声明
词法环境 (Lexical Environment):let/const 声明
this 绑定
外部环境引用 (Outer Environment Reference):形成作用域链
3.2.2 调用栈(Call Stack) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function first ( ) { console .log ('first' ); second (); console .log ('first end' ); } function second ( ) { console .log ('second' ); third (); console .log ('second end' ); } function third ( ) { console .log ('third' ); } first ();
3.2.3 变量查找过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const globalVar = 'global' ;function outer ( ) { const outerVar = 'outer' ; function inner ( ) { console .log (outerVar); } inner (); } outer ();
3.2.4 执行上下文的创建阶段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function example ( ) { console .log (a); var a = 1 ; let b = 2 ; } example ();
3.3 闭包(Closure)是什么 3.3.1 闭包的定义 定义 :函数”记住”了它被定义时的词法作用域环境,而不是调用时的环境。
直观理解 :函数把外层变量”打包带走”,即使外层函数早已返回。
3.3.2 基础闭包示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function createCounter (start = 0 ) { let count = start; return function inc (step = 1 ) { count += step; return count; }; } const counter = createCounter ();console .log (counter ()); console .log (counter ()); console .log (counter ()); function createSharedState ( ) { let state = 0 ; return { increment : () => ++state, decrement : () => --state, getValue : () => state }; } const shared = createSharedState ();console .log (shared.increment ()); console .log (shared.increment ()); console .log (shared.getValue ());
3.3.3 闭包的形成条件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function outer ( ) { const outerVar = 'outer' ; function inner ( ) { console .log (outerVar); } return inner; } function createClosure ( ) { const secret = 'secret' ; return function ( ) { return secret; }; } const getSecret = createClosure ();console .log (getSecret ());
3.3.4 闭包的实际应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 function createBankAccount (initialBalance ) { let balance = initialBalance; return { deposit (amount ) { if (amount > 0 ) balance += amount; return balance; }, withdraw (amount ) { if (amount > 0 && amount <= balance) { balance -= amount; return balance; } throw new Error ('Insufficient funds' ); }, getBalance ( ) { return balance; } }; } const account = createBankAccount (100 );console .log (account.getBalance ()); account.deposit (50 ); console .log (account.getBalance ()); function createMultiplier (factor ) { return function (number ) { return number * factor; }; } const double = createMultiplier (2 );const triple = createMultiplier (3 );console .log (double (5 )); console .log (triple (5 )); const module = (function ( ) { let privateVar = 0 ; function privateFunction ( ) { return privateVar; } return { publicMethod ( ) { return privateFunction (); }, setValue (val ) { privateVar = val; } }; })();
3.4 闭包的常见用途 3.4.1 数据私有化 外部只能通过函数访问内部状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function createPerson (name ) { let age = 0 ; return { getName ( ) { return name; }, getAge ( ) { return age; }, haveBirthday ( ) { age++; }, }; } const person = createPerson ('Alice' );console .log (person.getName ()); person.haveBirthday (); console .log (person.getAge ());
3.4.2 惰性计算/缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function createLazyValue (computeFn ) { let cachedValue = null ; let computed = false ; return { get ( ) { if (!computed) { cachedValue = computeFn (); computed = true ; } return cachedValue; }, reset ( ) { computed = false ; cachedValue = null ; } }; } const expensive = createLazyValue (() => { console .log ('Computing...' ); return Math .random () * 1000 ; }); console .log (expensive.get ()); console .log (expensive.get ()); function memoize (fn ) { const cache = new Map (); return function (...args ) { const key = JSON .stringify (args); if (cache.has (key)) { return cache.get (key); } const result = fn.apply (this , args); cache.set (key, result); return result; }; }
3.4.3 函数工厂 根据配置返回行为不同的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function createValidator (rules ) { return function (value ) { for (const rule of rules) { if (!rule.test (value)) { return { valid : false , error : rule.message }; } } return { valid : true }; }; } const emailValidator = createValidator ([ { test : v => v.includes ('@' ), message : 'Must contain @' }, { test : v => v.length > 5 , message : 'Too short' } ]); console .log (emailValidator ('test@example.com' )); console .log (emailValidator ('test' ));
3.4.4 模拟模块 早期 IIFE + 闭包实现私有变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const MyModule = (function ( ) { let privateVar = 0 ; const privateConst = 'secret' ; function privateFunction ( ) { return privateVar; } return { publicMethod ( ) { return privateFunction (); }, setValue (val ) { privateVar = val; }, getConstant ( ) { return privateConst; } }; })(); console .log (MyModule .publicMethod ()); MyModule .setValue (42 );console .log (MyModule .publicMethod ());
3.5 闭包常见坑 3.5.1 循环与 var 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 for (var i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 100 ); } for (let i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 100 ); } for (var i = 0 ; i < 3 ; i++) { (function (j ) { setTimeout (() => console .log (j), 100 ); })(i); } for (var i = 0 ; i < 3 ; i++) { setTimeout (function (j ) { console .log (j); }.bind (null , i), 100 ); }
3.5.2 内存占用 闭包长时间存在会持有外部变量引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function createHandlers ( ) { const largeData = new Array (1000000 ).fill (0 ); return Array .from ({ length : 1000 }, (_, i ) => { return function ( ) { console .log (i); }; }); } function createHandlersOptimized ( ) { return Array .from ({ length : 1000 }, (_, i ) => { return function ( ) { console .log (i); }; }); } let handlers = createHandlers ();handlers = null ;
3.5.3 意外的闭包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const buttons = document .querySelectorAll ('button' );for (let i = 0 ; i < buttons.length ; i++) { buttons[i].addEventListener ('click' , function ( ) { console .log (i); }); } for (let i = 0 ; i < buttons.length ; i++) { buttons[i].addEventListener ('click' , (function (index ) { return function ( ) { console .log (index); }; })(i)); }
3.5.4 闭包与 this 的混淆 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const obj = { value : 1 , getValue : function ( ) { return function ( ) { return this .value ; }; } }; console .log (obj.getValue ()()); const obj2 = { value : 1 , getValue : function ( ) { return () => { return this .value ; }; } }; console .log (obj2.getValue ()()); const obj3 = { value : 1 , getValue : function ( ) { const self = this ; return function ( ) { return self.value ; }; } }; console .log (obj3.getValue ()());
4. 特殊函数特性 4.1 IIFE(立即执行函数表达式) 4.1.1 基本概念 IIFE(Immediately Invoked Function Expression)立即执行函数表达式,作用:立刻创建一个独立作用域,避免全局变量污染。
4.1.2 语法形式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (function ( ) { const hidden = 1 ; console .log ('init' ); })(); (() => { console .log ('arrow iife' ); })(); !function ( ) { console .log ('negation' ); }(); +function ( ) { console .log ('unary plus' ); }(); void function ( ) { console .log ('void' ); }();
4.1.3 实际应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 (function ( ) { const privateVar = 'private' ; function privateFunction ( ) { return privateVar; } window .MyLibrary = { publicMethod : privateFunction }; })(); const Module = (function ( ) { let state = 0 ; return { increment ( ) { state++; }, getState ( ) { return state; } }; })(); for (var i = 0 ; i < 3 ; i++) { (function (index ) { setTimeout (() => console .log (index), 100 ); })(i); } (function ( ) { const config = loadConfig (); setupApp (config); })();
4.1.4 带参数的 IIFE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 (function (global , $ ) { global .MyApp = { init : function ( ) { $('.button' ).on ('click' , this .handleClick ); } }; })(window , jQuery); (function (deps ) { const { api, logger } = deps; })({ api : MyAPI , logger : MyLogger });
4.2 arguments 对象 4.2.1 基本特性 arguments 是类数组 对象,有 length 和按索引访问,但没有数组原型方法:
1 2 3 4 5 6 7 8 9 function example ( ) { console .log (arguments .length ); console .log (arguments [0 ]); console .log (arguments [1 ]); console .log (Array .isArray (arguments )); } example (1 , 2 , 3 );
4.2.2 转换为数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function convertArguments ( ) { const arr1 = Array .from (arguments ); const arr2 = [...arguments ]; const arr3 = Array .prototype .slice .call (arguments ); const arr4 = Array .prototype .slice .apply (arguments ); return { arr1, arr2, arr3, arr4 }; }
4.2.3 箭头函数中的 arguments 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const arrow = ( ) => { }; function outer ( ) { const inner = ( ) => { console .log (arguments ); }; inner (); } outer (1 , 2 , 3 ); const arrowWithRest = (...args ) => { console .log (args); console .log (args.map (x => x * 2 )); };
4.2.4 arguments 与剩余参数的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function withArguments (a, b ) { console .log (arguments .length ); console .log (arguments [0 ], arguments [1 ], arguments [2 ]); } withArguments (1 , 2 , 3 );function withRest (a, b, ...rest ) { console .log (rest.length ); console .log (rest[0 ]); } withRest (1 , 2 , 3 );function legacyFunction ( ) { const args = Array .from (arguments ); } function modernFunction (...args ) { }
4.2.5 arguments 的特殊行为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function linked (a, b ) { arguments [0 ] = 100 ; console .log (a); } linked (1 , 2 );function strictLinked (a, b ) { 'use strict' ; arguments [0 ] = 100 ; console .log (a); } strictLinked (1 , 2 );
4.3 严格模式 "use strict" 4.3.1 开启方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 "use strict" ;function strictFunction ( ) { } function nonStrict ( ) { } function strictFunction ( ) { "use strict" ; }
4.3.2 主要影响 1. 禁止隐式创建全局变量
1 2 3 4 "use strict" ;function test ( ) { undeclaredVar = 1 ; }
2. 普通函数独立调用时 this 为 undefined
1 2 3 4 5 6 7 8 9 10 11 "use strict" ;function test ( ) { console .log (this ); } test ();function nonStrict ( ) { console .log (this ); } nonStrict ();
3. 删除不可删属性会抛错
1 2 "use strict" ;delete Object .prototype ;
4. 重复参数名会抛错
1 2 3 4 "use strict" ;function bad (a, a ) { }
5. with 语句被禁用
1 2 3 4 5 "use strict" ;const obj = { x : 1 };
6. 八进制字面量被禁用
1 2 3 "use strict" ;const oct = 0o123 ;
7. eval 和 arguments 不能作为标识符
4.3.3 严格模式的实际应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 "use strict" ;function processData (data ) { result = data.map (x => x * 2 ); return result; } function EventHandler ( ) { this .value = 1 ; document .addEventListener ('click' , function ( ) { console .log (this ); }); } function calculate (a, b ) { return a + b; }
5. this 的绑定规则(逐条对照记忆) 5.1 决定 this 的唯一因素:调用位置 按优先级从高到低:
5.1.1 new 调用 this 指向新创建的实例。箭头函数不能 new。
1 2 3 4 5 6 7 8 9 10 11 12 function Person (name ) { this .name = name; } const person = new Person ('Alice' );console .log (person.name ); const ArrowPerson = (name ) => { this .name = name; };
5.1.2 显式绑定 call / apply / bind 指定的对象。
1 2 3 4 5 6 7 8 9 10 11 12 function greet ( ) { console .log (`Hello, ${this .name} ` ); } const obj1 = { name : 'Alice' };const obj2 = { name : 'Bob' };greet.call (obj1); greet.apply (obj2); const bound = greet.bind (obj1);bound ();
5.1.3 隐式绑定 作为对象的方法被调用,obj.fn() 中 this 是 obj。
1 2 3 4 5 6 7 8 9 10 11 12 const obj = { name : 'Object' , greet ( ) { console .log (`Hello, ${this .name} ` ); } }; obj.greet (); const fn = obj.greet ;fn ();
5.1.4 默认绑定 独立函数调用,非严格模式下是全局对象,严格模式下是 undefined。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function test ( ) { console .log (this ); } test (); function nonStrict ( ) { console .log (this === window ); } nonStrict ();"use strict" ;function strict ( ) { console .log (this ); } strict ();
5.1.5 箭头函数 没有自己的 this,直接用定义处外层 的 this,且无法被以上方式改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const obj = { name : 'Object' , regular : function ( ) { console .log (this .name ); }, arrow : () => { console .log (this .name ); } }; obj.regular (); obj.arrow (); const arrow = ( ) => console .log (this );arrow.call ({ x : 1 });
5.2 事件处理中的 this 5.2.1 DOM 事件处理器 DOM 事件处理器(非箭头)默认指向触发事件的元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const btn = document .getElementById ('btn' );btn.addEventListener ('click' , function ( ) { console .log (this ); console .log (this .id ); }); btn.addEventListener ('click' , () => { console .log (this ); });
5.2.2 类/组件中的事件处理 在类/组件中用箭头函数做回调,可捕获实例 this,避免手动绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Button extends React.Component { constructor (props ) { super (props); this .state = { count : 0 }; } handleClick = () => { this .setState ({ count : this .state .count + 1 }); }; handleClick2 ( ) { this .setState ({ count : this .state .count + 1 }); } render ( ) { return <button onClick ={this.handleClick} > Click</button > ; } } class Counter { constructor ( ) { this .count = 0 ; this .increment = () => { this .count ++; }; } }
5.3 call / apply / bind 5.3.1 call 即时调用,参数逐个传:
1 2 3 4 5 6 7 8 9 10 11 function greet (greeting, punctuation ) { console .log (`${greeting} , ${this .name} ${punctuation} ` ); } const obj = { name : 'Alice' };greet.call (obj, 'Hello' , '!' ); const numbers = [5 , 6 , 2 , 3 , 7 ];const max = Math .max .call (null , ...numbers);
5.3.2 apply 即时调用,参数数组传:
1 2 3 4 5 6 7 8 9 10 function greet (greeting, punctuation ) { console .log (`${greeting} , ${this .name} ${punctuation} ` ); } const obj = { name : 'Alice' };greet.apply (obj, ['Hello' , '!' ]); const numbers = [5 , 6 , 2 , 3 , 7 ];const max = Math .max .apply (null , numbers);
5.3.3 bind 返回一个永久绑定 this 与部分参数的新函数(常用于事件回调、定时器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function greet (greeting, punctuation ) { console .log (`${greeting} , ${this .name} ${punctuation} ` ); } const obj = { name : 'Alice' };const bound = greet.bind (obj, 'Hello' ); bound ('!' ); class Component { constructor ( ) { this .handleClick = this .handleClick .bind (this ); } handleClick ( ) { console .log (this ); } } function multiply (a, b, c ) { return a * b * c; } const double = multiply.bind (null , 2 ); console .log (double (3 , 4 )); const obj = { value : 0 , start ( ) { setInterval (function ( ) { this .value ++; }, 1000 ); }, startFixed ( ) { setInterval (function ( ) { this .value ++; }.bind (this ), 1000 ); } };
5.3.4 三者对比
方法
调用时机
参数形式
返回值
用途
call
立即调用
逐个传递
函数返回值
立即调用并指定 this
apply
立即调用
数组传递
函数返回值
立即调用并指定 this(参数是数组)
bind
不调用
逐个传递
新函数
创建绑定 this 的新函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function example (a, b, c ) { console .log (this .name , a, b, c); } const obj = { name : 'Test' };example.call (obj, 1 , 2 , 3 ); example.apply (obj, [1 , 2 , 3 ]); const bound = example.bind (obj, 1 , 2 );bound (3 );
5.4 函数借用(Function Borrowing) 5.4.1 基本概念 借用数组方法处理类数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 function example ( ) { const arr = Array .prototype .slice .call (arguments ); console .log (Array .isArray (arr)); return arr.map (x => x * 2 ); } example (1 , 2 , 3 ); const nodeList = document .querySelectorAll ('div' );Array .prototype .forEach .call (nodeList, (node, index ) => { console .log (`Node ${index} :` , node); });
5.4.2 常见应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function sum ( ) { return Array .prototype .reduce .call ( arguments , (acc, val ) => acc + val, 0 ); } sum (1 , 2 , 3 , 4 ); const buttons = document .querySelectorAll ('button' );Array .prototype .map .call (buttons, btn => btn.textContent );const str = 'hello' ;Array .prototype .forEach .call (str, (char, index ) => { console .log (`${index} : ${char} ` ); }); const obj = { 0 : 'a' , 1 : 'b' , 2 : 'c' , length : 3 }; const arr = Array .prototype .slice .call (obj);
5.4.3 现代替代方案 1 2 3 4 5 6 7 8 9 10 11 12 13 const nodeList = document .querySelectorAll ('div' );Array .prototype .forEach .call (nodeList, callback);const nodes = Array .from (nodeList);nodes.forEach (callback); [...nodeList].forEach (callback); const texts = Array .from (nodeList, node => node.textContent );
5.5 this 绑定的复杂场景 5.5.1 多层调用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const obj1 = { name : 'obj1' , obj2 : { name : 'obj2' , greet ( ) { console .log (this .name ); } } }; obj1.obj2 .greet (); const fn = obj1.obj2 .greet ;fn ();
5.5.2 回调函数中的 this 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const obj = { name : 'Object' , data : [1 , 2 , 3 ], process ( ) { this .data .forEach (function (item ) { console .log (this .name , item); }); const self = this ; this .data .forEach (function (item ) { console .log (self.name , item); }); this .data .forEach (item => { console .log (this .name , item); }); this .data .forEach (function (item ) { console .log (this .name , item); }.bind (this )); } }; obj.process ();
5.5.3 构造函数中的 this 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function Person (name ) { this .name = name; setTimeout (function ( ) { console .log (this .name ); }, 100 ); setTimeout (() => { console .log (this .name ); }, 100 ); setTimeout (function ( ) { console .log (this .name ); }.bind (this ), 100 ); const self = this ; setTimeout (function ( ) { console .log (self.name ); }, 100 ); } const person = new Person ('Alice' );
6. 代码片段(可直接粘贴试验) 6.1 闭包计数器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function createCounter (start = 0 ) { let count = start; return { inc (step = 1 ) { count += step; return count; }, reset ( ) { count = start; }, value ( ) { return count; }, setValue (newValue ) { count = newValue; } }; } const c = createCounter (10 );console .log (c.value ()); c.inc (); c.inc (5 ); c.reset (); c.setValue (100 ); console .log (c.value ());
6.2 节流与防抖(完整版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function throttle (fn, wait = 100 , options = {} ) { let last = 0 ; let timer = null ; const { leading = true , trailing = true } = options; return function (...args ) { const now = Date .now (); if (!last && !leading) { last = now; } if (now - last >= wait) { if (timer) { clearTimeout (timer); timer = null ; } last = now; return fn.apply (this , args); } else if (trailing && !timer) { timer = setTimeout (() => { last = Date .now (); timer = null ; fn.apply (this , args); }, wait - (now - last)); } }; } function debounce (fn, wait = 200 , immediate = false ) { let timer = null ; return function (...args ) { const callNow = immediate && !timer; clearTimeout (timer); timer = setTimeout (() => { timer = null ; if (!immediate) { fn.apply (this , args); } }, wait); if (callNow) { fn.apply (this , args); } }; } const handleScroll = throttle (() => { console .log ('Scrolling...' ); }, 100 ); const handleInput = debounce ((value ) => { console .log ('Searching for:' , value); }, 300 );
6.3 用闭包做简单缓存(Memoize) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function memoize (fn, keyGenerator = JSON .stringify ) { const cache = new Map (); return function (...args ) { const key = keyGenerator (args); if (cache.has (key)) { console .log ('Cache hit' ); return cache.get (key); } console .log ('Cache miss' ); const result = fn.apply (this , args); cache.set (key, result); return result; }; } const slowAdd = (a, b ) => { let sum = 0 ; for (let i = 0 ; i < 1000000 ; i++) { sum += i; } return a + b; }; const fastAdd = memoize (slowAdd);console .log (fastAdd (1 , 2 )); console .log (fastAdd (1 , 2 )); const memoizedFetch = memoize ( async (url) => { const response = await fetch (url); return response.json (); }, (args ) => args[0 ] );
6.4 arguments 与剩余参数对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function demo (a, b ) { console .log ('Arguments length:' , arguments .length ); console .log ('Arguments:' , arguments [0 ], arguments [1 ]); console .log ('Is array:' , Array .isArray (arguments )); const arr = Array .from (arguments ); console .log ('Converted array:' , arr.map (x => x * 2 )); } demo (1 , 2 , 3 ); function demoRest (a, b, ...args ) { console .log ('Rest length:' , args.length ); console .log ('Rest:' , args); console .log ('Is array:' , Array .isArray (args)); console .log ('Mapped:' , args.map (x => x * 2 )); } demoRest (1 , 2 , 3 , 4 , 5 ); function legacySum ( ) { const args = Array .from (arguments ); return args.reduce ((a, b ) => a + b, 0 ); } function modernSum (...args ) { return args.reduce ((a, b ) => a + b, 0 ); }
6.5 this 绑定示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 const obj = { x : 1 , show ( ) { console .log (this .x ); }, arrow : () => { console .log (this .x ); } }; const fn = obj.show ;fn (); fn.call ({ x : 2 }); fn.apply ({ x : 3 }); obj.show (); const bound = obj.show .bind ({ x : 4 });bound (); obj.arrow (); const obj2 = { x : 10 , nested : { x : 20 , show ( ) { console .log (this .x ); } } }; obj2.nested .show (); setTimeout (obj.show , 100 ); setTimeout (() => obj.show (), 100 ); setTimeout (obj.show .bind (obj), 100 );
6.6 函数组合与管道 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function compose (...fns ) { return function (value ) { return fns.reduceRight ((acc, fn ) => fn (acc), value); }; } function pipe (...fns ) { return function (value ) { return fns.reduce ((acc, fn ) => fn (acc), value); }; } const add1 = x => x + 1 ;const multiply2 = x => x * 2 ;const square = x => x * x;const composed = compose (square, multiply2, add1);console .log (composed (5 )); const piped = pipe (add1, multiply2, square);console .log (piped (5 ));
6.7 柯里化(Currying) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function curry (fn ) { return function curried (...args ) { if (args.length >= fn.length ) { return fn.apply (this , args); } else { return function (...nextArgs ) { return curried.apply (this , args.concat (nextArgs)); }; } }; } function add (a, b, c ) { return a + b + c; } const curriedAdd = curry (add);console .log (curriedAdd (1 )(2 )(3 )); console .log (curriedAdd (1 , 2 )(3 )); console .log (curriedAdd (1 )(2 , 3 )); const multiply = (a, b ) => a * b;const curriedMultiply = curry (multiply);const double = curriedMultiply (2 );console .log (double (5 ));
6.8 函数重载模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function createOverload ( ) { const overloads = []; function overload (...args ) { for (const { condition, fn } of overloads) { if (condition (...args)) { return fn.apply (this , args); } } throw new Error ('No matching overload' ); } overload.add = function (condition, fn ) { overloads.push ({ condition, fn }); return overload; }; return overload; } const process = createOverload () .add ( (arg ) => typeof arg === 'string' , (str ) => `String: ${str} ` ) .add ( (arg ) => typeof arg === 'number' , (num ) => `Number: ${num * 2 } ` ) .add ( (arg ) => Array .isArray (arg), (arr ) => `Array: ${arr.join(', ' )} ` ); console .log (process ('hello' )); console .log (process (5 )); console .log (process ([1 , 2 , 3 ]));
7. 高级主题 7.1 生成器函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function * numberGenerator ( ) { yield 1 ; yield 2 ; yield 3 ; } const gen = numberGenerator ();console .log (gen.next ()); console .log (gen.next ()); console .log (gen.next ()); console .log (gen.next ()); function * fibonacci ( ) { let a = 0 , b = 1 ; while (true ) { yield a; [a, b] = [b, a + b]; } } const fib = fibonacci ();console .log (fib.next ().value ); console .log (fib.next ().value ); console .log (fib.next ().value ); console .log (fib.next ().value );
7.2 异步函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function fetchData (url ) { try { const response = await fetch (url); const data = await response.json (); return data; } catch (error) { console .error ('Error:' , error); throw error; } } const data = fetchData ('https://api.example.com/data' );console .log (data instanceof Promise ); async function processData ( ) { const data1 = await fetchData ('url1' ); const data2 = await fetchData ('url2' ); return { data1, data2 }; }
7.3 函数式编程模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const numbers = [1 , 2 , 3 , 4 , 5 ];const doubled = numbers.map (n => n * 2 );const evens = doubled.filter (n => n % 2 === 0 );const sum = evens.reduce ((a, b ) => a + b, 0 );const result = numbers .map (n => n * 2 ) .filter (n => n % 2 === 0 ) .reduce ((a, b ) => a + b, 0 ); function pureAdd (a, b ) { return a + b; } const operations = [ x => x * 2 , x => x + 1 , x => x ** 2 ]; const applyOperations = (value ) => operations.reduce ((acc, op ) => op (acc), value); console .log (applyOperations (5 ));
8. 速记口诀与最佳实践 8.1 速记口诀
**”声明整体升,表达式不升值”**:声明能提前用,表达式要先定义。
**”默认只认 undefined,null 不顶班”**:默认值只在 undefined 时触发。
**”箭头不 new、无 this、无 arguments”**:想要动态 this 用普通函数。
**”闭包记定义环境,不记调用环境”**:this 看调用,变量查词法。
**”this 先 new,后 call/apply/bind,再看点号,最后默认,箭头例外”**。
8.2 最佳实践 8.2.1 函数声明选择 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function formatDate (date ) { return new Intl .DateTimeFormat ().format (date); } const handler = condition ? function ( ) { } : function ( ) { }; array.map (item => item * 2 ); const obj = { value : 1 , bad : () => this .value , good ( ) { return this .value ; } };
8.2.2 参数处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function apiCall (url, { method = 'GET' , headers = {} } = {} ) { } function safeDivide (a, b ) { if (typeof a !== 'number' || typeof b !== 'number' ) { throw new TypeError ('Arguments must be numbers' ); } if (b === 0 ) { throw new Error ('Division by zero' ); } return a / b; }
8.2.3 闭包使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function createCounter ( ) { let count = 0 ; return { }; } for (var i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 100 ); } for (let i = 0 ; i < 3 ; i++) { setTimeout (() => console .log (i), 100 ); }
8.2.4 this 绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 class Component { handleClick = () => { }; } const bound = obj.method .bind (obj);const fn = obj.method ;fn ();
8.3 调试技巧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const process = function processData (data ) { return data.map (); }; function debugFunction ( ) { console .trace ('Function called' ); } function complexFunction ( ) { debugger ; } function measurePerformance (fn ) { return function (...args ) { const start = performance.now (); const result = fn (...args); const end = performance.now (); console .log (`Function took ${end - start} ms` ); return result; }; }
9. 总结 9.1 核心概念回顾
函数声明 vs 表达式 :提升行为不同,选择取决于使用场景
参数处理 :默认值、剩余参数、解构的灵活运用
箭头函数 :词法 this 绑定,适合回调,不适合方法
高阶函数 :函数作为一等公民,实现抽象和组合
递归 :三要素,注意栈溢出和性能优化
作用域 :全局、函数、块级作用域,作用域链查找
闭包 :函数”记住”定义环境,实现私有化和函数工厂
this 绑定 :new > 显式 > 隐式 > 默认,箭头函数例外
9.2 实践建议
优先使用函数声明 用于工具函数和需要提升的场景
使用箭头函数 用于回调和需要捕获外层 this 的场景
使用剩余参数 替代 arguments,获得真正的数组
理解闭包 但避免意外的闭包和内存泄漏
明确 this 绑定 ,必要时使用 bind 或箭头函数
使用严格模式 ,获得更好的错误提示和安全性
合理使用递归 ,注意性能和栈溢出问题
9.3 进一步学习
ES6+ 新特性:生成器、异步函数、类方法
函数式编程:纯函数、不可变性、函数组合
设计模式:工厂模式、模块模式、装饰器模式
性能优化:记忆化、惰性求值、尾调用优化
测试:单元测试、函数测试、边界情况测试
记住 :函数是 JavaScript 的核心,深入理解函数机制是写出高质量代码的基础。多实践、多思考、多调试,才能真正掌握函数的精髓。