当前位置: 首页 > news >正文

js闭包与高阶函数

闭包

在js中闭包有两个紧密度非常高的概念与之关联:一.变量的作用域,二.变量的生存周期。那么什么是闭包,我们先看一个闭包的函数: `

function closure () {
    var num = 0;
    return function () {
        if (arguments.length) {
            for (var i = 0, len = arguments.length; i < len; i ++) {
                num += arguments[i]
            }
            return num;
        }
    }
}
var runClosure = closure();
console.log(runClosure(1,3,5,7,9));  //  输出: 25
console.log(runClosure(11,13,15,17,19));    // 输出: 100
复制代码

`

这是一个很典型且简单闭包函数,closure函数里的匿名函数被返回出来,也就是runClosure,runClosure在外部进行调用,而runClosure所运行的环境是closure这个函数体里的作用域里,而closure这个函数体里的作用域是个封闭的空间,而这样的调用关系就是闭包。简言之:就是一个函数里,包含了另外一个函数,且被外部调用执行,这就形成了闭包。

使用闭包是个很自然的过程,并没有什么特别的,重点是闭包的知识点,文章开头有说到,闭包有两个关联度非常高的概念:变量的作用域与变量的生存周期,先说明,这句话并不是我说的,这句话出自 《javaScript设计模式》一书中,我非常认可这句话,所以照搬了过来。

那么为什么说变量的作用域与变量的生存周期呢,从上面的代码我们可以看出,runClosure是运行在closure这个函数的的局部作用域里,num这个变量也同样生存在这个作用域里面,从我么执行两次runClosure就可以看出,num变量是一直存在的,即便我们在执行一次,它也是在现有结果下进行累加的。我们都知道,在js中存在着全局作用域与局部作用域,全局作用域的变量生存周期是永久的,除非你的页面关闭,而局部作用域里的变量,一般函数体里则是局部作用域,局部作用域里的变量一般跟随调用的函数执行的结束而结束,再次调用就又将是个新的。而闭包里的变量,因为被外包访问到,所以闭包环境里的变量就不能被销毁,便就继续存活着,而这些就是闭包里的知识点,掌握这些知识点,我们就可以利用闭包的特性完成许多奇妙的工作了,比如下面要说的高阶函数。而闭包常见的应用有哪些呢?我们从上面的这个函数里,也可以得出两条结论:

  • 一:封装变量,防止变量被全局变量污染;
  • 二:延长局部变量的生存周期;

高阶函数

什么是高阶函数呢,在《javaScript设计模式》一书中同样有说明:

  • 函数可以作为参数传递;
  • 函数可以作为返回值输出;

满足这些条件之一的都可以称之为高阶函数了,作为参数传递的应用场景就是我们常见的回调函数了:

`

var arr = [13, 25, 10, 17, 8]
arr.sort(function(a, b){
    return a - b;
})
复制代码

`

像上面数组里sort方法里的这个比较函数就属于高阶函数。而函数作为返回值输出,我们闭包的那个例子里的runClosure就是个高阶函数了。那么为什么会有高阶函数这个概念呢:通俗的讲,高阶函数的应用都是为了解决我们实际开发当中遇到的问题的,高阶函数便因此而诞生的。

那么有哪些常见的高阶函数,它们又解决了我们的什么问题呢?下面我们看几个例子:

  1. 柯里化函数currying。柯里化函数又称部分求值,一个 currying 的函数首先会接受一些参数,接受了这些参数之后, 该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保 存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

`

我们先构建一个需求,假设我们需要对一些数据进行计算总和,我们的目的呢,是得到最后总和得结果,如果我们每一次我们每次把数据输入进去就进行计算,那么明显是浪费计算机资源得,而我们如果把所有得数据先进行存储,在发现后面没有数据了,就把数据计算出总和返回出来,通过这样得操作,我们就优化了性能。下面看代码:

var currying = function (fn) {
			var args = [];
			return function () {
				if (arguments.length) {
					[].push.apply(args, arguments);
					return arguments.callee;    // 意思是返回当前这个匿名函数
				} else { // 如果这个匿名函数参数里没有数字,则进行计算,并返回结果
					return fn.apply(this, args)
				}
			}
		}
		
		var total = (function () {
			var num = 0;
			return function () {
				for (var i = 0, l = arguments.length; i < l; i++) {
					num+=arguments[i];
				}
				return num;
			}
		})()
		
		var cont = currying(total)
		
	cont(1500); cont(3000, 6000); cont(12000); // 这些都未真正计算,只是存储
	console.log(cont()); // 真正计算,并返回结果  输出:22500
复制代码

`

  1. 函数节流throttle与函数防抖动debounce。函数节流与函数防抖动都有一个共同特点,就是不希望频繁的触发函数运行,比如我们用的onresize事件,onmousemove事件,这些事件都会不经意的被频繁触发,因为频繁的触发函数就要运行函数体,运行函数体就要占用计算资源,还有一些ajax请求也是,如果用户频繁的触发ajax,就会造成不必要的ajax通信,进而占用资源,浪费性能。因此我们就需要封装这样的方法,对于这些方法就是防抖动函数与节流函数了。那么节流与防抖动函数的差别,大家自行百度一下,这里暂不做赘述,我们先看代码实现:

