我也来说说啥是闭包,本来很简单的事,别人怎么都讲那么复杂

为啥js中有闭包这个东西,其他后台语言里没有?

当一个函数调用时,内部变量的查找会按作用域链条来查找,按理说不会有啥特殊情况出现,js之所以会出现闭包这个现象,原因就是你调用的这个函数是另外一个函数的返回值。

说到函数能作为返回值,这是跟js中函数类型是第一类对象这种语言设计方式有关,我会先介绍第一类对象对js的编码风格的影响。当然你直接可以看第二部分,关于闭包的说明。

第一部分

啥是第一类对象呢,我帮你百度了。

第一类对象不一定是面向对象程序设计所指的物件,而可以指任何程序中的实体。一般第一类对象所特有的特性为:

可以被存入变量或其他结构

可以被作为参数传递给其他函数

可以被作为函数的返回值

可以在执行期创造,而无需完全在设计期全部写出

即使没有被系结至某一名称,也可以存在

在js中object类型就是第一类对象。你能怎么使用object类型。就怎么能使用function类型。

这里先说一下js中函数的四大作用

1.可以调用执行。程序世界里,函数的基本作用就是可复用代码段的封装,可以直接调用。

2.可以按照对象的使用方式来使用。原因就是js中函数类型是第一类对象。准确的来说js中函数本身就是对象。对象能做啥,他当然也能做。

3.可以提供作用域。js中没有块级作用域的概念。函数是提供作用域的最小单位。

4.可以作为构造函数。这一作用算是函数的特殊作用。可以作用生成其他对象的模板。也就是可以通过函数来模拟类的实现。

在讲闭包之前,先大致对函数的使用方式与对象和数组(数组本来就是对象,当然函数也是)做个对比。

1.关于字面量

对象的字面量"{}"

[code]var a = new Object();

a.b = "xxx";

//等同于

var a = {};

a.b = "xxx";

//或者

var a = {

b : "xxx"

};

[/code]数组的字面量"[]"

[code]var a = new Array('a','b','c');

//等同于

var a = ['a','b','c'];[/code]

那么函数呢,我们平常声明函数的方式,可以理解成是一种字面量(我没有说是)

[code]function a(x,y){

return x + y;

}

//相当于如下对象的字面量

var a = new Function(x,y,"return x + y;");[/code]

注意:上面所有a 都是对象的引用

2.关于匿名函数

同样也有匿名对象和匿名数组,我们先看看他们是怎么使用的

[code]var b = ({

a : "xxx"

}).a

alert(b) // "xxx"

for(var i = 0; i < [1,2,3].length; i++){

console.log(i);

}[/code]

同样函数也有匿名的

[code]funcion(){

alert("11");

}

//因为函数的最基本功能是调用,匿名函数也可以调用(我习惯称呼为函数自执行,一般书上都叫函数立即调用表达式)

(function(){alert(11)})();[/code]

3.可以存进变量或者其他结构。

因为数组元素中可以存入数组,当然也可以存入函数。对象也是,键值对的值可以存入任何东西,当然也可以存入函数,这时我们一般都用匿名函数,例如

[code]var a = function(){};//这种声明函数的方式也叫函数直接量。

var a = {

say : function(){//....}

};[/code]

4.可以做为参数,传入函数也就是平常我们说的回调函数。

众所周知函数有参数和返回值

对象和数组作为参数没得说,写下函数相关的例子

[code]var a = function(b){

b();

};

// 可以传入匿名函数,jquery中各种回调都是匿名的

a(function(){alert("123");});

//传入有名字的函数,跟c声明的位置无关,这里涉及到变量提升的问题以及函数优先初始化的问题。

a(c);

function c(){

alert("222")

} [/code]

广义的讲,当然了,回调函数,传入参数不一定非得函数变量,但是一定要包含函数的结构(例如数组、object对象、自定义对象),如下

[code]var a = function(object)

object.say();

}

a({x :"2222",say :function(){alert("xxxx")}});[/code]

5.作为返回值

对象和数组作为函数的返回值没得说,写下函数相关的例子

[code]function a(){

return function(){

alert("22222");

};

}

(a())();//alert "22222";[/code]

[attachimg][attachimg][attachimg]

第二部分

现在还是说说为啥出了个闭包这个东西,原因[color=Orange]就是你调用的那个函数是另一个函数的返回值,当外部调用时,会沿着这个返回值的函数作用域链条来找其内部相关变量的。[/color]

先大致说下作用域链条的问题。

函数中识别变量,是一层层向外找的,首先在函数内部找,看看是不是内部声明的,然后再到上一层找,没找到,再往上,直到全局作用域。如果全局页面都没声明,那浏览器就报错了。这一层层中的层是什么东西呢,就是函数,因为函数提供最小的作用域。看个例子

[code]var a = 3;

var b = 4;

function outer(){

var a = 5;

var c = 7;

var d = 8;

console.log(a);//5,outer内部的

console.log(b);//4,全局的

var inner = function(){

var c = 6;

console.log(b);//4,全局的

console.log(c);//6,inner内部的

console.log(d);//8,outer内部的

//console.log(e); //报错,没找到

b = 0 //找到全局的

d = "xxx";

}

inner();

console.log(b);//0,找全局的b

console.log(d);//"xxx", outer内部的

}

outer();[/code]

作用域链条我们明白了,然后咱再来看看闭包的情形

[code]//代码1

function a(){

var x = 0;

return function(){

x++;//此函数的作用域链能看到x

console.log(x);

}

}

var fun = a();//a返回的是个函数,保存起来没问题。

fun()//打印1

fun()//打印2[/code]

为啥打印2而不是1呢,原因是

