【javascript基础】8、闭包

原文:【javascript基础】8、闭包

前言


函数和作用域啥的我们前面已经了解了,现在就要学习闭包了,这是一个挺晦涩的知识点,初学者可能会感觉不好理解,但是高手都不不以为然了,高手就给我提点意见吧,我和新手一起来学习什么是闭包。

例子

先不说定义,先看一个题,看看大家能得出正确的结果不,


function test(){
var arr = [];
for(var i = 0;i<10;i++){
arr[i] = function(){
return i;
}
}
return arr;
}

var fns = test();
console.log(fns[9]()); // 值是多少?
console.log(fns[0]());//值是多少?

结果就是

10
10

你做对了吗?

什么是闭包


我们知道,javascript中的变量作用域分为全局变量和局部变量,全局的变量我们在什么地方都可以使用,但是局部变量就不是这样的了,我们只能在该变量的作用域中得到,换句话说就是我们在函数的内部可以使用函数外部的变量,但是我们在函数的外部却不能使用函数内部定义的局部变量,但是在实际中我们就是想要在函数的外部使用函数内部定义的变量那该怎么办呢?例子来了

function test(){
var inner = 10;
}
alert(inner);//error?咋办

咋办呢?我们知道,在内部我们可以访问到这个变量,我们还知道有一个操作符return可以返回想要的值,那我就在内部定义一个函数来访问这个变量,然后在返回这个函数不就行了,实践一下


function test(){
var inner = 10;
function inFun(){
alert(inner);//
};
return inFun;
}
var outter = test();
outter();//10;

我们做到了,为自己鼓鼓掌,有时候我们就该不断鼓励自己一下,不要给自己太大的压力,我们不是富二代,在不鼓励一下自己怎么能成为富二代他爹呢。

这就是闭包了,官方没有给出闭包一个完整的准确的定义,民间流传的是在一个函数内定义一个函数,并且这个内部函数可以在外面访问,这时候就形成了闭包。看看上面函数的结构,一个函数返回了一个内部函数,我们知道在正常情况下,一个函数执行结束之后,里面的变量会被释放,也就是说,在test()这句执行之后,里面的inner应该被释放了才对,但是我们发现,outter()时我们拿到了inner的值,这就是闭包的特性:如果闭包中使用了局部的变量,那么这个变量会一直贮存在内存中,闭包会一直保持这个值,一直到外部的函数没有被引用为止,看例子


function closure(){
var num = 0;
function add(){
console.log(++num);
}
return add;
}
var test1 = closure();//形成一个闭包,保持着自己的一个num变量
test1 ();//1
test1 ();//2
var test2 = closure();//又一个闭包,保持了一个自己的num变量
test2 ();//1
test2 ();//2

好玩不?这就是闭包的神奇的地方,也是让身为初学者的我们感到彷徨的地方,相信我,我会让你们理解明白的。要想释放num占用的内存,就该这样

test1 = null;
test2 = null;

简单解析下这个例子:在执行 var test1 =
closure()时,由于closure()返回到是一个函数,这里就相当于test1变量指向了一个函数add,但是这个add函数有自己的作用域和活动对象,都存在了test1中,执行test1()时,会寻找num变量,由于闭包存储了该变量就可以直接取到,并且自加1,再一次执行test1()时会继续在test1执行的add函数的执行环境和作用域中查找,发现num为1了,就找到了这个num;在执行var
test2 = closure()时,会重新创建一个闭包,重新存储执行环境和活动对象,所以这是和第一次完全没有关系的。

闭包的机制


  1. 函数也是对象,有[[scope]]属性(只能通过JavaScript引擎访问),指向函数定义时的执行环境上下文。

  2. 假如A是全局的函数,B是A的内部函数。执行A函数时,当前执行环境的上下文指向一个作用域链。作用域链的第一个对象是当前函数的活动对象(this、参数、局部变量),第二个对象是全局window。

  3. 当执行代码运行到B定义地方, 设置函数B的[[scope]]属性指向执行环境的上下文作用域链。

  4. 执行A函数完毕后,若内部函数B的引用没外暴,A函数活动对象将被Js垃圾回收处理;反之,则维持,形成闭包。

  5. 调用函数B时,JavaScript引擎将当前执行环境入栈,生成新的执行环境,新的执行环境的上下文指向一个作用域链,由当前活动对象+函数B的[[scope]]组成,链的第一个对象是当前函数的活动对象(this、参数、局部变量组成),第二个活动对象是A函数产生的,第三个window。

  6. B函数里面访问一个变量,要进行标志符解析(JavaScript原型也有标识符解析),它从当前上下文指向的作用域链的第一个对象开始查找,找不到就查找第二个对象,直到找到相关值就立即返回,如果还没找到,报undefined错误。

  7. 当有关A函数的外暴的内部引用全部被消除时,A的活动对象才被销毁。

