重读JS(四)数据类型、作用域和内存问题
本章内容
-
理解基本类型和引用类型的值
-
理解执行环境
-
理解垃圾收集
JavaScript的变量与其他语言的变量有很大区别。JavaScript变量松散类型的本质,决定了它只是特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的声明周期内改变。尽管从某种角度看,这可能是一个既有趣又强大,同时又容易出问题的特性,但JavaScript变量实际的复杂程度还远不如此。
一、基本类型和引用类型的值
基本类型值指的是简单的数据段,而引用类型值值那些可能由多个值构成的对象。
5种基本数据类型:undefined
、null
、boolean
、number
、string
引用数据类型:Object(Array
,Date
,RegExp
,Function
)
补充:
1.为了和下一章的基本包装类型区分(new Blooean()、new Number()、new String()),这里的基本数据类型一律小写,看到很多地方大家都大写,这个问题困惑我许久,直到看到基本包装类型才恍然大悟。
2.Object是一个基础类型,其他所有类型都从Object继承了基本的行为。Array,Date,RegExp,Function会在下一章内容详细介绍。
区别
基本数据类型值在内存中占据固定大小的空间,因此保存在栈内存中,且不能为其添加属性值。
引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址。
JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果obj1拷贝了obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是obj1将栈内存的引用地址复制了一份给obj2,因而它们共同指向了一个堆内存对象;
为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?1)堆比栈大,栈比堆速度快;2)基本数据类型比较稳定,而且相对来说占用的内存小;3)引用数据类型大小是动态的,而且是无限的,引用值的大小会改变,不能把它放在栈中,否则会降低变量查找的速度,因此放在变量栈空间的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响;4)堆内存是无序存储,可以根据引用直接获取;
按引用访问:js不允许直接访问保存在堆内存中的对象,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值;
传递参数
ECMAScript中所有函数的参数都是按值来传递的,
对于基本数据类型值,只是把变量里的值传递给参数,之后参数和这个变量互不影响,相当于复制。
对于引用值,对象变量里面的值是这个对象在堆内存中的内存地址,因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因,因为它们都指向同一个对象;
function setName(obj){
obj.name = "Nicholas"
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
function setName(obj) {
obj.name = "Nicholas";
var newobj = new Object();
obj = newobj; //指针指向改变了
obj.name = "Adagio";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
检测类型
typeof
基本数据类型使用typeof可以返回其基本数据类型,但是null类型会返回object,因为null值表示一个空对象指针;
instanceof
虽然在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,用处不大。通常我们更想知道的是这个对象是什么类型的。
根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。
alert(person instanceof Object);
alert(colors instanceof Array);
alert(pattern instanceof RefExp);
alert(Array instanceof Object); //true
使用typeof操作符检测函数时,返回'function'。在Safari5及之前版本和Chrome7及之前版本中使用typeof检测正则表达式时,也返回'function'。ECMA-262规定任何在内部实现[[Call]]方法的对象都应该应用typeof操作符返回'function'。上述浏览器中的正则使用了。在IE和Firefox中正则表达式使用typeof返回'object'
二、执行环境及作用域
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理时会在后台使用它。
全局和局部执行环境
在Web浏览器中,是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序退出-例如关闭网页或浏览器时被销毁)。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain),保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时质保函一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链的下一个变量对象来自包含(外部)环境,而在下一个对象则来自下一个包含环境,这样,一直延续到全局执行环境(作用域链中的最后一个对象)标识符解析是沿着作用域链一级一级地搜索标识符的过程,过程始终从作用域链的前端开始,然后逐级地向后回溯,之道找到标识符为止。例:
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = tempColor;
//这里可以访问tempColor、anotherColor和tempColor
}
// 这里可以访问color和anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color
changeColor();
作用域链(swapColors()->changeColor()->window)
延长作用域链
try-catch语句的catch块
with语句
这两个语句都会在作用域链的前端添加一个变量对象。对于with来说,会将指定的对象添加到作用域链中。对于catch语句来说,会创建一个新的变量对象,其中包含的是被刨除的错误对象的声明。例子:
function buildUrl(){
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
在此,with语句接受的是loaction对象,因此其变量对象中就包含了location对象的所有属性和方法,而这个变量对象就被添加到了作用域链的前端。当在with语句中引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。
至于with语句内部定义的url变量,是函数执行环境的一部分,自然可以作为函数的值被返回。
⭐没有块级作用域
在其他类C的语言中,由花括号封闭的代码块都有自己的作用域,但JavaScript不一定。
if语句中的变量声明会将变量添加到当前的执行环境。
for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。
1.声明变量
使用var声明的变量会自动被添加到最接近的环境中。在函数内部是函数的局部环境;在with语句中是函数环境。如果初始化变量时没有使用var声明,会被自动添加到全局环境
2.查询标识符
访问局部变量(color)和访问全局变量(window.color)更快
三、圾收集
JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。所需内存的分配和无用内存的回收实现了自动管理。
原理:找出那些不再使用的变量,然后释放其占用的内存。垃圾收集器会按固定的时间间隔(或代码执行中预定的手机时间)周期性地执行这一操作。
垃圾收集策略
- 标记清除
给当前不使用的值加上标记,然后再回收其内存。
- 引用计数(不再用)
跟踪记录所有值被引用的次熟。
性能问题
随着IE7的发布,其JavaScript引擎的垃圾收集例程改变工作方式:触发垃圾收集的变量分配、字面量和(或)数组元素的临界值被动态调整为动态修正。如果垃圾收集例程回收的内存分配量低于15%,则临界值加倍。如果回收了85%的内存分配量,则将临界值重置回默认值。
在有的浏览器中可以触发垃圾收集过程,比如IE:window.CollectGarbage()方法,Opera:window.opera.collect()。但不建议这样做
管理内存
虽然存在垃圾收集机制,但系统分配给Web浏览器的可用内存数量通常比桌面应用程序少,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统奔溃。因此,确保占用最少的内存可以让页面获得更好的性能。最佳方式就是为执行中的代码保存必要的数据,一旦不再用,最好通过将其值设为null来释放内存(解除引用)。
这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动解除引用。
解除一个值的引用作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
var globalPerson = creatPerson("Nicholas")
// ....
// 手动解除globalPerson的引用
globalPerson = null;