【javascript】Javascript闭包

  在描述闭包的实现与用途前,需要了解以下一些知识点。




  执行上下文(执行上下文环境) 


1 console.log(a);     //Uncaught ReferenceError: a is not defined
2
3 console.log(b);    //undefined
4 var b;
5
6 console.log(c);    //undefined
7 var c = 10;

  看上面一个例子,当直接在控制台输出a时,提示a未定义,这个很容易就理解了。不过在"var b"和"var c = 10"执行前输出b和c的值时,得到的结果却是undefined。因为代码是一条一条执行的,在执行console.log()之前,"var b"和"var c = 10"必定是还未执行的。而结果却与a的输出结果不同,这是怎么一回事?

 第二种情况:

1 console.log(this);

输出:

第三种情况:

1 console.log(f1);  
2 function f1(){};  //函数声明
3
4 console.log(f2);
5 var f2 = function (){};  //函数表达式

输出: 

  从这里可以看出来,在一段js代码拿过来真正一句一句运行之前,浏览器做了一些“准备工作”,其中就包括对变量的声明,而不是赋值,变量赋值是在赋值语句执行的时候进行的,还有函数的声明的赋值。而函数表达式类似与第一种情况中的var c = 10,两者结果也是相同的。当然还有this的赋值等等。

  下面总结一下浏览器在准备中做了哪些工作:

    变量、函数表达式 —— 变量声明,默认赋值为undefined;

    this —— 赋值

    函数声明 —— 赋值

  这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。

  以上的一切都是在全局环境中进行的。

  在函数中,除了以上数据之外,还会有其他数据。

1 function f(x){
2     console.log(arguments);
3     console.log(x);
4 }
5 f(10);

输出:

  以上代码展示了在函数体的语句执行之前,arguments变量和函数的参数都已经被赋值。

  从而我们可以看出来,函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。

  进一步来说,函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。

总结一下:

  全局代码的上下文环境数据内容为:


普通变量(包括函数表达式),

如: var a = 10;


声明(默认赋值为undefined)


函数声明,

如: function fn() { }


赋值


this


赋值

  如果代码段是函数体,那么在此基础上需要附加:


参数


赋值


arguments


赋值


自由变量的取值作用域


赋值

  给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。




  在执行js代码时,会有数不清的函数调用次数,会产生许多个上下文环境。这么多上下文环境该如何管理,以及如何销毁而释放内存呢?这就引入了一个概念——执行上下文栈



  在这里插播一个概念 —— this

  在平时,this使用频率非常高,我们在很多情况下都会用到它。而无论在哪个位置获取this,都是有值的。在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。因为this的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境。

  接下来,分四种情况描述this的取值:

  情况1:构造函数

    所谓构造函数就是用来new对象的函数。其实严格来说,所有的函数都可以new一个对象,但是有些函数的定义是为了new一个对象,而有些函数则不是。另外注意,构造函数的函数名第一个字母大写(规则约定)。例如:Object、Array、Function等。

    如果函数作为构造函数用,那么其中的this就代表它即将new出来的对象。这里有一个情况,当函数的调用不是new一个对象,而是直接调用函数时,其中的this的值为window。

    这里还有一个问题,在构造函数的prototype中,this代表着什么。其实不仅仅是构造函数的prototype,即便是在整个原型链中,this代表的也都是当前对象的值。

  情况2:函数作为对象的一个属性

    如果函数作为对象的一个属性时,并且作为对象的一个属性被调用时,函数中的this指向该对象。

  情况3:函数用call或者apply调用

    当一个函数被call和apply调用时,this的值就取传入的对象的值。 

  情况4:全局 & 调用普通函数

    在全局环境下,this永远是window,普通函数在调用时,其中的this也都是window。



下面继续学习上下文栈。

  执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。这其实就是一个压栈出栈的过程——执行上下文栈。如图:

  在上下文栈中,同一时间内只有一个活跃的上下文环境。

  当一个函数被调用时,将产生一个新的执行上下文环境,并被压入执行上下文栈中,原先的栈顶执行上下文环境进入非活跃状态,新压入的执行上下文环境则进入活动状态。

  当函数调用完成后,栈顶执行上下文环境被弹出栈,并被回收机制回收销毁,而新的栈顶上下文环境则进入活跃状态。

  不过这只是一个理想的运行情况,有时候执行上下文环境并不会说销毁就会被销毁的,这就是后面要介绍的——闭包。




   作用域 & 作用域链



  无论在哪个编程语言中,都有一个作用域的概念。而在javascript中,除了全局作用域之外,只有函数可以创建的作用域。作用域在函数定义时就已经确定了。而不是在函数调用时确定。

  所以,我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式。因为在函数中,如果声明变量时没用"var",则所声明的变量为全局变量。

  作用域是一个很抽象的概念,类似于一个“地盘”,如下图:

  作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

  作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

  我们把作用域与上下文环境结合起来看,如下图:

  

  在上图中,我们可以看到,作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

  所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

  这里有一个概念——自由变量。所谓"自由变量",即在这一个作用域中调用的变量,并没有在这个作用域中声明,而是在其他作用域声明的变量,即称之为"自由变量"。

  而我们在这个作用域中调用自由变量时,究竟要到哪里去获取这个变量的值呢?有人会说,到这个作用域的父作用域调用。这个说法有点模糊,有时候会产生歧义。正确来说,应该是:要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”。其实这也就是所谓的"静态作用域"。

  上面描述的只是跨一步作用域去寻找。

  如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

  这个一步一步“跨”的路线,我们称之为——作用域链。

  下面我们来用文字描述一下这个过程:(假设a为自由变量)  

    第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;

    第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;

    第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;

    第四步,跳转到第一步。




  接下来就是主菜 —— 闭包



   关于闭包,百度的解释是:闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。。。看起来有点云里雾里的。

  其实,我们只要了解应用的两种情况即可——函数作为返回值,函数作为参数传递。

  函数作为返回值:

 1 function fn(){
 2     var a = 10;
 3
 4     function bar(x){
 5         if (X < a){
 6             console.log(X);
 7         }
 8     }
 9
10     return bar;
11 }

  函数作为参数被传递:

 1 var
 2     max = 10;
 3     fn = function(X){
 4          if (X > max){
 5              console.log(X);
 6          }
 7          else{
 8              console.log(max);
 9          }
10      };
11
12 (function(f) {
13      var max = 100;
14      f(15);
15  })(fn);

  这里的输出结果为: 15。原因为取max的值时取的是创建这个函数的那个作用域中获取,而不是“父作用域”。所以取的值为10,而不是100。

  结合前面学过的,我们可以看看下面这张图:

  在图中我们可以看到,执行完第17行,fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。注意,重点来了:因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。因此,这里的fn()上下文环境不能被销毁,还依然存在与执行上下文栈中。

  执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态。

  执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10。而重点在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。从而fn上下文环境依旧在栈中没有销毁。

  虽说闭包的使用增加了内存的消耗,而且使用不当有可能造成内存泄露的问题发生,不过其还是有很多的用途。譬如匿名自执行函数、匿名自执行函数、实现封装以及实现面向对象中的对象,传统的对象语言都提供类的模板机制等等。

