(1)说说你对闭包的理解
使用闭包主要是为了设计私有的方法和变量。
闭包的优点是可以避免全局变量的污染,实现属性私有化。
闭包的缺点:闭包会常驻内存,增加内存使用量,使用不当容易造成内存泄漏 (因此在不用的时候需要删除)
闭包的特性:①函数嵌套函数②函数内部可以引用外部的参数和变量③参数和变量不会以垃圾回收机制回收
(2)看以下这段代码并回答以下问题
for(var i=0;i<5;i++){
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button "+i));
btn.addEventListener('click',function(){
console.log(i);
})
document.body.appendChild(btn);
}
①当用户单击“Button 4”的时候,控制台会输出什么???
答:无论用户点击哪个button,控制台会输出数字5。因为当调用onclick方法的时候,for循环已经结束,变量i的值已经为5。
②提供一个或者多个可获取当前i的值的实现方案
方案一:使用闭包实现
for(var i=0;i<5;i++){
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button "+i));
btn.addEventListener('click',(function(i){ //这里使用了闭包,将循环变量存储了
return function(){
console.log(i);
}
})(i));
方案二:使用ES6语法
for(let i=0;i<5;i++){ //这里使用了let let具备块级作用域
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button "+i));
btn.addEventListener('click',function(){
console.log(i);
});
document.body.appendChild(btn);
}
方案三:封装全部调用到新的匿名函数中
for(var i=0;i<5;i++){
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button "+i));
(function(){
btn.addEventListener('click',function(){
console.log(i);
});
})(i);
document.body.appendChild(btn);
}
(3)什么是闭包?
为说明闭包,创建一个闭包
function hello(){
//函数执行完毕,变量仍然存在
var num = 100;
var showResult = function(){
alter(num);
}
num++;
return showResult;
}
var showResult=hello();
showResult();//执行结果,弹出101
执行hello()后,hello闭包内部的变量会存在,而闭包内部函数变量不会存在。使得JS的垃圾回收机制不会回收hello()占用的资源,因为hello内部函数执行的时候需要依赖hello()中的变量。
(4)以下代码将输出什么?闭包在这里能起什么作用?
for(var i=0;i<5;i++){
(function(){
setTimeout(function(){console.log(i)},i*100);
})();
}
将输出5,5,5,5,5 而不是0,1,2,3,4
原因是每个函数先将循环执行完之后,再执行循环体中的函数(跟函数的调用,事件的执行有关),因此就会引用存储在i中的最后一个值 5
闭包可以为每次迭代创建一个唯一的作用域。存储作用域内的循环变量。
for(var i=0;i<5;i++){
(function(i){
setTimeout(function(){console.log(i)},i*100);
})(i);
}
这样就可以输出0,1,2,3,4
(5)下列的递归代码在数组列表偏大的情况下会导致堆栈溢出,在保留原递归模式的基础上,怎么解决这个问题?
var list = readHugeList();
var nextListItem = function(){
var item = list.loop();
it(item){
nextListItem();
}
}
改为以下代码:
var list = readHugeList();
var nextListItem = function(){
var item = list.loop();
it(item){
setTimeout(nextListItem(),0); //事件循环操纵递归
}
}
之所以堆栈溢出会被消除,是因为事件循环操纵了递归,而不是调用堆栈。该方法从头到尾都没有直接递归调用,所以无论迭代多少次,调用堆栈一直保持清空的状态。
(6)看下一段代码,控制台会输出什么?为什么?
(function(x){
return (function(y){console.log(x)})(2);
})(1);
控制台会输出1。因为这里创建了闭包,闭包可以访问外部函数的变量和参数。
(7)输出什么?
const add = () => {
const cache = {};
return num => {
if (num in cache) {
return `From cache! ${cache[num]}`;
} else {
const result = num + 10;
cache[num] = result;
return `Calculated! ${result}`;
}
};
};
const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
Calculated! 20
From cache! 20
From cache! 20
分析:add
函数是一个记忆函数。 通过记忆化,我们可以缓存函数的结果,以加快其执行速度
如果我们使用相同的参数多次调用addFunction
函数,它首先检查缓存中是否已有该值,如果有,则返回缓存值,这将节省执行时间。如果没有,那么它将计算该值,并存储在缓存中。