这是我参与「第五届青训营」笔记创作活动的第6天。这里记录下JS阶段学到的高阶函数。在React中,高阶函数的应用是非常普遍的,于vue可以在事件绑定中直接绑定事件并传入参数不同,React要求事件绑定的必须是一个函数。因此在需要给函数传参时,必须要写成高阶函数的形式。
一、本堂课重点内容:
JS高阶函数
二、详细知识点介绍:
JS中的高阶函数——HOF。
HOF
高阶函数的特点主要有以下几点:
- 以函数作为参数
- 以函数作为返回值
- 常作为函数的装饰器
下面这些代码是高阶函数的模板。返回的函数和作为参数的fn是等价的。
function HOF0(fn) {
// 返回一个函数。当这个函数执行的时候,传入的参数就是这里的..args
// 比如说当绑定给 onclick 事件时,得到的参数就是 MouseEvent 事件类型
// 因为触发了事件调用,就好比是 HOF0(fn)(),那么事件自然就作为参数填入了第二个括号
return function(...args) {
return fn.apply(this, args);
}
}
常见的高阶函数
事件只执行一次函数 Once
这个就是对高阶函数和JS函数闭包closure最基本的利用。利用函数闭包保存了作为参数传入once的真正函数func的词法环境,在执行一次func后立即将函数func置为null,确保其只会执行一次。
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
const foo = once(() => {
console.log('bar');
});
节流阀函数 throttle
节流阀函数的核心就是 在数据一次变化/某事件一次触发后 进行一段时间的冷却,在冷却期间任何对于数据尝试性的变化/对事件尝试性的触发都不生效。其本质是:确保在一段时间内,数据只变化一次/事件只能被有效触发一次,可以理解为“冷却”。
节流阀函数的一个典型的应用是 防止按钮的多次点击——比如说B站发送弹幕的“冷却”时间,避免机器人恶意刷屏。
// 节流函数
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
btn.onclick = throttle(function(e){
circle.innerHTML = parseInt(circle.innerHTML) + 1;
circle.className = 'fade';
setTimeout(() => circle.className = '', 250);
});
防抖函数 debounce
防抖的核心思想就是 等持续变化的数据停止变化/频繁触发的事件停止再次被触发 一小段时间后,再执行函数真正的逻辑。其实现和防抖不同,本质上是 在每一次检测的数据发生改变时,新建一个计时器或者重置之前已有的计时器,除非计时器计时归零时,数据没有发生下一次的变化,此时才执行函数真正的逻辑。 可以理解为:允许不断触发事件,但是每次触发事件都会“刷新读条”,直到“读条结束”才会执行函数真正的逻辑。
防抖函数应用于诸如 输入框输入时、检测鼠标移动时 等情景。个人感觉其特点是:所监测的数据高速变化,而我们只对数据变化的阶段性结果感兴趣,不追求记录数据的瞬时变化。
var i = 0;
setInterval(function(){
bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
// 防抖函数
function debounce(fn, dur){
dur = dur || 100;
var timer;
return function(){
// 很明显的重置计时器的操作
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
document.addEventListener('mousemove', debounce(function(evt){
var x = evt.clientX,
y = evt.clientY,
x0 = bird.offsetLeft,
y0 = bird.offsetTop;
console.log(x, y);
var a1 = new Animator(1000, function(ep){
bird.style.top = y0 + ep * (y - y0) + 'px';
bird.style.left = x0 + ep * (x - x0) + 'px';
}, p => p * p);
a1.animate();
}, 100));
延时函数 Consumer
延时函数和防抖函数不同,防抖只关心在频繁变化后的阶段性结果,而延时函数如同“记账”一般,无论触发了多少次事件,都会以一定的时间间隔一次次执行完毕。
function consumer(fn, time){
let tasks = [], // 用于记录任务的“账本”
timer; // 执行一次函数主体逻辑的间隔
return function(...args){
// 利用bind()函数,将参数args作为fn的参数,并将这个拥有了参数的新函数存入数组
tasks.push(fn.bind(this, ...args));
// 每隔一个时间间隔执行一次
if(timer == null){
timer = setInterval(() => {
tasks.shift().call(this)
if(tasks.length <= 0){
clearInterval(timer);
timer = null;
}
}, time)
}
}
}
btn.onclick = consumer((evt)=>{
let t = parseInt(count.innerHTML.slice(1)) + 1;
count.innerHTML = `+${t}`;
count.className = 'hit';
let r = t * 7 % 256,
g = t * 17 % 128,
b = t * 31 % 128;
count.style.color = `rgb(${r},${g},${b})`.trim();
setTimeout(()=>{
count.className = 'hide';
}, 500);
}, 800)
迭代批量操作函数
这里主要是为了实现自定义的迭代操作。
const isIterable = obj => obj != null
&& typeof obj[Symbol.iterator] === 'function';
function iterative(fn) {
// 如果说传给函数iterative(fn)()的第一个参数是可迭代的
return function(subject, ...rest) {
if(isIterable(subject)) {
const ret = [];
// 遍历可迭代参数的每一个成员值
for(let obj of subject) {
ret.push(fn.apply(this, [obj, ...rest]));
}
return ret;
}
return fn.apply(this, [subject, ...rest]);
}
}
const setColor = iterative((el, color) => {
el.style.color = color;
});
const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');
三、实践练习例子:
实践的例子在上面已经列出。
四、课后个人总结:
本章的知识点需要大量的实例和参考资料来辅助理解。
五、引用参考:
我主要是基于老师讲解提供的代码仓库进行理解和分析,并记录了自己的心得。