基本类型值
Undefined、Null、Boolean、Number和String,按值来保存。复制基本类型值时,会完全复制出一个独立的变量。
引用类型值(对象)
保存在内存中的对象,按引用访问。复制引用类型值时,副本其实是一个指针,指向堆内存中的对象,两者其实指向同一对象。
在函数传递参数时,参数都是按值传递的。当参数为对象时,虽然是值传递,但还是会按引用来访问同一个对象。内部可修改引用,但对外部将不会产生影响。
每个函数都有自己的执行环境(之前this文章里已经提过了,也就是执行上下文),当执行流进入一个函数时,函数环境就会被推入一个环境栈。函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链
,其可以保证对执行环境有权访问的所有变量和函数的有序访问。变量对象搜索的过程就是从前向后遍历作用域链的过程。
作用域链: 当前函数环境(argument…) -> 上级外部环境 -> 上上级外部环境
由于这样的机制,我们发现我们无法访问到函数内部的变量,因为作用域链总是向上的。因而产生了闭包,先看一下闭包的概念:
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.
方法一(返回函数):
|
|
方法二(利用回调函数):
|
|
方法三(返回对象):
|
|
方法四(执行外部函数):
|
|
方法五(在函数声明中不加var):
|
|
主要目的在于将函数内部的函数传递到其所在词法作用域之外,它都会持有对原始定义作用域的引用,通过它就可以访问函数内部的变量。一般函数在执行后其作用域就会被销毁,引擎使用垃圾回收器释放不再使用的内存空间。但由于闭包的存在,仍可以从外部访问到函数作用域内部,保持着这个引用,所以闭包也可能导致内存的泄露。
经典例子,这样会输出5个6:
|
|
这里我认为原因与setTimeOut也有一定的关系,setTimeout执行是浏览器开定时器线程,过setTimeout设置的时间后,向浏览器的事件队列插入执行的函数,js引擎线程执行完当前所有代码之后,开始执行事件队列里的代码,此时引用的i已经为6了。
正确的写法应该是利用一个IIFE(立即执行函数表达式):
|
|
另外,在ECMAScript中只采用静态作用域,闭包是一系列函数,并且静态保存所有父级的作用域。通过这些保存的作用域来搜寻到函数中的自由变量。
|
|
总之,闭包是代码块和创建该代码块的上下文中数据的结合,可以说所有的函数都是闭包,在它们定义时就已经保存了上层上下文的作用域链。
JavaScript自动分配内存,也由垃圾回收器自动回收,找出不再继续使用的变量,然后释放其占用内存,固定时间,周期执行操作。
标记清除
先会给存储在内存中的所有变量加上标记,去掉环境中的变量以及被环境中的变量引用的变量的标记。此后若再次被标记的变量将被清除,它们已经被认为是无法访问到的了,也就不需要了。
引用计数
值被引用一次,计数就加一,被赋予其它值,计数就减一,当计数为0时,说明无法被访问,那么就回收内存。但会发生循环引用,内存就无法回收,从而造成内存泄露。