1.闭包3特性、优缺点。
-
特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收 -
优缺点:
优点:
- 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
- 匿名自执行函数可以减少内存消耗
缺点:
- 被引用的私有变量不能被垃圾回收机制回收,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
- 由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
-
闭包的用途,它的最大用处有两个:
- 一个是前面提到的可以读取函数内部的变量
- 另一个就是让这些变量的值始终保持在内存中。
2.我对闭包理解
- 父函数中有个子函数,子函数用到父函数中的变量,子函数不会马上被执行,可能会通过 定时器 、 事件 、主动调用 去触犯子函数的执行,一旦子函数执行就创建闭包环境,这时候子函数就是一个闭包函数。
- 如:通过调用父函数获取到的子函数就是闭包函数,它拥有自己一块空间,他用到的父函数中的变量不会被垃圾回收机制回收,被保存在相应的闭包空间中。
- 每调用一次父函数就会创建一个闭包函数,同时开辟一块空间存放它。
- 什么样的变量会让闭包函数开辟空间存放它?
- 父函数中的变量被相应的闭包函数用到,才会在相应的闭包函数中创建相应的同名变量,拿到父函数中变量值的作为初始值。(如2.1)
- 如果不是父函数中的变量,被闭包函数用到,并不会在闭包函数中创建同名变量。(如:2.3中,is不是在父函数中,而i被传递到父函数中,因此is无法在闭包函数中开辟空间存放它)
-
主动调用闭包函数,创建闭包空间
function fn1() { var i = 1; function fn2() { return ++i; } return fn2; } var result1 = fn1(); //获得闭包函数fn2 result1(); //得到2 result1(); //得到3 var result2 = fn1(); //获得闭包函数fn2 result2(); //得到2 result2(); //得到3 //对吧result1,result2,说明每个闭包具有自己的空间,且用到的父级变量不会被回收。
-
定时器中创建闭包函数,处理变量值问题。
//例:如何每隔一秒,依次从1输出到10? for(var i=0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); } //闭包解决:通过立即执行函数包裹定时器,立即执行函数每执行一次,定时器中的函数就会变成闭包函数同时创建一个闭包空间,变量i传到闭包空间中不会被回收,值被保存起来。 for(var i=0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i); }, 1000); })(i); }
-
什么样的变量会让闭包函数开辟空间存放它:闭包函数的父函数中的变量。
var test = 1; function fn () { var is = 100; //这变量会被保存在相应闭包空间 for(var i=0; i < 10; i++) { (function(i) { ++is setTimeout(function() { console.log(is); console.log(i); console.log(test); //test不会被保存在闭包中,因为他不在闭包的父函数中。 }, 1000); })(i); } } fn();
-
点击事件中创建闭包,处理变量值问题。
//例:如何实现依次点击li,依次弹出:1、2、3、4。
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0;i<length;i++){
elements[i].onclick=(function(i) {
function(){
alert(i);
}
})(i)
}
//闭包解决:
for(var i=0;i<length;i++){
elements[i].onclick=function(){
alert(i);
//由于i是全局变量(被点击事件函数所依赖),所以不会被垃圾回收机制回收。
}
}
3.闭包作为参数传递
闭包函数中用到其他作用域中的变量时,应该是从生命函数的地方往上找,而不是在调用的地方往上找。
var num = 15;
var fn1 = function(x) {
if(x > num) {
console.log(num)
}
}
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
4.异步导致变量值出现问题,如何处理?
解决方案:闭包、promise、asyns、let。
for(var i=0; i < 10; i++) {
setTimeout(function()
console.log(i);
}, 1000);
}
-
处理:使用立即执行的函数表达式(IIFE)来捕获每次迭代时i的值。
for(var i=0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i); }, 1000); })(i); }
-
处理:利用块级作用域(最简单,推荐)。
- let定义块级作用域变量,变量只在定义的花括号中有效
- 不存在变量提升。而且要求必须 等
let
声明语句执行完之后,变量才能使用 - let不允许在相同作用域内,重复声明同一个变量
for(let i=0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); } // 依次打印0 - 9
-
利用promise(使用promise的办法,是为了更好地理解promise)
for(var i=0; i < 10; i++) { new Promise((resolve, reject) => { var j = i; setTimeout(function() { console.log(j) }, 1000); }) }
-
利用async函数(为了理解async函数)
async function foo() { for(var i=0; i < 10; i++) { let result = await new Promise((resolve, reject) => { setTimeout(function() { resolve(i); }, 1000); }); console.log(result); } } foo(); // 每隔1s打印数字 0 - 9