JS中setTimeout的巧妙用法前端函数节流


Posted in Javascript onMarch 24, 2016

什么是函数节流?

函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等等事情,那么这时候窗口缩放的时候,有可能连续发多个请求,这并不是我们想要的,或者是说我们常见的鼠标移入移出tab切换效果,有时候连续且移动的很快的时候,会有闪烁的效果,这时候我们就可以使用函数节流来操作。大家都知道,DOM的操作会很消耗或影响性能的,如果是说在窗口缩放的时候,为元素绑定大量的dom操作的话,会引发大量的连续计算,比如在IE下,过多的DOM操作会影响浏览器性能,甚至严重的情况下,会引起浏览器崩溃的发生。这个时候我们就可以使用函数节流来优化代码了~

函数节流的基本原理:

使用一个定时器,先延时该函数的执行,比如使用setTomeout()这个函数延迟一段时间后执行函数,如果在该时间段内还触发了其他事件,我们可以使用清除方法 clearTimeout()来清除该定时器,再setTimeout()一个新的定时器延迟一会儿执行。

最近在某团队忙于一个项目,有这么一个页面,采用传统模式开发(吐槽它为什么不用React),它的DOM操作比较多,然后性能是比较差的,尤其当你缩放窗口时,可怕的事情发生了,出现了卡顿,甚至浏览器瘫痪。为什么呢?

由于该页面的DOM操作非常多,故窗口缩放每一帧时都会触发函数的执行,连续的重新DOM操作,这样对浏览器的开销是非常大的。既然在窗口缩放时,会让浏览器重新计算DOM,那么我们为什么不可以让DOM的计算延时呢,让窗口停止缩放后才重新计,这样不就节省了浏览器的开销,达到优化的效果了吗?

知识准备

1. setTimeout(code,millisec) 当然就是本文的主角了。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

code必需。要调用的函数后要执行的 JavaScript 代码串。

millisec必需。在执行代码前需等待的毫秒数。

提示:setTimeout() 只执行 code 一次。如果要多次调用,请使用 setInterval() 或者让 code 自身再次调用 setTimeout()。

广泛应用于定时器,轮播图,动画效果,自动滚动等等。

2. clearTimeout(id_of_setTimeout)

参数 id_of_settimeout由 setTimeout() 返回的 ID 值。该值标识要取消的延迟执行代码块。

3. fun.apply(thisArg[, argsArray])

apply() 方法在指定 this 值和参数(参数以数组或类数组对象的形式存在)的情况下调用某个函数

该函数的语法与call()方法几乎相同,唯一的区别在于,call()方法接受的是一个参数列表,而apply()接受的是一个包含多个参数数组的(或类数组对象)。

参数

thisArg

在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray

一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

4. fun.call(thisArg[, arg1[, arg2[, ...]]])

该 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.

参数

thisArg

在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

arg1, arg2, ...

指定的参数列表。

当调用一个函数时,可以赋值一个不同的 this 对象。this 引用当前对象,即 call 方法的第一个参数。通过 call 方法,你可以在一个对象上借用另一个对象上的方法,比如Object.prototype.toString.call([]),就是一个Array对象借用了Object对象上的方法。

作用:

使用call方法调用父构造函数

使用call方法调用匿名函数

使用call方法调用匿名函数并且指定上下文的'this'

这里插个题外话:

apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))。你也可以使用 arguments 对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。

从 ECMAScript 第5版开始,可以使用任何种类的类数组对象,就是说只要有一个 length 属性和[0...length) 范围的整数属性。例如现在可以使用 NodeList 或一个自己定义的类似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的对象。

call, apply方法区别是,从第二个参数起, call方法参数将依次传递给借用的方法作参数, 而apply 直接将这些参数放到一个数组中再传递, 最后借用方法的参数列表是一样的.

应用场景:当参数明确时可用call, 当参数不明确时可用apply给合arguments

现在先给出一个例子

总所皆知,onscolll,onresize等是非常耗性能,窗口缩放时打印数字。

var count = ;
window.onresize = function () {
count++;
console.log(count);
}

在chrome浏览器中伸缩浏览器窗口大小,打印如下

这显然不是我们想要的,那如果我们换成ajax请求的话,那么就会缩放一次窗口会连续触发多次ajax请求,下面我们试着使用函数节流的操作试试一下;当然加个settimeout()的定时器就好了,

第一种封装方法

var count = ;
function oCount() {
count++;
console.log(count);
}
window.onresize = function () {
delayFun(oCount)
};
function delayFun(method, thisArg) {
clearTimeout(method.props);
method.props = setTimeout(function () {
method.call(thisArg)
}, )
}

第二种封装方法

构造一个闭包,使用闭包的方式形成一个私有的作用域来存放定时器timer, timer是通过传参数的形式引入的。

var count = ;
function oCount() {
count++;
console.log(count);
}
var funs= delayFun(oCount,);
window.onresize = function () {
funs()
};
function delayFun(func, wait) {
var timer = null;
return function () {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
func.apply(context, args);
}, wait)
};
}

对第二种方法优化一下,性能会更好

这里返回一个函数,如果它被不间断地调用,它将不会得到执行。该函数在停止调用 N 毫秒后,再次调用它才会得到执行。如果有传递 ‘immediate' 参数,会马上将函数安排到执行队列中,而不会延迟。

