[每天学3章,一周掌握js]第1章:变量、作用域和内存问题
第一节:变量——基本类型和引用类型
【基本数据类型】:保存在栈中的数据段,如undefined、Null、boolean、Number、String
按值访问,直接操作位于【栈内存】中的值
【引用数据类型】:保存于堆内存中的对象,大小不固定,栈内存保存的只是访问地址,如Object/Date/Array/RegExp/Function。
按引用访问,通过存储的地址,访问位于【堆内存】中的对象
栈存储值和存储地址的区别
1.动态属性
可以给引用类型动态的添加属性,以便以后使用,而基本类型则无法添加。
例如:
1
var person=new Object();
2
person.name="jim";
3
alert(person.name);
2.复制变量值
复制基本数据类型,实际是复制的栈内存中的数据段,复制后的操作,对原变量不产生影响。而复制引用类型,则是复制的栈内存中的地址,对此引用类型的操作,会在原类型中反映出来,例如:
1
var p1=new Object();
2
var p2=p1;
3
p1.name="jim";
4
alert(p2.name);// 同样会输出 jim
3.传递参数
ECMAScript 中所有函数的参数都是按值传递的。
向参数传递基本类型的值时,被传递的值会被复制给一个局部变量,即命名参数,就是arguments对象中的一个元素。
向参数传递引用类型的值时,会把栈内存的地址复制给一个局部变量,因此这个局部变量的变化会引起原引用类型的变化,进而反映在函数外部。
示例:
1
function showname(obj){
2
obj.name="pp";
3
}
4
var person=new Object();
5
showname(person);
6
alert(person.name);// pp
纠错:如果局部作用域中修改的变量会在全局作用域中反映出来,就说明参数是按引用传递的。(x)
示例:
1
function showname(obj){
2
obj.name="pp";
3
var obj=new Object();
4
obj.name="pp2"
5
}
6
var person=new Object();
7
showname(person);
8
alert(person.name);// 依然输出pp
在这里,如果参数是按引用传递的话,那么person传递给obj后,obj得到的就是指向person的指针,obj重新定义Object并将name改为pp2后,则person也会相应的改变。如果是引用传递,重定义obj相当于通过指针重定义person,实际person.name输出的还是pp,说明person并没有被改变,那么就证明,参数不是按引用传递的!
这里person传递给obj的实际是person保存在栈内存中指向堆内存中对象的地址,即person和obj两个对象指向同一个堆内存中的对象,然后,obj重定义后,obj不再指向原对象,而是指向新创建的局部对象,按照js的运行机制,该对象会在函数执行完后立即删除,函数外部无法再次访问。
4.检测变量类型
typeof 操作符是检测基本类型变量的得力助手。检测引用类型的话,就需要使用instanceof操作符。
instanceof会检测出引用类型变量是具体的哪个类型的对象。例如:
1
alert(person instanceof Object ) //检测变量person是否是Object类型
2
alert(colors instanceof Array ) //检测变量colors是否是Array类型
3
//...........
注意:根据规定,所有引用类型的值,都是Objcect的实例,因此,检测一个引用类型的值和Object的构造函数时,instanceof都会返回true。
BUG:使用typeof检测函数时,该操作符会返回"function"。在safari和chrome浏览器中使用typeof检测正则表达式时,会错误di返回"function".
第二节:执行环境及作用域
1.相关定义说明
变量对象:每个执行环境都有一个与之对应的变量对象,环境中定义的所有变量和函数都保存在这个对象中,这个对象我们无法访问,但是解析器会在后台调用。
注意:web中的全局执行环境是【window对象】,因此所有全局变量和函数都是作为window对象的属性和方法创造的。
环境的销毁:执行环境中的代码全部执行完毕之后,该环境也会被销毁,保存在其中的所有变量和函数定义也会随之销毁。全局环境直到程序退出(例如关闭浏览器或网页时)才会销毁。
环境执行机制:每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
作用域链及作用:当代码在一个环境中执行的时候,会创建变量对象的【作用域链】。作用域链的作用就是保证环境有权访问的所有变量和函数的有序访问。
2.延长作用域链:
1
以下语句会延长作用域链:
2
3
try-catch语句中的catch块;
4
5
with语句;
说明:
正常情况下,作用域链的前端当前代码所在环境的的变量对象。
但是这两个语句,会在变量对象的前面临时添加一个新的变量对象。该临时对象会在执行完后被删除。
链接:深入理解js函数作用域和作用域链
http://my.oschina.net/ws2042/blog/65257
3. 注意——js没有块级作用域
js不像其他语言一样,用{}标明块级作用域,js没有块级作用域,例如:
1
if(true){
2
var color="red";
3
}
4
alert(color);// red
1
for(var i;i<10;i++){
2
//....
3
}
4
alert(i); //10
以上两个例子,如果是在其他有块级作用域的语言中是错误的。
1.声明变量的作用:
通过var 字符声明变量后,变量会被自动添加到最近的作用域中,如果没有var声明的话,那么默认添加到全局作用域中。
例如:
1
function test(){
2
var a=10;
3
var b=2;
4
c=a+b;
5
}
6
test();
7
alert(a); // error
8
alert(b); // error
9
alert(c); // 12
2.查询标识符:
当在某个环境中为了读取或写入引入一个标识符时,要通过搜索来确定该标识符代表什么。搜索过程从作用域的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到则停止,变量就绪。如果局部环境没有,则继续沿作用域链向上搜索。
之前也说过,每个环境作用域的前端一般都是该作用域所有变量和函数的变量对象,js解析器会对其进行访问。现在向上搜索时就是搜索该变量对象。更具体的解释查看上述章节中的引用链接。
因此从读取速度方面考虑,局部变量,要比全局变量读取的更快些。
第3节:内存问题——垃圾收集
js相对其他语言来说,在垃圾收集,内存释放方面有其很便捷的一方面,无需手工的跟踪内存进行释放,这些js解析器会自动释放内存。
1.垃圾收集策略:
标记清除
引用计数
简单罗列下,不做详细说明。
2.性能问题
本节主要说下因为浏览器垃圾回收策略的不同引起的性能问题:
首先还是臭名昭著的IE6:
IE6中,垃圾回收是根据内存分配量运行的,当有256个变量、4096个对象(或数组)字面量和数组元素 或者64k的字符串中任何一个满足时,垃圾收集器就会运行。
那么这样也产生了一个问题,存在于ie6中的问题,如果一个js中包含有上述的那么多变量,那么该脚本很可能在生命周期中一直保有那么多的变量,这样一来,垃圾收集器就会频繁的运行,从而产生严重的性能问题。
IE7中的收集策略已经对此做出了修复。ie7中的各项临界值与ie6是相等的,不同的是,当垃圾回收的内存量低于总量的15%,这个临界值就会翻倍,如果回收内存量到85%,则会hui'fu默认临界值。
引用计数策略引发的性能问题:
采用引用计数策略作为垃圾回收机制的浏览器,当js脚本中出现循环引用的情况时,对于循环引用的变量其引用数永远不会为0,那么这两个变量也就不会被回收,从而造成内存浪费。
例如:
1
function family(){
2
var a=new Object();
3
var b=new Object();
4
a.brother=b;
5
b.sister=a;
6
}
7
//此例子中a和b形成了循环引用,对于引用次数策略的垃圾回收器,不会将其回收,即便该局部作用域已经销毁。a、b将仍占有内存。
同样悲剧的是:
IE中的访问非原生的javascript对象时,也会出现这样的问题,因为IE中非原生js对象,例如BOM 和DOM中的对象就是使用c++以COM(组件对象模型)对象的形式实现的,而COM中的垃圾回收机制就是引用计数。
解决循环引用引起的内存问题,最好的办法是在不使用相互引用的变量时,将他们手动的断开引用。
例如:
1
var element=document.getElementBtId("some_element");
2
var myObject=new Object();
3
myObject.element=element;
4
element.someObject=myObject;
5
6
//这样断开引用
7
8
myObject.element=null;
9
element.someObject=null;
3.管理内存
确保在不影
补充:web前端 , JavaScript ,