什么是闭包?闭包的作用及应用场景

什么是闭包?闭包的作用及应用场景

文章目录

一、什么是闭包二、闭包的作用三、使用闭包的注意点四、思考题五、应用场景参考文章

一、什么是闭包

假设,把下面三行代码放在一个立即执行函数中。 三行代码中,有一个局部变量local,有一个函数foo,foo里面可以访问到local变量。

好了这就是一个闭包:

「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。

有的同学就疑惑了,闭包这么简单么?

「我听说闭包是需要函数套函数,然后 return 一个函数的呀!」

比如这样:

function foo(){

var local = 1;

function bar(){

local++;

return local;

}

return bar;

}

var func = foo()

func()

这里面确实有闭包,local变量和bar函数组成了一个闭包(Closure)。

为什么要函数套函数呢?

是因为需要局部变量,所以才把 local 放在一个函数里,如果不把 local 放在一个函数里,local 就是一个全局变量了,达不到使用闭包的目的——隐藏变量(等会会讲)。

有些人看到「闭包」这个名字,就一定觉得要用什么包起来才行。其实这是翻译问题,闭包的原文是 Closure,跟「包」没有任何关系。

所以函数套函数只是为了造出一个局部变量,跟闭包无关。

为什么要 return bar 呢?

因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。

所以 return bar 只是为了 bar 能被使用,也跟闭包无关。

二、闭包的作用

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。

如果不用闭包,你可以直接用一个全局变量:

window.lives = 30 // 还有三十条命

这样看起来很不妥。万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人「直接访问」这个变量。怎么办呢?

用局部变量。

但是用局部变量别人又访问不到,怎么办呢?

暴露一个访问器(函数),让别人可以「间接访问」。

代码如下:

!function(){

var lives = 50

window.奖励一条命 = function(){ // 简明起见,用了中文

lives += 1

}

window.死一条命 = function(){

lives -= 1

}

}()

那么在其他的 JS 文件,就可以使用 window.奖励一条命() 来涨命,使用 window.死一条命() 来让角色掉一条命。

看到闭包在哪了吗?

闭包是 JS 函数作用域的副产品。换句话说,正是由于 JS 的函数内部可以使用函数外部的变量,所以这段代码正好符合了闭包的定义。而不是 JS 故意要使用闭包。

JS变量作用域存在于函数体中即函数体,并且变量的作用域是在函数定义声明的时候就是确定的,而非在函数运行时。只要你懂了 JS 的作用域,你自然而然就懂了闭包,即使你不知道那就是闭包。

三、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

四、思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一

var name = "The Window";

var object = {

name : "My Object",

getNameFunc : function(){

return function(){

return this.name;

};

}

};

alert(object.getNameFunc()()); // The Window

代码片段二

var name = "The Window";

var object = {

name : "My Object",

getNameFunc : function(){

var that = this;

return function(){

return that.name;

};

}

};

alert(object.getNameFunc()()); // My Object

五、应用场景

例子1

function fn(){

var a = 1;

return function(){

return ++a;

}

}

alert(fn()());

alert(fn()()); //每次执行都会初始化 a = 1

//2 2

例子2

function outerFn(){

var i = 0;

function innerFn(){

i++;

console.log(i);

}

return innerFn;

}

var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址

inner();

inner();

inner();

var inner2 = outerFn();

inner2();

inner2();

inner2();

//1 2 3 1 2 3

例子3

(function() {

var m = 0;

function getM() {

return m;

}

function seta(val) {

m = val;

}

window.g = getM;

window.f = seta;

})();

f(100);

console.log(g()); //100 闭包找到的是同一地址中父级函数中对应变量最终的值

例子4

var add = function(x) {

var sum = x;

var tmp = function(x) {

sum = sum + x;

return tmp;

}

tmp.toString = function() {

return sum;

}

return tmp;

}

alert(add(1)(2)(3)); //6

alert(add(1)(2)(3)(4)); //10

上面代码中,例add(1)(2)(3),代码执行add(1)的时候,声明了add函数的局部变量sum并赋值为1,同时返回子函数tmp,这样add(1)(2)(3)就相当于tmp(2)(3),因为tmp函数需要用的sum这个变量,使得add执行完毕之后并没有清除sum这个局部变量的数据。这样执行tmp(2)的时候将2与sum相加保存在sum上,同时返回自身tmp。这时sum为3,tmp(2)(3)就相当于tmp(3),然后运行tmp(3),把3与sum相加保存在变量sum上,同时返回tmp。这时add(1)(2)(3)运行之后结果是tmp函数(sum=6),而用console.log()函数显示结果的时候会将里面的内容自动转换为字符串,所以console.log(tmp)相当于console.log(tmp.toString()),而这个toString()函数被重定义为return sum,所以结果就是console.log(sum)//6,以此类推如果后面还有括号那么sum将继续加下去到最后剩下tmp然后运行toString()返回结果。

点这里了解柯里化函数。

例子5

function fun(n,o) {

console.log(o);

return {

fun:function(m) {

return fun(m,n);

}

};

}

var a = fun(0); //undefined

a.fun(1); //0

a.fun(2); //0

a.fun(3); //0

var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2

var c = fun(0).fun(1);

c.fun(2);

c.fun(3); //undefined 0 1 1

例子6

//事件处理函数中闭包的写法

var lis = document.getElementsByTagName("li");

for(var i = 0; i < lis.length; i++){

(function(i){

lis[i].onclick = function(){

console.log(i);

};

})(i);

}

上面代码中,事件函数中用到了循环值i,利用闭包其实就是为了把当前的循环值传递进去。

例子7

function fn(){

var arr = [];

for(var i = 0;i < 5;i ++){

arr[i] = function(){

return i;

}

}

return arr;

}

var list = fn();

for(var i = 0,len = list.length; i < len; i ++){

console.log(list[i]());

}

//5 5 5 5 5

例子8

function fn(){

var arr = [];

for(var i = 0;i < 5;i ++){

arr[i] = (function(i){

return function (){

return i;

};

})(i);

}

return arr;

}

var list = fn();

for(var i = 0,len = list.length; i < len; i ++){

console.log(list[i]());

}

//0 1 2 3 4

上面例子7和例子8代码对比,例子8在for循环里arr[i] = 这一句主要是赋予它一个闭包,赋予的是立即执行函数“(function(i{…}))(i)”,执行之后返回的函数保存了i的值。相比较例子7只是赋予了普通函数,返回的i在最后调用的时候才获取,这时候i已经变成5了, 如果把例子7的for循环中var i = 0改为let i = 0,结果就和例子8一样,因为let会保证i每次都是不同的。

参考文章

https://zhuanlan.zhihu.com/p/22486908 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html https://blog.csdn.net/weixin_43586120/article/details/89456183

相关风雨

常见的数学符号读法
365bet线上娱

常见的数学符号读法

🌊 06-30 💨 阅读 8895
无影礼包抽枪规律 总价14900钻
pc365buy

无影礼包抽枪规律 总价14900钻

🌊 07-16 💨 阅读 2971
csgo龙狙为什么这么贵 csgo龙狙会掉价吗
365bet线上娱

csgo龙狙为什么这么贵 csgo龙狙会掉价吗

🌊 07-06 💨 阅读 2589
蘑菇中毒有哪些症状,该如何解救?
be365

蘑菇中毒有哪些症状,该如何解救?

🌊 07-03 💨 阅读 8664