function delayFun (func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// 用法
var myEfficientFn = delayFun (function() {
// 所有繁重的操作
}, );
window.addEventListener('resize', myEfficientFn);

函数不允许回调函数在指定时间内执行多于一次。当为一个会频繁触发的事件分配一个回调函数时,该函数显得尤为重要。

setTimeout这么厉害,那么我们是可以在项目中大量使用吗?

我个人是不建议的,在我们业务中,基本上是禁止在业务逻辑中使用setTimeout的,因为我所看到的很多使用方式都是一些问题好解决,setTimeout作为一个hack的方式。

例如,当一个实例还没有初始化的前,我们就使用这个实例,错误的解决办法是使用实例时加个setTimeout,确保实例先初始化。

为什么错误?这里其实就是使用hack的手段

第一是埋下了坑,打乱模块的生命周期

第二是出现问题时,setTimeout其实是很难调试的。

我认为正确的使用方式是,看看生命周期(可参考《关于软件的生命周期 》),把实例化提到使用前执行。

有关JS中setTimeout的巧妙用法前端函数节流,小编就给大家介绍到这里,希望对大家有所帮助!

Javascript 相关文章推荐
浅析ajax请求json数据并用js解析(示例分析)
Jul 13 Javascript
node.js中的events.emitter.removeListener方法使用说明
Dec 10 Javascript
jQuery构造函数init参数分析
May 13 Javascript
JavaScript中通过提示框跳转页面的方法
Feb 14 Javascript
javascript验证手机号和实现星号(*)代替实例
Aug 16 Javascript
JS实现用户注册时获取短信验证码和倒计时功能
Oct 27 Javascript
angular2中router路由跳转navigate的使用与刷新页面问题详解
May 07 Javascript
js轮播图的插件化封装详解
Jul 17 Javascript
深入理解JavaScript 中的执行上下文和执行栈
Oct 23 Javascript
webpack是如何实现模块化加载的方法
Nov 06 Javascript
Webpack的Loader和Plugin的区别
Nov 09 Javascript
JavaScript canvas实现文字时钟
Jan 10 Javascript
Zero Clipboard实现浏览器复制到剪贴板的方法(多个复制按钮)
Mar 24 #Javascript
不能不知道的10个angularjs英文学习网站
Mar 23 #Javascript
AngularJS 让人爱不释手的八种功能
Mar 23 #Javascript
java中String类型变量的赋值问题介绍
Mar 23 #Javascript
基于javascript实现泡泡大冒险网页版小游戏
Mar 23 #Javascript
基于javascript实现句子翻牌网页版小游戏
Mar 23 #Javascript
基于jquery编写的放大镜插件
Mar 23 #Javascript
You might like
PHP判断变量是否为0的方法
2014/02/08 PHP
php多文件上传实现代码
2014/02/20 PHP
php实现的ping端口函数实例
2014/11/12 PHP
laravel中数据显示方法(默认值和下拉option默认选中)
2019/10/11 PHP
JavaScript prototype属性使用说明
2010/05/13 Javascript
IE6、IE7中setAttribute不支持class/for/rowspan/colspan等属性
2011/08/28 Javascript
javascript 闭包
2011/09/15 Javascript
通过js动态操作table(新增,删除相关列信息)
2012/05/23 Javascript
javascript设置金额样式转换保留两位小数示例代码
2013/12/04 Javascript
jQuery判断当前点击的是第几个li的代码
2014/09/26 Javascript
Windows 系统下设置Nodejs NPM全局路径
2016/04/26 NodeJs
JavaScript获取IP获取的是IPV6 如何校验
2016/06/12 Javascript
3分钟快速搭建nodejs本地服务器方法运行测试html/js
2017/04/01 NodeJs
js 提取某()特殊字符串长度的实例
2017/12/06 Javascript
vue上传图片到oss的方法示例(图片带有删除功能)
2018/09/27 Javascript
js打开word文档预览操作示例【不是下载】
2019/05/23 Javascript
微信小程序scroll-view点击项自动居中效果的实现
2020/03/25 Javascript
[57:22]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第五场
2018/04/10 DOTA
python在多玩图片上下载妹子图的实现代码
2013/08/13 Python
Python中字典的基本知识初步介绍
2015/05/21 Python
Python获取央视节目单的实现代码
2015/07/25 Python
python实现媒体播放器功能
2018/02/11 Python
python实现图片九宫格分割
2021/03/07 Python
Python实现搜索算法的实例代码
2020/01/02 Python
python except异常处理之后不退出,解决异常继续执行的实现
2020/04/25 Python
Python 实现3种回归模型(Linear Regression,Lasso,Ridge)的示例
2020/10/15 Python
详解matplotlib中pyplot和面向对象两种绘图模式之间的关系
2021/01/22 Python
Python爬虫实现selenium处理iframe作用域问题
2021/01/27 Python
CSS3感应鼠标的背景闪烁和图片缩放动画效果
2014/05/14 HTML / CSS
法国综合购物网站:RueDuCommerce
2016/09/12 全球购物
优衣库澳大利亚官网:UNIQLO澳大利亚
2017/01/18 全球购物
2013年入党人员的自我鉴定
2013/10/25 职场文书
初中女生自我鉴定
2013/12/19 职场文书
元旦晚会策划方案
2014/02/18 职场文书
机械专业毕业生自我鉴定2014
2014/10/04 职场文书
岗位竞聘报告范文
2014/11/06 职场文书