Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_.debounce源码学习 #18

Open
wolfdu opened this issue Dec 23, 2017 · 0 comments
Open

_.debounce源码学习 #18

wolfdu opened this issue Dec 23, 2017 · 0 comments

Comments

@wolfdu
Copy link
Owner

wolfdu commented Dec 23, 2017

https://wolfdu.fun/post?postId=5a3de8af25322c62a62e7b12

具体场景,在开发blog的后台管理项目时,关于文章输入我希望能做成像印象笔记那种,在输入过程中对文章内容进行实时的保存,这样如果每次输入都进行一次保存操作,这样会对后台造成很大的压力同时会出现数据丢失的情况。也就引入了今天学习的主题debounce

什么是debounce?

结合开篇给出的场景,我们不希望每次输入都执行保存操作,而是希望在我持续输出后进行思考的时候进行保存的操作。既然做不到在我思考的时候保存( ∙̆ .̯ ∙̆ ),那就在我输入停顿一定时间后执行保存操作吧。

简单解释一下debounce的原理:在持续触发某个事件时,该事件的回调函数不会立即触发,而是在最后一个触发事件结束一段时间后去执行回调函数。

来看一个简单的栗子:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>debounce</title>
    <script>
      document.addEventListener('DOMContentLoaded', function(event){
        var input = document.getElementsByTagName('input')[0];
        input.addEventListener('input', function(event){
          console.log(`input: ${event.currentTarget.value}`);
        }, false);
      });
    </script>
  </head>
  <body>
    please input:<input type="text">
  </body>
</html>

我们在input中输入内容,然后在控制台中打印我们输入的内容。

项目中的解决办法是使用underscore提供的debounce方法解决的,我们这里也使用underscore来解决这个问题看看效果(这里就只写js代码了):

document.addEventListener('DOMContentLoaded', function(event){
        var print = function(value){ // 打印方法作为回调函数
            console.log(`input: ${value}`);
        };
        // 这里需要引入underscore.js
        // 使用debounce函数防抖
        var inputDebounce = _.debounce(print, 1000);

        var input = document.getElementsByTagName('input')[0];
        input.addEventListener('input', function(event){
            inputDebounce(event.currentTarget.value)
        }, false);
});

这里我们可以对比一下效果,没使用debounce时我们每次输入都会在控制台打印输入结果,使用了debounce后会在我们输入结束1000ms后才在控制台打印最终的输入结果 o‿≖✧

现象到本质

以上看来,知道了debounce的作用,那么它到底是如何实现这个功能的呢?
这里我们先参考着underscore的功能实现,模拟出自己的debounce方法。

从前面的简单解释可以知晓,我们会为目标函数设置一个定时器,当定时器未执行时,事件再次被触发,则清除当前的定时器,然后设置新的定时器,直到定时器timeout执行目标函数。

“debounce”模拟第一版debounceOne

function debounceOne(fun, wait){
	var timeout;
	return function(){
		// 清除定时器	
		clearTimeout(timeout);
		// 设置定时器
		timeout = setTimeout(fun, wait);
	}
};

我们使用debounceOne替换underscore来跑一下示例,我们可以发现在我们输入的时候并不会在控制台打印输出,而是在停止输入后1000ms后打印的但是结果是这个样子的input: undefined输出的并不是我们输入的结果,接下来我们搞定传参的问题。

“debounce”模拟第二版debounceTwo

这里我们可以使用apply来指定函数执行的上下文和参数,对apply有疑问的可以参见JavaScript模拟实现call,apply,bind等方法

function debounceTwo(fun, wait){
	var timeout;
	return function(){
		// 获取当前this,用于指定目标函数的this值
		var context = this;
		// 获取调用时的参数
		var args = arguments;

		clearTimeout(timeout);
		timeout = setTimeout(function(){
			fun.apply(context, args);
		}, wait);
	}
};

到目前为止我们的debounceTwo基本以及能满足我们以上的需求了。

那么,我们来看看underscore的debounce实现吧,是不是有点小兴奋٩(๑ᵒ̴̶͈᷄ᗨᵒ̴̶͈᷅)و

_.debounce

直接上源码:

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
	var timeout, args, context, timestamp, result;

	var later = function() {
		var last = _.now() - timestamp;

		if (last < wait && last >= 0) {
			timeout = setTimeout(later, wait - last);
		} else {
			timeout = null;
			if (!immediate) {
				result = func.apply(context, args);
				if (!timeout) context = args = null;
			}
		}
	};

	return function() {
		context = this;
		args = arguments;
		timestamp = _.now();
		var callNow = immediate && !timeout;
		if (!timeout) timeout = setTimeout(later, wait);
		if (callNow) {
			result = func.apply(context, args);
			context = args = null;
		}

		return result;
	};
};

从代码量上来看,underscore的实现功能应该要复杂点,但是整体的实现的策略和我们模拟的是一样的。

underscore:我们不一样( ¯ ¨̯ ¯̥̥ )

首先我们可以发现多了一个immediate参数,从字面上理解应该是立即执行之类的功能。
我们来看看代码的具体实现,这里我就放出带注释源码了。

_.debounce = function(func, wait, immediate) {
	var timeout, args, context, timestamp, result;

	var later = function() {// 定时器回调函数
		// 定时器回调函数与触发事件的时间差
		// 用于判断是否需要执行目标函数
		var last = _.now() - timestamp;

		// 当时间差小于设定的等待时间,重新设置定时器
		if (last < wait && last >= 0) {
			timeout = setTimeout(later, wait - last);
		} else {// 时间差大于等待时间
			// 释放引用
			timeout = null;
			// 这里需要判断是否为立即执行模式
			// 如果为立即执行模式,则不再进行目标函数的执行了
			if (!immediate) {
				// 非立即执行模式,执行目标函数
				result = func.apply(context, args);
				if (!timeout) context = args = null;
			}
		}
	};

	// 返回一个闭包函数
	return function() {
		// 指定上下文
		context = this;

		// 获取传入参数
		args = arguments;

		// 当前事件触发的时间戳,每次触发事件都会更新时间戳
		timestamp = _.now();

		// 用于判断是否立即执行目标函数
		// 判断immediate的boolean值是否为true,设定立即执行模式
		// 还要同时满足当前没有设置定时器,也就是判断是否为首次执行
		// 当满足执行模式为立即执行且首次触发事件时,则立即执行目标函数
		var callNow = immediate && !timeout;

		// 设置定时器
		if (!timeout) timeout = setTimeout(later, wait);
		if (callNow) { // 判断是否立即执行目标函数
			// 立即执行目标函数,缓存目标函数返回值
			result = func.apply(context, args);
			// 释放引用资源
			context = args = null;
		}
		// 返回目标函数返回值
		return result;
	};
};

本质回归现象

防抖的本质是通过减少实际逻辑处理来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。
那么对于函数的去抖debounce一般都有那些应用场景呢?看了这嘛久,最后还是要学以致用(๑˘ ˘๑)

  • 对于文本输入验证(一般是后台验证)
  • 拖拽时的mousemove事件

(⊙o⊙)…暂时只想的到这嘛多,后面遇到了在来补充。

吼啦,debounce的解析先到这里吧。
最近也在看underscore的源码,以此来巩固自己的基础知识,应该不会写系列文章,但是遇到困惑的地方或者引发思考的东西,会用写blog的方式来系统整理相关的东西,写blog的过程就是学习的过程。

参考文档:
JavaScript专题之跟着underscore学防抖

若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant