JavaScript的作用域:
作用域,在维基百科上解释是:在电脑程序设计中,作用域(scope,或译作有效范围)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种,局部作用域又称为函数作用域。
全局作用域:
在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
1:程序最外层定义的函数或者变量
var a = "tsrot";
function hello(){
alert(a);
}
function sayHello(){
hello();
}
alert(a); //能访问到tsrot
hello(); //能访问到tsrot
sayHello(); //能访问到hello函数,然后也能访问到tsrot
2:所有window对象的属性和方法
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
局部作用域(函数作用域)
局部作用域在函数内创建,在函数内可访问,函数外不可访问。
function hello(){
var a = "tsrot";
alert(a);
}
hello(); //函数内可访问到tsrot
alert(a); //error not defined
作用域链是什么:
了解作用域链之前我们要知道一下几个概念:
- 变量和函数的声明
- 函数的生命周期
- Activetion Object(AO)、Variable Object(VO)
变量和函数的声明
在JavaScript引擎解析JavaScript代码的时候,首先,JavaScript引擎会把变量和函数的声明提前进行预解析,然后再去执行其他代码。
变量声明:变量的声明只有一种方式,那就是用var关键字声明,直接赋值不是一种声明方式。这仅仅是在全局对象上创建了新的属性(而不是变量)。它们有一下区别:
(1)因为它只是一种赋值,所以不会声明提前
alert(a); // undefined
alert(b); // error "b" is not defined
b = 10;
var a = 20;
(2)直接赋值形式是在执行阶段创建
alert(a); // undefined, 这个大家都知道
b = 10;
alert(b); // 10, 代码执行阶段创建
var a = 20;
alert(a); // 20, 代码执行阶段修改
(3)变量不能删除(delete),属性可以删除
a = 10;
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined
var b = 20;
alert(window.b); // 20
alert(delete b); // false
alert(window.b); // 仍然为 20,因为变量是不能够删除的。
函数声明:函数的声明有三种方式
(1)function name( ){ }直接创建方式
function add(a,b){
return a+b;
}
add(5,4);
(2)new Funtion构建函数创建
var add=new Function("a", "b", "return a+b;");
add(4,5);
(3)给变量赋值匿名函数方法创建
var add = function(a,b){
return a+b;
}
add(4,5);
后面两种方法,在声明前访问时,返回的都是一个undefined的变量。当然,在声明后访问它们都是一个function的函数。
注意 :如果变量名和函数名声明时相同,函数优先声明。
JavaScript作用域链
作用域就是变量与函数的可访问范围。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
执行环境(execution context)
每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
举个例子:
<script>
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
</script>
上面代码执行情况演示:
一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁)
但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
我们再来看一段代码:
name="lwy";
function t(){
var name="tlwy";
function s(){
var name="slwy";
console.log(name);
}
function ss(){
console.log(name);
}
s();
ss();
}
t();
当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显
name是"slwy"。
但执行ss()时,作用域链是: ss()->t()->window,所以name是”tlwy"
下面看一个很容易犯错的例子:
<html>
<head>
<script type="text/javascript">
function buttonInit(){
for(var i=1;i<4;i++){
var b=document.getElementById("button"+i);
b.addEventListener("click",function(){ alert("Button"+i);},false);
}
}
window.onload=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>
当文档加载完毕,给几个按钮注册点击事件,当我们点击按钮时,会弹出什么提示框呢?
很容易犯错,对是的,三个按钮都是弹出:"Button4",你答对了吗?
当注册事件结束后,i的值为4,当点击按钮时,事件函数即function(){ alert("Button"+i);}这个匿名函数中没有i,根据作用域链,所以到buttonInit函数中找,此时i的值为4,
所以弹出”button4“。