博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
js闭包与高阶函数
阅读量:6511 次
发布时间:2019-06-24

本文共 4911 字,大约阅读时间需要 16 分钟。

闭包

在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));  //  输出: 25console.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为案例演示:

`

    // 分时函数	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();	}复制代码

`

总结

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

转载地址:http://vwcfo.baihongyu.com/

你可能感兴趣的文章
fnmatch源码阅读
查看>>
U9249 【模板】BSGS
查看>>
单片机小白学步系列(九) 用万用焊板搭建实验电路
查看>>
Tomcat PK Resin
查看>>
(转)全文检索技术学习(三)——Lucene支持中文分词
查看>>
Node.js+Koa开发微信公众号个人笔记(一)准备工作
查看>>
Android 图片缓存处理
查看>>
MySQL数据库锁定机制
查看>>
elasticsearch
查看>>
阿里盒马领域驱动设计实践
查看>>
vuex 存值 及 取值 的操作
查看>>
HDU 2242 考研路茫茫——空调教室(边双连通)
查看>>
如何在C#项目中使用NHibernate
查看>>
使用vigil 监控微服务系统包含可视化界面
查看>>
安装python包到指定虚拟环境
查看>>
力扣(LeetCode)21
查看>>
网页视频流m3u8/ts视频下载
查看>>
聊聊flink的TableFactory
查看>>
Python 基础起步 (十) 什么叫函数?
查看>>
每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
查看>>