深入理解javascript函数进阶系列第三篇——函数节流和函数防抖
前面的话
javascript中的函数大多数情况下都是由用户主动调用触发的,除非是函数本身的实现不合理,否则一般不会遇到跟性能相关的问题。但在一些少数情况下,函数的触发不是由用户直接控制的。在这些场景下,函数有可能被非常频繁地调用,而造成大的性能问题。解决性能问题的处理办法就是函数节流和函数防抖。本文将详细介绍函数节流和函数防抖
常见场景
下面是函数被频繁调用的常见的几个场景
1、mousemove事件。如果要实现一个拖拽功能,需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 dom 的位置来进行样式改变。如果不加以控制,每移动一定像素而触发的回调数量非常惊人,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死。
2、window.onresize事件。为window对象绑定了resize事件,当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。如果在window.onresize事件函数里有一些跟DOM节点相关的操作,而跟DOM节点相关的操作往往是非常消耗性能的,这时候浏览器可能就会吃不消而造成卡顿现象
3、射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
4、搜索联想(keyup事件)
5、监听滚动事件判断是否到页面底部自动加载更多(scroll事件)
对于这些情况的解决方案就是函数节流(throttle)或函数去抖(debounce),核心其实就是限制某一个方法的频繁触发
定时器管理
在介绍函数防抖和函数节流之前,首先要介绍一下定时器管理
定时器管理有两种机制:
第一种是只要当前函数没有执行完成,任何新触发的函数都会被忽略,可以实现在持续触发事件的情况下,一段时间内只执行一次事件的效果,即函数节流
简易代码如下
function fn(method, context) {
//忽略新函数
if(method.tId){
return false;
}
method.tId = setTimeout(function() {
method.call(context);
}, );
}
第二种是只要有新触发的函数,就立即停止执行当前函数,转而执行新函数,可以实现在持续触发事件的情况下,一定在事件触发n秒后执行,如果n秒内又触发了这个事件,则以新的事件的时间为准,还是n秒后执行,即函数防抖,简易代码如下
function fn(method, context) {
//停止当前函数
clearTimeout(method.tId);
method.tId = setTimeout(function() {
method.call(context);
}, );
}
函数防抖
函数防抖,字面上来说,是利用函数来防止抖动。在执行触发事件的情况下,元素的位置或尺寸属性快速地发生变化,造成页面回流,出现元素抖动的现象。通过函数防抖,使得元素的位置或尺寸属性延迟变化,从而减少页面回流
简单的防抖函数代码如下,该函数接受2个参数,第一个参数为需要被延迟执行的函数,第二个参数为延迟执行的时间
<style>
body {
margin: ;
}
.show{
width: 260px;
height: 100px;
font-size: 20px;
text-align: center;
line-height: 100px;
background: lightgreen;
}
</style>
<div class="show" id="show"></div>
<script>
let count =
const oShow = document.getElementById('show')
const changeValue = () => { oShow.innerHTML = count ++ }
const debounce = (fn, wait=) => {
return () => {
clearTimeout(fn.timer)
fn.timer = setTimeout(fn, wait)
}
}
oShow.addEventListener('mousemove', debounce(changeValue))
</script>
效果如下:
但是,changeValue()方法中的this指向window,下面来修正this指向
const debounce = (fn, wait=) =>{
return function() {
clearTimeout(fn.timer)
fn.timer = setTimeout(fn.bind(this), wait)
}
}
还有一个问题,changeValue()方法中的e为undefined,下面来修正e的值
const debounce = (fn, wait=) =>{
return function() {
clearTimeout(fn.timer)
fn.timer = setTimeout(fn.bind(this, ...arguments), wait)
}
}
或者,使用apply方法
const debounce = (fn, wait=) =>{
return function() {
clearTimeout(fn.timer)
fn.timer = setTimeout(() => {
fn.apply(this, arguments)
}, wait)
}
}
函数节流
函数节流,即限制函数的执行频率,在持续触发事件的情况下,间断地执行函数;实现方法对应定时器管理的第一种策略,只要当前函数没有执行完成,任何新触发的函数都会被忽略
const throttle = (fn, wait=) =>{
return function() {
if(fn.timer) return
fn.timer = setTimeout(() => {
fn.apply(this, arguments)
fn.timer = null
}, wait)
}
}
数组分块
在前面关于函数节流和函数防抖的讨论中,提供了限制函数被频繁调用的解决方案。下面将遇到另外一个问题,某些函数确实是用户主动调用的,但因为一些客观的原因,这些函数会严重地影响页面性能
一个例子是创建WebQQ的QQ好友列表。列表中通常会有成百上千个好友,如果一个好友用一个节点来表示,在页面中渲染这个列表的时候,可能要一次性往页面中创建成百上千个节点
在短时间内往页面中大量添加DOM节点显然也会让浏览器吃不消,看到的结果往往就是浏览器的卡顿甚至假死。代码如下:
var ary = [];
for ( var i = ; i <= ; i++ ){
ary.push( i ); // 假设 ary 装载了 1000 个好友的数据
};
var renderFriendList = function( data ){
for ( var i = , l = data.length; i < l; i++ ){
var div = document.createElement( 'div' );
div.innerHTML = i;
document.body.appendChild( div );
}
};
renderFriendList( ary );
这个问题的解决方案之一是数组分块技术,下面的timeChunk函数让创建节点的工作分批进行,比如把1秒钟创建1000个节点,改为每隔200毫秒创建8个节点
数组分块是一种使用定时器分割循环的技术,为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器
在数组分块模式中,array变量本质上就是一个“待办事宜”列表,它包含了要处理的项目。使用shift()方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过arguments.callee调用同一个匿名函数
数组分块的重要性在于它可以将多个项目的处理在执行队列上分开,在每个项目处理之后,给予其他的浏览器处理机会运行,这样就可能避免长时间运行脚本的错误。一旦某个函数需要花50ms以上的时间完成,那么最好看看能否将任务分割为一系列可以使用定时器的小任务
下面是数组分块模式的简易代码
function chunk(array,process,context){
setTimeout(function(){
//取出下一个条目并处理
var item = array.shift();
process.call(context,item);
//若还有条目,再设置另一个定时器
if(array.length > ){
setTimeout(arguments.callee,);
}
},);
}
var data = [,,,,,,,,,];
function printValue(item){
var div = document.getElementById('myDiv');
div.innerHTML += item + '<br>';
}
chunk(data.concat(),printValue);
下面是数组分块的详细代码,timeChunk函数接受3个参数,第1个参数是创建节点时需要用到的数据,第2个参数是封装了创建节点逻辑的函数,第3个参数表示每一批创建的节点数量
var timeChunk = function( ary, fn, count ){
var obj,t;
var len = ary.length;
var start = function(){
for ( var i = ; i < Math.min( count || , ary.length ); i++ ){
var obj = ary.shift();
fn( obj );
}
};
return function(){
t = setInterval(function(){
if ( ary.length === ){ // 如果全部节点都已经被创建好
return clearInterval( t );
}
start();
}, ); // 分批执行的时间间隔,也可以用参数的形式传入
};
};
最后进行一些小测试,假设有1000个好友的数据,利用timeChunk函数,每一批只往页面中创建8个节点
var ary = [];
for ( var i = ; i <= ; i++ ){
ary.push( i );
};
var renderFriendList = timeChunk( ary, function( n ){
var div = document.createElement( 'div' );
div.innerHTML = n;
document.body.appendChild( div );
}, );
renderFriendList();
深入理解javascript函数进阶系列第三篇——函数节流和函数防抖的更多相关文章
- 深入理解javascript选择器API系列第三篇——h5新增的3种selector方法
× 目录 [1]方法 [2]非实时 [3]缺陷 前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuer ...
- 深入理解javascript选择器API系列第三篇——HTML5新增的3种selector方法
前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuery的称赞,很多是由于jQuery方便的元素选择器 ...
- 深入理解javascript函数进阶系列第四篇——惰性函数
前面的话 惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了.本文将详细介绍惰性 ...
- 深入理解DOM事件机制系列第三篇——事件对象
× 目录 [1]获取 [2]事件类型 [3]事件目标[4]事件代理[5]事件冒泡[6]事件流[7]默认行为 前面的话 在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事 ...
- 深入理解DOM事件类型系列第三篇——变动事件
× 目录 [1]删除节点 [2]插入节点 [3]特性节点[4]文本节点 前面的话 变动(mutation)事件能在DOM中的某一部分发生变化时给出提示,这类事件非常有用,但都只能使用DOM2级事件处理 ...
- 深入理解脚本化CSS系列第三篇——脚本化CSS类
前面的话 在实际工作中,我们使用javascript操作CSS样式时,如果要改变大量样式,会使用脚本化CSS类的技术,本文将详细介绍脚本化CSS类 style 我们在改变元素的少部分样式时,一般会直接 ...
- 深入理解javascript函数系列第三篇——属性和方法
× 目录 [1]属性 [2]方法 前面的话 函数是javascript中的特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本 ...
- 深入理解javascript函数系列第三篇
前面的话 函数是javascript中特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本文是深入理解javascript函数 ...
- 深入理解javascript函数进阶系列第一篇——高阶函数
前面的话 前面的函数系列中介绍了函数的基础用法.从本文开始,将介绍javascript函数进阶系列,本文将详细介绍高阶函数 定义 高阶函数(higher-order function)指操作函数的函数 ...
随机推荐
- 深入理解 Linux 的 RCU 机制
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:梁康 RCU(Read-Copy Update),是 Linux 中比较重要的一种同步机制.顾名思义就是"读,拷贝更新&quo ...
- VB 用代码创建的控件和接收事件
在声明公共变量的位置加上这句就可以了 Dim WithEvents NewButton As Button form_load中添加 NewButton = New Button New ...
- JavaScript 语言精粹读书笔记
最近在看 赵泽欣 / 鄢学鹍 翻译的 蝴蝶书, 把一些读后感言记录在这里. 主要是把作者的建议跟 ES5/ES5.1/ES6 新添加的功能进行了对比 涉及到的一些定义 IIFE: Immediatel ...
- 一场围绕着‘Deeping Learning’的高考
Deep Learning的基本思想和方法 实际生活中,人们为了解决一个问题,如对象的分类(对象可是是文档.图像等),首先必须做的事情是如何来表达一个对象,即必须抽取一些特征来表示一个对象,如文本的处 ...
- AngularJS学习篇(二十一)
AngularJS 动画 AngularJS 提供了动画效果,可以配合 CSS 使用. AngularJS 使用动画需要引入 angular-animate.min.js 库. <!doctyp ...
- 使用(Unicode字符)让inline水平元素换行
为了实现上面效果: <dl> <dt>提问:</dt><dd>为什么没有男朋友?</dd> </dl> <dl ...
- NFS服务安装及配置
服务器环境:CentOS6.9 Linux 2.6.32-696.el6.x86_64 安装NFS服务 nfs客户端和服务端都只需要安装nfs-utils包即可,并且yum安装时会连带安装rpcbi ...
- Ubuntu开启ApacheRewrite功能
参考原文 : http://www.knowsky.com/888354.html 1.安装好apache2之后,手动命令启用 执行加载Rewrite模块: a2enmod rewrite 执行 ...
- linux 更新源miss问题
1.之前新装的linuxMint 执行 apt-get install vim 安装失败 发现原因是源更新失败导致,后来执行apt-get update 发现老是获取失败,查了google总结出以下解 ...
- Jquery中attr 和 prop的区别和联系
昨天在选择借款方类型的时候总是会出现选择要点两次的现象,比如点击公司,第一次点击选择公司,没有选中,必须在次点击才可以选中,总感觉是有点延迟加载的意思,后来审查元素, 发现是redio元素,这样的话就 ...