JavaScript变量、作用域和内存问题

来源:互联网 发布:直销软件开发价格 编辑:程序博客网 时间:2024/05/22 10:01

JavaScript变量、作用域和内存问题

JavaScript变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。

4.1 基本类型和引用类型的值

基本类型值指简单的数据段,而引用类型值指那些可能由多个值构成的对象。5种基本类型是按值访问的,因为可以操作保存在变量中的实际值。而引用类型的值时保存在内存中的对象。JavaScript不允许直接访问内存中的位置,即不能直接操作对象的内存空间。

4.1.1动态的属性

对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法,但是我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。

4.1.2 复制变量值

从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该字号复制到为新变量分配的位置上。两个变量完全独立。

var num1=5;var num2 = num1;

从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到新变量分配的空间中。不同的是这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此改变其中一个,另一个也会改变。

var obj1 = new Object();var obj2 = obj1;obj1.name = "Nicholas";alert(obj2.name);// Nicholas;

4.1.3传递参数

ECMAScript中所有函数的参数都是按值传递的。即把函数外部的值复制给函数内部的参数和把值从一个变量复制到另一个变量是一样的(基本类型、引用类型分别按照自己的方式)
在向参数传递基本类型时,被传递的值会被复制给一个局部变量(命名参数或者说arguments中的一个元素)。在向参数传递引用类型时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反应在函数的外部。

function addTen(num){    num +=10;    return num;}var count = 20;var result= addTen(count);alert(count);//20,没有变化alert(result);//30此处传递的是基本类型,两个变量相互独立互不影响function setName(obj){    obj.name = "Nicholas";}var person = new Object();setName(person);alert(person.name);//"Nicholas"为引用添加属性时,操作的是真实的对象,在函数内部,obj和person引用的是同一个对象。因为person指向的堆内存中只有一个,而且是全局对象。

有人认为,在局部作用域中修改的对象会在全局作用域中反映出来,所以参数是按引用传递的,这是错误的说法。例

 function setName(obj){    obj.name = "Nicholas";    obj = new Object();    obj.name = "Greg";}var person = new Object();setName(person);alert(person.name);//"Nicholas"如果person是按引用传递的,那么person.name 就应该是"Greg".但实际上确是“Nicholas”,这说明即使在函数内部修改了参数的值,但是原始的引用仍然保持不变。实际上,当函数内部重写obj时,这个变量的引用就是一个局部对象了,而这个局部对象会在执行完之后销毁。

4.1.4 检测类型

typeof是确定一个变量是String、Number、Boolean、Undefined的最佳工具,如果变量是一个对象或者Null,则typeof会返回“Object”
typeof在检测引用类型的值时用处不大。通常我们不想知道某个值是对象,而是想知道它是什么类型的对象。为此引入了instanceof操作符

result = variable instanceof constructor

如果变量是给定引用类型的实例,那么instanceof操作符就会返回true。

alert(person instanceof Object);//truealert(colors instanceof Array);//truealert(pattern instanceof RegExp);//true由于所有引用类型都是Object的实例,因此在检测引用类型和Object构造函数时,instanceof总会返回true;用instanceof检测基本类型的值时,总会返回false

4.2 执行环境和作用域

每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的一个执行环境。全局执行环境直到应用程序退出–例如关闭网页或者浏览器时才会被销毁。

在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

每个函数都有自己的执行环境,当执行流进入到一个函数时,函数的环境会被推入一个环境栈中。而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

当代码在一个环境执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在的环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象最开始只包含一个变量,即为arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象是来自包含环境,接着是下一个包含环境,一直延续到全局执行环境;全局执行环境的变量对象始终是作用域链的最后一个对象。

标识符解析过程是沿着作用域链一级一级地搜索标识符的过程。

var color = "blue";function changeColor(){    if(color ==="blue") color = "red";    else color = "blue";}changeColor();alert("Color is now "+ color);// Color is now red;在此例中,changeColor()的作用域链包含两个对象:它自己的变量对象(arguments)和全局环境的变量对象。可以在作用域链上找到color所以可以在函数内部访问color

此外,在局部变量中定义的变量可以在局部环境中与全局变量互换使用

var color = "blue";function changeColor(){    var anthorColor = "red"    function swapColors(){        var tempColor = anthorColor;        anthorColor = color;        color = tempColor;        //此处可以访问到 color anthorColor 和 tempColor    }    //可以访问到color anthorColor,但是不能访问tempColor    swapColors();}//此处只能访问到colorchangeColor();

4.2.1 延长作用域链

有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。具体来说,就是当执行流进入到下列任何一个语句时,作用域链就会得到加长:
1) try-catch语句的catch块
2)with语句
对于with语句来说,会将指定的对象添加到作用域链中,对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

