【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

View Code

你做对了吗?

 

什么是闭包

我们知道,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。当然还有其他的办法这里不说了。

小结

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

 

 

 

 

 

时间: 2024-11-02 00:38:30

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

JavaScript基础篇(6)之函数表达式闭包_javascript技巧

 其实js支持函数闭包的主要原因是因为js需要函数能够保存数据.这里的保存数据是只函数在运行结束以后函数内变量的值也会进行保存.至于为什么js需要在函数内可以保存数据,那就是js是一种函数式语言.在函数内保存数据是函数式语言的一大特征. 回顾前面介绍过的三种定义函数方式 functiosu(numnumreturnunum//函数声明语法定义 vasufunction(numnum)returnunum}//函数表达式定义 vasuneFunction("num""num&q

全面理解JavaScript中的闭包_基础知识

引子 闭包是有权访问另一个函数作用域中的变量的函数. 闭包是javascript中很难理解的部分,很多高级的应用都依靠闭包来实现的,我们先来看下面的一个例子: function outer() { var i = 100; function inner() { console.log(i); } } 上面代码,根据变量的作用域,函数outer中所有的局部变量,对函数inner都是可见的:函数inner中的局部变量,在函数inner外是不可见的,所以在函数inner外是无法读取函数inner的局部

【javascript基础】3、变量和作用域

原文:[javascript基础]3.变量和作用域 前言 这篇和大家说一下javascript中的变量和作用域,由于是将基础嘛,主要给大家捋一下知识,不想翻开书复习的道友可以看一下,打算刚开始学习javascript的同学可以扫一眼. PS:jQuery源码交流群( 239147101)等你来,群里高手云集,让我受益匪浅,尽量少灌水. 变量 javascript中有两种变量,分别是基本类型和引用类型,基本类型是Null,Undefined,String,Boolean,Number这五种,前面简

JavaScript基础函数整理汇总_基础知识

这里给大家整理汇总了一些javascript的基础函数,都是比较常用和实用的.整理出来也有助于大家更好的理解javascript. 复制代码 代码如下: <script type="text/javascript">     /*创建函数和字面量函数*/     /*         function add(){             alert("函数创建成功")         };         var testfunction=functi

javascript基础知识分享之类与函数化_基础知识

1.对象适合于收集和管理数据,容易形成树型结构. Javascript包括一个原型链特性,允许对象继承另一对象的属性.正确的使用它能减少对象的初始化时间和内存消耗. 2.函数它们是javascript的基础模块单元,用于代码复用.信息隐藏和组合调用.函数用于指定对象的行为.一般来说,编程就是将一组需求分解成一组函数和数据结构的技能. 3.模块我们可以使用函数和闭包来构造模块.模块是一个提供接口却隐藏实现状态和实现的函数或对象. 1.自定义类型--构造函数模式(伪类模式) 在基于类的系统中,对象是

JavaScript基础教程——入门必看篇_基础知识

JavaScript他是一种描述性语言,其实他并不难学,只要用心学,一定会学好,我相信大家在看这篇文章的时候,一定也学过HTML吧,使用JavaScript就是为了能和网页有更好的交互,下面切入主题.     一. JavaScript 1.什么是JavaScript JavaScript是一种描述性语言,也是一种基于对象(Object)和事件驱动(Event Driven)的,并具有安全性的脚本语言. 2.JavaScript的特点 JavaScript主要用来向HTML页面添加交互行为. J

JavaScript中的闭包原理分析_javascript技巧

我们来看一个定义: Closure 所谓"闭包",指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 这说明了,JavaScript中的闭包是包含了上下文的函数,也就是说,这个函数的作用基础,是它所处的环境,这是不能超越的,跟线性代数是不是有一点似曾相识的感觉呢? 换个角度看,闭包的作用是为了实现OO.JavaScript中,没有像C++那样的public.private.protect属性标识, 建立起类比较困难."类

前端技术系列课程(No.2) &amp;#8211; HTML/CSS/JavaScript基础

本次课为系列课程的第二课,由舒克带来的"HTML/CSS/JavaScript"基础知识,课程中着重介绍了HTML的语义化,因为语义化是前端工程师最容易理解又最难掌握的,比如何时使用什么样的标签,这取决于前端工程师对标签和页面内容的理解.要注意频道首页和详情页是不同的,频道首页中的信息比较琐碎,详情页中的内容更像"文章",因此内容的语义是有差别的.这一点需要注意. http://www.slideshare.net/lijing00333/htmlcssjs 在HT

JavaScript中消除闭包的一般方法介绍

 这篇文章主要介绍了JavaScript中消除闭包的一般方法介绍,本文直接给出了操作示例,需要的朋友可以参考下     JavaScript 的闭包是一个其主动发展的特性, 也是一个被动发展的特性. 也就是说, 一方面, JS 有了闭包能更好解决一些问题. 另一方面, JS 为了解决某些问题, 而不得不使用闭包勉强来解决问题. 前者这里不讨论, 如果 JS 闭包能更好的解决问题, 当然使用闭包更好. 我讨论的是后者, 是因为 JS 本身的限制, 而不得不磕磕绊绊地用闭包来解决的问题, 例如"变量