闭包之我见


前言

当一涉及到闭包问题时,很多前端新手往往感到头疼,作为JS的一大难点与特色,闭包问题往往是很多人噩梦,本人作为一名前端入门小白,对于闭包问题一直带有困惑,经过多日的研究之后,对于闭包的知识有了一些了解和看法,所以写一篇博客希望和大家一起学习和进步。

一、什么是闭包?

闭包的基本定义指的是:有权访问另外一个函数作用域中的变量的函数,个人看来就是可以理解为能够读取另一个函数作用域的变量的函数。
简单示例:

function outer() {
  var a = '样例1'
  var inner = function () {
  console.log(a)   
 }
  return inner 
  // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
  }

通过上面例子我们可以看出闭包好像与匿名函数“相似”,但是闭包主要是站在作用域的角度上来定义的,正是由于inner访问到outer作用域的变量,所以inner就是一个闭包函数,而在外部无法读取outer函数内部声明的变量。
所以在这里可以简单理解为:闭包就是能够读取其他函数内部变量的函数闭包,所以我一般理解是:“定义在一个函数内部的函数“。


二、闭包的用途

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在,保证在函数调用之后变量不会消失。

1.读取函数内部变量

从上面我自己对于闭包的理解就可以看出,读取或者返回函数内部的变量是闭包的主要作用之一:作为函数体内部与外部数据交换的桥梁

代码如下(示例):

function f1() {
  var n = 1;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 1

函数f2作为函数f1的返回值,且f2内含有f1的内部变量,所以就可以实现在外部获得f1的内部变量了(注意JS的特殊性只有函数内部的子函数才能读取内部变量。

2.数据的保存

当函数的内部变量被闭包保存时,使得它始终在内存中,不会在调用结束后,被垃圾回收机制回收。

代码如下:

function createIncrementor(start) {
  return function () {
    return a++;
  };
}

var inter = createIncrementor(5);

inter() // 5
inter() // 6
inter() // 7

上面的例子,a是函数createIncrementor的内部变量。通过闭包,a的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境一直存在。所以,闭包可以看作是函数内部作用域的一个接口,用来保存内部数据。

造成这样的原因就在于inter始终在内存中,而inter的存在依赖于createIncrementor,因此也始终在内存中,不会被数据清理。

3.私有特点

闭包还有一个特殊作用在于封装对象的私有属性和私有方法。

上代码:

function Person(name) {
  var age;
  function setAge(n) {
    age = n;
  }
  function getAge() {
    return age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25

在上面的样例中,函数Person的内部变量age,通过闭包getAge和setAge实现的私有化变量,变成了返回对象p1的私有变量。


三.闭包的缺点及避免

上面谈了闭包的主要作用在于,函数内部变量的保存或者返回,这样闭包的缺点就显而易见了

1.缺点

(1)内存的消耗与泄露

通常来说,函数的内部变量会随着执行期上下文一起销毁,但是,由于闭包引用另外一个函数的变量,因此这个变量无法被销毁!这意味着,闭包比一般的函数需要更多的内存消耗。

并且,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量吗,所以闭包的滥用会导致过多的内存消耗。

注意:内存消耗很大尤其在IE浏览器中需要关注。由于IE使用非原生javascript对象实现DOM对象,因此闭包会导致内存泄露问题。
上代码:

       function A(){
            var a = document.createElement(“div”),//
                in =“Hello”;
            a.onclick = function(){
                alert(in);
            }
        }
        A();

以上的闭包会在IE下导致内存泄露,假设A()执行时创建的作用域对象CopyA,CopyA引用了DOM对象a,
  DOM对象a引用function(alert(in)),函数function(alert(in))引用了CopyA,这是一个循环引用,在IE会导致内存泄露。

(2).性能问题

使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失。

因此在脚本中,最好小心使用闭包,它同时会涉及到内存和速度问题。不过我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

2.解决

闭包的解决方式说起来其实十分简单,主要就是减少闭包的滥用行为,并且在退出函数之前,将不使用的局部变量全部删除。

注意:

1.函数内部变量变化问题

当闭包会返回父函数的内部变量时,此时会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

2.this的指向问题

当闭包内部含有this时,千万注意函数的作用域问题!!!
上代码:

var object = {
   name: ''object",
   getName: function() {
      return function() {
           console.info(this.name)
      }
  }
}
object.getName()()    // underfined

上面的例子中,因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows,所以无法读取到this.name而出错。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值