JavaScript作用域和变量提升
一、JS的作用域
1.JS采用词法作用域
首先,我们得知道JavaScript采取的是词法作用域,而不是动态作用域。
那么什么是词法作用域,什么是动态作用域呢?
引用一下我以前在知乎上看到的一句精简的描述:
词法作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查找。
动态作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数调用时的环境中找。
如果对这句话还是不太明白的朋友,可以观察以下代码:
var a = "这是第一个a";
function func01() {
console.log(a); //先在当前作用域中查找,如果没有则访问全局的作用域
}
function func02() {
var a = "这是第二个a";
func01()
}
func01(); //打印结果为:这是第一个a
func02(); //打印结果为:这是第一个a(倘若JS用的是动态作用域,这里将输出第二个a)
2.JS没有块作用域(try,catch为特例)
在大多数语言中,都是有块作用域的,比如C、C++、C#、Java、Object-C等等,而JS没有块作用域。
下面分别是C和OC的代码
#include <stdio.h>
int main() {
int a = 1;
if (1) {
int a = 2;
printf("%d, ", a); // 2
}
printf("%d\n", a); // 1
}
NSString *str = @"码农1号";
if (true) {
NSString *str = @"码农2号";
NSLog(@"%@", str); //码农2号
}
NSLog(@"%@", str); //码农1号
在C和OC中,if花括号内定义的变量,在if语句执行完后,就被销毁了。那么我们再来看看类似的代码运行在JS中。
var a = "global";
if (true) {
var a = "if";
console.log(a); //if
}
console.log(a); //输出为:if 证明if语句内定义的变量,是全局变量
那么在JS中,我们能否像其它语言一样创建出类似块作用域的效果呢?答案是可以的,利用闭包!
因为在JS在只存在全局作用域和函数作用域,因此我们可以巧妙的利用一下函数作用域创建出块作用域的效果,我们把上面的JS代码改造一下:
var a = "global";
if (true) {
(function() {
var a = "if";
console.log(a); //if
})();
}
console.log(a); //global
上面代码在if内创建了一个闭包,它是一个立即调用的匿名函数,因此这里创建出了一个函数作用域,在此函数作用域里定义的变量,将不影响全局变量,并且函数执行完后,该变量会被销毁。
这样利用闭包的好处是:限定作用域,不怕污染全局变量。
二、JS中的变量提升和函数提升
1.变量的提升
在JS代码编译时,函数声明和变量定义会被解释器移动到其所在作用域的最顶部!
怎么理解这句话呢?我们来看下面代码
console.log(a); //报错
console.log(a); //undefined
var a = 10;
为什么上面代码不报错?因为上面代码在编译器解析完后,变成了下面的形式:
var a;
console.log(a);
a = 10;
那么我们再看下面的例子,猜猜它们的输出结果
var a = "global";
fun();
function fun() {
console.log(a); //1.?
var a = "local";
}
console.log(a); //2.?
输出结果是 1. undefined, 2. global
上面的代码的正确解析如下:
var a;
function fun() {
var a; //此变量所处域为函数域,因此其作用域最顶部是这里
console.log(a); //undefined
a = "local";
}
a = "global"
fun();
console.log(a); //global (函数域创建的变量不污染到外面)
2.函数表达式的提升(非函数声明)
先看例子:
console.log(fun); //undefined
var fun = function () {
console.log("我是一个函数");
}
上面的函数提升跟变量的提升一样,解析完如下:
var fun;
console.log(fun); //undefined
fun = function () {
console.log("我是一个函数");
}
3.函数声明的提升
先看例子:
console.log(fun); //整个函数打印出来
function fun() {
console.log("我是一个函数");
}
解析完如下:
function fun() {
console.log("我是一个函数");
}
console.log(fun); //整个函数打印出来
由此我们可以得出结论:JavaScript编译时,对于函数表达式的提升是跟普通变量提升一样的,仅提升声明但不赋值。对于函数声明的提升,则是整个提升,我们可以理解为声明+赋值的提升。
总结:
-
只有函数可以限定作用域
-
在函数内部允许访问外部的变量,但外部不能访问函数内部变量
-
如果当前作用域中有该变量,则不考虑外部作用域的同名变量
-
对于变量和函数表达式的提升,仅提升声明,不赋值
-
对于函数声明的提升,是声明+赋值
本农搞了个小练习给大家练练手,如果做对基本对于该文你也就懂了,先不要看最下面的解析结果
var a = "global";
function fun() {
a = "local";
return;
function a() {}
}
fun();
alert(a);
解析结果为:
var a;
function fun() {
function a() {}
a = "local";
return;
}
a = "global"
fun(); //function a(){} 可以理解为 var a = function(){},函数域内声明的变量不影响全局
alert(a); //输出为global