当前位置: 首页 > 编程笔记 >

javascript中闭包closure的深入讲解

萧心水
2023-03-14
本文向大家介绍javascript中闭包closure的深入讲解,包括了javascript中闭包closure的深入讲解的使用技巧和注意事项,需要的朋友参考一下

简介

闭包closure是javascript中一个非常强大的功能。所谓闭包就是函数中的函数,内部函数可以访问外部函数的作用域范围,从而可以使用闭包来做一些比较强大的工作。

今天将会给大家详细介绍一下闭包。

函数中的函数

我们提到了函数中的函数可以访问父函数作用域范围的变量,我们看一个例子:

function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 alertAddress();
}
parentFunction();

上面的例子中,我们在parentFunction中定义了一个变量address,在parentFunction内部定义了一个alertAddress方法,在该方法内部访问外部函数中定义的address变量。

上面代码运行是没问题的,可以正确的访问到数据。

Closure闭包

函数中的函数有了,那么什么是闭包呢?

我们看下面的例子:

function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 return alertAddress;
}
var myFunc = parentFunction();
myFunc();

这个例子和第一个例子很类似,不同之处就是我们将内部函数返回了,并且赋值给了myFunc。

接下来我们直接调用了myFunc。

myFunc中访问了parentFunction中的address变量,虽然parentFunction已经执行完毕返回。

但是我们在调用myFunc的时候,任然可以访问到address变量。这就是闭包。

闭包的这个特性非常拥有,我们可以使用闭包来生成function factory,如下所示:

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

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12

其中add5和add10都是闭包,他们是由makeAdder这个function factory创建出来的。通过传递不同的x参数,我们得到了不同的基数的add方法。

最终生成了两个不同的add方法。

使用function factory的概念,我们可以考虑一个闭包的实际应用,比如我们在页面上有三个button,通过点击这些button可实现html" target="_blank">html" target="_blank">修改字体的功能。

我们可以先通过function factory来生成三个方法:

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

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

有了这三个方法,我们把DOM元素和callback方法绑定起来:

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

使用闭包实现private方法

对比java来说,java中有private访问描述符,通过private,我们可以指定方法只在class内部访问。

当然,在JS中并没有这个东西,但是我们可以使用闭包来达到同样的效果。

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()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

我们在父function中定义了privateCounter属性和changeBy方法,但是这些方法只能够在内部function中访问。

我们通过闭包的概念,将这些属性和方法封装起来,暴露给外部使用,最终达到了私有变量和方法封装的效果。

闭包的Scope Chain

对于每个闭包来说,都有一个作用域范围,包括函数本身的作用域,父函数的作用域和全局的作用域。

如果我们在函数内部嵌入了新的函数,那么就会形成一个作用域链,我们叫做scope chain。

看下面的一个例子:

// global scope
var e = 10;
function sum(a){
 return function(b){
 return function(c){
  // outer functions scope
  return function(d){
  // local scope
  return a + b + c + d + e;
  }
 }
 }
}

console.log(sum(1)(2)(3)(4)); // log 20

闭包常见的问题

第一个常见的问题就是在循环遍历中使用闭包,我们看一个例子:

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();

上面的例子中,我们创建了一个setupHelp函数,setupHelp中,onfocus方法被赋予了一个闭包,所以闭包中的item可以访问到外部function中定义的item变量。

因为在循环里面赋值,所以我们实际上创建了3个闭包,但是这3个闭包共享的是同一个外部函数的作用域范围。

我们的本意是,不同的id触发不同的help消息。但是如果我们真正执行就会发现,不管是哪一个id,最终的消息都是最后一个。

因为onfocus是在闭包创建完毕之后才会触发,这个时候item的值实际上是变化的,在循环结束之后,item的值已经指向了最后一个元素,所以全部显示的是最后一条数据的help消息。

怎么解决这个问题呢?

最简单的办法使用ES6中引入的let描述符,从而将item定义为block的作用域范围,每次循环都会创建一个新的item,从而保持闭包中的item的值不变。

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

