本来以为自己对JS的作用域已经理解了,但是最近碰到面试和面试题,突然才发现自己对JS的作用域及作用域链理解的还不是那么透彻。重新学习了一下,再做总结。
一、函数作用域
先看一小段代码:
var scope="global";
function t(){
console.log(scope);
var scope="local";
console.log(scope);
}
t();
PS:console是浏览器调试工具提供的对象,可以打印输出一些信息。
第一句输出的是: "undefined",而不是 "global";第二讲输出的是:"local"
你可能会认为第一句会输出:"global",因为代码还没执行var scope="local",所以肯定会输出“global"。
有这种想法是理所当然的,毕竟 JS 的执行也是逐条执行。但是,JS 中 var 有变量提升效果!什么意思呢,就是说:在某个函数体里定义变量时,这个变量会在函数体内的代码执行前预先定义!
也就是说,在执行第一条 console.log() 的时候,t 函数内的变量 scope 已经定义了!
所以上述代码可以重写如下:
var scope="global";
function t(){
var scope;
console.log(scope);
scope="local";
console.log(scope);
}
t();
由于 JS 中 var 变量提升效果,局部变量在整个函数体始终是由定义的,我们可以将变量声明“提前”到函数体顶部,同时变量初始化还在原来位置。
为什么说 JS 没有块级作用域呢,有以下代码为证:
var name="global";
if(true){
var name="local";
console.log(name);
}
console.log(name);
结果输出都是是“local”。
如果有块级作用域,明显 if 语句将创建局部变量 name,并不会修改全局变量 name,可是没有这样,因为 JS 中没有块级作用域。(ES6 中新增了 let 变量声明,可以是变量的作用域限制在 {} 中,也就相当于实现了块级作用域)
所以下面的代码也就很好理解了。
function t(flag){
if(flag){
var s="ifscope";
for(var i=0; i < 2; i++);
}
console.log(i);
console.log(s);
}
t(true);
输出结果:2、"ifscope"
二、变量作用域
还是首先看一段代码:
function t(flag){
if(flag){
s="ifscope";
for(var i = 0; i<2; i++);
}
console.log(i);
}
t(true);
console.log(s);
就是上面的翻版,知识将声明s中的var去掉。
程序会报错还是输出 "ifscope" 呢?
结果是会输出:"ifscope"
这主要是 JS 中没有用 var 声明的变量都会变成全局变量,也就是顶层对象的属性。
所以你用 console.log(window.s) 也是会输出 "ifconfig"。
当使用 var 声明一个变量时,创建的这个属性是不可配置的,也就是说无法通过delete运算符删除。
var name = 1; // 不可删除
sex = "girl"; // 可删除
this.age = 22; // 可删除
三:作用域链
先来看一段代码:
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"。
四、with语句(严格模式下不可用)
说到作用域链,不得不说 with 语句。with 语句主要用来临时扩展作用域链,将语句中的对象添加到作用域的头部。
看下面代码
person={
name:"yhb",
age:22,
height:175,
wife:{
name:"lwy",
age:21
}
};
with(person.wife){
console.log(name);
}
通过 with 将person.wife 添加到作用域链头部,再执行 console.log() 的时候,通过作用域链会优先找到 wife 对象的属性 name,也就是结果输出为 "lwy"。