JavaScript函数核心概念与实战技巧

发布时间:2026/9/19 10:46:11
JavaScript函数核心概念与实战技巧 1. JavaScript函数基础与核心概念在编程世界中函数就像厨房里的多功能料理机——你把食材参数放进去选择功能逻辑处理就能得到加工好的菜品返回值。作为JavaScript的核心构建块函数的重要性怎么强调都不为过。我见过太多初学者因为对函数理解不透彻导致后期学习闭包、回调等高阶概念时举步维艰。函数本质上是一段可重复调用的代码块它解决了代码复用和逻辑封装的问题。想象你要在程序中多次计算商品折扣价没有函数的话就得到处复制粘贴相同的计算代码而使用函数只需要定义一次就能反复调用。更重要的是良好的函数设计能让代码像乐高积木一样模块化这也是现代前端框架如React、Vue重度依赖函数式编程的原因。重要提示虽然现代JavaScript支持箭头函数等新特性但建议初学者先从传统的function声明开始学习这有助于建立完整的函数作用域概念。2. 函数定义与调用的四种姿势2.1 函数声明式最经典的方式function calculateDiscount(price, rate) { return price * (1 - rate); } console.log(calculateDiscount(100, 0.2)); // 输出80这种定义方式会被JavaScript引擎提升hoisting意味着你可以在声明前调用它。但实际开发中我强烈建议避免这种操作因为会导致代码可读性变差。2.2 函数表达式更灵活的用法const greet function(name) { return Hello, ${name}!; };这种方式定义的函数不会被提升必须定义后才能调用。我在实际项目中更倾向使用这种方式因为它强制形成了良好的编码习惯——先定义后使用。2.3 箭头函数ES6新特性const square x x * x;箭头函数不仅语法简洁更重要的是它没有自己的this绑定。在React开发中这能避免很多this指向的坑。但要注意箭头函数不能用作构造函数也没有arguments对象。2.4 构造函数方式较少使用const sum new Function(a, b, return a b);这种方式存在安全风险和性能问题除非特殊场景如动态生成函数否则不建议使用。我在实际工作中几乎从未用过这种写法。3. 函数参数的高级玩法3.1 默认参数设置ES6之前我们需要这样设置默认值function log(message, level) { level level || info; // 旧式默认值 }现在可以更直观function log(message, level info) { console[level](message); }但要注意默认参数是在调用时求值的每次调用都会创建一个新的默认值对象。3.2 剩余参数Rest Parametersfunction sum(...numbers) { return numbers.reduce((acc, curr) acc curr, 0); }这比传统的arguments对象更直观可靠。我在处理不定数量参数时一定会用这种方式。3.3 参数解构function drawChart({size big, coords {x: 0, y: 0}, radius 25} {}) { console.log(size, coords, radius); }这种写法在配置对象处理时特别有用能显著提升代码可读性。4. 作用域与闭包实战解析4.1 作用域链的坑与解法function init() { var name Mozilla; // name是init函数的局部变量 function displayName() { // displayName是内部函数 console.log(name); // 使用了父函数中声明的变量 } displayName(); } init();这里有个常见误区闭包不是刻意创建的而是JavaScript作用域规则的自然结果。我在面试中经常看到候选人强行制造闭包其实完全没必要。4.2 经典的循环闭包问题for (var i 0; i 5; i) { setTimeout(function() { console.log(i); // 输出5个5 }, 100); }解决方案// 使用let for (let i 0; i 5; i) { setTimeout(() console.log(i), 100); } // 或使用IIFE for (var i 0; i 5; i) { (function(j) { setTimeout(() console.log(j), 100); })(i); }这个例子完美展示了var和let在作用域上的区别也是面试必考题。5. 高阶函数与函数组合5.1 函数作为参数回调模式function fetchData(callback) { // 模拟异步操作 setTimeout(() { callback(数据加载完成); }, 1000); } fetchData(message console.log(message));这是Node.js中非常常见的模式但要注意回调地狱问题。在实际项目中我通常会结合Promise或async/await来优化。5.2 函数作为返回值工厂模式function createMultiplier(factor) { return function(number) { return number * factor; }; } const double createMultiplier(2); console.log(double(5)); // 10这种模式在创建特定行为函数时非常有用比如React中的高阶组件(HOC)就是基于这个原理。5.3 函数组合const compose (...fns) x fns.reduceRight((acc, fn) fn(acc), x); const add5 x x 5; const multiply3 x x * 3; const square x x * x; const transform compose(square, multiply3, add5); console.log(transform(2)); // ((2 5) * 3)^2 441函数式编程中组合比继承更灵活。我在Redux的中间件机制中经常使用这种模式。6. 异步函数的最佳实践6.1 从回调到Promise// 回调地狱 getData(function(a) { getMoreData(a, function(b) { getMoreData(b, function(c) { console.log(最终结果:, c); }); }); }); // Promise链式调用 getData() .then(a getMoreData(a)) .then(b getMoreData(b)) .then(c console.log(最终结果:, c)) .catch(err console.error(err));在实际项目中Promise极大地改善了异步代码的可读性。但要注意总是添加catch处理错误。6.2 async/await的优雅写法async function processData() { try { const a await getData(); const b await getMoreData(a); const c await getMoreData(b); console.log(最终结果:, c); } catch (err) { console.error(处理失败:, err); } }这是我目前最推荐的异步处理方式代码就像同步代码一样直观。但要注意await只能在async函数中使用。7. 性能优化与调试技巧7.1 避免不必要的函数创建// 不推荐每次渲染都创建新函数 function Component() { return button onClick{() console.log(点击)}按钮/button; } // 推荐使用useCallback缓存 function Component() { const handleClick useCallback(() console.log(点击), []); return button onClick{handleClick}按钮/button; }在React等框架中这个优化能避免不必要的子组件重渲染。7.2 使用console的高级功能function complexFunction() { console.time(执行耗时); // 复杂计算... console.timeEnd(执行耗时); // 输出执行时间 console.trace(当前调用栈); // 查看调用关系 }这些console方法在性能分析和调试时非常有用比单纯的console.log强大得多。7.3 记忆化(Memoization)优化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; }; } const factorial memoize(n n 1 ? 1 : n * factorial(n - 1));对于计算密集函数记忆化能大幅提升性能。React的useMemo就是基于类似原理。8. 实战练习项目8.1 实现一个简单的任务队列class TaskQueue { constructor(concurrency 1) { this.concurrency concurrency; this.running 0; this.queue []; } push(task) { this.queue.push(task); this.next(); } next() { while (this.running this.concurrency this.queue.length) { const task this.queue.shift(); task(() { this.running--; this.next(); }); this.running; } } } // 使用示例 const queue new TaskQueue(2); queue.push(done { setTimeout(() { console.log(任务1完成); done(); }, 1000); });这个模式在处理并发控制时非常实用比如文件上传、批量请求等场景。8.2 实现一个简单的状态机function createStateMachine(definition) { let currentState definition.initialState; const transitions definition.transitions; return { state() { return currentState; }, transition(event) { const nextState transitions[currentState][event]; if (!nextState) throw new Error(无效转换: ${currentState} - ${event}); const beforeExit definition.states[currentState].exit; if (beforeExit) beforeExit(); currentState nextState; const afterEnter definition.states[nextState].enter; if (afterEnter) afterEnter(); return this.state(); } }; } // 使用示例 const fsm createStateMachine({ initialState: idle, states: { idle: { enter: () console.log(进入空闲状态), exit: () console.log(离开空闲状态) }, // 其他状态... }, transitions: { idle: { start: running }, running: { stop: idle } } });状态机模式在前端复杂交互和游戏开发中非常有用能有效管理状态流转。8.3 实现一个简单的中间件系统function createMiddlewarePipeline(context) { const middleware []; let index 0; const use fn middleware.push(fn); const run () { if (index middleware.length) return Promise.resolve(); const layer middleware[index]; try { const next () run(); return Promise.resolve(layer(context, next)); } catch (err) { return Promise.reject(err); } }; return { use, run }; } // 使用示例 const pipeline createMiddlewarePipeline({ req: {}, res: {} }); pipeline.use(async (ctx, next) { console.log(中间件1开始); await next(); console.log(中间件1结束); }); pipeline.run();这个模式是Express、Koa等框架的核心机制理解它对于深入学习Node.js非常重要。9. 常见问题与解决方案9.1 this指向问题const obj { name: Alice, greet: function() { console.log(Hello, ${this.name}); } }; const greet obj.greet; greet(); // 输出 Hello, undefined解决方案// 使用bind const boundGreet obj.greet.bind(obj); // 或使用箭头函数 const obj { name: Alice, greet: () { console.log(Hello, ${this.name}); } };这个问题在事件处理回调中特别常见React类组件中通常需要在constructor中bind方法。9.2 异步错误处理async function fetchData() { try { const res await fetch(api/data); const data await res.json(); return data; } catch (err) { console.error(获取数据失败:, err); throw err; // 继续向上抛出 } } // 调用处也需要try-catch (async () { try { await fetchData(); } catch { // 处理错误 } })();在async函数中不要忘记处理可能的错误否则会导致静默失败。9.3 内存泄漏问题function setupListener() { const data getHugeData(); document.addEventListener(click, () { console.log(data.length); // 持有data引用 }); }解决方案function setupListener() { const data getHugeData(); const handler () { console.log(data.length); }; document.addEventListener(click, handler); // 适时移除 return () document.removeEventListener(click, handler); }在单页应用中忘记移除事件监听器是常见的内存泄漏原因。React的useEffect钩子中尤其需要注意这点。10. 函数式编程实践10.1 纯函数的好处// 不纯的函数 let taxRate 0.1; function calculateTax(amount) { return amount * taxRate; // 依赖外部状态 } // 纯函数 function calculateTaxPure(amount, rate) { return amount * rate; }纯函数更容易测试和维护输出完全由输入决定。我在Redux的reducer函数中始终坚持这个原则。10.2 不可变性实践// 修改原数组 const addItem (array, item) { array.push(item); return array; }; // 不修改原数组 const addItemImmutable (array, item) [...array, item];在React的状态更新中保持不可变性非常重要这能确保组件正确重渲染。10.3 函数柯里化function curry(fn) { return function curried(...args) { if (args.length fn.length) { return fn.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); }; } }; } const add curry((a, b, c) a b c); console.log(add(1)(2)(3)); // 6柯里化在函数组合和延迟执行场景中很有用虽然日常开发中直接使用的情况不多但理解这个概念很重要。11. 现代JavaScript函数特性11.1 可选链操作符(?.)const user { profile: { name: Alice } }; // 旧写法 const name user user.profile user.profile.name; // 新写法 const name user?.profile?.name;这个特性大大简化了深层对象属性访问的安全检查我在TypeScript项目中大量使用。11.2 空值合并运算符(??)const timeout settings.timeout ?? 3000; // 仅在null/undefined时使用默认值比||更精确因为||会对所有假值0、等生效。在配置处理时特别有用。11.3 顶层await// 在模块顶层可以直接使用await const data await fetchData(); console.log(data);这个特性简化了模块的异步初始化但要注意它会让模块加载变成异步操作。12. 测试与调试进阶12.1 单元测试最佳实践// 测试纯函数特别简单 function sum(a, b) { return a b; } describe(sum函数, () { it(应该正确相加两个数字, () { expect(sum(1, 2)).toBe(3); expect(sum(-1, 1)).toBe(0); }); });对于纯函数测试用例应该覆盖各种边界情况。我通常会为每个函数编写3-5个测试用例。12.2 使用debugger语句function complexCalculation(input) { debugger; // 执行到这里会暂停 // 复杂逻辑... }结合浏览器开发者工具debugger比console.log更适合调试复杂逻辑。但记得提交代码前移除这些语句。12.3 性能分析function heavyTask() { performance.mark(start); // 繁重计算... performance.mark(end); performance.measure(耗时, start, end); const measures performance.getEntriesByName(耗时); console.log(耗时: ${measures[0].duration}ms); }对于性能关键的函数使用Performance API进行精确测量比console.time更专业。13. 设计模式中的数应用13.1 策略模式const strategies { add: (a, b) a b, subtract: (a, b) a - b, multiply: (a, b) a * b }; function calculate(strategy, a, b) { return strategies[strategy](a, b); }这种模式在需要动态选择算法时非常有用比如表单验证规则。13.2 装饰器模式function withLogging(fn) { return function(...args) { console.log(调用 ${fn.name} 参数:, args); const result fn.apply(this, args); console.log(结果:, result); return result; }; } const add withLogging((a, b) a b); add(2, 3); // 会输出调用日志装饰器在不修改原函数的情况下扩展功能在AOP编程中很常见。13.3 观察者模式function createEventEmitter() { const listeners {}; return { on(event, fn) { (listeners[event] || (listeners[event] [])).push(fn); }, emit(event, ...args) { (listeners[event] || []).forEach(fn fn(...args)); } }; }这是事件驱动架构的基础Node.js的EventEmitter就是典型实现。14. 浏览器环境下的函数技巧14.1 防抖与节流function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer setTimeout(() fn.apply(this, args), delay); }; } function throttle(fn, interval) { let lastTime 0; return function(...args) { const now Date.now(); if (now - lastTime interval) { fn.apply(this, args); lastTime now; } }; } // 使用示例 window.addEventListener(resize, throttle(() { console.log(窗口大小改变); }, 200));这两个函数是优化高频事件如滚动、调整大小的必备工具。Lodash等库提供了更完善的实现。14.2 使用requestAnimationFramefunction animate(element) { let start null; function step(timestamp) { if (!start) start timestamp; const progress timestamp - start; element.style.transform translateX(${Math.min(progress / 10, 200)}px); if (progress 2000) { window.requestAnimationFrame(step); } } window.requestAnimationFrame(step); }对于动画效果requestAnimationFrame比setTimeout/setInterval更高效流畅。14.3 使用Proxy进行拦截const validator { set(target, prop, value) { if (prop age !Number.isInteger(value)) { throw new TypeError(年龄必须是整数); } target[prop] value; return true; } }; const person new Proxy({}, validator); person.age 30; // 正常 person.age 三十; // 抛出错误Proxy API可以实现各种高级拦截操作比如数据验证、自动持久化等。15. Node.js环境下的函数技巧15.1 错误优先回调function readFile(callback) { fs.readFile(file.txt, utf8, (err, data) { if (err) return callback(err); callback(null, data); }); } // 使用 readFile((err, data) { if (err) return console.error(读取失败:, err); console.log(文件内容:, data); });这是Node.js的传统异步模式虽然现在多用Promise但很多老模块仍采用这种方式。15.2 使用util.promisifyconst util require(util); const fs require(fs); const readFile util.promisify(fs.readFile); async function processFile() { try { const data await readFile(file.txt, utf8); console.log(data); } catch (err) { console.error(err); } }这个工具函数能快速将回调风格的函数转换为Promise版本非常实用。15.3 流处理中的函数应用const { Transform } require(stream); const upperCaseTr new Transform({ transform(chunk, encoding, callback) { this.push(chunk.toString().toUpperCase()); callback(); } }); process.stdin .pipe(upperCaseTr) .pipe(process.stdout);Node.js的流处理大量使用函数式模式Transform流特别适合数据处理管道。16. 函数安全与防御性编程16.1 参数验证function createUser({name, age, email} {}) { if (!name || typeof name ! string) { throw new TypeError(无效的用户名); } // 其他验证... }永远不要相信外部输入对函数参数进行严格验证能避免很多运行时错误。16.2 使用严格模式use strict; function unsafe() { undeclaredVar 42; // 在严格模式下会抛出错误 }严格模式能捕获一些常见错误如意外创建全局变量。我建议在所有新项目中启用。16.3 防止原型污染function merge(target, source) { for (const key in source) { if (key __proto__ || key constructor) continue; target[key] source[key]; } return target; }在处理用户提供的对象时要特别小心__proto__等特殊属性避免原型链污染攻击。17. 函数式React组件实践17.1 自定义Hook封装function useLocalStorage(key, initialValue) { const [value, setValue] useState(() { const stored localStorage.getItem(key); return stored ! null ? JSON.parse(stored) : initialValue; }); useEffect(() { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; } // 使用 const [name, setName] useLocalStorage(name, );自定义Hook是React函数组件中复用逻辑的最佳方式比高阶组件更灵活。17.2 使用useCallback优化function Parent() { const [count, setCount] useState(0); const increment useCallback(() { setCount(c c 1); }, []); // 依赖数组为空函数不会重建 return Child onClick{increment} /; }对于传递给子组件的事件处理函数使用useCallback能避免不必要的子组件重渲染。17.3 使用useMemo缓存计算function ExpensiveComponent({ items }) { const sortedItems useMemo(() { return [...items].sort((a, b) a.value - b.value); }, [items]); // 仅在items变化时重新计算 return div{sortedItems.map(/*...*/)}/div; }对于耗时的计算过程useMemo能显著提升性能特别是在频繁渲染的场景中。18. TypeScript中的函数增强18.1 函数类型定义type Calculator (a: number, b: number) number; const add: Calculator (a, b) a b;TypeScript的类型系统能让函数签名更清晰减少运行时类型错误。18.2 函数重载function formatDate(timestamp: number): string; function formatDate(date: Date): string; function formatDate(value: number | Date): string { const date typeof value number ? new Date(value) : value; return date.toISOString(); }函数重载能提供更精确的类型提示特别是在处理多种参数类型时。18.3 泛型函数function identityT(arg: T): T { return arg; } const output identitystring(hello);泛型增强了函数的灵活性在工具函数和集合操作中特别有用。19. 函数式工具库实践19.1 Lodash函数式工具const users [ { name: Alice, age: 25 }, { name: Bob, age: 30 } ]; const names _.map(users, name); const adults _.filter(users, user user.age 18);Lodash提供了大量经过优化的工具函数特别适合数据处理场景。19.2 Ramda的纯函数风格const R require(ramda); const isEven n n % 2 0; const double n n * 2; const processNumbers R.pipe( R.filter(isEven), R.map(double), R.take(3) ); processNumbers([1, 2, 3, 4, 5, 6]); // [4, 8, 12]Ramda强调纯函数和不可变是函数式编程的绝佳工具库。19.3 RxJS的函数式响应编程import { fromEvent } from rxjs; import { throttleTime, map } from rxjs/operators; fromEvent(document, click) .pipe( throttleTime(1000), map(event event.clientX) ) .subscribe(x console.log(x));RxJS将函数式编程与响应式编程结合特别适合处理事件流和异步数据流。20. 函数性能优化进阶20.1 尾调用优化function factorial(n, acc 1) { if (n 1) return acc; return factorial(n - 1, n * acc); // 尾调用形式 }在ES6严格模式下这种形式的递归不会增加调用栈可以避免栈溢出。但要注意不是所有引擎都实现了这个优化。20.2 Web Worker中的函数使用// main.js const worker new Worker(worker.js); worker.postMessage({ cmd: calculate, data: 42 }); worker.onmessage e console.log(e.data); // worker.js self.onmessage function(e) { if (e.data.cmd calculate) { const result heavyCalculation(e.data.data); self.postMessage(result); } };将计算密集型函数放到Web Worker中能避免阻塞主线程提升页面响应速度。20.3 使用WASM优化关键函数// 加载WASM模块 WebAssembly.instantiateStreaming(fetch(optimized.wasm)) .then(obj { const fastCalculation obj.instance.exports.fastCalculation; console.log(fastCalculation(42)); // 调用WASM函数 });对于性能极其敏感的算法可以考虑用Rust等语言编写并编译为WASM在JavaScript中调用。21. 函数调试高级技巧21.1 使用console.tablefunction getUsers() { return [ { id: 1, name: Alice, age: 25 }, { id: 2, name: Bob, age: 30 } ]; } console.table(getUsers());对于结构化数据console.table比console.log更直观特别适合数组和对象数组。21.2 条件断点function processItems(items) { items.forEach(item { // 在Chrome调试器中设置条件断点: item.id 42 console.log(item); }); }现代浏览器调试器支持设置条件断点只在特定条件下暂停提高调试效率。21.3 使用debug函数function createDebug(namespace) { return function(...args) { if (localStorage.getItem(debug) namespace) { console.log([${namespace}], ...args); } }; } const debug createDebug(myapp); debug(这条消息只在调试时显示);这种模式比到处写console.log更可控可以通过localStorage动态启用/禁用调试输出。22. 函数文档与注释规范22.1 JSDoc标准注释/** * 计算两个数字的和 * param {number} a - 第一个加数 * param {number} b - 第二个加数 * returns {number} 两个参数的和 * throws {TypeError} 当参数不是数字时抛出 */ function add(a, b) { if (typeof a ! number || typeof b ! number) { throw new TypeError(参数必须是数字); } return a b; }良好的JSDoc注释能让代码更易维护还能被编辑器用于智能提示。22.2 TypeScript类型注释interface User { name: string; age: number; } function createUser(name: string, age: number): User { return { name, age }; }TypeScript的类型注释既是文档也是约束能显著提升代码质量。22.3 示例代码注释// 好的示例注释 function parseQueryString(query) { // 将 a1b2 转换为 { a: 1, b: 2 } return query.split().reduce((acc, pair) { const [key, value] pair.split(); acc[key] value; return acc; }, {}); }示例注释应该展示函数的典型用法而不是重复函数名已经表达的内容。23. 函数命名最佳实践23.1 动词开头的命名// 好的命名 function calculateTotal() {} function getUserById() {} function isValidInput() {} // 不好的命名 function total() {} // 缺少动词 function user() {} // 过于模糊 function check() {} // 动词太通用函数名应该明确表达其行为通常以动词开头遵循做什么而不是是什么的原则。23.2 一致的命名约定// 要么全用动词开头 function fetchData() {} function renderList() {} // 要么全用名词表示动作 function dataFetcher() {} function listRenderer() {}在项目中保持一致的命名风格比选择哪种风格更重要这能提高代码可读性。23.3 避免误导性名称// 不好的例子 function getData() { return fetch(/api).then(r r.json()); // 实际是异步操作 } // 更好的命名 async function fetchData() { const res await fetch(/api); return res.json(); }函数名应该准确反映其行为特别是对于异步操作使用fetch/load等前缀比get更合适。24. 函数重构技巧24.1 提取函数// 重构前 function processOrder(order) { // 验证逻辑... // 计算逻辑... // 保存逻辑... } // 重构后 function processOrder(order) { validateOrder(order); const total calculateTotal(order); saveOrder(order, total); }将长函数拆分为多个小函数每个函数只做一件事能显著提高可读性和可维护性。24.2 合并重复代码// 重构前 function createAdmin() { const user { role: admin }; validate(user); save(user); } function createUser() { const user { role: user }; validate(user); save(user); } // 重构后 function createUser(role) { const user { role }; validate(user); save(user); }消除重复代码是重构的重要目标之一能减少维护成本和出错几率。24.3 简化条件逻辑// 重构前 function getPrice(user) { if (user.type vip) { return basePrice * 0.8; } else if (user.type premium) { return basePrice * 0.7; } else { return basePrice; } } // 重构后 const priceStrategies { vip: base base * 0.8, premium: base base * 0.7, default: base base }; function getPrice(user) { const strategy priceStrategies[user.type] || priceStrategies.default; return strategy(basePrice); }用对象查找替换复杂的条件判断通常能使代码更清晰、更易扩展。25. 函数式编程实用案例25.1 解析URL查询参数const parseQuery query query.split().reduce((params, pair) { const [key, value] pair.split(); params[key] decodeURIComponent(value); return params; }, {}); // 使用 const params parseQuery(nameAliceage25);这个例子展示了如何用函数式风格处理字符串解析避免了命令式循环和临时变量。25.2 实现一个简单的Reduxfunction createStore(reducer) { let state; const listeners []; const getState () state; const dispatch action { state reducer(state, action); listeners.forEach(listener listener()); }; const subscribe listener { listeners.push(listener); return () { const index listeners.indexOf(listener); listeners.splice(index, 1); }; }; dispatch({}); // 初始化state return { getState, dispatch, subscribe }; }这个简化版Redux实现展示了函数式状态管理的核心思想纯函数reducer和不可变状态。25.3 实现Promise.allfunction promiseAll(promises) { return new Promise((resolve, reject) { const results []; let completed 0; promises.forEach((promise, index) { Promise.resolve(promise) .then(value { results[index] value; completed; if (completed promises.length) { resolve(results); } }) .catch(reject); }); if (promises.length 0) { resolve(results); } }); }这个实现帮助我们深入理解Promise的工作原理展示了如何处理异步集合操作。26. 函数式React模式26.1 受控组件抽象function useFormInput(initialValue) { const [value, setValue]