转自:https://mp.weixin.qq.com/s/puZeIzQ6XCVNIFjrqSz7Tg
收藏:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
参考这位园友的文章:https://www.cnblogs.com/itjeff/p/10106855.html
<!-- https://mp.weixin.qq.com/s/puZeIzQ6XCVNIFjrqSz7Tg -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS中的闭包应用</title>
<label>JS:监听文本框内容变化</label>
<input id="input" type="text">
<!-- <script>
/*
*一.闭包的概念和特性
*/
function makeFab()
{
let last=1,current=1;
return function inner()
{
[current,last] = [current + last,current]
return last;
}
}
/*
*这是一个生成斐波那契数列的例子。makeFab的返回值就是一个闭包,makeFab像一个工厂函数,
每次调用都会创建一个闭包函数,如例子中的fab。
fab每次调用不需要传参数,都会返回不同的值,因为在闭包生成的时候,它记住了变量last和current,以至于在后续的调用中能够返回不同的值。
*能记住函数本身所在作用域的变量,这就是闭包和普通函数的区别
*
*/
let fab = makeFab();
console.log(fab());//1
console.log(fab());//2
console.log(fab());//3
console.log(fab());//5
/*
*二.闭包——函数式编程之魂
*/
function confirm(confirmText,confirmCallback,cancelCallback){
// 插入提示框DOM,包含提示语句、确认按钮、取消按钮
// 添加确认按钮点击事件,事件函数中做dom清理工作并调用confirmCallback
// 添加取消按钮点击事件,事件函数中做dom清理工作并调用cancelCallback
}
function removeItem (id) {
confirm(‘确认删除吗?‘, () => {
// 用户点击确认, 发送远程ajax请求
api.removeItem(id).then(xxx)
}, () => {
// 用户点击取消,
console.log(‘取消删除‘)
})
}
/*
*这个例子中,confirmCallback正是利用了闭包,创建了一个引用了上下文中id变量的函数,
这样的例子在回调函数中比比皆是,并且大多数时候引用的变量是很多个。
试想,如果语言不支持闭包,那这些变量要怎么办?作为参数全部传递给confirm函数,
然后在调用confirmCallback/cancelCallback时再作为参数传递给它们?显然,这里闭包提供了极大便利。
*
*/
/*
*三.闭包的一些列子
*/
//1.防抖,节流函数
/*
前端很常见的一个需求是远程搜索,根据用户输入框的内容自动发送ajax请求,然后从后端把搜索结果请求回来。
为了简化用户的操作,有时候我们并不会专门放置一个按钮来点击触发搜索事件,而是直接监听内容的变化来搜索(比如像vue的官网搜索栏)。
这时候为了避免请求过于频繁,我们可能就会用到“防抖”的技巧,即当用户停止输入一段时间(比如500ms)后才执行发送请求。
*/
function debounce(func,time){
let timer=0;
return function(...args){
timer && clearTimeout(timer)
timer = setTimeout(()=>{
timer =0;
func.apply(this,args)
},time)
}
}
input.onkeypress = debounce(function(){
console.log(input.value);//事件处理逻辑
},500)
//debounce函数每次调用时,都会创建一个新的闭包函数,该函数保留了对事件逻辑处理函数func以及防抖时间间隔time以及定时器标志timer的引用。
/**
*2.节流函数
**/
function throttle(func,time)
{
let timer =0;
return function(...args){
if(timer) return;
func.apply(this,args)
timer = setTimeout(() =>timer =0,time)
}
}
</script> -->
<!-- js中闭包 学习地址:https://www.cnblogs.com/itjeff/p/10106855.html -->
<script type="text/javascript">
/*
闭包的本质:在一个函数内部创建另一个函数
3个特性:
(1)函数嵌套函数。
(2)函数内部可以引用函数外部的参数和变量。
(3)参数和变量不会被垃圾回收机制回收。
以下以闭包的两种主要形式来研究:
*/
//第1种形式:函数作为返回值
function a()
{
var name =‘dov‘;
return function()
{
return name;
}
}
var b = a();
consolel.log(b());// 输出:dov
/*
解析: 在上面的代码中,a()中的返回值是一个匿名函数,这个函数在
a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给
全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值
*/
//再来看一个闭包经典的例子
function fn()
{
var num =3;
return function(){
var n=0;
console.log(++n);
console.log(++num);
}
}
var fn1 = fn();
fn1();// 1 4
fn1();// 1 5
/*
解析:一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,
但在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候就相当于fn1
=function(){var n=0...},并且匿名函数内部引用着fn里的变量num,所以变量num
无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量
连同自己一起销毁,于是最后就剩下了孤零零的num,于是这里就产生了内存消耗的问题。
*/
for(var i=0;i<5;++i)
{
setTimeout(function(){
console.log(i+ ‘ ‘);
},100);
}
/**
解析:按照预期它依次输出 1 2 3 4 5 而结果它输出了五次5,这是为什么?
原来由于js是单线程的,所以在执行for循环的时候定时器setTimeout被安排
到任务队列中排队等待执行,而在等待过程中for循环就已经在执行,等到
setTimeout可以执行的时候,for循环已经结束,i的值也已经编程5,所以打印
出来五个5,那么为了实现预期结果应该如下改:(ps:如果把for循环里面的var变成let
,也能实现预期结果)
*/
for(var i=0;i<5;++i)
{
(function(i){
setTimeout(function(){
},100);
}(i));
}
/*
引入闭包来保存变量i,将setTimeout放入立即执行函数中,将for循环中的循环值i作为参数
传递,100ms后同时打印出 1 2 3 4 5
那如果想实现每隔100ms分别输出数字,该如下改:
**
for(var i=1;i<5;i++)
{
(function(i){
setTimeout(function(){
console.log(i);
},i*100);
})(i)
}
/*
在这段代码中,相当于同时启动3个定时器,i*100是为4个定时器分别设置了不同的时间,
同时启动,但是执行时间不同,每个定时器间隔都是100毫秒,
实现了每隔100毫秒就执行一次打印的效果。
**/
//第2种形式:闭包作为参数传递
var num0 =15;
var fn1 =function(x){
if(x>num)
{
console.log(x);
}
}
void function(fn2){
var num =100;
fn2(30)
}(fn1)
/*
在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,
30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,
而是创建函数的作用域中的num,这里函数创建的作用域是全局作用域下,所以num取的
是全局作用域中的值 15,即30>15,打印30
*/
/*
总结:闭包的好处与坏处:
好处:
(1)保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突;
(2)在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
(3)匿名自执行函数可以减少内存消耗;
坏处:
(1)其中一点上面已经体现,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,
解决的方法是可以在使用完变量后手动将它赋值为null;
(2)其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量
存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
**/
</script>
<!-- js中匿名闭包:阮一峰总结 学习地址:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html -->
<!-- js中匿名函数 学习地址:https://www.cnblogs.com/ranyonsue/p/10181035.html -->
</head>
<body>
<div>
<label>JS:监听文本框内容变化</label>
<input id="input">
</div>
</body>
</html>