时间: 2024-10-13 15:55:27

【javascript】Javascript闭包的相关文章

javascript 关于闭包的知识点

javascript 关于闭包的认识 概念:闭包(closure)是函数对象与变量作用域链在某种形式上的关联,是一种对变量的获取机制. 所以要大致搞清三个东西:函数对象(function object).作用域链(scope chain)以及它们如何关联(combination) 首先要建立一个印象,在js中,几乎所有的东西可以看作对象,除了null和undefined.比如常用的数组对象.日期对象.正则对象等. var num = 123; // Number var arr = [1,2,3

javascript Closure 闭包

引用自 http://www.cnblogs.com/mguo/archive/2013/06/19/3143880.html /*一.变量的作用域要理解闭包,首先必须理解Javascript特殊的变量作用域.变量的作用域无非就是两种:全局变量和局部变量.Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量.*/ var n=999; function f1(){ alert(n); } f1(); // 999//另一方面,在函数外部自然无法读取函数内的局部变量. funct

JavaScript中闭包实现的私有属性的getter()和setter()方法

注意: 以下的输出都在浏览器的控制台中 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>闭包</title> </head> <body> <script type="text/javascript"> /** * 利用闭包实现 * 这个函数给对象o增加了属性存储器方法 * 方法名称为ge

javascript学习-闭包

javascript学习-闭包 1.什么是闭包 大多数书本中对闭包的定义是:“闭包是指有权访问另一个函数作用域中的变量的函数.”.这个概念过于抽象了,对初学者而言没啥帮助.好在<Javascript忍者秘籍>5.1中给了一个例子来进一步的解释了什么是闭包: var outerValue= 'ninja'; var later; function outerFunction() { var innerValue = "samurai"; function innerFunct

两个示例介绍JavaScript的闭包

JavaScript的闭包有两个用途:一个是访问函数内部的变量:另一个是让变量的值在作用域内保持不变.函数是JavaScript 中唯一有作用域的对象,因此JavaScript的闭包依赖于函数实现,下面结合两则示例对JavaScript的闭包做简单说明. 1. 访问函数内部的变量 JavaScript中没有严格意义上的类,所以我们常用函数来模拟类.我们可以像下面这样模拟一个Counter类,并在其中定义一个count私有变量: 在JavaScript中,函数可以访问其外部定义的变量,而外部不能访

JavaScript基础–闭包

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

javascript中闭包的工作原理

一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包.不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”.看下面这段代码: 1 2 3 4 5 6 7 function a() { var i = 0; function b() { ale

关于Javascript的闭包

要理解javascript的闭包 关键是弄明白三样事情 1.变量作用域 一言以蔽之  函数内部可以访问函数外部的字段  而反之不行 1 var n=999; 2 3 function f1(){ 4 alert(n); 5 } 6 7 f1(); // 999 1 function f1(){ 2 var n=999; 3 } 4 5 alert(n); // error 此处有一坑是 若在函数内声明变量不使用var关键字 相当于是声明全局变量 也就是window的变量 2.this的含义 th

javascript中闭包的原理与用法小结(转)

一.在javaScript中闭包的五种表现形式如下: 1 /** 2 * Created by admin on 2016/12/26. 3 *//* 4 //向函数对象添加属性 5 function Circle(r){ 6 this.r=r; 7 } 8 Circle.prototype.PI=3.1415926; 9 Circle.prototype.area=function(){ 10 return this.PI*this.r*this.r; 11 }; 12 var c=new C

深入理解JavaScript的闭包特性 如何给循环中的对象添加事件

初学者经常碰到的,即获取HTML元素集合,循环给元素添加事件.在事件响应函数中(event handler)获取对应的索引.但每次获取的都是最后一次循环的索引.原因是初学者并未理解JavaScript的闭包特性. 有个网友问了个问题,如下的html,为什么点击所有的段落p输出都是5,而不是alert出对应的0,1,2,3,4. <!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> &