浅谈Javascript嵌套函数及闭包

【嵌套函数】
  JavaScript允许嵌入的函数,允许函数用作数据,并且在函数词法作用域下面,可以产生与传统面向对象语言不同的惊人地方。
  首先,JavaScript的函数是通过词法来划分作用域的,而不是动态的划分作用域的,于是,函数的是在定义它们的作用域中运行,而不是在执行它们的作用域中运行,所以,当嵌套函数和它的外围函数定义在同一个词法作用域中的时候,是很容易理解的。比如下面很平淡无奇的代码:


代码如下:

var x = 'global';
function f () {
var x = 'local';
function g() {
alert(x);
}
g();
}
f(); // 'local'

当f()调用的时候,作用域链可以理解为由两部分组成,包含f这一调用的调用对象,然后后面是全局对象。此时查找x的值,会先从f的调用对象中查找,如果没有,再查找后面全局对象中x。同理,g因为是f的一个嵌套函数,那么,g调用的时候,作用域链应该就是由三部分组成了,g的调用对象,f的调用对象,和全局对象。函数g是要输出x的值,所以会先在g的调用对象中查找x的值,g中没有定义,接下来查找外围f调用对象中x的定义,于是找到了x='local',那么就会输出x,而不会继续往下查找全局对象了。 如果f中也没定义x的值,那么就会继续查找作用域链后面的全局对象,结果就是global了。如果全局对象中也没定义,那么自然就是undefined。
  好了,我们对作用域链有了个初步的理解,同时我们知道,闭包有两个比较常用的用途,一个是可以利用它访问到局部变量,另一个是可以把它外围作用域中的变量值存储在内存中而不在函数调用完毕后就销毁。
  下面接着看一个平淡无奇的例子,或许可以帮助理解为什么闭包可以把外部变量值保存在内存中了。


代码如下:

function makeFunc (x) {
return function () {return x++}
}
var a = [makeFunc(0), makeFunc(1), makeFunc(2)];
alert(a[0]());
alert(a[1]());
alert(a[2]());

执行结果为0,1,2 ;也没有什么特别的地方,这也是严格的词法作用域的正常表现。每次makeFunc调用完毕后,它的调用对象会从作用域链中移除,再没有任何对它的引用,最终通过垃圾收集而完结。说的详细一点,我们可以这样理解。
  makeFunc每次调用的时候,会为他创建一个调用对象放置到作用域链中。针对makeFunc这个函数而言,这个调用对象包含一个属性x(也就是函数的参数,因为函数参数可以看做调用对象的一个属性),makeFunc会返回一个匿名嵌套函数的引用,接下来这个匿名嵌套函数执行,又会创建一个调用对象,放置到作用域链中,匿名函数返回x的值,(注意:匿名函数的调用对象中是没有x的定义的,于是它会引用到它外围的函数makeFunc的调用对象,访问到x)然后x加1,至此,匿名函数执行完毕,它调用对象从作用域链中移除, 然后makeFunc也执行完毕,makeFunc调用对象也被移除。由于它的调用对象中包含x,所以x也随着它的销毁而销毁。不会保存下来。
  以上就是函数的详细的执行过程,请仔细理解后看看下面改动的代码:


代码如下:

var x = 0;
function makeFunc () {
return function () {return x++}
}
var a = [makeFunc(), makeFunc(), makeFunc()];
alert(a[0]());
alert(a[1]());
alert(a[2]());

