JavaScript作用域
W3 school这样解释的:
在 JavaScript 中,对象和函数也是变量。作用域决定了从代码不同部分对变量、对象和函数的可访问性。
局部作用域和全局作用域
JavaScript中,有两种作用域类型——局部作用域和全局作用域。
变量的可访问性由作用域决定,如果变量a
在函数体中声明,那么它会成为该函数的局部变量,外部是不可访问的。
var global = 321;
function private() {
var a = 123; //局部变量,外部不可见
console.log(a);//123
console.log(global);//321
}
private();
console.log(a); //错误:Uncaught ReferenceError: a is not defined
上面的代码中,global
是全局变量,函数体中也可以访问到它,但是a
是声明在函数体中的变量,作用域局限于函数体中。当外面调用输出它时,就会报错,因为未定义。
JavaScript 变量的有效期
JavaScript 变量的有效期始于其被创建时。
局部变量会在函数完成时被删除。
全局变量会在您关闭页面是被删除。
预编译
在练习之前,我们先提一下JS中的预编译,等会我们将会用到
1、创建AO对象
就是隐式的创建一个AO(Activation Object)空对象。2、查找形参和变量声明,将形参名和变量名作为AO对象的属性,值为undefined
注意此处是变量声明(必须带var),只是查找形参名和变量声明名不赋值。3、将形参与实参统计
即修改AO对象中属性名为形参的值为传入的实参,如果没有形参此步略过。4、查找函数声明,函数名作为AO对象的属性,函数体作为的值
注意:此处是函数声明,而非匿名函数和函数表达式 。总结:预编译过程就是查找变量声明、形参和函数声明的过程,并不初始化赋值,在解释执行阶段才会进行初始化 。
练习
对于作用域的一些基础我们已经了解差不多了,来结合一下题目来测试一下自己的掌握度吧。
可以先自己试着分析,然后我会在后面把这些题目逐一进行分析。
//题目一:
var a = "aa";
function test() {
console.log(a);
var a = "bb";
console.log(a);
}
test();
alert(a);
//题目二:
(function() {
var a = 5
function a() {}
console.log(a);
function b() {}
b = 6;
console.log(b);
var c = d = b
})();
console.log(d);
console.log(c);
//题目三:
var foo = {
n: 1
};
(function(foo) {
console.log(foo.n);
foo.n = 3;
var foo = {
n: 2
}
console.log(foo.n);
})(foo)
console.log(foo.n)
解析
//题目一:
var a = "aa";
function test() {
console.log(a);
var a = "bb";
console.log(a);
}
test();
alert(a);
预编译结束后然后逐行执行:
首先全局中的a = undefined ——> “aa”;
调用test()
时首先执行console.log(a)
,由于预编译期间声明了局部变量a,并没有赋值,此时a = undefined
,控制台打印结果为 undefined 。(如果test函数中没有var a,执行时就会往上找,找到全局中的变量a进行输出。)
紧接着test中的局部变量a = undefined ——> “bb”;
执行console.log(a)
,由于上面刚刚给变量a进行了赋值"bb",所以控制台输出 bb
最后alert(a)
这里就要区分开全局变量和局部变量的作用域,很明显这里执行结果会弹出结果: aa
//题目二:
(function() {
var a = 5
function a() {}
console.log(a);
function b() {}
b = 6;
console.log(b);
var c = d = b;
})();
console.log(d);
console.log(c);
预编译结束后然后逐行执行:
a = 函数体 ——> 5;
执行console.log(a),控制台打印 5
b = 函数体 ——> 6;
执行console.log(a),控制台打印 6
执行c=d=b,即同时给c和d赋值等于6 。(注意:这里的d未用var关键字声明,自动成为全局对象)
立即执行函数执行后就被销毁了,但是d是全局对象,所以console.log(d)可以找到d并打印出 6 。c是的作用域在立即执行函数中,当立即执行函数执行完被销毁后,也就无法在全局中找到它,所以结果报错Uncaught ReferenceError: c is not defined
。
//题目三:
var foo = {
n: 1
};
(function(foo) {
console.log(foo.n);//foo1
foo.n = 3;
var foo = {
n: 2
}
console.log(foo.n);
})(foo)
console.log(foo.n)
执行console.log(foo.n)
因为函数体中声明的局部变量foo没有找到属性n,但找到了传进来的实参foo,所以输出 1
后面foo.n = 3
改变的是全局的foo对象中属性n的属性值(因为只有全局中的foo具有属性n。) 1 变成 3
foo = {n:2}
是将foo重新赋值了(且是在立即执行函数内部重新声明的,作用域是在立即执行函数内生效的)。
console.log(foo.n)
输出 2 。
立即执行函数销毁后,局部作用域的 foo={n: 2}
不见了,输出的是全局中的foo,此时foo = {n: 3};故输出 3 。
属性n。) 1 变成 3
foo = {n:2}
是将foo重新赋值了(且是在立即执行函数内部重新声明的,作用域是在立即执行函数内生效的)。
console.log(foo.n)
输出 2 。
立即执行函数销毁后,局部作用域的 foo={n: 2}
不见了,输出的是全局中的foo,此时foo = {n: 3};故输出 3 。