JavaScript 变量作用域和内存问题
Posted zhangjun2013551829
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 变量作用域和内存问题相关的知识,希望对你有一定的参考价值。
You got a dream,you gotta protect it.
变量、作用域和内存问题
javascript 的变量与其他语言的变量有很大不同,因其松散类型的本质,决定了它只是在特定时间,用于保存特定值的一个名字而已,它的值和数据可以在脚本生命周期内随时改变。
1. 基本类型和引用类型的值
ECMAScript 变量可能包含两种不同数据类型的值:基本类型和引用类型。
基本类型值表示简单的数据段,引用类型值表示由多个值构成的对象。在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
JavaScript 有五大基本数据类型:Undefined、Null、Boolean、Number 和 String。
在很多语言中,字符串以对象的形式表示,是引用类型的,但是 JavaScript 则没有遵守这一惯例。
JavaScript 不允许直接访问内存中的位置,因此不能直接操作对象的内存空间,只能通过操作对象的引用来间接的操作实际的对象。
大部分高级语言都屏蔽了对内存的操作,由 GC 去管理对象。只有少数像 C/C++ 这种介于汇编语言和高级语言之间的语言,才允许直接操作对象的内存。
JavaScript 在存储基本数据类型时,因为基本类型值占据空间大小固定,所以存储在栈内存中;
在存储引用数据类型时,因为引用类型的值是对象,所以存储在堆内存中。
包含引用类型值的变量实际上包含的只是一个指向该对象的指针,而非对象本身,因此复制时复制的其实是指针。
特别的,当你为一个对象添加属性的时候,你操作的是实际的对象,而非对象的引用。
1.1 动态的属性
定义一基本类型值或者引用类型值的方式是类似的:创建变量并赋值。
我们只能给引用类型值动态地添加属性,尽管给基本类型值添加属性不会报错,但是并没有添加成功。
1.2 复制变量值
基本类型值的复制是通常意义上的复制,复制完成后,两个变量的值不会相互影响。
引用类型值的复制虽然也是将存储在变量对象中的值复制一份到为新变量分配的空间中,但是这个存储在变量对象中值实际上是一个指针,指向存储在堆内存的一个对象。
复制完成后,两个变量将引用同一个对象,改变其中一个将影响另一个。
1.3 传递参数
ECMAScript 中所有函数的参数都是按值传递的,将一个函数外部的值传给函数内部的对应参数,就相当于进行了一次复制操作,基本类型值的传递就像基本类型值的复制,引用类型值的传递就像引用类型值的复制。
向参数传递一个基本类型值,被传递的值会被复制给一个局部变量,不管这个局部变量如何变化,都不会影响函数外部;
向参数传递一个引用类型值,被传递的值会被复制给一个局部变量,因为被传递的值并非真正的值,而是值在堆内存中的地址,因此这个局部变量的变化会影响函数外部。
访问变量有按值访问和按引用访问两种方式,而参数传递只有按值传递一种方式。
有很多人认为,在局部作用域中修改的值会影响到全局作用域,就说明参数是按引用传递的,有一个证据可以证明这种想法是错误的:
当我们在函数中重写一个包含引用类型值的参数时,这个参数会变成一个局部变量,针对这个参数的后续的修改再也不能影响函数外部了。如果参数是按引用传递,那么针对参数的重写和修改,是会原原本本地传递到函数外部的。
1.4 检测类型
typeof 操作符是确定一个变量是字符串,数字,布尔值还是 undefined 的最佳方法,但是它在面对对象或者 null 的时候,只会统一返回 "object"。
如果我们想知道某个对象的类型,可以使用 instanceof 操作符,例如:
student instanceof person //变量 instanceof 构造函数
2.执行环境及作用域
执行环境(execution context)分为全局执行环境和函数执行环境,它决定了变量的生命周期,以及哪些代码可以访问环境中的变量,每个执行环境都有一个与之关联的变量对象(variable object),执行环境中的所有变量和函数都保存在这个变量对象中。代码无法访问这个变量对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外层的一个执行环境,根据 ECMAScript 实现的寄主环境不同,表示全局执行环境的对象也不同。在Web浏览器中表示全局执行环境的是 window 对象,所有全局变量和函数都是作为 window 对象的属性和方法而创建的。
如果当前执行环境是函数,则将其活动对象(activation object)作为插入作用域链的变量对象。
活动对象最开始只包含一个 arguments 变量。在全局执行环境中不存在 arguments 这个变量。
当代码进入一个执行环境,就会创建一个用于搜索变量和函数的作用域链(scope chain)。作用域链的第一个变量对象始终来自当前执行环境,作用域链中第二个变量对象来自包含当前执行环境的外部执行环境,第三个变量来自更外一层的外部执行环境,以此类推,最后一个变量对象始终来自全局执行环境。
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。我们可以在函数内部访问在函数外部定义的变量,就是因为函数的作用域链既包含了自己的变量对象,也包含了所有外部环境的变量对象。
在解析一个变量或函数时,始终从作用域链的第一个变量对象开始搜索,然后逐级回溯。作用域链确保了函数内所有变量和函数的有序访问。
函数的参数也被当作变量来对待,所以其访问规则与执行环境中的其他变量相同。【是被当作函数外部的变量来对待,还是被当作函数内部的变量来对待?】
某个执行环境中的所有代码执行完毕后,该环境被销毁,与之对应的变量对象、包括保存在变量对象中的所有变量和函数定义、和变量对象的作用域链也一同被销毁。全局执行环境会在应用程序退出时才会被销毁,例如浏览器的 window 对象会在关闭网页或浏览器时销毁。
2.1 延长作用域链
虽然执行环境的类型只有全局和局部(函数)两种,但有办法来延长作用域链。有两个语句可以在作用域链的前端临时加一个变量对象,临时添加的变量对象会在代码执行后移除。【是语句执行后移除还是整个代码执行后移除?】
-
try-catch 语句的 catch 块
catch 语句会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明,这个新的变量对象会被添加到 catch 语句所在执行环境的变量对象的作用域链的前端
【添加到前端的意义何在?我自己试了一下,依然不能在catch语句外部访问到错误信息】。 -
with 语句
with 语句会创建一个新的变量对象,此变量对象包含了其接收的对象的所有属性和方法,并且这个新的变量对象会被添加到 with 语句所在执行环境的变量对象的作用域链的前端。因此在 with 语句的执行环境中,不仅够访问到 with 语句内部定义的变量和函数,还能访问到 with 语句接受的对象的属性和方法。
2.2 没有块级作用域
JavaScript 没有块级作用域,在其他语言中,由花括号封闭的代码块都有自己的作用域(相当于 ECMAScript 的执行环境),因此经常会导致理解上的困惑。
for (var i=0;i<10;i++>){}
alert (i);
以上代码,对于有块级作用域的语言,如 C,C++ 或 Java 中,由 for 语句创建的变量 i 在 for 循环结束后就会被销毁,但对于 JavaScript 来说,变量 i 依然会存在于循环外部的执行环境中。
- 声明变量
使用 var 声明的变量会被自动添加到最近的环境中,在函数内部,最接近的环境就是函数环境,在 with 语句中,最接近的环境就是函数环境。
如果初始化变量时没有使用 var 或其他声明,该变量就会被添加到全局环境,但这种做法会导致变量难以管理,不建议这样做。 - 查询标识符
当在某个环境中为了读取或写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么,标识符解析是沿着作用域链一级一级地搜索标识符的的过程,搜索始终从作用域链的第一个变量对象开始,然后逐级回溯。如果局部环境中存在同名标识符,就不会使用父环境中的标识符。
3. 垃圾收集
JavaScript 具有自动垃圾收集机制,不像 C 和 C++ 之类的语言需要开发人员手动跟踪内存使用情况。垃圾收集器会按固定间隔时间执行回收操作,用于标识垃圾的策略通常有以下两种。
3.1 标记清除
这是 JavaScript 最常用的垃圾收集方式,垃圾收集器会标记当前环境中无法访问到的变量,销毁他们并回收内存空间。
3.2 引用计数
跟踪记录每个变量被引用的次数,当变量的引用次数为零时说明无法再访问这个变量了,它就会被回收,但是循环引用会让一些变量的引用次数永远无法为零,从而导致内存泄漏。
3.3 性能问题
IE7 之前的垃圾收集器存在缺陷,引发的严重性能问题促使 IE7 重写了其垃圾收集例程。调用 window.CollectGarbage() 方法可以让 IE 立即执行垃圾收集。
3.4 管理内存
分配给 Web 浏览器的可用内存数量通常是比较少的,而内存数量与页面性能总是息息相关,因此优化内存占用就很有必要。
及时手动地将不再有用的数据的值设为 null 来释放引用,确保内存中只保存必要的数据,这种做法叫解除引用。这一做法适用于大多数全局变量和全局变量的属性,局部变量会在离开执行环境时自动被解除引用。
解除引用不意味着自动回收内存,只是让值脱离执行环境以便垃圾回收器回收。
以上是关于JavaScript 变量作用域和内存问题的主要内容,如果未能解决你的问题,请参考以下文章