序
对于前端开发工程师来说,闭包是一个非常难理解和攻克的概念!因为闭包的生成不仅与变量的范围有关,还与变量的生命周期密切相关[1]。最后,我可以肯定地告诉你,闭包在实际开发过程中被广泛使用,所以你必须掌握它们。
我们先来看闭包的定义:闭包是一个函数,它可以访问另一个函数范围内的变量。创建闭包的一种常见方法是在一个函数中创建另一个函数。
//函数fn的局部变量user可以通过闭包得到。
函数fn(){ 0
var用户=' atguigu
返回函数(){ 0
返回用户;
}//通过匿名函数返回局部变量user。
}
console . log(fn()());//atguigu通过box()()直接调用匿名函数的返回值
var b=fn();
console . log(b());//atguigu调用匿名函数返回值的另一种方法可以通过闭包实现函数中局部变量的积累:
函数fn(){ 0
var num=100
返回函数(){ 0
数量;
返回数字;
}
}
var b=fn();//获取函数
//你会发现局部变量num在内存中并没有消失。
console . log(b());//调用匿名函数
console . log(b());//第二次调用匿名函数,实现累加。因为闭包所在范围内返回的局部变量不会被破坏,所以会占用内存。过度使用闭包会导致性能下降,因此建议您在必要时使用闭包。作用域链的机制会导致一个问题,即循环中匿名函数得到的任何变量都是最后一个值。
函数fn(){ 0
var arr=[];
//i是fn函数中的局部变量。
for(var I=0;I 3;I){ 0
arr.push(函数(){ 0
返回I;
});
}
返回逮捕;
}
var b=fn();
for(var I=0;i b .长度;I){ 0
console . log(b[I]());//3
}以上例子的输出结果都是3,是循环结束后I的值。这是因为在for循环期间,数组中的匿名函数不会自己执行。调用匿名函数时,通过闭包得到的I已经是3,所以每次输出都是3。如果要输出0,1,2的结果,可以进行以下调整:
函数fn(){ 0
var arr=[];
for(var I=0;I 3;I){ 0
arr.push((function(num ))
//将立即执行函数返回的匿名函数放入数组中。
//因为num是函数的参数,所以有自己独立的作用域。
返回函数(){ 0
返回数字;
};
})(I));
}
返回逮捕;
}
var b=fn();
for(var I=0;i b .长度;I){ 0
console . log(b[I]());//0,1,2
}通过立即执行匿名函数,将立即执行后返回的函数直接赋给array arr。每个循环将I的值传递给num,由于num在函数中,所以它有自己独立的作用域,所以num得到的值就是每个循环传递的I的值,即0,1,2。
接下来,让我们看看闭包中的这个对象:
这个对象指的是什么取决于函数运行的环境。如果函数是全局调用的,那么函数中的这个指的是窗口对象。对象,通过闭包,如果运行环境是window,这就是window。因为闭包不是这个对象的方法。
var color=' red
函数fn(){ 0
返回this.color
}
var obj={
颜色: '黄色',
fn : function(){ 0
返回函数(){//返回匿名函数。
返回this.color
}
}
}
console . log(fn());//red直接从外部将此称为窗口。
var b=obj . fn();//b是window下的变量,得到的值是obj对象下fn方法返回的匿名函数。
console . log(b());//红色因为它运行在窗口环境中,这意味着“窗口”
//您可以通过调用或应用来更改函数中的这一点。
console . log(b . call(obj));//黄色
console . log(b . apply(obj));//黄色
console . log(fn . call(obj));//黄色您可以通过变量在前面的作用域中获得这一点。
p>var color = "red"; function fn(){ return this.color; } var obj = { color:"yellow", fn:function(){ var _this = this;//将this赋值给变量_this return function(){ return _this.color;//通过_this获得上一个作用域中的this指向 } } } console.log(fn());//red var b = obj.fn(); console.log(b());//yellow可以通过构造方法传参来访问私有变量
function Desk(){ var str = "";//局部变量str,默认值为"" this.getStr = function(){ return str; } this.setStr = function(value){ str = value; }; } var desk = new Desk(); //为构造函数的局部变量写入值。 desk.setStr("atguigu"); //获取构造函数的局部变量 console.log(desk.getStr());//atguigu闭包常见的作用
1、模拟块级作用域(匿名自执行函数)
if () {},for () {} 等没有作用域,所以在其块内声明的变量,在外部是可以使用的。
//javaScript没有块级作用域的概念 function fn(num){ for(var i = 0; i < num; i++){} console.log(i);//在for外部i不会失败 } fn(2); if(true){ var a = 13; } console.log(a);//在if定义的变量在外部可以访问通过匿名自执行函数可以模拟块级作用域
(function(){ //i在外部就不认识啦 for(var i = 0; i < count; i++){} })(); console.log(i);//报错,无法访问由于外部无法访问自执行函数内的变量,因此在函数执行完后会立刻被销毁,在外部无法访问。从而可有效避免在多人开发时由于全局变量过多而造成的命名冲突问题。另外由于作用域链的机制,局部变量要比全局变量的访问速度更快,可以提升程序的运行速度!
2、对结果进行缓存
写一个用于实现所有参数和的函数:
var fn = function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum; } console.log(fn(1,2));//3以上函数接收一些number类型的参数,并返回这些参数之和。由于每次传递的参数相同,所以返回的结果是一样的。这样势必会造成了一种浪费,在此时咱们可以通过缓存机制来提高这个函数的性能。
var fn = (function(){ var cache = {}//将结果缓存到该对象中 return function(){ var str = JSON.stringify(arguments); if(cache[str]){//判断缓存中是否存在传递过来的参数,存在直接返回结果, 无需计算 return cache[str]; }else{//进行计算并返回结果 var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return cache[str] = sum; } } })()上面的示例将计算后的结果缓存到局部变量cache当中,在调用这个函数时,先在缓存中查找,如果找不到,则进行计算,然后将结果放到缓存中并返回,如果找到了,直接返回查找到的值。
最后我们总结知道,闭包解决了以下两个问题:
1. 可以读取函数内部的变量
2. 让这些变量的值始终保持在内存中。不会在函数调用后被清除
但同时,闭包也有它的缺点:
1. 由于闭包会是的函数中的变量都被保存到内存中,滥用闭包很容易造成内存消耗过大,导致网页性能问题。解决方法是在退出函数之前,将不再使用的局部变量全部删除。
2. 闭包可以使得函数内部的值可以在函数外部进行修改。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
了解更多内容:
前端安全相关面试题
前端JS高阶面试题
前端项目性能优化-面试题
前端ES6高频面试题
前端设计模式-面试题