作用域与作用域链知识小结
作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天我在这里将作用域和作用域链做一个小小的总结,希望能帮到大家。
一.作用域的作用:
任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
1. 全局变量
在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
(1)最外层函数和在最外层函数外面定义的变量其执行环境是window,即全局环境,例如:
var color = "blue"; //定义一个全局变量
function getcolor(){
return color;
}
getcolor();
alert(color); //blue
这里color是一个全局变量,在函数getcolor中的color仍然是blue,全局变量也能作用在局部函数中。
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
color = "blue";
function getcolor(){
var color= “red”;
return color;
}
getcolor();
alert(color); //blue
这里并未用var定义color,所以color会被自动解析成为一个全局变量,以至最后输出为blue。
(3)所有没有块级的作用域,其当前执行环境可以为window,即全局环境,例如:
if(true){
var color = "red";
}
alert(color); //red;
在if中定义的color也在其执行环境外生效,因为它不是块级作用域,在if中定义的变量会被添加到当前的执行环境中,它不像在c,c++,或java中,当执行完if后,将变量直接销毁,所有在for循环中要尤其注意这一点,如:
for(var i=0;i<20;i++){
var sum =0;
sum+=i;
}
alert(sum); //19;
alert(i); //20;
在运行过程中i被添加在当前执行环境,遂成为全局变量,所以for循环执行完后,i的值为20.
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
2. 局部变量
与全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,在一些地方也会看到有人把这种作用域称为函数作用域。如:
var color = "blue";
function getcolor(){
var color = "red";
return color;
}
alert(getcolor()); //red;
alert(color); //blue;
这里在函数getcolor内声明的变量是一个局部变量,所以它的执行环境只是函数getcolor,当函数执行完毕后该变量便会被销毁,所以最后的alert输出的是blue,即全局变量的值,而不是重新给color赋值的red,只有在执行getcolor,即局部变量的作用环境的时候,该变量才会生效。再如:
function getcolor(){
var color = "red";
return color;
}
alert(getcolor()); //red;
alert(color); //undefined;该color在函数外部是访问不到的。
此外,在局部作用域中定义的变量可以在局部环境中与全局变量互换使用,如下:
var color = "blue";
function change(){
var color1 = "red";
function getcolor(){
var color2 = color1;
color1 = color;
color = color2; //这里可以访问color,color1,color2
}
getcolor(); //这里能访问color,color1
}
change(); //这里只能访问color
其作用环境关系如下图:
所以这里的color2只能作用在函数getcolor中,而color1可以作用在函数getcolor和change中,color则可以作用在全局,所以它可在函数getcolor中与color1,color2交换使用。
3.延长作用域链
函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
1)with语句是对象的快捷应用方式,用来避免书写重复代码,如下:
function initUI(){
with(document){
var bd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i < len){
update(links[i++]);
}
getElementById("btnInit").οnclick=function(){
doSomething();
};
}
}
这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了,如下图:
因此在程序中应避免使用with语句,在这个例子中,只要简单的把document存储在一个局部变量中就可以提升性能。
2)try-catch语句中的catch语句。
当try代码块中发生错误时,执行过程会跳转到catch语句,然后把异常对象推入一个可变对象并置于作用域的头部。在catch代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中。如下:
try{
doSomething();
}catch(ex){
alert(ex.message); //作用域链在此处改变
}
请注意,一旦catch语句执行完毕,作用域链机会返回到之前的状态。try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。你可以通过优化代码来减少catch语句对性能的影响。一个很好的模式是将错误委托给一个函数处理,例如:
try{
doSomething();
}catch(ex){
handleError(ex); //委托给处理器方法
}
优化后的代码,handleError方法是catch子句中唯一执行的代码。该函数接收异常对象作为参数,这样你可以更加灵活和统一的处理错误。由于只执行一条语句,且没有局部变量的访问,作用域链的临时改变就不会影响代码性能了。
注意:
1.访问变量有按值和按引用两种,而参数只能按值进行传递,如下:
function add(num){
num+=10;
return num;
}
var count =20;
var result =add(count);
alert(count); //20,没有变化
alert(result); //30
这里的函数add有一个参数,而参数实际上是函数的局部变量,变量count作为参数别传入函数中,将其值赋给num,参数num被加上10,但是这个变化是不会影响函数外count的值的,因为参数num与变量count是互不相识的,只是有着相同的值而已,如果是按引用传递的,那么函数外部的count的值也是会被影响从而变成30.
再如:
function setName(obj) {
obj.name = "liwenqi";
obj = new Object();
obj.name = "lion";
}
var person = new Object();
setName(person);
alert(person.name); //liwenqi
在函数外定义一个对象person,将这个对象传递给函数setName后,其name属性被设置为“liwenqi”,然后又将一个新的对象赋值给obj,此时再将name属性修改为lion。如果person是按引用传递的话,那么在修改name属性为“lion”后,person对象便会被自动修改为指向name属性值为“lion”的新对象,但是在输出perso.name时,其输出仍旧为“liwenqi”,这说明在函数内部修改参数的值,其原始引用仍旧未变。实际上,在函数内重新定义的obj对象,是一个局部对象,它在函数执行完毕后会被销毁。
2.声明变量他的作用域符合就近原则,即:会被添加到最近的执行环境中。
之前就说过,如果局部变量未加var声明,那么他会被加到全局环境中。如下:
3.作用域的最前端是当前的执行环境
挡在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么,搜索过程中从作用域链的最前端开始。如下:
var color="blue";
function getcolor(){
return color;
}
alert(getcolor()); //blue
其执行环境关系:return color---->getcolor()--->window;
var color="blue";
function getcolor(){
var color = "red";
return color;
}
alert(getcolor()); //red;
alert(color);//blue
其执行环境关系:函数内部定义的color:return color---->getcolor(),而函数外部定义的color:color--->window。
4.标记的清除
这里可以通过翻转某个位来记录一个变量何时进入的执行环境,或者使用一个“进入执行环境”的变量列表及一个“离开环境”的变量列表,来跟踪哪个变量发生了变化。
大部分的浏览器的js实现都使用标记清除式的垃圾收集策略。
调用:
window.collectGarbage() //会立即执行垃圾的收集---->这是对于IE而言的。
window.Opera.collect() //也会启动相同的效果,当然他是对于其他浏览器而言
5.管理内存
优化内存的最佳方式,就是为执行代码只保存必要的数据。
如:解除引用,一旦数据不再被使用,便将其值设置为null,用于将引用释放。
~~但是,他只适用于大多数全局变量和全局对象的属性。
~~其实解除引用的真正目的是让其脱离当前执行环境,方便下次垃圾回收的时候将其回收。
function person(name) {
var person = new Object();
person.name = name;
return person;
}
var localperson = person("liwenqi");
localperson = null; //手动解除localperson的引用。
小生的总结到此为止,希望对你有所帮助!