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

为啥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这里是小白聚集地,欢迎初学和进阶中的小伙伴。”

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,027评论 0 2
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,727评论 0 38
  • --- 学习目标: - 掌握编程的基本思维 - 掌握编程的基本语法 typora-copy-images-to: ...
    YFBigHeart阅读 1,046评论 0 2
  • 不管你是成功达人亦或是名利皆无,幸福和烦恼总是不分等级悄然而至,你的开心的不开心的所有不愿意跟其他人分享的都可以找...
    bailu89阅读 250评论 0 0