知识小储备
ECMAScript 的数据有两种类型:基本类型值和引用类型值,基本类型指的是简单的数据段,引用类型指的是可能由多个值构成的对象。Undefined、Null、Boolean、Number 和 String 是值类型,其他都是引用类型。
垃圾回收
我们创建的原始类型、对象、函数等等,都会占用内存。为了防止溢出,我们就需要对不用的数据进行删除。这就是垃圾回收。
可触及(Reachability)
JavaScript 内存管理的关键概念是可触及(Reachability)。
我的理解就是还处于被引用状态。
将全局(无论是window还是global)比作树根,我们创建的原始类型、对象、函数等等比作一个个枝杈。如果可以从window不断层的知道某个变量,那这个变量就是可触及的,不可回收的。
举个例子:
// user has a reference to the object
let user = {
name: "John"
};
箭头代表的是对象引用。全局变量 "user"
引用了对象{name: "John"}
(简称此对象为 John)。John 的 "name"
属性储存的是一个原始值,所以无其他引用。
如果覆盖 user
,对 John 的引用就丢失了:
user = null;
现在 John 变得不可触及,垃圾回收机制会将其删除并释放内存。
内存机制
js的内存空间分为栈 (stack)、堆 (heap);其中栈存放变量,堆存放复杂对象。
借用一张图直观感受一下
栈内存
只能存放基本数据类型的数据和对象类型的引用地址也叫哈希码。里面的数据后进先出。
对栈内数据进行复制修改时:
堆内存
是用来存储 “数组类型” 和“对象类”的数据。特点是存储空间大。
对堆内数据进行复制修改时:
理解闭包
有了前面的铺垫,我们再来看看闭包是怎么回事。还是举个例子:
//决策层开会决定生产新一代phone手机,就弄了个叫PhoneFactory的企划案
let Proposal = function(){
//新一代手机信息被封装在企划案中
let version = "XX", money = 10000
return function (){
//生产新的手机
return {
version: version,
money: money
}
}
}
//执行者根据策划案建成了一个工厂, 工厂方法每执行一次就产出一个手机
let Factory = new Proposal();
let phone1 = Factory();
console.log(phone1.version)
对于手机的version, money而言, 它是策划书Proposal的内部变量,而Proposal的同级phone1应该是访问不到。但实际上我们还是拿到了手机的版本和价格数据。这种反常的现象我们就叫它Closure,中文名闭包。
原因
我们不管它为什么叫这个名字,先看看具体是什么原因产生的。
根据上面说的垃圾回收机制。函数Proposal在执行过之后(第16行)就没有引用。那么Proposal久应该被回收。里面的所有内部变量也应该被回收了。
但实际上 Proposal返回了一个新的函数Factory。而这个Factory是要能够访问到它生成时的同级以及父祖辈变量。而Proposal内部变量version, money就有了新的引用。因而阻止了被回收。就像Factory生成了一个新的泡泡把它能访问到的作用域包裹了起来。这就是闭包形成的原因了。
基于上面的js内存机制的知识,我们可以画出下面这张图:
图简陋了点。。。。
但也可以看出,对于变量version,money而言。虽然他们本身在proposal的黄色作用域中。但也在fatory生成的时候也被包含在了fatory打的可访问的作用域气泡内。不仅他们,甚至更外层的也都被包含在内。
在proposal这个泡泡破碎之后,只有当打的红色泡泡也破了,这些变量才会真的被回收。这也是为什么闭包用多了会影响性能的原因。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。