您的位置:首页 > Web前端 > JavaScript

关于Javascript 的作用域

2015-11-01 14:45 441 查看
Javascript是一门神奇的语言,这句话向来没错。与其它的高级语言不同,无论是从变量定义上还是类继承上等,Javascript都有自己的一套独立的风格。这一次着重理解Javascript里面的作用域。

Javascript的作用域分三种:全局作用域,函数作用域,闭包,还有一个概念,叫做作用域链,那么,分别是什么意思呢?我们一个一个来慢慢解释。

全局作用域

全局作用域,很普通的一个概念,所有语言大同小异。

//a位于全局作用域中
var a = 0;

function func1 () {}
function func2 () {}


很明显,变量a就位于全局作用域,并不属于任何一个函数所私有,大家都可以访问。

函数作用域

Javascript是一门解释性语言,它的作用域基于词法作用域的,所以,不存在块级作用域这一说法。在Javascript里面,使用函数作用域来代替所谓的块级作用域。

我们先来解释一下什么是词法作用域

《Javascript权威教程》有一句话是这样说的:“Javascript的函数在定义它们的作用域里运行,而不是在执行它们的作用域里运行”。什么意思呢?我们来看一段代码。

var a = 'a';

getValue();

function getValue () {
console.log(a);
var a = 'b';
console.log(a);
}


试想一下,这段代码可以运行吗?运行的结果又是什么?

很明显,这一段代码肯定可以运行,输出的结果分别是
undefined
a
,这是为什么呢?

首先,我们来解释第一个现象——关于这段代码的运行问题。

我们都知道,函数必须在实现之后调用,或者说,在调用之前,必须得先声明了,但是和C语言或者其它的类C等高级语言不同,Javascript里可以先调用函数,再对函数进行功能实现,并不用声明什么的。这时候,我们就要想到那句话了:Javascript的函数在定义它们的作用域里运行,而不是在执行它们的作用域里运行

通俗一点就是说,Javascript里的函数在调用的时候,根本不管这个函数在哪里声明,是前还是后,只要这个函数定义在这个全局空间里,那么,在调用函数的时候,就能找到这个函数的声明。这就是词法作用域

然后,我们来看第二个现象——关于运行的结果。

初学Javascript的人,看到这段代码,一般都会回答运行结果为
a
b
,然而,事实总是残酷的,所谓too young,too simple。

我们先来解释运行结果的第二个
b
。很明显,在程序输出变量b的值之前有一个赋值语句
var a = 'b'
,所以,可以直接输出a的结果就是
b
。这是我们都能够理解的。

然后,我们再来看第一个结果
undefined
,这是为什么呢?不是已经定义了变量
a
的值了吗?这时候,就要涉及到另外一个概念了——变量提升(Hoisting)

所谓变量提升(Hoisting),意思就是,无论变量定义在哪里,只要你定义了这个变量,这个变量就会被提升到最顶部。所以,这也就不难理解为什么可以先调用函数再对函数进行声明了。

但是有另外一种情况,如果函数的声明是采用的
var func = function () {}
格式的声明的话,就必须在函数调用前声明函数。

那。。。刚才那个,按照变量提升的说法,结果不应该是两个
b
吗?

别急,我还没说完呢。变量提升,提升的只是变量的定义而已。它的赋值语句并不会随着变量提升到顶部。所以,刚才的代码可以写成这样:

var a = 'a';

getValue();

function getValue () {
var a;
console.log(a);
a = 'b';
console.log(a);
}


这样,就一清二楚了吧。第一个结果
undefined
,变量
a
只是定义了,还没赋值,当然会输出
undefined
。而后面又给
a
赋值为
'b'
,所以,当然会输出
'b'
啦。这就是变量提升(Hoisting)的作用。

OK,我们在这里多说了两个概念,分别是词法作用域变量提升(Hoisting)。现在,我们应该能够理解这两个概念了。所以,现在来理解Javascript的函数作用域

我们先看一段代码:

function myName () {
var me = 'Erichain';
console.log(me);
}
myName();
console.log(me);


运行这段代码,结果是输出
Erichain
和收到一个报错
ReferenceError: Can't find variable: me


我们来分析一下,其实这个道理很简单。在函数内部,定义了一个变量
me
,赋值为
'Erichain'
,然后,我们输出,很正确的一个流程。但是,当我们再到函数外部去输出这个变量的时候,却报错了。

Javascript的变量,定义在函数体内,那么,这个变量就只能在函数体内访问,就如同私有变量一样。一旦这个函数运行结束,这个变量也随之销毁。——这就是Javascript的函数作用域

