闭包
在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
就是个高阶函数了。那么为什么会有高阶函数这个概念呢:通俗的讲,高阶函数的应用都是为了解决我们实际开发当中遇到的问题的,高阶函数便因此而诞生的。
那么有哪些常见的高阶函数,它们又解决了我们的什么问题呢?下面我们看几个例子:
- 柯里化函数
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复制代码
`
- 函数节流
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)复制代码
`
- 分时函数,分时函数的目的其实很简单,当我们有大量的数据需要进行处理的时候,比如我们要给页面创建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(); }复制代码
`
总结
在这个章节里,介绍了闭包与高阶函数,并展示了三种常见的高阶函数的应用,而高阶函数的应用,归根揭底,是用来处理我们日常开发中的一些问题,更多的目的是为了优化性能的操作。今天分享就到这,喜欢的朋友点个赞,谢谢。