JS中上下文分类
- 全局上下文EC(G):会创建全局变量对象VO(G)
- 私有上下文:函数执行,会形成全新的私有上下文
- 块级私有上下文
创建函数
需要开辟一个堆内存,对象的堆内存中,存储的是它的键值对。
函数的堆内存中存储的:
-
代码串: 是它的代码,而且是以 字符串 形式存储的。
-
作用域[[scope]]:创建函数的时候,就声明了它的作用域scope。scope的值就是当前创建函数所处的上下文。在哪个上下文中创建的,作用域就是谁。
-
prototype: 原型对象
-
__proto__: 基于所有的函数都是Function的实例。包括Function内置类也是自身的实例。实例对象都有__proto__属性。所以每个函数也都有。而且Function.__proto__===Function.prototype
-
name: 函数名
-
length: 形参个数
执行函数
-
会形成一个全新的私有上下文即当前上下文,也会有私有变量对象。即AO。Active Object活动对象(变量对象的一种,函数形成上下文中的变量对象叫做AO)。全局的变量对象成为VO(G)。
额外知识点——闭包的形成:里面的私有变量,受到了私有上下文的保护,不受外界干扰。有可能形成不被释放的上下文,里面的私有变量和一些值,就会保存起来。这些值可以供其下级上下文中调取使用。我们把函数的这种 保存保护 机制,称之为闭包。!! 闭包只是一种机制。 闭包具有保存保护机制。有的说任何函数都是闭包,因为存在自己的私有作用域,但是这样的话闭包存在很短暂,所以更多的理解是产生了,不释放的空间,这种情况称为闭包。看自己的理解。
创建好后进栈执行
-
初始化
-
初始化作用域链 Scope-chain <当前上下文,函数作用域(创建函数的时候的上下文)(相当于当前上下文的上级上下文)>
-
初始化this,函数执行的时候,函数的执行主体,和执行上下文不是一个概念。函数执行主体:谁执行的这个函数,全局的this是window,我们研究的都是函数中的this。函数执行上下文:在哪里执行。
-
初始化arguments
-
(实参集合[类数组集合]):{ 0:第一个实参, 1:第二个实参....以此类推, length:实参的个数, callee:function (){}:获取函数本身,方便递归调用}
-
在JS的非严格模式下,当初始化arguments和形参赋值完成之后,会给两者建立一个映射机制。arguments的每一项和对应的形参变量绑定在一起。一个修改都修改。而且只会发生在代码执行之前建立这个机制。(如果实参和形参不对应,那么arguments和形参只有对应的会映射上,那么没有赋值的哪项,再修改,不用影响arguments)
-
在JS的严格模式下,没有映射机制,也没有arguments.callee这个属性;
-
箭头函数中没有arguments
-
-
形参声明并赋值:在当前上下文中声明一个形参变量,并且赋值。
-
变量提升
-
代码执行:把之前创建函数在堆内存中存储的代码字符串拿出来,变为代码一行行执行
作用域链查找机制:私有上下文中代码执行,如果遇到一个变量,首先看是否为自己的私有变量,如果是私有的,则操作自己的。如果不是私有的,则基于作用域链向其上下文中查找, 看是否为上级上下文中私有的......一直找到EC(G)全局上下文为止,这种查找的过程就是作用域链查找机制。
-
出栈释放
重点:
函数每执行一次,都会形成一个全新的私有上下文,和之前执行形成的上下文没有必然的联系。即便之前的被保存下来了(闭包的情况)。
函数执行,它的上级作用域(上下文)是谁和函数在哪里执行是没有关系的,只和在哪创建有关系:在哪里创建的,它的作用域[[scope]]就是谁,也就是他的上级上下文就是谁!!
GC:浏览器的垃圾回收机制(内存释放机制)
-
堆内存释放 -不同浏览器处理机制不太一样。
方案一 谷歌为主的: 查找引用。
浏览器在空闲或者指定时间内,查看所有的堆内存,把没有被任何东西占用的堆内存释放掉。但是占用的是不会释放的。
方案二 IE为主的: 引用计数方式。
创建了堆内存,被占用一次,则浏览器计数+1,取消占用则计数-1。当记录的数字为0,则内存释放掉。某些情况会导致计数数据混乱,出现内存泄漏现象,即该释放没释放。
手动释放,将变量赋值为null。
特殊: 当前上下文中开辟的某个堆内存(函数和对象),被当前上下文以外的变量或者其他事物所占用,此时当前上下文是不能被出栈释放的。
-
栈内存释放
加载页面,形成一个全局的上下文,只有页面关闭的后,全局上下文才会被释放。
函数执行会形成私有的上下文,进栈执行;当函数中的代码执行完成,大部分情况下,形成的上下文都会被出栈释放掉,以此优化栈内存大小。特殊情况不是这样的,比如闭包
几种常见的内存泄漏
-
全局变量
全局变量什么时候需要自动释放内存空间很难判断,所以在开发中尽量避免使用全局变量,以提高内存有效使用率。
-
未移除的事件绑定
dom元素虽然被移除了,但元素绑定的事件还在,如果不及时移除事件绑定,在IE9以下版本容易导致内存泄漏。现代浏览器不存在这个问题了,了解一下即可。
let div = document.querySelector(".div"); let name = 'lmy' let handler = function () { console.log(name); } div.addEventListener('click', handler, false) div.parentNode.removeChild(div) // 在IE9以下的老版本事件还在 -
无效的dom引用
有时候将dom作为对象的key存储起来很有用,但是在不需要该dom时,要记得及时解除对它的引用。
var ele = { node: document.getElementById('node') }; document.body.removeChild(document.getElementById('node')); // 此时ele中还存在对node的引用 -
定时器setInterval/setTimeout
看下面的一段定时器代码,一旦我们在其它地方移除了node节点,定时器的回调便失去了意义,然而它一直在执行导致callback无法回收,进而造成callback内部掉数据resData也无法被回收。所以我们应该及时clear定时器。
let resData = 100 let callback = function () { let node = document.querySelecter('.p') node && (node.innerHTML = resData) } setInterval (callback, 1000)
另外单独说一下闭包,闭包和内存泄漏没有半毛钱关系,只是由于IE9之前的版本垃圾收集机制的原因,导致内存无法进行回收,这是IE的问题,现代浏览器基本都不存在这个问题。当然闭包要是使用不当肯定是会造成内存泄漏。
如有理解不正确的,欢迎指出~