我们的变量
me
是定义在函数体内部的,所以,
myName()
函数一旦运行完成,变量
me
随之销毁,所以,在外面输出的话,当然会报错了。

再看一段代码来理解函数作用域,并且,理解Javascript里是没有块级作用域的。

function testScope () {
var myName = 'Erichain';
for ( var i = 8; i < 10; i++ ) {
console.log(myName);
}
console.log(i);
}
testScope();
console.log(i);
console.log(myName);


这段代码输出的结果分别是:两次
Erichain
10
和两个报错,错误都是找不到变量
i
myName


根据函数作用域,后面两个报错不难理解。我们着重看
10
这个结果。我们发现,在函数内部,其实没有定义
i
这个变量,只是在for循环里定义了。要是在C或者类C语言里,变量
i
在循环体结束之后就销毁了。但是,在Javascript里,不存在块级作用域,所以,在循环体内部定义的变量,就相当于在函数内部定义的变量,在函数内部依然可以访问。这也是Javascript和其他语言不同的一个地方。

闭包

Javascript里的最特别的功能之一,当然是它的闭包。什么是闭包呢?通俗一点说就是:在函数里面声明并且实现函数,即所谓“函数的嵌套”。看一段代码自然就明白了。

function closure () {
var newVal = 'Erichain';
function getNewVal () {
console.log(newVal);
}
getNewVal();
}
closure();


这段代码的运行结果将会输出
Erichain


我们把函数拆分为外函数
closure()
和内函数
getNewVal()
,同时,在外函数的内部,调用了内函数来运行。所以,调用
closure()
函数的同时也随即调用了
getNewVal()
。所以,能够输出结果。这就是一个很简单的闭包的实现。但是,这不是我要说的重点。本篇文章重点在作用域,所以,我们来分析一下闭包的作用域。

我们把上面的这段代码稍微修改一下。

function closure () {
var newVal = 'Erichain';
function getNewVal () {
var newName = 'Zain';
console.log(newVal);
console.log(newName);
}
getNewVal();
console.log(newName);
}
closure();


运行这段代码,我们会得到两个输出结果:
Erichain
Zain
,另外,还有一个报错:
ReferenceError: Can't find variable: newName


我们来解释一下这个现象。

在上文中,有讲到,函数内部定义的变量只能函数自身访问。同理,闭包是函数内部的函数,所以,闭包里面所定义的变量,也只有闭包内部能够访问。但是,闭包能够访问外部函数的变量。这就是Javascript的闭包的作用域。

作用域链

说完了Javascript的三种作用域,那么,接下来理解作用域链也就不是什么大问题了。

还是先看一段代码:

var myName = 'Erichain';
function getMyName () {
console.log(myName);
}
getMyName();


运行这段代码,输出的结果将是
Erichain


我们发现,我们在函数里并没有定义变量
myName
,但是,最终函数却没有报错,而是正常输出结果。而这个结果,恰好是在全局空间里定义的变量
myName
的值。这就要涉及到Javascript的作用域链了。

调用函数的时候,函数会先从函数内部找寻变量,如果找不到,那么就会一层一层的往上寻找,直到找到这个变量为止

getMyName()
函数里,我们虽然没有定义变量
myName
,但是,函数在全局空间里找到了这个变量,所以,可以使用这个变量输出其值。

再看一段代码来理解。

var myName = 'Erichain';
function getMyName () {
var myName = 'Zain';
console.log(myName);
}
getMyName();


那么,这段代码又会输出怎么样的结果呢?答案是
Zain


用上面的话来解释:函数在其内部就找到了变量
myName
,所以,他就不需要再往上去寻找变量。所以这里会输出
Zain


这也就是Javascript作用域链的工作机制。

最后,加上一个小tip。关于Javascript对变量的内存分配和回收的问题

第一,Javascript的局部变量,也就是函数内部定义的变量,在函数调用完成之后会自动销毁,不需要人工在进行手动销毁;

第二,Javascript的全局变量和闭包里的变量在定义之后,如果不对其进行销毁的话,会一直存在内存空间,污染全局空间,必要的时候,需要对其进行手动销毁,怎么做呢?

var a = 'Erichain';
console.log(a);
a = null;
console.log(a);


为变量赋值为
null
即可销毁这个变量。

以上为本人所总结的有关于Javascript的作用域的知识,每天学习一点,每天进步一点。如果有什么疑问或者问题,希望大家能与我交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息