4.2.2 没有块级作用域

JavaScript没有块级作用域

if(true){    var color = "blue";}alert(color);//blue;

此处在if语句中定义了变量color。如果在java和C++等中,color会在if语句执行完之后被销毁,但在JavaScript中if语句中变量声明会将变量添加到当前的执行环境中,在使用for语句时,尤其要牢记这一差异

for(var i =0;i<10;i++){    dosomething(i);}alert(i);//10

声明变量

使用var声明的变量会自动被添加到最接近的环境中。初始化变量时没有使用var声明,该变量会自动被添加到全局环境中。

funtion add(num1,num2){    var sum = num1+num2;    return sum;}var result = add(10,20);//30;alert(sum);//由于sum不是有效的变量,因此会导致错误

如果省略了var关键字

funtion add(num1,num2){    sum = num1+num2;    return sum;}var result = add(10,20);//30;alert(sum);//30;作为全局变量

查询标识符

搜索过程从作用域链的最前端开始,向上逐级查询与给定名字匹配的标识符,一找到就停止,即在搜索过程中,如果存在一个局部变量的定义,则搜索会自动停止,不再进入另一个变量对象。换言之,如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符。

var color = "blue";function getColor(){    var color = "red";    return color;}alert(getColor());//redvar color = "blue";function getColor(){   return color;}alert(getColor());//blue

4.3 垃圾回收

JavaScript也具有垃圾回收机制。执行环境会负责管理代码执行过程中使用的内存。在JavaScript中所需内存的分配和无用内存的回收完全实现了自动管理。

原理:找到那些不再继续使用的变量,然后释放器占用的内存。为此,垃圾收集齐会按照固定的时间间隔,周期性地执行这一操作。

4.3.1 标记清除

Javascript中最常用的就是标记清除。当变量进入环境时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将标记为“离开环境”。

4.3.2 引用计数

原理:引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量将一个引用类型值赋给该变量时,则这个值的引用次数就是 1 。如果同一个值又被赋给另一个变量,则该值的引用次数加 1 。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1 。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放哪些引用次数为 0 的值所占用的内存。缺点:循环引用注意:BOM和DOM中的对象就是C++以COM对象的形式实现,而COM对象的垃圾收集机制采用的就是引用计数策略。即只要在IE中涉及COM对象,就会存在循环引用的问题。为了解决这个问题,IE9 把BOM和DOM对象都转换了真正的JavaScript对象。

4.3.3 性能问题

IE的垃圾收集器是根据内存分配量运行的,具体一点就是256个变量、4096个对象和数组元素或者64KB的字符串。达到上述任何一个临界值,垃圾收集器就会运行。随着IE7的发布,其JavaScript引擎的垃圾收集例程改变了工作方式:触发垃圾收集的变量分配、字面量的临界值被调整为动态修正。如果垃圾收集例程回收的内存分配量低于15%,则变量、字面量的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界值重置回默认值。

4.3.4 管理内存

分配给Web浏览器的可用内存数量通常比分配给桌面应用程序的少,这样做的目的主要出于安全方面的考虑,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统奔溃。一旦数据不再有用,最好通过将其值设置为null来释放其引用---这个做法叫做接触引用。

4.4 小结

1)基本类型值和引用类型值具有的特点

+ 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中+ 从一个变量向另一个变量复制基本类型的值,会创建这个值的副本+ 引用类型的值是一个对象,保存在堆内存中;+ 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针+ 从一个变量向另一个变量复制引用类型的值,复制的起始是指针,因此两个变量最终都指向同一个对象。+ 确定基本类型可以使用typeof,而确定哪种引用类型可以使用instanceof操作符

2)所有变量都存在一个执行环境中,这个执行环境决定了变量的生命周期:

+ 执行环境也有全局执行环境和函数执行环境的区别+ 函数的局部环境不仅可以访问函数作用域中的变量,还可以访问其父环境甚至全局环境+ 全局环境只能访问在全局环境中定义的变量和函数+ 变量的执行环境有助于确定应该何时释放内存。

3)垃圾回收

+ 离开作用域的值将被自动标记为可以回收,并在垃圾收集器期间被删除+ “标记清除”是目前主流的垃圾收集算法,+ “引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。+ 当代码中存在循环引用的现象时,“引用计数”算法就会导致问题。+ 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。
原创粉丝点击