引言:这一节我们对执行环境及作用域以及JavaScript的内存、垃圾机制等进行总结。
执行环境:执行环境(或者直接称:环境)是JavaScript 中最为重要的一个概念,执行环境定义了变量或函数有权访问的其他数据,决定了他们的各自行为。每个执行环境都有一个与之关联的 变量对象 ,环境中定义的所有变量和函数都保存咋这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。
全局执行环境是最外层的一个环境,在WEB浏览器中,全局执行环境被认为是 window 对象,因此也可以认为所有全局变量和函数都是作为 window 对象的属性和方法创建的。某个执行环境中所有代码执行完毕后该环境销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁)。
作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的作用就是保证执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。在作用域链中的下一个变量对象来自包含(外部)环境,而再下一个对象则来自下一个包含环境,这样一直延续到全局环境,全局执行环境的变量对象始终是作用域链中的最后一个对象。这句话有些绕难以理解,我们直接看例子:
var color="blue";
function changeColor(){
var anotherColor="red";
function swapColors(){
var tempColor=anotherColor;
anotherColor =color;
color =tempColor;
//这里可以访问 color anotherColor 和 tempColor
}
//这里可以访问 color和 anotherColor 但不能访问tempColor
swapColors();
}
//这里只能访问 color
changeColor();
以上涉及三个执行环境,即全局变量环境、changeColor() 的局部变量环境,还有 swapColors()的局部变量环境。而从上面的例子也能够看出。这个执行的的作用域链是由内部到外部的也就是上面提到的“用域链中的下一个变量对象来自包含(外部)环境,而再下一个对象则来自下一个包含环境,这样一直延续到全局环境”。通过这样的方式,内部函数可以使用外部函数的变量以及全局的变量,但是反过来,我们在外部不能直接访问到内部函数的变量。这之后也就出现了闭包(之后我们会总结)。
延长作用域链 :当执行函数流入下列任何一个语句时,作用域链就会得到加长:1、try—catch 语句的 catch 块 2、with语句。
举个栗子:
function buildUrl(){
var qs="?debug=true"
with(location){
var url =href+qs;
}
return url;
}
在这里 with 接收的是location 对象,因此其变量中就包含了location 对象的所有属性和方法,而这个对象呗添加到了作用域前端。因此在这里我们就可以引用到 变量qs 。至于with 语句内部,则定义了一个url 变量,所以 url 就成了函数执行环境的一部分,所以就可以作为函数的值被返回。
ps (在IE8及其之前的版本,cath 的错误对象会被添加到执行环境中而不是在catch 的环境中,所以会出现在IE8 及其之前的版本中可以在catch 外部访问错误对象的情况,这种问题在IE9 以及得以修复)。
垃圾回收机制:
JavaScript 具有垃圾回收机制,也就是说,执行环境会负责管理代码执行过程使用的内存。这种垃圾回收机制实现原理很简单:找出那些不用的变量,然后释放其占用的资源。为此,垃圾收集器会按照固定时间间隔(或代码执行中预定的收集时间周期性的执行这一操作)。
局部变量的正常生命周期:局部变量只在函数执行的过程中存在,而在这个过程中,会为局部变量在栈(或堆)上分配相应的空间,以便储存它的值。然后在函数中使用这些变量,直至函数结束,此时,局部变量就没有存在的必要了,因此可以释放他们的内存。在这种情况下很容易判断变量是否有存在的必要,但是,并非所有情况都那么容易得出结论。垃圾收集器必须跟踪哪个变量有用哪个没用。对于不在有用的变量打上标签,以便后面释放其资源。用于标识无用变量的策略可能会因时实而异,但是具体到浏览器中实现,则有两种情况:
1、标记清除: 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等,这些并不重要,重
要的是使用什么策略,原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回收器相会这些带有标记的变量机器所占空间。
大部分浏览器都是使用这种方式进行垃圾回收,区别在于如何标记及垃圾回收间隔而已,只有低版本IE,不出所料,又是IE。。。
2、引用计数:另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0
时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
引用计数这个方式很容易理解,但是在使用时会碰上一个问题,那就是循环引用:
function pro(){
var a=new Object();
var b=new Object();
a.Obj=b;
b.obj=a;
} 在这个函数中,a、b通过各自的属性相互引用。也就是说二者的引用次数都是2。 这就很尴尬了,当函数执行完毕后,二者的引用次数都不是0,那么这时候引用计数的方式就不行了。如果这个方法被执行多次,那么里面的变量就会堆积,这不是我们想要看到的结果。
我们知道,IE中有一部分对象并不是原生JavaScript对象。例如,其BOM和DOM中的对象就是使用C++以COM(Component Object
Model,组件对象)对象的形式实现的,而COM对象的垃圾回收器就是采用的引用计数的策略。因此,即使IE的Javascript引擎使用标记清除的策略来实现的,但JavaScript访问的COM对象依然是基于引用计数的策略的。说白了,只要IE中涉及COM对象,就会存在循环引用的问题。看看下面的这个简单的例子:
var element = document.getElementById("some_element"); var myObj =new Object(); myObj.element = element; element.someObject = myObj;
上面这个例子中,在一个DOM元素(element)与一个原生JavaScript对象(myObj)之间建立了循环引用。其中,变量myObj有一个名为element的属性指向element;而变量element有一个名为someObject的属性回指到myObj。由于循环引用,即使将例子中的DOM从页面中移除,内存也永远不会回收。
不过上面的问题也不是不能解决,我们可以手动切断他们的循环引用。
myObj.element = null; element.someObject =null;
这样写代码的话就可以解决循环引用的问题了,也就防止了内存泄露的问题。
不过为了解决上述问题,IE9 把BOM 和DOM 对象都转成真正的JavaScript对象。这样就避免了两种垃圾回收算法并存导致的问题。也就防止了内存泄露的问题。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
本章完。下一章预告:
引用类型。
我们知道JavaScript中有两种类型,数据类型和引用类型。前面的总结已经让我们对基础数据类型有了了解,接下来让我们走进引用类型的世界。
原文地址:https://www.cnblogs.com/wxhhts/p/9426240.html