
首先,关于作用域和闭包是一个很深的问题,困扰着很多初学者,包括我在内,也在初学阶段困扰不堪,此文依据《JavaScript权威指南(第六版)》进行对作用域的浅谈,分上下两部分,上部分主要说变量优先级和变量提升以及声明提前,下部分说作用域链和闭包问题。提前说明,本文面向初学者,且不涉及es6新特性,这也是个人建议,先把es5基础打好,再慢慢的去进阶。废话不多说,正文开始。
变量作用域
一个变量的作用域是指这个变量能被调用和使用的区域,在函数内声明的变量是局部变量,作用域在函数内部,函数参数也是局部变量。全局变量则是在所有函数外被声明的变量。
函数内变量优先级
函数内的变量优先级高于全局变量,如果在函数内声明的局部变量和全局变量重名,那么在函数局部范围内,局部变量的赋值和计算将顶替全局变量原有的值,但是在函数之外继续引用全局变量,全局变量原来的值不变。

函数内变量污染
当函数内声明变量不是采用var const let 等方式声明,而是直接创建变量时,函数内的变量将会提升为全局变量,如果存在同名的全局变量,将会污染全局变量。
这种方法可以用于函数内的变量提升。
var scope = 'global'; //创建一个全局变量
var myscope = ''
function checkscope2() {
scope= 'local'; // 全局变量scope的值被替换成local
var hand = scope +'hello' // 声明局部变量赋值为 local hello
myscope= hand; // 创建了一个全局变量,并把局部变量提升。
return [scope,myscope];
}
console.log(checkscope2()); // 返回函数结果。
console.log(scope); // 被局部变量污染的全局变量 scope
console.log(myscope) // 等于局部变量hand
$>
[ 'local', 'localhello' ]
local
localhello
可以看到,在函数内创建变量时,不用var 声明 就会变成全局函数,而利用这种方法可以做到变量的提升。
函数内变量的嵌套
因为JavaScript在编程时经常要对函数一层层嵌套,导致了变量作用域含混不清,这里特此记一下,一个简单的实例:
var scope = ' global scopr'; // 全局变量 现在值时global scope
function checkscope(){ // 第一层函数
var scope = 'local scope'; //第二层函数,创建了一个函数内的全局变量
function nested(){
var scope = 'nested scope'; // 第三层函数 创建了一个第三层函数内的局部变量
return scope; 这里放回 socope 的结果,是netsed scope
}
return nested(); 返回netstd的值 也是 netsed scope
}
console.log(checkscope())// 所以这里的值是 nested scope
同样 如果在函数嵌套中定义了全局变量,也是会影响原有的全局变量的值。
函数作用域的声明提前
声明提前,当函数内要调用一个全局变量的变量名,并打算赋值时,由于作用域的限制,函数内的局部变量其实类似于重新创建了一个变量,仅仅时和函数外部的全局变量重名,所有的操作不影响函数外的全局变量的值(那些不采用var const let等声明方式直接进行进行创建变量并赋值的除外)而基于此原理,JavaScript有一个声明提前的特性,即
- 。当试图在函数内直接调用一个全局变量的时候,如果函数内有全局变量和局部变量重名,就会发生声明提前。
- JavaScript首先会自动创建一个为空的局部变量而非调用全局变量的值,因为是空值,所以会发生undefined
看一段代码:
var scope ="global";
function f(){
var test = scope +"test"; // 声明一个变量,变量时test 他的值时 scope+'test'
var scope = 'local';
console.log(scope,test);
}
f();
$> local undefinedtest
可以看到以上代码中,有一个全局函数 scope 他的值是global,而在第三行代码中,在函数内创建了一个变量test ,他的值理应是globaltest。但是实际上他的结果是undefinedtest。这里就是声明提前,以上代码在执行时候其实是这样的。
var scope = "global";
function f(){
var scope;
var test = scope+"test";
var scope = 'local';
console.log(scope,test);
}
f();
$> local undefinedtest
和上边说的一样 首先会创建一个局部变量,这就是代码执行时候的声明提前,如果换一种写法,把第五行和第四行互换一下。
var scope ="global";
function f(){
var scope = 'local';
var test = scope +"test";
var scope = 'local';
console.log(scope,test);
}
f();
$> local localtest
就会变成这样子,第四行 test变量值里的scope 已经变成了被赋值的局部函数了。
如果要实现在函数内调用全局函数的值,那就在函数内不要使用同名的全局变量,声明提前会覆盖掉全局变量的值。可以用以下方法接受全局变量的参数值。
var scope = "global";
function f(){
var handle = scope
var test = handle+"test";
console.log(handle,test);
}
f();
$>global globaltest
这里就完全接受全局函数的值,如果你试图向下面那样做。
var scope = "global";
function f(){
var handle = scope
var test = handle+"test";
var scope;
scope ="local"
console.log(handle,test);
}
f();
$>undefined undefinedtest
毫无意外的undefined。所以 在函数内如果要接受全局变量的值,不要再函数体内创建和全局变量重名的变量。
总结:
- 函数内局部变量优先级高,如果和全局变量重名,将会替换全局变量的值。
- 再函数内不用声明语句声明变量,变量将会从局部变量提升为全局变量。
- 函数内局部变量具有声明提前特性,如果要接收全局变量的值,局部变量不要和全局变量重名。
参考文献:
[1] 弗拉纳根(David Flanagan,美).JavaScript权威指南(第六版) 北京:机械工业出版社 , 2012.3