这段是其他的地方的,就是说了执行环境和作用域的理解闭包怎么维持变量的。

闭包的应用

一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,这既是函数也是弊端。我们可以利用闭包封装一些私有的属性,例如


var factorial = (function () {
var cache = [];
return function (num) {
if (!cache[num]) {
if (num == 0) {
cache[num] = 1;
}
cache[num] = num * factorial(num - 1);
}
return cache[num];
}
})();

封装了一个内部私有的属性来缓存结果。

下面流行的模块模式,它允许你模拟公共,私有以及特权成员


var Module = (function(){
var privateProperty = ‘foo‘;

function privateMethod(args){
//do something
}

return {

publicProperty: "",

publicMethod: function(args){
//do something
},

privilegedMethod: function(args){
privateMethod(args);
}
}
})();

另一个类型的闭包叫做立即执行函数表达式,是一个在window上下文中自我调用的匿名函数:


(function(window){

var a = ‘foo‘;

function private(){
// do something
}

window.Module = {

public: function(){
// do something
}
};

})(this);

闭包的弊端


由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public
Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

解释例子

回到开始的例子,这是闭包的经典的例子,这个和其他的例子和有些不一样,我们分析一下,这里用了一个数组,其实这里我们执行一次var fns =
test(),形成了10个闭包,数组的每一个项存了一个闭包,这与其他的例子是不一样的,其他的例子是函数执行一次形成了一个闭包,所以这个10个闭包的初始的执行环境是一样的,每一个闭包使用了i这个变量,这个变量在函数var fns
= test()执行之后变为了退出循环的那个i的值10,JavaScript是解释型的语言,所以在执行数组中的闭包的时,会找到此时i的值10;看看arr的结果

现在想怎样解决这个问题呢?我们想想,这10个闭包形成时的执行环境和活动对象是一样的,现在考虑的就是要在初始时就不一样,我们知道函数的作用域是一层一层的,那我们就需要在这之间家一层作用域,这层作用域要有不同的i的值,我们想到了自执行匿名函数,(funciton(){})(),我们把i的值穿进去,按值传参就是相当于复制了一份变量嘛,在(funciton(){})()外部的作用域中的i的值的改变不会改变内部的i的值,试一下


function test(){
var arr = [];
for(var i = 0;i<10;i++){
(function(i){ arr[i] = function(){return i;}})(i);
}
return arr;
}

var fns = test();
console.log(fns[9]()); // 值是9
console.log(fns[0]());//值是0

当然也可以这样


function test(){
var arr = [];
for(var i = 0;i<10;i++){

arr[i] = (function(i){return function(){return i}})(i);
}
return arr;
}

var fns = test();
console.log(fns[9]()); // 值是9
console.log(fns[0]());//值是0

这两个的实质都是在闭包形成之前,给每一个闭包包上一层作用域,在这个作用域中传一个参数,是每一个闭包上一级的作用域中都有不同的i。当然还有其他的办法这里不说了。

小结

闭包的应用场景挺多的,在模块化编程中很重要的,有些地方说函数也是闭包,还是那就话,概念不重要,理解会用才是最现实的。

【javascript基础】8、闭包,布布扣,bubuko.com

时间: 2024-10-05 07:39:49

【javascript基础】8、闭包的相关文章

javaScript基础之闭包

不管是Jquery还是EXTJS,现代的js框架中大量应用了js的一些特性,比如:匿名函数,闭包等等,导致源代码非常难以阅读. 不过要想真正的使用好前台的UI技术,还是需要去深入的理解这些概念.   在这里推荐几篇比较好的文章介绍javaScript基础: 当JavaScript从入门到提高前需要注意的细节:闭包部分:http://msdn.microsoft.com/zh-cn/library/hh968324.aspx 学习Javascript闭包(Closure):http://www.r

JavaScript基础–闭包

