JS---变量、作用域和内存问题
http://www.cnblogs.com/amy2011/archive/2013/05/22/3091401.html
由于Javascript是松散型的,所以其变量只是在特定时间用于保存特定值的一个名字而已,并不存在某个变量必须保存某种类型的值的规则,变量的值以及其数据类型都可以在脚本的声明周期内改变
一、基本类型与引用类型
1.1 基本类型:保存在栈内存中的简单数据段,值保存在内存中的一个位置
1.2 引用类型:保存在堆内存中的对象,变量保存的仅仅是一个指针,这个指针指向内存中的另一个位置,该位置保存对象
1.2.1 5种基本数据类型:Undefined、Null、Boolean、Number、String在内存中分别占有固定大小的空间,保存在栈内存中
1.2.2 按值访问:对于保存基本数据类型的变量,我们操作的是它们实际保存的值
1.2.3 引用类型的值由于大小不固定,因此保存在堆内存中,但存放引用类型值的堆内存的内存地址大小是固定的,因此我们将这个地址保存在栈内存中,当操作引用类型的值时,先到栈内存 读取该引用类型值的堆内存地址,然后在找到保存在堆内存中的引用类型的值
1.2.4 按引用访问:因为我们操作的不是实际的值,而是被这个值所引用的对象
1.2.5 保存在栈内存中的每个值分别占据固定大小的空间,因此可以按照顺序来访问
1.3 动态属性:只能给引用类型值动态的添加属性
1.4 复制变量值:针对基本数据类型值的复制,会在栈中创建一个新值,然后把该值复制到为新变量分配的位置上
针对引用类型值时,复制的只是一个指针,复制操作结束后,两个变量实际上将引用同一个对象
var num1=5; var num2=num1; num1=6; alert(num2);//5 /**/ var obj1=new Object(); var obj2=obj1; obj1.name="Jim"; alert(obj2.name);//"Jim"
1.5 传递参数:所有的参数传递全部是按值传递,虽然访问变量的时候有按值和按引用两种方式,但是参数传递只能按值传递。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,这个变量的变化不会反映在函数外面
function setName(obj){ obj.name="Jim"; obj=new Object(); obj.name="Greg"; } var p=new Object(); setName(p); alert(p.name);//"Jim"
//如果是按引用传递的话,那么p就会被自动修改为指向其name属性为”Greg“的新对象,但是由下图分析可知,参数传递是按值传递
1.6 检测类型:typeof操作符是确定一个变量是字符串、数值、布尔、还是undefined,以及object的最佳工具,但是对于一个对象或者null,却只能返回"object",因此在检测引用类型值的时候,引入了instanceof操作符
function Name(){ } var p=new Name(); alert(p instanceof Name);//true
二、执行环境和作用域
2.1 执行环境定义了变量和函数有权访问的其他数据,每个执行环境都有一个变量对象,环境中的所有变量和函数都保存在这个对象中,我们编写的代码无法访问这个对象
2.2 全局执行环境:最外围的一个执行环境,在web浏览器中,全局执行环境是window对象,因此全局变量和函数都作为window对象的属性和方法创建的
2.3 当某个执行环境的所有代码执行完毕后,该环境会被销毁,保存在其中的变量和函数也会被销毁
2.4 全局执行环境的销毁是在关闭网页或者浏览器时
2.5 每个函数都有自己的执行环境,当执行到某个函数时,会把控制权交给该函数,直到该函数执行完毕后,再把控制权交给之前的环境
2.6 当代码在一个环境(例如一个函数)中执行时,会创建变量对象的作用域链
2.7 作用域链的作用是保证对执行环境中的变量和函数的有序访问
2.8 作用域链的前端始终是当前代码的执行环境的变量对象
2.9 如果执行环境是函数,那么变量对象是活动对象,初始只包含一个变量arguments对象,外层是包含该环境的环境,外层的外层是包含该环境的环境的环境,一直延伸到全局执行环境
2.10 全局执行环境始终是作用域链的最后一个对象
2.11 在搜索某个标识符(如变量名,函数名等)时,会沿着作用域链一级一级的搜索,直到找到,搜索停止,这也就达到了2.7中提到的作用域链的作用(保证对执行环境中的变量和函数的有序访问)
var color="blue"; function change(){ var another="red"; function swap(){ var temp=another; another=color; color=temp; }//这里swap函数只是定义,并没有执行 swap();//这里才是swap函数的执行 }//这里change函数只是定义,并没有执行 change();//这里才是change函数的执行
2.12 内部环境可以通过作用域链一级一级访问都所有外部的变量以及函数,包括最外层的全局执行环境,但是外部环境却不能访问内部环境的变量和函数,作用域链只能从下向上搜索,不能从下向上搜索
2.13 例如swap函数可以访问全局变量color,但是全局变量window却不能访问到another或者temp
2.14 延长作用域链的方式:第一try-catch语句中的catch块,第二with语句,这两个语句都会在作用域链的前端添加一个变量对象,对于with语句来说,其变量对象中包含着为指定对象的所有属性和方法所作的变量声明。对于catch语句来时,其变量对象中包含的是被抛出的错误对象的声明,这些变量对象是只读的
2.15 浏览器的兼容性问题1注意IE与标准在try-catch语句延长作用域链的不同之处:即使在catch块的外部也可以访问到错误对象
2.16 js没有块级作用域的概念,所谓块级作用域就是由花括号封闭的代码块都有自己的作用域,但是js中使用var声明的变量,会被自动添加到距离最近的可用的执行环境中,未使用var声明被初始化的变量,会被自动添加到全局环境,其作用范围跟花括号没有关系
2.17 查询标识符时也会根据作用域链,从下向上一级一级进行搜索,搜索到后,就停止,因此,如果局部环境存在同名的标识符,就不会使用父环境中的标识符
三、垃圾收集机制
3.1 js具有自动垃圾收集机制,垃圾收集器会按照固定的时间间隔周期性地执行这一操作,两种方式:标记清除和引用计数
3.2 标记清除:js中最常用的垃圾收集方式,当执行流进入到相应的环境(即当执行流调用某个函数)时,该函数中声明了某个变量,那么这个变量就被标记为“进入环境”,从逻辑上讲,永远不能释放进入环境的变量的内存,当变量离开环境时,则被标记为“离开环境”。可利用翻转某个特殊的位来记录
3.3 引用计数:跟踪记录每个值被引用的次数,将一个引用类型的值赋值给一个变量,那么这个引用类型的值的引用次数就加1,相反,如果这个变量被赋值了其他值,这个引用类型的值的引用次数就减1,当引用次数为0时,就说明没有办法再访问这个引用类型的值了,那么她所占的内存空间会被垃圾回收器给回收
3.4 循环引用:如果对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的指针,就形成了循环引用,它们的引用次数永远不会为0,为了避免循环引用问题,最好是在不使用它们的时候,手工断开它们之间的连接
3.5 内存限制问题:js中分配给web浏览器的可用内存数量通常比分配给桌面应用程序的少,原因是为了防止运行js的网页耗尽全部内存而导致系统崩溃
3.6 解除引用:由于内存的限制,所以我们要优化内存占用,最佳策略就是执行代码只保存必要的数据,一旦数据不再用,最好通过将其值设置为null来释放对其的引用,这种方法称为解除引用
3.7 解除引用的适用范围:解除引用适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动被解除引用
3.8 对解除引用需要声明的一点:解除引用并不代表收回该值所占的内存,解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其收回