`
qq466862016
  • 浏览: 128180 次
  • 来自: 杭州
社区版块
存档分类
最新评论

Javascript 闭包全面解析

阅读更多

        今天有人问了下有关javascript的闭包的问题,自己也没有看相关的文档,只是模糊的回答了下。回答完之后感觉那样对自己不好,一定要弄清javascript的闭包。正好在火狐开发者社区看到一篇有关闭包的文章https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures 

一、什么是闭包?

     闭包是指函数有自主独立的变量,也就是说定义闭包中的函数可以记忆它创建时候的“环境”。

二、 语法的作用域

看看下面的函数

 

<script type="text/javascript">


function init() {
	
	var name ="dongtian";
	function displayName(){
		alert(name);
	}
}

init();
</script>

 

 

在函数init()里面定义一个变量名称为name的临时变量,然后定义一个名称为displayName 的内部函数。此内部函数仅仅只有在本init方法内可用,其他都没有办法调用,你看displayName函数内并没有自己的局部变量,然而它可以访问到外包函数的变量-可以使用父函数使用的变量。

我们更改上面的代码

 

<script type="text/javascript">


function init() {
	
	var name ="dongtian";
	function displayName(){
		alert(name);
	}
	//仅仅在init方法体内调用
	displayName();
}

init();
</script>

 这是作用域的一个例子,在javascript 中,变量的作用域是嵌套函数可以访问外部的变量。

 

 

三、闭包

我们先看下面的一个例子

 

<script type="text/javascript">


function init() {
	
	var name ="dongtian";
	function displayName(){
		alert(name);
	}
	//仅仅在init方法体内调用
	return displayName;
}

var display = new init();

display();
</script>

        运行代码与上面的结果一样,这段代码看起来比较别扭,但是运行正常,我们都知道一个函数的作用域仅仅在运行期可用,当init方法运行完之后我们会认为name变量已经失效不可用了,但是虽然运行没有问题,实际上是对应name变量不是那样不可用已经失效了。

 

   因为这个init已经变成闭包了,是闭包的一个特殊对象,它有两部分构成:函数以及创建函数的环境,环境由闭包创建时候在作用域的任何局部变量组成,-上面我们可以说 init 是个闭包,由displayName函数和闭包创建时候的name="dongtian"组成。

下面看一个加法器的一个闭包的写法:

 

<script type="text/javascript">

function  subAdd(x) {
	
	return function(y) {
		return x +y;
	}
}


var add1 = subAdd(2);
var add2 = subAdd(5);

alert(add1(2));
alert(add2(3));


</script>

 

 

   这个例子我们定义了subAdd()函数,带有一个参数x,并且返回一个新的函数,返回的函数中带有一个参数y并返回 x和y的和。从本质上讲 subAdd()函数是个工厂函数,创建它并且要指定值并且包含一个求和的函数。在上面的例子中我们通过此函数工厂创建了两个函数一个将参数2求和一个将参数5求和,从这我们可以看出此两个函数都是闭包,它们共享函数的定义并且他们处在不同的环境中。

运行结果是 弹出 4 和 8

 

四、为何使用闭包

理论就是这些了 — 可是闭包确实有用吗?让我们看看闭包的实践意义。闭包允许将函数与其所操作的某些数据(环境)关连起来。这显然类似于面向对象编程。在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

       因而,一般说来,可以使用只有一个方法的对象的地方,都可以使用闭包。

       在 Web 中,您可能想这样做的情形非常普遍。大部分我们所写的 Web JavaScript 代码都是事件驱动的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常添加为回调:响应事件而执行的函数。

         以下是一个实际的示例:假设我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如页眉)的字号:

 

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
}

 

 

       我们的交互式的文本尺寸按钮可以修改 body 元素的 font-size 属性,而由于我们使用相对的单位,页面中的其它元素也会相应地调整。

 

以下是 JavaScript:

 

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

 

 

      size12size14 和 size16 为将 body 文本相应地调整为 12,14,16 像素的函数。我们可以将它们分别添加到按钮上(这里是链接)。如下所示:

 

 

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

 

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

 

 

五、用闭包模拟私有方法

 

      诸如 Java 在内的一些语言支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。

对此,JavaScript 并不提供原生的支持,但是可以使用闭包模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

下面的示例展现了如何使用闭包来定义公共函数,且其可以访问私有函数和变量。这个方式也称为 

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

         这里有很多细节。在以往的示例中,每个闭包都有它自己的环境;而这次我们只创建了一个环境,为三个函数所共享:Counter.increment,Counter.decrement  Counter.value

 

         该共享环境创建于一个匿名函数体内,该函数一经定义立刻执行。环境中包含两个私有项:名为privateCounter 的变量和名为 changeBy 的函数。 这两项都无法在匿名函数外部直接访问。必须通过匿名包装器返回的三个公共函数访问。

 

        这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法范围的作用域,它们都可以访问privateCounter 变量和 changeBy 函数。

 

         您应该注意到了,我们定义了一个匿名函数用于创建计数器,然后直接调用该函数,并将返回值赋给Counter 变量。也可以将这个函数保存到另一个变量中,以便创建多个计数器。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

      请注意两个计数器是如何维护它们各自的独立性的。每次调用 makeCounter() 函数期间,其环境是不同的。每次调用中, privateCounter 中含有不同的实例。

       这种形式的闭包提供了许多通常由面向对象编程U所享有的益处,尤其是数据隐藏和封装。

 

六、在循环中创建闭包---- 一种常见的错误

    在 JavaScript 1.7 引入 let 关键字 之前,闭包的一个常见的问题发生于在循环中创建闭包。参考下面的示例:

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>

 

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

 

      数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的输入域的 ID。通过循环这三项定义,依次为每一个输入域添加了一个 onfocus 事件处理函数,以便显示帮助信息。

运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个输入域上,显示的都是关于年龄的消息。

          该问题的原因在于赋给 onfocus 是闭包setupHelp中的匿名函数而不是闭包对象;在闭包(setupHelp)中一共创建了三个匿名函数,但是它们都共享同一个环境(item)。在 onfocus 的回调被执行时,循环早已经完成,且此时 item 变量(由所有三个闭包所共享)已经指向了 helpText 列表中的最后一项。

    解决这个问题的一种方案是使onfocus指向一个新的闭包对象。

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

           这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的环境。在这些环境中,help 指向 helpText 数组中对应的字符串。

 

七、性能

       如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用,方法都会被重新赋值一次(也就是说,为每一个对象的创建)。

       考虑以下虽然不切实际但却说明问题的示例:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

     上面的代码并未利用到闭包的益处,因此,应该修改为如下常规形式:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

    或者改成

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

 

   在前面的两个示例中,继承的原型可以为所有对象共享,且不必在每一次创建对象时定义方法。

八、总结

记住闭包的作用一般有两种,所有局部变量永驻内存和外包无法访问到局部变量。

 

3
3
分享到:
评论

相关推荐

    JavaScript内核系列 pdf

    总之,JavaScript内核系列的资料全面覆盖了JavaScript语言的各个方面,无论是初学者还是有经验的开发者,都能从中获益,提升自己的编程能力。深入学习并实践这些知识点,将使你在JavaScript的世界里游刃有余。

    闭包权威指南] Closure:The Definitive Guide Michael Bolin

    本书是关于JavaScript闭包的深度解析与应用实践的权威之作,深入探讨了闭包在JavaScript中的各种应用场景和技术细节。 **作者简介:** Michael Bolin是一位资深的软件工程师,拥有多年的编程经验,专注于...

    javascript手册全套必备

    "JavaScript手册全套必备"集合了从基础到高级的各种资源,旨在为开发者提供全面的学习材料。以下是一些重要的JavaScript知识点,结合描述中的内容进行展开: 1. **JavaScript 入门**: JavaScript 的基础学习包括...

    JavaScript权威指南(JavaScript犀牛书一本)

    3. **作用域和闭包**:解析了JavaScript中的变量作用域规则以及闭包的概念,这是解决许多复杂问题的基础。 4. **异步编程**:详述了事件循环、回调函数、Promise、async/await等异步处理方式,帮助开发者应对...

    JavaScript必看全面总结.zip

    这份名为“JavaScript必看全面总结.zip”的压缩包文件包含了一份深入的JavaScript学习笔记,旨在帮助开发者全面掌握这一动态类型的脚本语言。以下是根据标题和描述提炼出的JavaScript关键知识点: 1. **基础语法**...

    javaScript全面分析——中文版

    JavaScript全面分析——中文版是为想要快速理解和掌握JavaScript编程语言的学者精心编写的教程。JavaScript是一种广泛应用于Web开发的脚本语言,它在浏览器端运行,为网页添加交互性,使得用户界面更加生动活泼。本...

    javascript语言精粹 pdf

    总的来说,《JavaScript语言精粹》涵盖了JavaScript的各个方面,从基础到高级,从语法到实战,全面解析了这个强大且广泛应用的语言。无论是新手还是老手,都能从中受益匪浅,提升自己的JavaScript编程技能。

    JAVASCRIPT完全解析

    根据提供的标题“JAVASCRIPT完全解析”及描述“JAVASCRIPT完全解析,不错的PDF”,我们可以推断出这份文档是一本关于JavaScript编程语言的详细教程。JavaScript是一种广泛使用的脚本语言,在前端开发、后端开发乃至...

    深入理解JavaScript系列(.chm)

    深入理解JavaScript系列(3):全面解析Module模式 深入理解JavaScript系列(4):立即调用的函数表达式 深入理解JavaScript系列(5):强大的原型和原型链 深入理解JavaScript系列(6):S O L I D五大原则之...

    深入理解JavaScript系列

    深入理解JavaScript系列(3):全面解析Module模式 深入理解JavaScript系列(4):立即调用的函数表达式 深入理解JavaScript系列(5):强大的原型和原型链 深入理解JavaScript系列(6):S.O.L.I.D五大原则之...

    org.mozilla.javascript-1.7.2.jar

    Rhino的核心特性在于其对ECMAScript规范的全面支持,这使得JavaScript代码能够在Java环境中无缝执行。1.7.2版本发布于2009年,虽然相对较旧,但仍然包含了许多关键的JavaScript语言特性,如函数、对象、数组、正则...

    JavaScript语言精粹(高清电子版)和高性能JavaScript 双语版

    2. **作用域和闭包**:讲解了如何管理变量的作用域,以及闭包如何为JavaScript提供强大的特性,例如模块化和记忆化。 3. **原型和继承**:JavaScript的面向对象编程特性,如原型链、构造函数和实例化。 4. **异步...

    javascript权威指南电子书及全套源码

    《JavaScript权威指南》是JavaScript编程领域的一本经典之作,由David Flanagan撰写,第六版的中文扫描版在压缩包中提供了全面的JavaScript语言知识。这本书深入浅出地讲解了JavaScript的核心概念,包括语法、类型、...

    javascript全教程

    JavaScript全教程是一个旨在引导初学者全面理解JavaScript编程语言的学习资源。JavaScript,通常简称为JS,是一种广泛用于网页和网络应用的脚本语言。它在浏览器环境中运行,为动态交互、网页动画、数据处理以及Web...

    全面解析JavaScript Module模式

    JavaScript Module模式是编程实践中一种非常重要的设计模式,它主要用于实现JavaScript代码的模块化和封装,以提高代码的可维护性和可重用性。模块模式的关键在于利用匿名函数和闭包来创建私有作用域,从而保护内部...

    《JAVASCRIPT语言精髓与编程实践》.周爱民PDF

    《JAVASCRIPT语言精髓与编程实践》是周爱民撰写的一本深入解析JavaScript编程的著作,这本书旨在帮助读者理解JavaScript的核心概念,并将其应用于实际的编程实践中。JavaScript,作为全球最广泛使用的脚本语言,是...

    javascript经典源代码最全

    "javascript经典源代码最全"这个压缩包文件显然包含了大量JavaScript的源代码示例,旨在帮助开发者理解和学习JavaScript的核心概念、语法以及常见应用场景。下面我们将深入探讨JavaScript的关键知识点,并结合...

    javascript权威指南 epub书及源码

    《JavaScript权威指南》是一本备受推崇的编程教材,专注于JavaScript语言的深度解析。这本书由David Flanagan撰写,为读者提供了全面而深入的JavaScript知识。Epub格式的电子书便于在各种设备上阅读,而源码文件则...

Global site tag (gtag.js) - Google Analytics