js函数相关高级用法
面试中经常会问到防抖、节流、函数柯里化,其他的一般不会问。
防抖函数(debouncing)
核心思想:对同一个函数进行连续调用时,只有最后次调用生效,
实现方式:使用setTimeout方法,每次调用时,清除上一次的timer,并将本次的timer记录下来就可以保证只有最后一次调用会生效
1 2 3 4 5 6 7 8 9 10 11 | function debounce(method,time){ var timer = null ; return function (){ var context = this ; //在函数执行的时候先清除timer定时器; if (timer)clearTimeout(timer); timer = setTimeout( function (){ method.call(context); },time); } } |
节流函数(throttling)
核心思想:对同一个函数进行连续调用时,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用
实现方式:使用setTimeout方法,给定两个时间,后面的时间减去前面的时间,到达我们给定的时间就去触发一次这个事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function throttle(method,time){ var timer = null ; var startTime = new Date(); return function (){ var context = this ; var endTime = new Date(); var resTime = endTime - startTime; //判断大于等于我们给的时间采取执行函数; if (resTime >= time){ method.call(context); //执行完函数之后重置初始时间,等于最后一次触发的时间 startTime = endTime; } } } |
函数柯里化(function currying)
把一个接收多个参数的函数分解成逐层调用的函数,每一层接收一部分参数,余下的参数由下一层再进行分解。
假如有如下函数:
1 2 3 4 | function add(a,b,c,d,e){ return a+b+c+d+e; } console.log(add(1,2,3,4,5)); //15 |
现在要求每一步调用最多只能传2个参数,改写为柯里化版本如下:
1 2 3 4 5 6 7 8 | function curryAdd(a,b){ return (c,d)=>{ return (e)=>{ return a+b+c+d+e; } } } console.log(curryAdd(1,2)(3,4)(5)); //15 |
看似把简单问题进行了复杂化,那么柯里化函数有什么作用呢?
最大的用处就是可以固定不可变参数和可变参数,消除重复参数
1 2 3 4 5 6 7 8 | function ajax(url,type,data){ // ... } ajax( 'www.my.com' , 'GET' ) ajax( 'www.my.com' , 'POST' )<br> //采化柯里化固定重复参数 let newAjax=curryAjax( 'www.my.com' )<br> //只需要传入可变参数即可 newAjax( 'GET' ) newAjax( 'POST' ) |
纯函数(pure function)
所谓纯函数就是一个函数返回的结果只依赖于其参数,并且在执行过程中没有副作用,这样的函数就叫纯函数。
条件1:返回结果直依赖参数
1 2 3 | const a = 1 const foo = (b) => a + b foo(2) // => 3 |
上面的代码中,foo函数的返回值依赖了外部的a变理,因此不是纯函数
1 2 3 | const a = 1 const foo = (x, b) => x + b foo(1, 2) // => 3 |
上面的代码中,foo函数的返回值只跟其参数有关且没有副作用,因此是纯函数
条件2:执行过程没有副作用
什么是副作用?所谓副作用就是指函数执行过程中对外产生了可观察的变化。看例子
1 2 3 4 5 6 7 8 | const a = 1 const counter = { x: 1 } const foo = (obj, b) => { obj.x = 2 return obj.x + b } foo(counter, 2) // => 4 counter.x // => 2 |
上面代码中foo
的结果只跟传入参数有关,但在内部加了一句 obj.x = 2
,计算前 counter.x
是 1,但是计算以后 counter.x
是 2。foo
函数的执行对外部的 counter
产生了影响,修改了外部传进来的对象,也就是产生了副作用,因此不是纯函数。
1 2 3 4 5 6 7 | const a = 1 const counter = { x: 1 } const foo = (obj, b) => { return obj.x + b } foo(counter, 2) // => 3 counter.x // => 1 |
上面代码中foo的结果只跟传入的参数有关,且计算的过程里面并不会对传入的对象进行修改,计算前后的 counter
不会发生任何变化,计算前是 1,计算后也是 1,它现在是纯的。
下面的代码也是纯函数,因为对象是在函数内定义的,对外部不可见。
1 2 3 4 5 | const foo = (b) => { const obj = { x: 1 } obj.x = 2 return obj.x + b } |
惰性载入函数(lazy function)
使用场景:当一个函数中的判断分支只用执行一次(第一次调用时执行),后续不会再变化,则可以使用惰性函数来提高性能。
1 2 3 4 5 6 7 8 | var addEvent = function (elem, type, handler) { if (window.addEventListener) { elem.addEventListener(type, handler, false ); } else if (window.attachEvent) { elem.attachEvent( 'on' + type, handler); } }; |
上面的函数是一个事件监听函数,每次调用时都会判断使用标准的事件监听函数还是IE事件监听函数,其实只用判断一次就可以知道该使用哪种监听函数,改为惰性函数的代码如下:
var addEvent=function(elem, type, handler){ if (window.addEventListener) { addEvent=function(elem, type, handler){ elem.addEventListener(type, handler, false); } } else if (window.attachEvent) { addEvent=function(elem, type, handler){ elem.attachEvent(type, handler, false); } } //当addEvent函数第一次被外部调用时,会被覆盖为另外的函数,但覆盖后的函数体的代码不会执行,因此需要执行一次 addEvent(elem,type,handler); }
另一种方法是声明后立即给出合适的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var addEvent2=( function (elem, type, handler){ if (window.addEventListener) { return function (elem, type, handler){ elem.addEventListener(type, handler, false ); } } else if (window.attachEvent) { return function (elem, type, handler){ elem.attachEvent(type, handler, false ); } } })(document.body, 'click' , function (){ console.log( '单击了' ); }) console.log(addEvent2) |
记忆函数
核心思想:让一个函数记住处理过的参数,并可以根据相应的参数把结果缓存起来,避免重复计算。
实现方式:
1.使用闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function memoryFunction(fn){ var cacheArgs,cacheReturn; return function (a){ if (a===cacheArgs){ return cacheReturn; } cacheArgs=a; cacheReturn=fn.call( null ,a); return cacheReturn; } } var demo=memoryFunction((a)=>a*1000); var b=demo(1), c=demo(1); console.log(b,c) |
上面代码中,第二次调用demo函数时,直接返回值,不会再次计算
2.函数也是一个对象,因此可以动态的给函数增加静态的属来记录缓存键和结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function isPrime(value) { if (!isPrime.answers) { isPrime.answers = {}; } //创建缓存 if (isPrime.answers[value] !== undefined) { return isPrime.answers[value]; } //检查缓存键 var prime = value !== 0 && value !== 1; // 1 is not a prime for ( var i = 2; i < value; i++) { if (value % i === 0) { prime = false ; break ; } } //缓存计算的结果 return isPrime.answers[value] = prime; } |
组合函数(compose function)
作用:将需要嵌套执行的函数平铺。嵌套执行指的是,一个函数的返回值将作为另一个函数的参数。
function C(){ console.log('C') return function(){ console.log('C函数回调执行') } } function B(CM){ console.log('B') return function(){ console.log('B函数回调执行') CM() } } function A(BM){ console.log('A') return function(){ console.log('A函数回调执行') BM() } } A(B(C()))()
改为compose方式后调用如下;
compose(A,B,C)()
compose函数的实现:
function compose(...funcs) { //没有传入函数参数,就返回一个默认函数(直接返回参数) if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { // 单元素数组时调用reduce,会直接返回该元素,不会执行callback;所以这里手动执行 return funcs[0] } // 依次拼凑执行函数 return funcs.reduce((prev, current) => (...args) => prev(current(...args))) }
举例分析:compose(f4,f3,f2,f1)()
- reduce回调函数第一次执行时,返回值为 函数
(...args) => f4(f3(...args))
,作为下一次执行的prev参数 - 回调函数第二次执行时,返回值为 函数
(...args) => f4(f3(f2(...args)))
,作为下一次执行的prev参数 - 回调函数第三次执行时,返回值为 函数
(...args) => f4(f3(f2(f1(...args))))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了