[color=Orange]因为a中返回个函数,我们要调用这个函数,浏览器一看,你要运行的是函数,函数是有作用域链条的,哦,x我能找到,保证不报错的。里面的x当然也能自增加了[/color]

说的直白点就像如下代码一样

[code]//代码2

var x = 0;

var fun = function(){

x++;

console.log(x);

}

fun();//打印1

fun();//打印2[/code]

[color=Orange]补充:[/color]经网友提醒,闭包有占用内存的问题,这里说下,因为代码1中fun是一个函数的引用,浏览器对应的会对其作用域链条中的变量x做了保存,因而会占用内存。达到的效果就跟代码2中的x一样。

要释放其内存可以把其引用置空,使a返回的那个匿名函数无引用指向它,自然垃圾回收器会回收的。代码如下

[code]//代码3

function a(){

var x = 0;

return function(){

x++;//此函数的作用域链能看到x

console.log(x);

}

}

var fun = a();//a返回的是个函数,保存起来没问题。

fun()//打印1

fun()//打印2

//以后不再使用了,注意要释放内存

fun = null;

[/code]

注意:

[color=Orange]如果我换种调用方法呢

(a())();//打印1

(a())();//打印1

诶,为啥第二次不打印2了呢。原因很简单,因为两次调用返回的不是同一个函数引用,因此是两条作用域链条。[/color]

说的直白点就像如下的代码

[code]var x1 = 0;

(function(){

x1++;

console.log(x1);

})();//打印1

var x2 = 0;

(function(){

x2++;

console.log(x2);

})();//打印1[/code]

这种使用方式,就不会有出现闭包常驻内存的情况,因为每次使用都匿名的,当然了,也失去了闭包的意义。

大体闭包这种现象我是解释明白了。我没有给闭包下明确的定义,不同的书有不同的说法。

有的说,返回的那个函数是闭包,有的说返回的函数提供的作用域链条是闭包。有的甚至把其得到效果说是闭包,

大体是这么说的,通过这种方式,能访问某个部函数内部的私有变量,这种方式称为闭包。

不管怎么说都是跟函数的作用域链条相关的。更有甚者也有说所有函数都是闭包。

我个人觉得会出现闭包这个东西,主要原因就是跟js中函数是第一类对象有关,因为你调用的一个函数可能不是直接声明的,而是其他函数直接return的函数或者return某种结构中的一个函数。

关于是返回某种结构的中函数,举例如下

[code]function a(){

var x = 0;

var y = {name :"张三"};

var f1 = function(){

x ++;

}

var f2 = function(name){

y.name = name;

}

return [f1,f2];

}

var b= a()

b[0]();

b0;[/code]

再写个

[code]function a(){

var name = null;

var f1 = function(n){

name = n;

};

var f2 = function(){

return name;

};

return {

setName : f1,

getName : f2

}

}

var o =a();

o.setName("老姚");

var myName = o.getName();

console.log(myName);[/code]

如果在讲上述例子改写新的形式,把函数改成匿名的(有的人甚至觉得匿名函数是闭包,那样我会说,看来所有函数都是闭包了),就是一种设计模式:模块模式。

[code]var person = (function(){

var name = null;

var f1 = function(n){

name = n;

};

var f2 = function(){

return name;

};

return {

setName : f1,

getName : f2

}

}

)();

person.setName("老姚");

console.log(person.getName());[/code]

由此可以看出来应用闭包不只是简单的写个计数器啥的。

第三部分

​下面也是一种闭包的应用。

原先是闭包,改后也也是闭包。达到的效果是,添加一层隔绝,来满足我们需要的结果。

有时我们本意不想用闭包的,

如下,我想弹出0,1,2的,结果都会弹出3.

[code]var fun = function(){

var a = [];

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

a.push(function(){

return i;

})

}

//console.log(i);//因为js中没有块级作用域,i最后变成3,而不是报错

return a;

}

var a = fun();

alert(a[0]());//3

alert(a[1]());//3

alert(a[2]());//3[/code]

可以改成

[code]var fun = function(){

var a = [];

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

a.push(function(j){

return function(){

return j;

};

}(i))

}

return a;

}

var a = fun();

alert(a[0]())//0

alert(a[1]())//1

alert(a[2]())//2[/code]

最开始的那个例子也可以避免闭包,改成

[code]function a(){

var x = 0;

return function(){

(function(y){//y写x也没问题,变量首先会在此作用找的

y++;

console.log(y);

})(x)

};

}

var fun = a();

fun();//输出1

fun();//输出1[/code]

[color=Red]注意:经网友指正,原文中的代码如下,是有问题的,原因就是第二个return,又创建了闭包。代码敲顺手了,还得细心验证才行。[/color]

此处要改成,“方式二”

[code]function a(){

var x = 0;

return function(x){

return function(){

x++;

console.log(x);

}

}(x);

}[/code]

还有一种情况也会出现闭包现象,把内部函数绑定了dom节点某种操作(onclick)的回调函数,没有写在return语句里。道理是一样的。写在return里,是return后调用,绑定到dom上,比如说触发点击事件后再调用,其道理是一样的,作用域链条该怎么找就怎么找。

闭包最起码的应用,就是我们可以把一些全局变量封装起来,通过这种方式来不污染全局,例如上面的模块模式例子。

最后:

“相信有很多想学前端的小伙伴,今年年初我花了一个月整理了一份最适合2018年学习的web前端干货,从最基础的HTML+CSS+JS到移动端HTML5等都有整理,送给每一位前端小伙伴,53763,1707这里是小白聚集地,欢迎初学和进阶中的小伙伴。”

我也来说说啥是闭包,本来很简单的事,别人怎么都讲那么复杂_第1张图片

你可能感兴趣的:(我也来说说啥是闭包,本来很简单的事,别人怎么都讲那么复杂)