JavaScript 闭包全方位解析

总结

概念: 有权访问另外一个函数做用域中的变量的函数

优势: 内存驻留、避免全局变量污染

缺点: 内存泄漏(?)、没法预知变量被更改

相关知识点: 做用域、内存驻留、内存泄露、JS执行机制、内存机制、垃圾回收机制

1、什么是闭包

一、做用域链

要理解什么是闭包、首先咱们要对JS中的做用域做用域链有必定的理解:前端

做用域: 简单来说就是一个变量可以被访问的范围,JS有三种做用域,分别是:全局做用域、函数做用域、块级做用域

做用域链: 在JS中做用域是一层一层嵌套的, 子做用域中能够访问父做用域中的变量, 咱们把这种能够一层一层向上访问的链式结构叫作做用域链.

~~ 当JS建立一个函数时, 首先会建立一个预先包含全局变量对象的做用域链, 保存在内部的Scope属性之中
当JS调用这个函数时, 会为此函数建立一个执行环境, 也就是函数上下文
而后复制函数的Scope的属性中的对象构建执行环境的做用域链~~浏览器

做用域与做用域链就比如是数学中的集合, 最大的即是全局做用域, 子集即是函数做用域, 子集中又能够有子集,全部的本身均可以向外访问,但全部的父级不能够向子集访问,相同的子集之间也不能够互相访问。安全

二、闭包的概念

有权访问另外一个函数做用域中的变量的函数 《JavaScript高级程序设计》闭包

咱们来理解一下这句话异步

  • 首先: 闭包是...函数
  • 而后: 有权访问另外一个函数做用域中的变量

那么怎么才有权访问另外一个函数做用域中的变量呢?
根据上文中子做用域中能够访问父做用域中的变量的特性,答案是: 成为另外一个函数的子函数函数

补充一点:

在《JavaScript权威指南》, 强调了函数体内部变量能够保存在函数做用域 函数对象能够经过做用域链相互关联起来,函数体内部变量能够保存在函数做用域内,这就是闭包。性能

三、闭包的结构

从严格的角度来说, 闭包须要知足三个必要的条件:学习

  1. 函数嵌套
  2. 访问父函数做用域中的变量
  3. 在函数声明做用域外被调用( 有异议,欢迎讨论 )

所以咱们猜测一个闭包的样子, 大概应该是这样的:线程

// 全局变量-全局做用域
var global = "global scope"; 
function partner() {
    // 局部变量-函数做用域
    var variable = 'Function scope';
    function children() {
        console.log(variable);
    }
}

// 此时子函数 children 访问了父函数 partner, 咱们就称子函数 children 为闭包.

2、闭包存在的意义

意义: 内存驻留

当咱们要实现一个计数器时, 首先用常规的方法来写:设计

// 计数器
var count = 0;
function counter() {
    console.log(count++);
}

counter(); // 1
counter(); // 2

上面的代码已经实现了咱们所需的功能, 可是它并不完美, 一方面全局的count变量可能形成变量污染, 另外一方面代码中的任何一个位置均可以轻松的修改这个count的值, 这是咱们所不能接受的!

所以, 咱们须要一个变量能够在counter函数中访问, 但它并不在全局做用域中, 且能够长时间的停留在内存当中不被浏览器的垃圾回收机制清除, 因而咱们就想到了闭包, 接下来咱们用闭包再实现一下计数器:

// 计数器
var counter = (function() {
    var count = 0;
    return function() {
        console.log(count++);
    }
})()

counter(); // 1
counter(); // 2

闭包仿佛结合了全局做用域与局部做用域的优势与一身,对于其余函数做用域而言,父函数做用域中的变量就像是父函数和闭包的一个 “私有变量” , 而对于父函数和闭包而言, 父函数做用域中的变量又好像身处 “全局做用域” 中.

3、闭包形成的影响

缺陷: 影响性能、变量修改
影响性能:

闭包的存在会致使函数中得变量一直存在于内存中,不能被垃圾回收机制清理, 致使内存消耗增长, 影响系统运行的性能,因此不能滥用闭包.

若是要使用闭包, 应该在使用结束时手动的清除闭包!

变量修改:

在闭包存在的时候,将父函数比作一个类,父函数中得局部变量就是类的私有属性,而闭包访问的变量就是类的公共属性,在父函数做用域和闭包函数中均可以对 variable 变量进行修改, 这是件使人头疼的事情, 由于你并不知道有多少闭包会在何时对你的变量进行修改, 这将形成你的程序极不稳定甚至执行异常.

4、闭包的经典案例

[操做内部变量]返回函数内部变量
function parent() {
    let name = 'sf'
    return {
        get() { return name },
        set(val) { name = val }
    }
}

const nameProxy = parent()
console.log(nameProxy.get()) // 'sf'
// 子函数children也能够定义为null在全局,而后在parent中赋值
[锁定变量状态]for循环中的定时器
// 由于setTimeout是异步的, 代码执行先同步后异步, 因此当它执行的时候for循环已经结束了
for(var i=0,len=10; i<len; i++) {
    setTimeout(() => console.log(i), 1);
}
// 10个10

// 闭包写法-IIFE
for(var i=0,len=10; i<len; i++) {
    ((i) => {
        setTimeout(() => console.log(i), 1);
    })(i)
}
[制造安全环境]彻底封闭的功能模块-IIFE
// 内部的变量不会污染全局变量, 能够放心的对多个模块进行合并
(function(window) {
    let a = 1
    let b = '2'
    function add() {
        a++
    }
})(window)

5、闭包的底层原理

执行父函数时, JS线程会对内部的子函数进行预编译, 看一看子函数中是否用到了父函数的内部变量
若是用到了, 为了保证在将来调用子函数时不出错, JS线程会在父函数执行完毕以后, 清空函数执行栈中的上下文以前, 将父函数中被用到的变量 copy 一份放在堆中, 供以后子函数引用.

6、其余相关的扩展

内存泄露

内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。

在闭包形成的影响中,咱们常常会听到一句话, 那即是:在IE中闭包的使用可能会致使内存泄漏。可是,在我学习V8引擎的过程当中发现,引起内存泄漏的缘由彷佛是循环引用,这让我对闭包和内存泄漏的关系产生了疑惑.

后续我将围绕内存泄漏从新整理一篇文章,这里先引用一篇文章中的一句话和一个例子:

在IE浏览器中,因为BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,若是两个对象之间造成了循环引用,那么这两个对象都没法被回收,但循环引用形成的内存泄露在本质上也不是闭包形成的。

做者:前端小学生\_f675  
连接:https://www.jianshu.com/p/66881ba3c8ba  
来源:简书  
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
// 内存泄漏
var ele = document.getElementById("someElement");
ele.click = function() {
    console.log(ele.id);
}
// 解决方案:使用结束后释放内存
var ele = document.getElementById("someElement");
var eleID = ele.id;
ele.click = function() {
    console.log(eleID);
}
ele = null;

垃圾回收

文章地址: 待更新

7、闭包的相关文章

相关文章
相关标签/搜索