现在x是一个全局变量了,执行结果为0,1,2;但是这个结果就与上面的有些不同了。下面我们还是从作用域链的方向来理解这个结果产生的原因。
  同样,makeFunc每次调用的时候会创建一个调用对象到作用域链中,由于它返回内部嵌套函数的引用,所以内部嵌套函数开始执行,又创建一个嵌套函数的调用对象到作用域链。然后返回x的值,注意,这里就不同了,嵌套函数的调用对象中没有x,它外围的makeFunc的调用对象中也没有x,只能接着往下查找到全局对象中,在全局对象中找到了x的定义,于是正常执行,返回x的值,x加1,然后嵌套函数完毕,调用对象移除,接着makeFunc完毕,调用对象也移除,可是因为他们的调用对象中都没有x,他们的调用对象销毁根本不会影响到x。于是,全局变量x值的改变就这样被保存下来了。
  注意,上面说的访问外围的调用对象只是为了帮助理解而不严格的说法,JavaScript不会以任何方式直接访问调用对象,但是,它定义的属性作为调用对象中作用域链的一部分,还是“活的”。另外,如果一个外围函数包含了两个或多个嵌套函数都对全局对象有引用,那么这些嵌套函数都共享同一个全局调用对象,并且其中一个对全局对象的改变对其他的都是可见的。
  好了,在JavaScript里,函数是将要执行的代码以及执行这些代码的作用域构成的一个综合体,广义的说,我们就可以把这种代码和作用域的综合体叫做闭包。
  【闭包】
  我们偶尔需要写一个需要通过调用来记住一个变量值的函数。于是,如果我们了解了作用域,就会知道,局部变量是很难做到的,因为函数的调用对象不能在调用后一直维持。全局变量可以做到,就如上面的例子一样,但是这样很容易造成全局变量污染。既然调用对象不能维持,那么我们不把值保存在调用对象中不就行了?!所以,下面是实现的一种方法:用函数对象自身的属性来保存。


代码如下:

uniqueID = function () {
if (!arguments.callee.id) arguments.callee.id = 0;
return arguments.callee.id ++;
}
alert(uniqueID()); //0
alert(uniqueID()); //1

如上,因为函数本身就是一个对象,所以,我们用它自身的一个属性来保存是可行的,但是这样做有一个问题,就是任何人在任何时候都可以通过unqueID.id强制访问到我们原本保存到的值并作出修改。这是我们不愿看到的。
  所以,通常,我们使用闭包来实现这件事。如下:


代码如下:

_uniqueID = (function(){
var id = 0;
return function () {return id ++}
})();
alert(_uniqueID()); //0
alert(_uniqueID()); //1

同样,我们也用作用链域来解释下结果。注意到_uniqueID本身就是一个匿名函数,它内部又有个匿名嵌套函数,我们直接调用的是_uniqueID(),也就是说,我们直接调用的其实是_uniqueID内部的嵌套函数,而它本身的调用对象没有定义id,于是引用外围的调用对象中的id,并返回,id加1,执行完毕,内层嵌套函数调用对象移出作用域链。而外围的id并没有被销毁,于是就这样保存了下来。
  有人可能会疑惑,不是说调用对象在函数执行完毕后就移除了作用域链吗,外围匿名函数(function(){})();也是调用完毕了的,应该调用对象也没了才对。
  是的,调用对象是在当前函数执行完毕后就结束引用,但是这里不要误解了上面_uniqueID()的调用,他并不是直接调用的外围函数,而是调用的嵌套函数,嵌套函数的作用域链是包含外围函数的作用域链的。所以在它的调用对象移除作用域链的时候是能够访问到这条作用域链上其他对象的属性并改变的。
  闭包本身就是个难以理解但是又非常有用的东西,希望能对有需要的人一些帮助吧。此外,资历所限,本人理解也可能有误,如发现,敬请指正。

(0)

