一、SetTimout和SetInterval
理解书中这部分内容需要对JavaScript运行机制有一些认识:
JavaScript运行机制:
www.ruanyifeng.com/blog/2014/1…
浏览器运行机制:zhuanlan.zhihu.com/p/102149546
提取了相关的几个要点:
(一)浏览器渲染进程
1、每个Tab页面对应一个浏览器渲染进程。
2、浏览器渲染进程是多线程进程,其中GUI渲染线程和JS引擎线程互斥。包括的线程有:
- GUI渲染线程:渲染浏览器页面。
- JS引擎线程: 解析并运行JavaScript。
- 事件触发线程:管理事件队列。当事件符合其触发条件时,该线程会把事件添加到事件队列,等待JS引擎处理。
- 定时器触发线程:计时并触发定时(setTimout,setInterval)。计时完毕回调函数会被事件触发线程添加到事件队列。
- 异步http请求线程:处理XMLHttpRequest请求的线程。监听到状态变化后回调函数会被事件触发线程添加到事件队列。
(二)JavaScript运行机制
- JS引擎线程可以处理同步任务和异步任务,事件触发线程管理者一个事件队列,异步任务(DOM事件回调,定时器回调,AJAX响应回调)有了运行结果就会往队列中放置一个事件。
- JS引擎线程优先执行同步任务,同步任务会压入执行栈。
- JS引擎处理完同步任务,空闲时就查询事件队列是否有异步任务,有则压入执行栈执行,无则挂着等待。
(一)SetTimeout
setTimeout(()=>{
console.log("HELLO WORLD")
},250);
以上面代码为例,基于事件循环机制的认识,定时器触发线程计算到250ms后,setTimeout回调函数才被推进事件队列。因此回调函数什么时候执行,还要看JavaScript引擎线程是否空闲,事件队列中有多少个没处理任务。理想情况下,事件队列里没有其他任务且JavaScript引擎线程空闲,就会在250ms之后执行回调。
再看书中例子,就很容易理解了,假设执行onclick的回调需要耗费300ms,改回调在执行到第5ms时创建定时器。
btn.onclick=function(){
setTimeout(()=>{
console.log("HELLO WORLD")
},250);
}
(二)SetInterval
JavaScript为避免定时器代码不断执行,做了一定的处理:当没有定时器的任何回调在执行时,才会将新的定时器回调添加到事件队列。在这种情况下,定时器回调的执行间隔可能回比与其小。假设执行循环定时器回调要300ms。
setInterval(()=>{
console.log("HELLO WORLD")
},250);
JavaScript进程时间线如下,回调的执行间隔理想情况下只有200ms。
二、函数防抖与节流
基本思想:某些代码可以在几乎没有间断的情况下重复执行,譬如一直滚动页面触发滚动回调,一直输入触发输入回调。但是不能让他这样做因为耗费资源又没意义,根据交互场景,采取函数防抖或者函数节流措施。
(一)函数节流
zhuanlan.zhihu.com/p/110810391
(二)函数防抖
zhuanlan.zhihu.com/p/110733457
(三)函数节流和函数防抖的区别
什么时候应该用函数节流?什么时候应该用函数防抖?
这里只根据上面链接中给的例子,总结一些理解。
函数防抖:搜索框不断输入,输入10个字符,如果每次输入都发送依次请求就发送10次,但是用户真正要搜索的内容是10个字符拼接起来的单词,所以中间9次就是徒劳的请求。所以,函数防抖就是延迟执行回调,等待某些工作完成再去执行回调。因为最终剩下的定时器就是最后一次输入设置的定时器,所以输入完成时间+定时时间之后,这个定时器回调才进入事件队列,等待执行。
函数节流:页面滚动到一定距离出现回到顶部按钮的例子,页面一直滚动就一直计算scrollTop。如果是用函数防抖的方式,最后就是页面完全不滚动了之后才会出现回到顶部按钮,显示这是不合理的。而函数节流,就是在通过指定执行回调时间单位>触发事件的时间间隔,降低执行回调频率。在页面滚动的场景中,就是每隔200ms执行一次回调,在200ms内,只有第一次执行是生效的,之后达到200ms才执行一次,直到没有滚动事件发生。
三、高阶函数技术
函数:函数被称为“一等公民”,指的是函数与其他数据类型一样,处于平等地位。函数是一种特殊类型的对象,它们是 Function objects。
(一)高阶函数
如果一个函数符合下面两个规范的任何一个,那该函数是高阶函数。
1、若A函数,接收的参数是一个函数,则A就可以称为高阶函数;
2、若A函数,调用的返回值是一个函数,那么A就可以称为高阶函数。
常用的高阶函数有:Promise、setTimeout、数组的很多方法(Array.prototype.map等)。
高阶函数参考:juejin.cn/post/684490…
(二)组合函数
组合函数参考:juejin.cn/post/689288…(下文引用这篇文章则标为链接1)
(三)函数柯里化
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。如下所示:
function sum(a){
return (b)=>{
return(c)=>{
return a+b+c
}
}
}
let result=sum(1)(2)(3)
下面是使用《JavaScript高级程序设计》给出的创建柯里化函数通用方式,对函数柯里化的例子:
//创建柯里化函数的通用方式,《JavaScript高级程序设计》
function curry(fn){
var args=Array.prototype.slice.call(arguments,1); //arguments是curry函数的参数数组
return function(){
var innerArgs=Array.prototype.slice.call(arguments); //arguments是这个匿名函数的参数数组
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
}
}
//要被柯里化的函数
var fn = function(a, b, c) {
return [a, b, c];
};
var r1 =curry(fn,1,2,3)();
console.log(r1); //[1,2,3]
var r2 =curry(fn,4)(5,6);
console.log(r2); //[4,5,6]
var r3 =curry(fn)(7,8,9);
console.log(r3); //[7,8,9]
不过在很多博客中,上面算是一个偏函数,柯里化函数是偏函数的一个特殊形式。
偏函数应用是固定一个函数的一个或多个参数,并返回一个可以接收剩余参数的函数; 柯里化是将函数转化为多个嵌套的一元函数,也就是每个函数只接收一个参数(实际可以一一个或多个参数)。
可以对上面的函数做一个改造,如下所示
//与上面相比,增加了一层,curry的入参只有指定的柯里化函数一个
function curry(fn){
return function(){
var args=Array.prototype.slice.call(arguments); //注意这里的差异
return function(){
var innerArgs=Array.prototype.slice.call(arguments);
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
}
}
}
var fn = function(a, b, c) {
return [a, b, c];
};
var r1 =curry(fn)(1,2,3)();
console.log(r1); //[1,2,3]
var r2 =curry(fn)(4)(5,6);
console.log(r2); //[4,5,6]
var r3=curry(fn)()(7,8,9);
console.log(r3); //[7,8,9]
参考“链接1”中的例子,利用ES6解构赋值可以进一步改造为:
function curry(fn){
return function(...args){
return function(...args1){
var finalArgs=args.concat(args1);
return fn.apply(null,finalArgs);
}
}
}
(四)惰性函数
惰性函数参考:juejin.cn/post/689288…