JavaScript变量作用域和内存问题(js高级程序设计总结)
1,变量
ECMAScript和JavaScript是等同的吗?个人认为是否定的。我的理解是这样的,ECMAScript是一套完整的标准或者说协议,而JavaScript是在浏览器上实现的一套脚本语言。也就是说,ECMAScript是JavaScript的父类标准。JavaScript是ECMAScript的具体实现。所有ECMAScript定义的数据类型或者语言特性实际上都是伪代码的形式规定的。当然如果可以,ECMAScript也可以有服务器实现,单片机实现(不一定恰当)。如果说ECMAScript是接口好像也挺形象。
JavaScript的基本数据类型(原始数据类型)有五种,分别是:Undefined、Null、Boolean、Number、String。基本数据类型指的是那些保存在栈内存中的简单数据段。为什么要保存到栈内存呢?因为基本数据类型值的大小实际上是固定不变的。换句话说,这些数据是完全保存在内存中的一个位置。因为此特点所以对于基本数据类型的访问实际上是按值访问,因为我们实际上操作的就是保存的值。
JavaScript中另一种数据类型叫做引用类型值。和基本数据类型不同,引用数据内型是保存在堆内存中的对象。意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。而这个变量指针是保存在栈内存中的。同理,此类型值的访问实际上是访问了指向对象的指针,从而间接的访问数据对象。
下面再聊一下变量的赋值过程:
var a=1; var b=a; a++; alert(a);//2 alert(b); //1
很明显了。基本数据类型的赋值操作实际上是独立的!
下面再看一个引用类型的例子:
var str = new String("hello,hz"); var newStr = str; str.name="huazi"; alert(str.name); //huazi alert(newStr.name); //huazi
实际上str和newStr是指向了同一块堆内存。当然,str和newStr都是在栈内存的指针变量。
2.函数参数问题
对于JavaScript的函数传参问题,分为两种情况。若是基本数据类型那么就传递的是引用,若是对象或者引用那么就是传递的引用。当然你也可以理解为所有的函数参数都是值传递,只不过值需要分为两种情况。值、指针。、
function test(data){ return data++; } var num = 10; var res = test(num); alert(num); //10 alert(res); //11
说明什么呢?值传递就是传值了,没有任何关联关系.
var obj = new Object("hello,hz"); console.log(obj.name);//Undefiend var fun1 = function(args){ args.name="huazi" return args; } var newObj = fun1(obj); console.log(newObj.name);//huazi console.log(obj.name);//huazi
传递引用.
3.JavaScript执行环境和作用域
要理解JavaScript的作用域必须得先聊一聊JavaScript的执行环境.执行环境是JavaScript中的一个比较重要的概念.执行环境定义了一个环境变量对象与环境关联.当然你是操作不了这个对象的.但是在浏览器解析脚本的时候是会用到的.执行环境中定义的函数和变量都保存在此变量对象之中.
全局的执行环境是最外围的执行环境.根据ECMAScript的宿主环境不同,表示环境的对象也是不同的.对于JavaScript来说,宿主是浏览器.也就是说此全局执行环境是window对象.当某个环境的所有代码被指向完毕之后,该环境将被销毁.但是需要注意的是全局对象window是不被销毁的.只有当关闭网页或者浏览器时window对象才会被销毁.
每个函数在执行的时候都会创建自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中.执行完毕,出栈.把控制权交还给之前的执行环境.这就是ECMAScript的执行流就是用此机制控制着.
作用域链是由变量对象构成的,作用域链的用途也很明确.就是保证对环境有权访问的所有变量和函数的有序访问.作用域的前端永远是当前运行的代码的变量对象.需要注意的是如果此时执行的是一个函数,那么将其活动对象作为变量对象.活动对象在最开始时候只包含一个变量,即参数对象arguments.作用域链中的下一个变量对象就是其父包含变量对象.变量作用域链的最后一个变量对象自然就是全局变量对象.
变量可访问性的判断是根据作用域链来查找标识符判断的,依次回溯.注意此处是依次回溯,所以存在优先级的问题.越靠近的变量对象越用可能被第一个找到并且调用.如果没有找到自然报错.
特别的try-catch和with语句会延长作用域链.因为with(?)和catch(error),?和error都是只读,所以都会被添加到所在执行环境的变量对象中.
JavaScript是没有块级作用域的.在写for循环的时候可能需要注意一下for(var i=0;….)此种写完,执行完for语句后i依然有值,而且不会立即释放。
4,垃圾回收内存管理
JavaScript和java一样是具有自动回收机制的,也就是说,执行环境会负责管理代码执行过程中使用的内存。而C/C++之流是要开发人员手动跟踪内存的使用情况的,这是造成很多问题的根源。JavaScript的垃圾回收机制很简单:找出那些不再继续使用的变量,然后释放其占用的空间。为此,垃圾回收收集器会按照固定的时间间隔周期性地执行这个操作。(不同的浏览器对于时间间隔的定义是不同的)。
回收策略:
1,标记清除
JavaScript最常用的垃圾收集方式就是标记清除。当变量进入环境时,就将变量标记为进入环境状态。当变量离开环境时将其标记为离开环境。可以使用任何方式来做标记,比如翻转某个特殊位置的位来标记,或者维护一个变量列表来跟踪标记。
垃圾收集器在运行的时候回给没个表里打上标记,然后取消掉未在环境中的变量标记。而在此后仍然带有标记的变量就会被视为准备删除的变量。原因是环境已经无法访问它们了。最后就是垃圾收集器内存清除了。
2,引用计数
引用计数是种已经被淘汰的策略,但是在IE的某些版本中,bom和dom元素的内存管理仍然采用引用计数。因为这些对象并不是JavaScript原生,是使用C++的COM实现。而COM是使用引用计数实现。所以还是有必要了解一下。 避免循环引用造成的bug。
引用计数的含义是跟踪记录每个值被引用的次数。当申明一个变量并且将一个引用类型值赋值给该变量时,这个值的引用次数加1.如果同一个值又被赋值给另一个值,那这个值的引用次数再加1.相反,如果包含对这个值引用的变量又取得了一个新值,则这个值的引用次数减1.当一个值的引用次数为0的时候,说明没有办法再来引用它了。垃圾回收。
为了安全和避免一些意外情况发生,建议在使用完全局变量,全局对象属性以及循环引用之后自行解除引用关系。obj=null;需要注意的是此种做法并没有立刻回收内存。只是让其脱离执行环境(肯定被标记了,下次收集器恐怕要消灭它)。