JavaScript基础–闭包 理解闭包的概念对于学习JavaScript至关重要,很多新手(包括我)开始学习闭包时,都会感觉似懂非懂,之前看了一些资料,整理了闭包的一篇博客,若有疏忽与错误,希望大家多多给意见. 概述 理解闭包的概念前,建议大家先回想一下JS作用域的相关知识,如果有疑问的同学,可以参考:JavaScript基础–作用域.闭包的定义如下: Closure is when a function is able to remember and access its lexical s

javascript基础-闭包

原理 函数里包含函数,即闭包. 包含函数的结果是,子函数会挟持父函数的活动对象.子函数在访问一个变量时,先读自身的活动对象,是否包含此变量,没有从父函数里找,还没有,去祖函数,层层回溯,直到window,还没有就返回undefined.如图: 优点 对象.模块化的基础. 缺点 增加额外的内存开销: 子函数销毁前都会挟持父函数的活动对象. IE8-(IE8和IE8以前)容易导致内存泄露: IE8-的DOM是由COM写的,标准浏览器(chrome,firefox,safari,opera)的内存回收

JavaScript基础——函数表达式、闭包

简介 函数表达式是JavaScript中的一个既强大又容易令人困惑的特性.定义函数的方式有两种:一种是函数声明,另一种就是函数表达式.函数声明的语法是这样的: function functionName(arg0 , arg1 , arg2--){ //函数体 } 首先是function关键字,然后是函数的名字,这就是指定函数名的方式.Firefox.Safari.Chrome和Opera都给函数定义了一个非标准的name属性,通过这个属性可以访问到给函数指定的名字.这个属性的值永远等于跟在fu

Javascript基础篇小结

Javascript基础篇小结 字数9973 阅读3975 评论7 喜欢28 转载请声明出处 博客原文 随手翻阅以前的学习笔记,顺便整理一下放在这里,方便自己复习,也希望你有也有帮助吧 第一课时 入门基础 知识点: 操作系统就是个应用程序 只要是应用程序都要占用物理内存 浏览器本身也是一个应用程序 浏览器本身只懂得解析HTML 调用浏览器这个应用程序的一个功能绘制 1.javascript介绍 JavaScript操作DOM的本质是=获取+触发+改变 目的:就是用来操作内存中的DOM节点 修改D

JavaScript基础细讲

JavaScript基础细讲 JavaScript语言的前身叫作Livescript.自从Sun公司推出著名的Java语言之后,Netscape公司引进了Sun公司有关Java的程序概念,将自己原有的Livescript 重新进行设计,并改名为JavaScript. JavaScript是一种基于对象和事件驱动并具有安全性能的脚本语言,有了JavaScript,可使网页变得生动.使用它的目的是与HTML超文本标识语言.Java 脚本语言一起实现在一个网页中链接多个对象,与网络客户交互作用,从而可

【javaScript基础】立即调用函数表达式

在javaScript中,每个函数被调用时,都会创建一个新的执行上下文.因为在一个函数里面定义的变量和函数只能在里面访问,在外面是不行的,上下文提供了一种很容易的方法来创建私有性. //makeCounter函数返回另外一个匿名函数,这个匿名函数能够访问到"私有"变量i, 好像有一点"特权"性. function makeCounter() { // i只能在makeCounter的里面被访问到 var i = 0; return function() { cons

学习之Javascript基础

1)HTML只是描述网页长相的标记语言,JavaScript是一种在浏览器端执行的语言,简称为JS,JavaScript是解释性语言,无需编译就可以随时运行,没有语法错误的部分还是能正确运行. 2)VS2010中有Javascript.JQuery的自动完成功能 3)JS点儿不出来的成员也许可用,点儿处理的成员也许不可用 4)VS2008的HTML编辑器中触发JavaScript自动完成:Ctrl+J 5)JS标签: <script type="text/javascript"&

JavaScript基础系列目录(2014.06.01~2014.06.08)

下列文章,转载请亲注明链接出处,谢谢! 链接地址: http://www.cnblogs.com/ttcc/tag/JavaScript%20%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%20%E6%80%BB%E7%BB%93/ 1. Javascript基础---语法(待完成) 2. JavaScript基础---数据类型(待完成) 3. JavaScript基础---Array数组(待完成) 4. JavaScript基础---正则表达式(待完成) 5. Jav