首先由于JavaScript的变量不同于其他语言,JavaScript变量属于松散类型,本质决定了他只是一个在特定时间内保存特定值的一个名字而已,也就是说变量的值和变量的数据类型都可能会在脚本的生命周期内改变。
基本类型和引用类型的值
ECMAscript变量可能包含两种不同数据类型的值。(基本类型 和 引用类型)
基本类型:简单的数据段(Undefined Null Boolean Number String)
引用类型:可能由多个值构成的对象。Objuect
首先我们知道,我们的变量是存储在内存中的,内存分为两种类型,堆内存和栈内存
基本类型的变量直接存放在栈内存中,而引用类型的变量实际存放在堆内存中,栈内存中存放的引用类型(对象)变量的指针、因此我们可以直接对基本类型的变量进行操作,而在操作对象的时候,实际上是在操作他的指针。(个人理解)(注意,栈存储的不一定是对象所处的物理地址,但是一定能够根据这个内容在堆中找到对应的对象)
总结:由于基本类型的值占用内存一般比较小,所以存放在内存空间较小的栈内存中(null例外,虽然是对象但是也存放在栈内存中),而对象类型的变量一般由多个值构成。所以说会存放在堆内存中,并且会将指向该堆内存地址的指针存放在栈内存中,这也就解释了为什么对象不可以进行比较,因为对象的比较实际上是指针的比较。
类型检测
type of(对象名)将会返回基本类型或者Object但是如果想要具体知道是那种对象类型就需要用到ECMAScript提供的instanceof操作符:
alert(对象名 instanceof Object)//如果对象是Object就恢复返回true
alert(对象名 instanceof Array)//如果对象是数组类型就恢复返回true
alert(对象名 instanceof RegExp)//如果对象是正则表达式类型就恢复返回true
注意:使用typeof操作符检测函数将会返回function 。在Safari 5以及之前版本和Chrome 7及之前版本也会将正则表达式认定为function,因为ECMA-262规定任何在内部实现call方法的对象在使用typeof操作符都会返回function。 在ie 和Firefox中正则表达式返回Object。
执行环境和作用域
执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。
每个执行环境都有一个自己的变量对象,环境中定义的变量和函数都存放其中,虽然这个变量对象对于我们不可见,但是在解析器处理数据的时候会有用到
全局执行环境一般被理解为window对象,所以 所有得全局变量和函数都是window对象的方法和属性。
当前执行环境的所有代码执行完毕以后,该环境以及其中的变量和函数都销毁。
作用域链:
当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
- 作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
- 全局执行环境的变量对象始终都是作用域链上的最后一个对象
活动对象,可以理解为当前执行环境中所有的对象和函数都是活动对象,如果当前执行环境是一个函数,那么这个函数就是活动对象,活动对象最开始只有一个变量arguements对象(这是一个类数组对象,用于存放你想传递给函数的参数,也就是函数后面括号里要放的东西)。那么这个作用域链的下一个变量对象来自外部函数,一层层包裹直到全局环境,所以全局环境是作用域链得最后一个对象。
标识符解析就是沿着作用域链一层层寻找标识符,搜索过程从作用域前端开始也就是当前执行环境的变量对象,一直找到全局环境,直到找到标识符为止。
延长作用域链
首先延长作用域有两种方法 一种是with 一种是try catch
先说with语句:
var person = {
age:62;
};
function a(){
var age =34 ;
with(person){
var url=age;
}
console.log(url);
}
实际上如果你知道输出结果会是62就应该是理解了;
首先with会延长作用域链,他将wih语句括号里装的对象(person)作为变量对象添加到作用域链头部,而且with语句中再使用person对象的属性时不需要person.age 因为解析器在寻找表示对应标识符的时候是从头部向尾部找,而with语句将对象直接放到头部,所以最先访问达到的是对象里的变量,也就解释了为什么返回的age是62而不是function a中的34 因为 function a的环境在作用域链的第二层 而person在第一层。
接下来就是try catch 首先说这个语句的用途,语句标记要尝试的语句块,并指定一个出现异常时抛出的响应。
var e=5;
try {
nonExistentFunction();
} catch (error) {
e=3;
console.error(e);
}
结果是返回e=3 因为catch大括号里的内容会被作为一个变量对象存放在作用域链的顶端。
JavaScript没有块级作用域
与c和c++不同,简单来说,var声明的函数会被自动添加到最接近的环境中,如果没有用var声明而是直接使用,例如 sum=32; 这样sun就会直接被添加到全局变量中。
注意虽然直接初始化未声明的对象不会报错,但是作用域问题会导致一系列的问题,而且在严格模式下,初始化未声明的对象会报错。
查询标识符
从作用域链的最顶端开始寻找,一直向后寻找知道找到,如果一直到全局作用域的变量对象依旧没有找到,就会报错。
所以说访问局部变量比访问全局变量要快一些,但是js引擎的优化差寻很不错,这个差别可以忽略不计。
垃圾收集
垃圾回收有两种机制,一种标记清除一种是计数清除
标记清除是将内存中所有的变量都添加一个标记,然后再将那些环境中的变量以及被环境中变量引用的变量的标记清除掉,在此之后再被加上标记的变量就会被视为要被删除的变量,因为这些变量无法被环境中的变量访问到了。
计数清除是将变量添加一个计数值,如果有一个变量引用他,他的值就加一,相同如果有一个变量不在引用他,那引用值就减一,当为0的时候就视为可以清除,
但是有一个问题就是循环引用,这样两个变量的标记都为1,所以这种情况要手动将变量置空,也就是解开这个循环。
一般来说浏览器都会使用标记清除,但是即使一个浏览器使用标记清除,在涉及到DOM元素和原生JavaScript元素间的循环引用依然无法清除,因为DOM元素并不是JavaScript对象。但是IE9将DOM 和BOM都转为真正的JavaScript对象,就解决了这种问题。
总结
基本类型值在内存占据固定的空间,存放在栈内存中,
从一个变量向另一个变量赋值基本类型的值,会创建这个值的副本,两者互不干扰。
引用类型的值是对象,保存在堆内存中。
包含引用类型值的变量实际上指向这个对象所在堆内存的指针。这个指针存放在栈内存中,
从一个变量向另一个变量复制引用类型的值,复制的是指针,这两个指针指向同一个对象。
确定值的基本类型用typeof 确定引用类型用instanceof
所有变量都存在于一个指向环境中,这个执行环境决定了变量的生命周期,以及那一部分代码可以访问其中的变量,总结如下:
执行环境有全局环境和局部环境(函数执行环境)
每次进入一个新的环境都创建一个用于搜索变量和函数的作用域链。
函数的局部环境不经有权访问函数作用域中的变量,而且有权访问其父环境中的任何数据。
变量的执行环境有助于确定何时释放内存。
JavaScript具有垃圾回收机制的语言:
离开作用域(执行环境)的值自动标记为可以回收,在下一次触发垃圾回收的时候清除掉。
标记清除是目前主流的方法,这种思想是给不使用的值加上标记。
计数回收思想是跟踪所有值被引用的次数,次数为0的回收,但是IE中访问非原生JavaScript对象会导致问题,循环引用。
为了有效的回收内存,要及时接触不再使用的全局对象,全局对象属性,以及循环引用变量的引用。