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可实现修改字体的功能。


我们可以先通过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中非常强大和有用的概念,希望大家能够喜欢。

相关文章
|
1月前
|
自然语言处理 JavaScript 前端开发
JavaScript中闭包:概念、用途与潜在问题
【4月更文挑战第22天】JavaScript中的闭包是函数及其相关词法环境的组合,允许访问外部作用域,常用于数据封装、回调函数和装饰器。然而,不恰当使用可能导致内存泄漏和性能下降。为避免问题,需及时解除引用,减少不必要的闭包,以及优化闭包使用。理解并慎用闭包是关键。
|
1月前
|
JavaScript 前端开发
解释JavaScript闭包的工作原理,并举例说明其在游戏开发中的应用。
JavaScript闭包允许内部函数访问并保持对外部函数变量的引用,即使外部函数执行结束。当函数返回内部函数时,形成闭包,继承父函数作用域链。在游戏开发中,闭包用于创建具有独立状态和行为的角色实例。例如,`createCharacter`函数创建角色并返回包含属性和方法的对象,内部函数如`getHealth`、`setHealth`和`attack`通过闭包访问并操作角色的变量。这种方式确保了每个角色的状态在不同实例间独立,是实现游戏逻辑的强大工具。
21 2
|
1月前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
17 0
|
1月前
|
JavaScript 前端开发
JavaScript 闭包:让你更深入了解函数和作用域
JavaScript 闭包:让你更深入了解函数和作用域
|
1天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包是函数访问外部作用域变量的能力体现,它用于封装私有变量、持久化状态、避免全局污染和处理异步操作。
【6月更文挑战第25天】JavaScript闭包是函数访问外部作用域变量的能力体现,它用于封装私有变量、持久化状态、避免全局污染和处理异步操作。闭包基于作用域链和垃圾回收机制,允许函数记住其定义时的环境。例如,`createCounter`函数返回的内部函数能访问并更新`count`,每次调用`counter()`计数器递增,展示了闭包维持状态的特性。
15 5
|
8天前
|
设计模式 自然语言处理 JavaScript
JavaScript进阶-函数表达式与闭包
【6月更文挑战第18天】JavaScript函数不仅是代码块,还是值,具备函数表达式和闭包等特性。函数表达式如匿名函数,可赋值、传参,但不提升,过度使用影响可读性。闭包允许访问外部作用域,即使父函数已结束,但不当使用可能导致内存泄漏。理解并妥善处理这些问题,如命名函数表达式、及时释放引用,能提升代码质量。通过实践深化对这些关键概念的理解至关重要。
|
9天前
|
缓存 前端开发 JavaScript
【JavaScript】JavaScript 中的闭包:从入门到精通
【JavaScript】JavaScript 中的闭包:从入门到精通
13 0
|
1月前
|
缓存 自然语言处理 JavaScript
JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当
【5月更文挑战第14天】JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当。闭包能记住并访问词法作用域,若函数返回后,其引用的对象未被释放,就会引发泄漏。例如,`createLeakyFunction`创建的闭包保留了对大型对象`someLargeObject`的引用,即使函数执行完毕,对象也无法被垃圾回收。避免泄漏的方法包括及时解除引用、清除事件监听器、使用WeakMap和WeakSet以及定期清理缓存。使用性能分析工具可检测和修复内存泄漏问题。
26 3
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包机制
闭包是JavaScript中一个重要且常被误解的概念。本文将深入探讨闭包的本质、工作原理以及在实际开发中的应用。通过详细解析闭包的定义、作用域链、内存管理等方面,读者将对闭包有更清晰的理解,并能够运用闭包解决实际开发中的问题。
|
1月前
|
前端开发 JavaScript
闭包在JavaScript中有许多应用场景
【5月更文挑战第7天】闭包在JavaScript中发挥关键作用,如封装私有变量和函数提升安全性,维护变量生命周期,实现高阶函数,模拟块级作用域,支持回调函数以处理异步操作,以及促进模块化编程,增强代码组织和管理。闭包是理解和掌握JavaScript高级特性的重要一环。
33 7