一、作用域
定义
作用域,即产生作用的特定区域。
javaScript的作用域,即js的变量或者函数产生作用的对应区域。也就是说区域内的可以访问区域外的变量和函数,但是区域外的则不能访问区域内的变量和函数。
分类
在ES5中,作用域分为两种:全局变量和局部变量。
(1)全局变量:所有地方均可访问(在函数外部声明的变量);
(2)局部变量:只能在函数内部访问(在函数内部用var关键字声明的变量以及函数的形参)。
在ES6中,新增了块级作用域。
(3)块级作用域:只能在距离最近的大括号的作用范围内访问(仅限于let声明的变量)。
应用
// 全局变量
var i = 0 ;
// 定义外部函数
function outer(){
// 访问全局变量
console.log(i); // (1)
function inner1(){
console.log(i); // (2)
}
// inner2中定义局部变量
function inner2(){
console.log(i); // (3)
var i = 1;
console.log(i); // (4)
}
inner1();
inner2();
console.log(i); // (5)
}
outer();//输出结果依次为:0 0 undefined 1 0
注释(1)处:outer()内未声明和定义局部变量i, 故js解析器会继续在函数外部寻找i变量,最终在全局变量中找到i,因此结果为0;
注释(2)处:inner1()和outer()内均未声明和定义局部变量i,因此结果为0。
总结1:js解析器查找变量,依次从作用域内部一层一层往外查找,找到后结束,找到最外部都没有找到,结果就为undefined(已声明,但未定义)。
注释(3)处:结果是undefined,为什么?
常见错误结果:0 inner2中变量i的声明与定义在此命令之后,找不到i变量,往函数外部查找,所以为全部变量i=0
常见错误结果:1 inner2中变量i已经声明定义,所以函数内部已经找到i了,所以为1;
注释(4)处:因为inner2()内部重新定义了局部变量i=1,故结果为1。
注释(5)处:outer()内未声明和定义局部变量i,因此结果为0。
总结2:js中存在变量提升(hoisting),但仅仅是变量提升,定义并未提升。
二、变量提升(hoisting)
定义
变量声明是在任意代码执行前处理的,在代码区中任意地方声明变量和在最开始(即变量对应的作用域范围的初始位置)的地方申明是一样的。也就是说,看起来一个变量可以在申明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的申明被自动移动到了函数或全局代码的最顶上。
说明:
声明变量在ES5中包括用以下关键字声明的对象:
(1)var
(2)function
声明变量在ES6中还包括用以下关键字声明的对象:
(3)function *
(4)let
(5)const
(6)class
应用
var temp = new Date();
f();
document.write("<br>"+"------------------分割线-------------------"+"<br><br>");
m();
document.write("<br>"+"---全局变量---temp="+temp+"<br>");
function f(){
document.write("f in! 0 temp="+temp+"<br>");
if(false){
var temp = "hello";
document.write("f in! 1 temp="+temp+"<br>");
}
document.write("f in! 2 temp="+temp+"<br>");
}
function m(){
document.write("m in! 0 temp="+temp+"<br>");
if(true){
var temp = "hello";
document.write("m in! 1 temp="+temp+"<br>");
}
document.write("m in! 2 temp="+temp+"<br>");
}
图一:
图二:
分析:
(1)图一在声明和定义之前就调用执行f()和m()函数,说明f()和m()的声明和定义分别进行了提升。
(2)f()和m()中分别定义了局部变量temp,由于在任何情况下,变量的声明都会提升到其作用域的顶部,因此f()和m()中的局部变量temp的声明会提升到各自函数的顶部,即图一等价于图二。
(3)由于改变的是每个函数中的局部变量temp的值,因此全局变量temp的值并不会被影响。
特殊情况的说明:
(1)变量重复声明
var a = 100;
alert(a);//100
var a = 200;
alert(a);//200
function fun2() {
alert(a);//underfind;
var a = 3;
alert(a);//3
}
fun2();
alert(typeof a);//unmber
var a = function() {}
alert(typeof a);//function
等价于以下的代码
var a;
var a ;
var a ;//多次声明会合并为一个对象
a=100;//赋值操作
alert(a);
a=200;
alert(a);
function fun2() {
var a;
alert(a);//underfind;
a = 3;//确定变量的数据类型,赋值
alert(a);//3
}
fun2();
alert(typeof a);//unmber
a=function(){};
alert(typeof a);//function
分析:
js的var变量只有全局作用域和函数作用域(局部作用域)两种,且声明会被提升,因此实际上a只会在最顶上开始的地方申明一次,故var a=200的声明会被忽略,仅用于赋值。
(2)函数的提升:
函数提升分为两种情况:
第一种:函数申明。如:function foo(){}
这种形式(在提升的时候,会被整个提升上去,包括函数定义的部分)
第二种:函数表达式。如:var foo=function(){}
这种形式(其实是var变量的声明定义,故只提升声明,不提升定义)
(3)实例分析:
案例一:
案例二:
分析:
(1)通常通过var声明一个变量,在没有赋值的情况下只做声明,没有定义变量的数据类型。
(2)通过function声明一个变量,在声明的同时会定义变量的数据类型为funtion。
(3)在同级作用域内,相同名称的变量会合并为一个变量,也就是后者会覆盖前者。
综上所述:
function foo(){};--------定义一个foo变量,数据类型为function;
var foo;-------------------定义一个foo变量,覆盖前者,然而没有定义变量类型。
因此注释(1)处foo的数据类型仍然是function。
当为foo变量重新赋值为500时,相当于重新定义了foo变量的类型
因此注释(2)处的foo数据类型为number。
三、总结
要彻底理解JS的作用域和变量提升(Hoisting),只要记住以下三点即可:
1、所有声明都会被提升到作用域的最顶端,变量声明提升仅仅是声明提升,定义不提升;函数声明会连带定义一起被提升;
2、同一个变量声明只进行一次,因此同一个变量的其他声明都会被忽略,从而变成赋值操作;
如:var i = 0 ; var i = 1 相当于 var i ; i = 0 ; i = 1;
3、函数声明的优先级优于变量声明,且函数声明会连带定义一起被提升。
来源:CSDN
作者:谁是听故事的人
链接:https://blog.csdn.net/chaopingyao/article/details/104499944