还有一种方法,就是再创建一个闭包:

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

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

这里用到了之前我们提到的function factory的概念,我们为不同的闭包创建了不同的作用域环境。

还有一种方法就是将item包含在一个新的function作用域范围之内,从而每次创建都是新的item,这个和let的原理是相似的:

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

第二个常见的问题就是内存泄露。

 function parentFunction(paramA)
 {
 var a = paramA;
 function childFunction()
 {
 return a + 2;
 }
 return childFunction();
 }

上面的例子中,childFunction引用了parentFunction的变量a。只要childFunction还在被使用,a就无法被释放,从而导致parentFunction无法被垃圾回收。

闭包性能的问题

我们定义了一个对象,并且通过闭包来访问其私有属性:

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

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

上面的对象会有什么问题呢?

上面对象的问题就在于,对于每一个new出来的对象,getName和getMessage方法都会被复制一份,一方面是内容的冗余,另一方面是性能的影响。

通常来说,我们将对象的方法定义在prototype上面:

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;
};

注意,我们不要直接重写整个prototype,这样会导致未知的错误,我们只需要根据需要重写特定的方法即可。

总结

闭包是JS中非常强大和有用的概念,希望大家能够喜欢。

到此这篇关于javascript中闭包closure的文章就介绍到这了,更多相关javascript闭包closure内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 本文向大家介绍Javascript闭包(Closure)详解,包括了Javascript闭包(Closure)详解的使用技巧和注意事项,需要的朋友参考一下 下面就是我的学习笔记,对于Javascript初学者应该是很有用的。 一、变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。 Javascript语言的特殊之处,就在于函

  • 本文向大家介绍JavaScript中的闭包(Closure)详细介绍,包括了JavaScript中的闭包(Closure)详细介绍的使用技巧和注意事项,需要的朋友参考一下 闭包是JavaScript中一个重要的特性,其最大的作用在于保存函数运行过程中的信息。在JavaScript中,闭包的诸多特性源自函数调用过程中的作用域链上。   函数调用对象与变量的作用域链   对于JavaScript中的每

  • 本文向大家介绍学习Javascript闭包(Closure)知识,包括了学习Javascript闭包(Closure)知识的使用技巧和注意事项,需要的朋友参考一下 一、变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。 另一方面,在函数外部自然无法读取

  • 本文向大家介绍C# 中闭包(Closure)详解,包括了C# 中闭包(Closure)详解的使用技巧和注意事项,需要的朋友参考一下 C# 中闭包(Closure)详解 这个问题是在最近一次英格兰 Brighton ALT.NET Beers 活动中提出来的。我发现,如果不用代码来演示,你很难单用话语把它解释清楚,所以,在这里,我打算用 C# 来解释一下什么是闭包(closures)。维基百科上说:

  • 本文向大家介绍深入解析JavaScript的闭包机制,包括了深入解析JavaScript的闭包机制的使用技巧和注意事项,需要的朋友参考一下 JavaScript 变量可以是局部变量或全局变量。 私有变量可以用到闭包。 全局变量 函数可以访问是有函数内部定义的变量,如: 实例 函数也可以访问函数外部定义的变量,如: 实例 后面一个实例中, a 是一个 全局 变量。 在web页面中全局变量属于 win

  • 本文向大家介绍LUA中的闭包(closure)浅析,包括了LUA中的闭包(closure)浅析的使用技巧和注意事项,需要的朋友参考一下 之前对closure一知半解,在网上也找不到一篇文章能把它说清楚,今天好像第一次对它有点清晰的了解 了,写个BLOG记念一下 lua的函数是一种 First-Class Value 的东西, 到底是啥? 就是它们与传统类型的变值没啥区别, 可以 存到一个变量中,

  • 主要内容:在闭包内部修改引用的变量,示例:闭包的记忆效应,示例:闭包实现生成器Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说: 函数 + 引用环境 = 闭包 同一个函数与不同引用环境组合,可以形成不同的实例,如下图所示。 图:闭包与函数引用 一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,

  • 闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。 不同的语言实