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