`

// 函数节流
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
	function throttle(fn, interval) {
	// last为上一次触发回调的时间
	let last = 0
	// 将throttle处理结果当作函数返回
	return function () {
	// 保留调用时的this上下文
	let context = this
	// 保留调用时传入的参数
	let args = arguments
	// 记录本次触发回调的时间
	let now = +new Date()		   
	// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
	if (now - last >= interval) {
	// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
			last = now;
			fn.apply(context, args);
		}
	}
}
	// 用throttle来包装scroll的回调
	const better_scroll = throttle(() => console.log('throttle函数节流'), 1000)	
	document.addEventListener('scroll', better_scroll);
	
// 函数防抖
//  fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
    function debounce(fn, delay) {
    // 定时器
    let timer = null
    // 将debounce处理结果当作函数返回
    return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 每次事件被触发时,都去清除之前的旧定时器
    if(timer) {
        clearTimeout(timer)
    }
    // 设立新定时器
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}
// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('debounce函数防抖'), 1000)
document.addEventListener('scroll', better_scroll)
复制代码

`

  1. 分时函数,分时函数的目的其实很简单,当我们有大量的数据需要进行处理的时候,比如我们要给页面创建1000个div标签,如果一次性添加,就会让浏览器承受不住,会让浏览器显得卡顿。那么我们该怎么办呢,分时函数就是用来应对这些场景的,分时函数的应用有点类似我们的懒加载,懒加载是不满足条件是不触发,分时函数是,无论怎样都要处理完,只是分批次进行的,下面我们以页面添加1000个div为案例演示:

`

    <button id="crearte-btn">开始创建</button>
    // 分时函数
	var  friend = []
	for (let n = 1; n < 1000; n++) {
		friend.push('好友:'+ n + '^_^');
	}
	var timeChunk = function (data, fn, count) {
		var obj, time;
		var len = data.length;
		
		var start = function () {
			for (let i = 0; i < Math.min(count || 1, data.length); i++) { // 小于10或者1
				var obj = data.shift(); // 删除自身一个元素
				fn(obj);
			}
		}
		return function () {
			time = setInterval(function () {	// 每个250毫秒执行一次start方法
				if (!data.length) {
					clearInterval(time)
				}
				start();
			}, 250)
		}
	}
	
	var renderElement = timeChunk(friend, function (n) {
		var div = document.createElement('div');
		div.innerHTML = n;
		document.getElementById('friend-div').appendChild(div);
	}, 10);
	
	document.getElementById('crearte-btn').onclick = function () {
		renderElement();
	}
复制代码

`

总结

在这个章节里,介绍了闭包与高阶函数,并展示了三种常见的高阶函数的应用,而高阶函数的应用,归根揭底,是用来处理我们日常开发中的一些问题,更多的目的是为了优化性能的操作。今天分享就到这,喜欢的朋友点个赞,谢谢。

相关文章:

  • java知识点1(this指针)
  • Confluent 修改开源许可证,限制云供应商滥用
  • vagrant设置虚拟机的名字
  • 寒假作业安排及注意点
  • 团队管理 - 团队发展五阶段
  • 线性代数---范数
  • Delphi 调用C#编写的WebService 参数为Null解决方法
  • BZOJ 1449 球队收益(最小费用最大流)
  • PHP学习笔记2
  • (原創) 未来三学期想要修的课 (日記)
  • 【转载】一个Sqrt函数引发的血案
  • webgis与web
  • 《围城》读后感:你的心是一座城,愿你城中有幸福
  • 2018年香港国际机场三项航空交通量均创新高
  • 数组去重的几种方式
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • 【EOS】Cleos基础
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 2017届校招提前批面试回顾
  • Angular 响应式表单之下拉框
  • CSS 专业技巧
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Javascript编码规范
  • learning koa2.x
  • magento2项目上线注意事项
  • Python连接Oracle
  • supervisor 永不挂掉的进程 安装以及使用
  • Vue ES6 Jade Scss Webpack Gulp
  • vue的全局变量和全局拦截请求器
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 关于for循环的简单归纳
  • 来,膜拜下android roadmap,强大的执行力
  • 排序(1):冒泡排序
  • 前端
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 如何优雅地使用 Sublime Text
  • 我与Jetbrains的这些年
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • Spring第一个helloWorld
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (39)STM32——FLASH闪存
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (十)T检验-第一部分
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • .NET CF命令行调试器MDbg入门(一)
  • .NET CORE Aws S3 使用
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET 常见的偏门问题
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...