js作用域以及变量提升

一、作用域

定义

作用域,即产生作用的特定区域。

javaScript的作用域,即js的变量或者函数产生作用的对应区域。也就是说区域内的可以访问区域外的变量和函数,但是区域外的则不能访问区域内的变量和函数。

分类

在ES5中,作用域分为两种:全局变量和局部变量。

(1)全局变量:所有地方均可访问(在函数外部声明的变量);

(2)局部变量:只能在函数内部访问(在函数内部用var关键字声明的变量以及函数的形参)。

在ES6中,新增了块级作用域。

(3)块级作用域:只能在距离最近的大括号的作用范围内访问(仅限于let声明的变量)。

异同点

{
	document.write("----chaopy----in---00-----b="+b+"<br><br>");//a&c is not defined on line 3
	let a = 5;
    var b = 3;
    const c = 4;
    document.write("----chaopy----in---01-----a="+a+"-----b="+b+"-----c="+c+"<br><br>");
	{
    	let a = 6;
        var b = 8;
        const c = 2;
        //let b = 5;// Identifier 'b' has already been declared on line 8
        document.write("--------chaopy----in---10-----a="+a+"-----b="+b+"-----c="+c+"<br><br>");
        //var a = 9;// Identifier 'a' has already been declared on line 11
        //let a = 10;//Identifier 'a' has already been declared on line 15
        //const c = 1;// Identifier 'c' has already been declared on line 15
        var b = 10;
        document.write("--------chaopy----in---11-----a="+a+"-----b="+b+"-----c="+c+"<br><br>");
        for(let i = 0; i<5;i++){
        	document.write("------------chaopy----in---12-----i="+i+"<br>");
        }
        document.write("<br>");//访问不了i,会报i is not defined
        for(var j = 0; j<5;j++){
        	document.write("------------chaopy----in---13-----j="+j+"<br>");
        }
        document.write("<br>--------chaopy----in---14-----j="+j+"<br><br>");
	}
    document.write("----chaopy----in---02-----a="+a+"-----b="+b+"-----c="+c+"<br><br>");
}
//const d;//Uncaught SyntaxError: Missing initializer in const declaration on line 29
document.write("chaopy----out---00-----b="+b+"<br><br>");//访问不了a&c,会报a&c is not defined

const arr = [1,2,3];
document.write("chaopy----out---01-----arr="+arr+"<br><br>");//访问不了a&c,会报a&c is not defined
//arr = [1,2,3,4,5];//Uncaught TypeError: Assignment to constant variable. on line 32
arr[3] = 4;
document.write("chaopy----out---02-----arr="+arr+"<br><br>");//访问不了a&c,会报a&c is not defined
arr[2] = 6;
document.write("chaopy----out---03-----arr="+arr+"<br>");//访问不了a&c,会报a&c is not defined

结果如下:

分析:

(1)用let在块中重新声明的变量不会影响块外声明的变量(参考line13和line28的打印结果);

(2)用var在块内声明的变量会直接影响块外的同名变量(参考line13和line28的打印结果);

(3)在循环中,let和var声明的变量的作用域不同,其中let是块级作用域,var是函数作用域(参考line22和line26的打印结果);

(4)作为html中的全局变量,var定义的全局变量属于window对象,let定义的全局变量不属于window对象;

作为全局变量,var和let声明变量的作用相似;作为函数内的局部变量,var和let的作用相似。

(5)在同一个作用域(块)中:

         a、通过let重新声明一个var变量是不允许的(参考line12的注释);

         b、通过var重新声明一个let变量是不允许的(参考line15的注释);

         c、为已有的const/let变量重新声明或赋值是不允许的(参考line14、15的注释);

         d、允许在程序的任何位置使用var重新声明javaScript变量(参考line18的打印结果);

(6)通过var声明的全局变量会提升到顶端,在声明之前就可以访问和使用(参考line3的打印结果);

  通过let定义的全局变量不会被提升到顶端,在声明之前使用会报错,变量从块的开头资质处于“暂时死区”直到声明为止;

  const定义的变量与let类似,但是不能重新赋值,且在声明时必须赋值(参考line30的注释);

(7)const没有定义常量,只是定义了对值的常量引用。用const声明的常量对象,可以更改常量对象的属性,但无法为常量对象重新赋值。常量数组可以添加新数组元素或者为已有数组元素重新赋值,但不能为常量数组重新赋值(参考line35、37、39的注释和打印)。

(8)在另外一个作用域中,重新声明const是允许的。

应用

// 全局变量
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、函数声明的优先级优于变量声明,且函数声明会连带定义一起被提升。

 

发布了33 篇原创文章 · 获赞 35 · 访问量 813

猜你喜欢

转载自blog.csdn.net/chaopingyao/article/details/104499944
今日推荐