javascript中的作用域
在ES6出来之前,我们只能够使用 var
来进行变量的声明,ES6出来之后,我们更多的是通过使用let
和const
来进行变量的声明(使用let
声明的变量是可以进行修改的,而使用const
声明的变量需要在声明的时候进行赋值,并且之后,该值是不能够进行修改的)。
什么是作用域
任何编程语言都有作用域的概念,简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。js的作用域是靠函数来形成的,也就是说一个函数的变量在函数外不可以访问。在ES6 之前的,变量的作用域只有函数作用域和全局作用域,在ES6之后才引入的了块级作用域。全局变量拥有全局作用域,在javascript代码中的任何位置都是有定义的。然而函数内声明的变量只在函数内有定义,它们属于局部变量,作用域具有局限性。
举个例子:
var a = 'AAAAAA'
function fun () {
var b = 'BBBBBB';
console.log('inner function a:', a);
console.log('inner function b:', b);
function innerFunc() {
var c = 'CCCCCC'
console.log('inner innerFunc a:', a)
console.log('inner innerFunc b:', b)
console.log('inner innerFunc c:', c)
}
innerFunc()
}
fun();
console.log('outer a:', a)
在上面的代码中,变量a
在全局范围内是有效的,所谓的全局范围就是指在程序的任何地方都是能够使用的。即b
只能在函数fun和innerFunc中使用,即变量b
只有效于当前的变量声明的函数及其子函数中。因此,我们很轻松就能知道上面的代码输出的结果就是:
全局作用域
在上面也说了,全局作用域就是:他任何位置使用var声明的的变量,函数除外,那么这个变量就是全局变量,全局变量可以在页面的任何位置使用。就比如说上面这个例子中的变量a
。在网页端,如果页面不关闭,该变量就会一直存在着。也同样会占用着内存,不会进行释放。
关于全局作用域,其实就是说我们在使用var
进行声明的时候,其实是给window
这个对象添加了一个成员a
,我们可以在页面中的任何地方通过window.a
来进行使用这个值,这个变量会跟随window
这个变量一直存在着,直到window
对象销毁,该变量也会随着销毁。
函数作用域
有关函数作用域,上面也说到了,就是在函数内部进行声明的,仅仅可只能在函数内部进行使用的变量,如果在别的地方进行使用,则会报错。我们先看一个例子:
function fun () {
var b = 'BBBBBB';
console.log('inner function value:', b);
}
fun();
console.log('outer function value:', b);
我们在浏览器中运行上面的代码,能够看到输出的结果为:
给出的报错内容是变量b
没有进行定义(此定义非undefined)。从上面的实验结果来看,这个变量b
是定义在函数fun内部,如果在函数内部,则可以随便使用。如果跳出来则不能够进行使用。
在javascript中not defined 和undefined是不同的两个概念。
undefined是JavaScript语言中定义的五个原始类中的一个,换句话说,undefined并不是程序报错,而是程序允许的一个值。
not defined是JavaScript在运行我们的JavaScript代码时,遇到没有声明就用来console或者运算的变量时报出来的错误。
块级作用域
自es6
以来,引入了块级作用域。所谓的块级作用域也就是所在一个代码块内(即一对花括号内)能够使用,而跳出这个代码块将不能够进行使用(但是在js中由于要兼容老的作用域的规则,因此,使用var
定义的变量在块级之外可能够使用,而使用let
定义的变量在块级之外就不能使用)。就比如说下面这段代码:
{
var _varNum = 123;
let _letNum = 456;
}
console.log('_varNum:', _varNum);
console.log('_letNum:', _letNum);
其输出的结果如下图:
我们看到了在控制台中输出的结果如上图所示,仅仅输出的_varNum
的值,而在输出_letNum
的值的时候却报错了。报错的结果根上面我们在函数外使用在函数内容定义的变量是一样的。
我们再看一个例子:
var a = 'a';
function fun () {
var b = 'b';
if (true) {
var c = 'c';
let d = 'd';
}
for (var e = 0; e < 0; e++ ) {
}
for (let f = 0; f < 0; f++) {
}
console.log('a:', a); // 输出 a
console.log('b:', b); // 输出 b
console.log('c:', c); // 输出 c
console.log('d:', d); // 报错
console.log('e:', e); // 输出 0
console.log('f:', f); // 报错
}
fun();
在上面的例子中我们看到的输出的结果。可能会有点疑问,为什么会输出变量c
,又为什么不会输出f
呢。当然,对于为什么会输出变量c
,这个很好解释,我们定义通过var
定义的变量c
,虽说这个变量c
是在if
后的一对儿花括号中,但是但是就像我们上面说的一样js中的var
是不具备块级作用域的,因此,这个变量c
的作用域是在函数fun
中的。当然对于为什么输出变量f
的时候报错,是因为定义变量f
是在for
中进行定义的,因此这个变量f
仅仅只作用于这个for
循环中。
变量提升
首先来确定一下概念相关的内容,什么是变量提升。对于个一个变量,我们在别的语言中(C, C++, C#, java)中,如果想要使用一个变量,必须先声明,然后才能够使用,但是在js中,我们可以先使用,然后再在与该变量相同的作用域中进行声明该变量。这就是变量的提升。举个例子:
console.log(a);
var a = 123;
console.log(a);
在上面的这段代码中,我们先是输出了变量a
,然后在后面进行声明了变量,该代码执行并为报错,反而输出的内容如下:
undefined
123
也就是说,第一次使用的变量是声明过的,其实上述代码在浏览器中的会转变成如下内容:
var a
console.log(a);
a = 123;
console.log(a);
这样是不是就相对来说比较容易理解了。
但是对于ES6 中的let
和const
来说,是不存在变量提升的概念的,就比如说上面的代码中,我们将定义变量a
的关键字var
换成let
或者是const
我们来看一下运行后的结果。
console.log(a);
let a = 'AAAAAA';
console.log(a);
上面说了变量的提升,下面说一下函数提升。有了之前的变量提升的介绍,对于函数提升应该比较容易理解,但是函数提升还是有区别的:
console.log(func1);
func1();
console.log(func2);
func2();
function func1 () {
console.log('使用function定义的函数:func1');
};
var func2 = function () {
console.log('使用var定义的函数:func1');
}
上面的代码执行的结果如下:
对于上面的输出func1
和调用func1()
会有输出,但是输出func2
为undefined
和调用func2
报错我们可以这么去理解,其实上面的代码在浏览器中解析的时候,是转换成了如下的内容:
// 因为函数提升,所以会将func1提升到前面
function func1 () {
console.log('使用function定义的函数:func1');
};
// 由于变量的提升导致的会将变量func2的声明提升到上面
var func2
console.log(func1);
func1();
console.log(func2);
func2();
func2 = function () {
console.log('使用var定义的函数:func1');
}
好了,上面就是我对作用域的一点小小的理解。