相关推荐

  • javascript 匿名函数的理解(透彻版)

    复制代码 代码如下: (function(){ //这里忽略jQuery所有实现 })(); (function(){ //这里忽略jQuery所有实现 })(); 半年前初次接触jQuery的时候,我也像其他人一样很兴奋地想看看源码是什么样的.然而,在看到源码的第一眼,我就迷糊了.为什么只有一个匿 名函数又没看到运行(当然是运行了--),就能有jQuery这么个函数库了?于是,我抱着疑问来到CSDN.结果相信现在很多人都很清楚了(因为在我之 后也不乏来者,呵呵~).当一个匿名函数被括起来,然后

  • JavaScript 闭包在封装函数时的简单分析

    wbkt2t最近发现了一个新名词:闭包.(自己落后了,要好好学习才行),baidu.google看了一下原理和实例,也明白了闭包的强大.JQuery也把闭包发扬光大了,网上一些个人开发的框架都是用了闭包了功能.知道了闭包的原理和使用发放,于是自己小小试验了一把,带着疑惑:使用闭包有什么好处?不使用会出现什么后果?写下了以下代码,也希望大家给小弟一些解答 使用闭包: 实例1 复制代码 代码如下: var $Darren; (function(){ var Obj={version:"1.0&quo

  • JavaScript 匿名函数(anonymous function)与闭包(closure)

    引入 匿名函数 闭包 变量作用域 函数外部访问函数内部的局部变量 用闭包实现私有成员 引入 闭包是用匿名函数来实现.闭包就是一个受到保护的变量空间,由内嵌函数生成."保护变量"的思想在几乎所有的编程语言中都能看到. 先看下 JavaScript 作用域: JavaScript 具有函数级的作用域.这意味着,不能在函数外部访问定义在函数内部的变量. JavaScript 的作用域又是词法性质的(lexically scoped).这意味着,函数运行在定义它的作用域中,而不是在调用它的作用

  • JavaScript中通过闭包解决只能取得包含函数中任何变量最后一个值的问题

    复制代码 代码如下: <script type="text/javascript"> function createArray() { var arr = new Array(); for (var i = 0; i < 10; i++) { arr[i] = function () { return i; } } return arr; } var funcs = createArray(); for (var i = 0; i < funcs.length;

  • Javascript的匿名函数讲解

    一.什么是匿名函数? 在Javascript定义一个函数一般有如下三种方式: 函数关键字(function)语句: 复制代码 代码如下: function fnMethodName(x){alert(x);} 函数字面量(Function Literals): 复制代码 代码如下: var fnMethodName = function(x){alert(x);} Function()构造函数: 复制代码 代码如下: var fnMethodName = new Function('x','al

  • (转载)JavaScript中匿名函数,函数直接量和闭包

    原文出处: http://www.dnew.cn/post/196.htm 先看下下面几种写法 1.function f(x){return x*x;};f(x); 2.(function(x){return x*x;})(x); 3.(function(x){return x*x;}(x)); 第一种我们应该都很熟悉了,这是我们经常使用的写法.第二第三种都是匿名函数的写法. ------------------------------------------------------------

  • 详谈JavaScript 匿名函数及闭包

    1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种:这也是最常规的一种 复制代码 代码如下: function double(x){     return 2 * x;   } 第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用. 复制代码 代码如下: var double = new Functio

  • 浅谈Javascript嵌套函数及闭包

    [嵌套函数] JavaScript允许嵌入的函数,允许函数用作数据,并且在函数词法作用域下面,可以产生与传统面向对象语言不同的惊人地方. 首先,JavaScript的函数是通过词法来划分作用域的,而不是动态的划分作用域的,于是,函数的是在定义它们的作用域中运行,而不是在执行它们的作用域中运行,所以,当嵌套函数和它的外围函数定义在同一个词法作用域中的时候,是很容易理解的.比如下面很平淡无奇的代码: 复制代码 代码如下: var x = 'global'; function f () { var x

  • 浅谈JS封闭函数、闭包、内置对象

    一.变量作用域指的是变量的作用范围,javascript中的变量分为全局变量和局部变量 1.全局变量:在函数之外定义的变量,为整个页面公用,函数的内部外部都可以访问. 2.局部变量:在函数内部定义的变量,只能在定义该变量的函数内部访问,外部无法访问.函数内部访问变量时,先在内部查找是否有此变量,如果有,就使用内部,如果没有,就去外部查找 二.封闭函数封闭函数是javascript中匿名函数的另外一种写法,创建一个一开始就执行而不用命名的函数. 1.一般函数的定义和执行函数 2.封闭函数的定义和执

  • 浅谈JavaScript function函数种类

    本篇主要介绍普通函数.匿名函数.闭包函数 1.普通函数介绍 1.1 示例 复制代码 代码如下: function ShowName(name) {     alert(name); } 1.2 Js中同名函数的覆盖 在Js中函数是没有重载,定义相同函数名.不同参数签名的函数,后面的函数会覆盖前面的函数.调用时,只会调用后面的函数. 复制代码 代码如下: var n1 = 1;   function add(value1) {     return n1 + 1; } alert(add(n1))

  • 浅谈JavaScript的函数及作用域

    函数和作用域是JavaScript的重要组成部分,我们在使用JavaScript编写程序的过程中经常要用到这两部分内容,作为初学者,我经常有困惑,借助写此博文来巩固下之前学习的内容. (一)JavaScript函数 JavaScript函数是指一个特定代码块,可能包含多条语句,可以通过名字来供其他语句调用以执行函数包含的代码语句. 1.JavaScript创建函数的方法有两种: 函数声明: function funcDeclaration(){ return 'A is a function';

  • 浅谈PHP匿名函数和闭包

    概述 闭包和匿名函数在PHP 5.3.0中引入,这两个特性非常有用,每个PHP开发者都应该掌握. 匿名函数其实就是没有名称的函数,匿名函数可以赋值给变量,还能像其他任何PHP函数对象那样传递.不过匿名函数仍然是函数,因此可以调用,还可以传入参数,适合作为函数或方法的回调. 闭包是指在创建时封装周围状态的函数,即使闭包所在的环境的不存在了,闭包中封装的状态依然存在. 创建匿名函数 创建匿名函数很简单: //将匿名函数赋给一个变量,通过变量名+()的形式来调用 $greet = function (

  • 浅谈javascript回调函数

    把函数作为参数传入到另一个函数中.这个函数就是所谓的回调函数 经常遇到这样一种情况,某个项目的A层和B层是由不同的人员协同完成.A层负责功能funA,B层负责funcB.当B层要用到某个模块的数据,于是他对A层人员说,我需要你们提供满足某种需求的数据,你给我提供一个接口. A层的人员说:我给你提供数据,怎么展示和处理则是B的事情. 当然B层不可能为你每个需求都提供一个数据接口,B给A提供一个通过的接口.B得到数据,然后B写函数去展示. 即,你需要和其他人合作,别人提供数据,而你不需要关注别人获取

  • 浅谈JavaScript for循环 闭包

    有个网友问了个问题,如下的html,为什么每次输出都是5,而不是点击每个p,就alert出对应的1,2,3,4,5. <html > <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>闭包演示</title> <script type="text/javascript&quo

  • 浅谈JavaScript节流和防抖函数

    概念 节流函数 间隔固定的时间执行传入的方法 目的是防止函数执行的频率过快,影响性能.常见于跟滚动,鼠标移动事件绑定的功能. 防抖函数 对于接触过硬件的人也许更好理解,硬件按钮按下时,由于用户按住时间的长短不一,会多次触发电流的波动,加一个防抖函数就会只触发一次,防止了无意义的电流波动引起的问题. 按键防反跳(Debounce)为什么要去抖动呢?机械按键在按下时,并非按下就接触的很好,尤其是有簧片的机械开关,会在接触的瞬间反复的开合多次,直到开关状态完全改变. 应用在前端时,常见的场景是,输入框

  • 浅谈Javascript中的函数、this以及原型

    关于函数 在Javascript中函数实际上就是一个对象,具有引用类型的特征,所以你可以将函数直接传递给变量,这个变量将表示指向函数"对象"的指针,例如: function test(message){ alert(message); } var f = test; f('hello world'); 你也可以直接将函数申明赋值给变量: var f = function(message){ alert(message); }; f('hello world'); 在这种情况下,函数申明

  • 浅谈JavaScript中变量和函数声明的提升

    现象: 1. 在JavaScript中变量和函数的声明会提升到最顶部执行. 2. 函数的提升高于变量的提升. 3. 函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找. 4. 匿名函数不会提升. 5. 不同<script>块中的函数互不影响. 例子: 函数声明提升高于变量声明 //同时声明变量a和函数a var a; function a() {} alert(typeof a); //显示的是"function",初步证明function的优先级高于var.

随机推荐