美文网首页
Javascript基础--闭包

Javascript基础--闭包

作者: mr_franklin | 来源:发表于2017-10-31 16:18 被阅读13次

写在前面

闭包是JavaScript中一个重要的概念,本文使用3w(what,why,how)原则总结一下闭包这个概念。

what,什么是闭包?

网上关于闭包的定义有很多,但大多过于繁杂。一种简洁的说法:闭包是一个有状态的函数(a stateful function)---- 首先,闭包是一个函数,它一定存在于外层函数内;其次,闭包“记住了”其外层函数拥有的变量,它能够访问外层函数的作用域。
下面的例子就形成了一个闭包:

function foo() {
    var data = "hello";
    function bar() {
        console.log(data);  // 打印 hello
    }
    return bar;
}
foo()();

函数bar就是一个闭包,它能够访问外层函数foo的变量data。当调用foo()()时,也就是调用了bar函数,将打印foo函数内部变量data的值。

why,为什么要用闭包,作用是什么?

闭包多用来提供“模块化”,起到“封装”代码的目的。
由于外部执行环境无法访问函数内部的变量,但可以通过将闭包暴露出来的方式,使外部获取对函数内部变量的访问权。
闭包的这个用法,类似于C++、Java等语言的公有函数(public),提供外界访问类的私有成员变量(private)的能力。
下面是一个闭包提供的例子:

function Person() {
  var name = "Joe";
  return {
    getName: function() {
      return name;
    },
    setName: function(newName) {
      name = newName;
    }
  }
}

var p = Person();
console.log(p.name);  // 打印undefined
console.log(p.getName()); // 打印Joe
p.setName('Mike');
console.log(p.getName()); // 打印Mike

Person函数的内部变量name只存在于Person的函数作用域内,无法被外界访问到,所以打印undefined。
通过闭包getName和setName,可以达到访问变量name的目的。

在es6中终于有了类的概念,上面的代码可以用类的方式改写:

class Person {
  constructor() {
    this.name = 'Joe';
  }
  getName() {
    return this.name;
  }
  setName(newName) {
    this.name = newName;
  }
}

var p = new Person();
console.log(p.name);  // 打印Joe
console.log(p.getName()); // 打印Joe
p.setName('Mike');
console.log(p.getName()); // 打印Mike

es6由于没有private概念,可以看到这里的name属性其实是可以被外界直接调用到的。

另外在回调函数(如callback,setTimeout)、异步执行的函数(如ajax请求)中,经常看到闭包的身影。闭包一定程度上简化了代码的写法。

how,闭包是如何形成的?

说起闭包的底层机制,就首先要搞懂js语言中的执行环境和作用域链的概念,这篇文章介绍了这些概念。
闭包的原理其实就是形成闭包的这个内层函数,在其自身的作用域链上,头结点(活动对象)是自身的变量对象,记录了自身内部的变量信息;而作用域链上的第二个节点是其外层函数的变量对象,记录了外层函数的变量信息----很显然,这个节点上保存的信息能够被闭包函数访问的到,闭包定义所说的“周围环境”,其实就是这个数据。
一般的,当外层函数执行完成后,js引擎会释放掉它的执行环境,作用域链等信息。但如果其内部含有闭包函数,由于闭包函数的作用域链上仍然引用着外层函数的变量对象(第二个节点),垃圾回收器判定这块内存仍然有在被使用(被闭包引用),就不会释放掉这块内存,所以即使外层函数执行完,我们依然能通过闭包函数,访问其外层函数的变量信息。经常有说闭包容易引起内存泄漏,也就是因为内存一直被闭包引用者,无法被垃圾回收导致的结果。
闭包的这种底层实现,有时候会引起看上去有点预想不到的结果,比如在for循环中。思考下面的例子,为什么打印出来的都是5呢?

function arrayFun() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr.push(function () {
            console.log(i);
        });
    }
    return arr;
}
var retArr = arrayFun();
for (var i = 0; i < 5; i++) {
    retArr[i]();   // 打印 5,5,5,5,5
}

由于在es6之前,只有全局作用域和函数作用域,i变量在函数arrayFun中,只有一份副本。那么闭包(此处是个匿名函数)在作用域链上引用的是同一个i值(i在执行完for循环后值变为5),当调用闭包时,访问作用域链上的i值,就都是5了。
在es6中引入了let和块作用域,i的值在块作用域中是不同的副本,所以就不会出现上面的情况了。仍然是这篇文章有代码实例,不再赘述。

相关文章

  • JavaScript: 零基础轻松学闭包

    JavaScript: 零基础轻松学闭包 参考链接 JavaScript: 零基础轻松学闭包(1)http://w...

  • 技术栈

    一、HTML、CSS基础、JavaScript语法基础。 二、JavaScript语法进阶。包括:作用域和闭包、t...

  • 2020前端技术栈

    一、HTML、CSS基础、JavaScript语法基础。二、JavaScript语法进阶。包括:作用域和闭包、th...

  • Javascript基础--闭包

    写在前面 闭包是JavaScript中一个重要的概念,本文使用3w(what,why,how)原则总结一下闭包这个...

  • [JavaScript基础] 闭包

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。总而言之,...

  • JavaScript基础 闭包

    回收机制 一个变量或函数执行完后,下面的代码没有在使用或调用时会被回收回去,释放出内存。定义一个变量、函数会在内存...

  • JavaScript----闭包

    javascript之闭包 闭包的概念     闭包(closure)是 JavaScript 的一种语法特性。 ...

  • 理解JavaScript中的闭包

    闭包并不复杂。学习和理解闭包的基础知识仅仅只需要10分钟。 什么是闭包? 闭包是JavaScript的一部分,每个...

  • 学习JavaScript闭包和作用域笔记

    JS JavaScript闭包和作用域 闭包 JavaScript高级程序设计中对闭包的定义:闭包是指有权访问另外...

  • Gradle开发-Groovy闭包

    # 闭包 闭包的基础知识 闭包的使用 闭包 this,owner,delegate 的理解 总结 ## 闭包的基础...

网友评论

      本文标题:Javascript基础--闭包

      本文链接:https://www.haomeiwen.com/subject/xkoepxtx.html