javascript作用域以及变量和函数的提升

198 阅读6分钟

javascript作用域

1 全局作用域

  • 直接编写在script标签中的JS代码,都在全局作用域
  • 全局作用域在页面打开时(javascript解释器启动时)创建,页面关闭时销毁
  • 在全局作用域中,有一个全局对象Window,它代表的是一个浏览器的窗口,由浏览器创建,可以直接使用。
  • 在客户端javascript中,在其表示的浏览器窗口中的所有JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个window属性引用其自身。
  • 在全局作用域中,创建的变量都会作为window对象的属性存,创建的函数都会作为window对象的方法保存

2 函数作用域

  • 调用函数时创建函数作用域,函数执行完毕销毁
  • 每调用一次函数就会创建一个新的函数作用域,他们之间相互独立
  • 在函数作用域中操作一个变量时,它会先在自身作用域中寻找,若无,则向上一级作用域寻找(例如上一级嵌套的函数),直至全局作用域
  1. 在函数作用域中访问同名全局变量,可以使用window全局对象
var a=10;
function fun()
{
 var a="局部变量a";
 function fun2()
  {
      console.log('a='+a)
      //局部变量a
      console.log("a="+window.a);
      //全局变量a
  }
  fun2();
}
  • 执行fun(); 输出结果:a=局部变量a; a=10

2.在函数作用域中也有声明提前的特性

        使用var关键字声明的变量,会在当前函数作用域中所有代码执行之前被声明,即变量在声明它们的函数体以及这个函数体嵌套的任意函数体都是有定义的

函数声明也会在函数中所有代码执行之前声明

var scope="global";
function fun()
{
    console.log(scope); //输出"undefined",而不是"global"
    var scope="local";
    console.log(scope) //输出“local”
}

你可能会误以为第一句输出是"global",其实是:"undefined".根据语法,它在自身作用域中找到了var的声明提前,所以不会选择上一级全局变量scope,实际执行步骤如下:

var scope="global";
function fun()
{
    var scope;//scope=undefined
    console.log(scope); //输出"undefined"
    scope="local";
    console.log(scope) //输出“local”
}

在函数中,不使用var声明的变量都会成为全局变量,必须使用!!

3.定义形参就相当于在函数作用域中声明变量

var e=23;
function fun(e){
    alert(e)      // 相当于var e;alert (e);
}
fun();
//输出undefined  

变量提升和函数提升

提升:

简单说就是在js代码执行前引擎会先进行预编译,预编译期间会将变量声明与函数声明提升至其对应作用域的最顶端

变量提升:

console.log(a);
var a = "a";
var foo = () => {
    console.log(a);
    var a = "a1";
}
foo();
//打印结果:
//undefined
//undefined

变量声明的提升是以变量所处的第一层词法作用域为“单位”的,即全局作用域中声明的变量会提升至全局最顶层,函数内声明的变量只会提升至该函数作用域最顶层。那么开始的一段代码经过预编译则变为:

var a;//全局作用域
console.log(a); // undefined
a = "a";
var foo = () => {//函数作用域
    var a; // 全局变量会被局部作用域中的同名变量覆盖
    console.log(a); // undefined
    a = "a1";
}
foo();

ES6新增了letconst关键字,使得js也有了“块”级作用域,而且使用letconst声明的变量和函数是不存在提升现象的,比较有利于养成良好的编程习惯。

函数提升:

console.log(foo1); // [Function: foo1]
foo1(); // foo1
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
function foo1 () {
    console.log("foo1");
};
var foo2 = function () {
    console.log("foo2");
};

即函数提升只会提升函数声明(函数提升是可以直接在函数声明之前调用该函数,并能成功执行它),而不会提升函数表达式(函数表达式就可以看作成变量提升)

  • 对于函数声明
fun();//我是一个函数声明
function fun(){     
    console.log('我是一个函数声明')
}
fun()//我是一个函数声明

由于 声明提升 的原因,函数声明可以在其作用域的任何地方被调用。

  • 对于函数表达式
fun     //fun is not defined 直接跳出
fun()   //fun is not defined 直接跳出
foo     //undefined    var foo 变量提升
foo()   //foo is not a function 直接跳出var foo = function fun(){
    console.log('我是一个函数表达式')
}
​
fun    //fun is not defined 直接跳出
fun()  //fun is not defined 直接跳出 不可以直接调用
console.log(foo)    //f fun(){console.log('我是一个函数表达式')}
foo()  //我是一个函数表达式var f = function(){
   console.log('我没有函数名,匿名函数')
}
f()   //我没有函数名,匿名函数

函数提升举例:

var a = 1;
function foo() {
    a = 10;
    console.log(a);
    return;
    function a() {};
}
foo();
console.log(a);

输出结果:

10 1

上面的代码块经过预编译后可以看做如下形式(只分析foo方法内部情况):

var a = 1; // 定义一个全局变量 a
function foo() {
    // 首先提升函数声明function a () {}到函数作用域顶端, 然后function a () {}等同于 var a =  function() {};最终形式如下
    var a = function () {}; // 定义局部变量 a 并赋值。
    a = 10; // 修改局部变量 a 的值,并不会影响全局变量 a
    console.log(a); // 打印局部变量 a 的值:10
    return;
}
foo();
console.log(a); // 打印全局变量 a 的值:1

进入执行上下文时,首先会处理函数声明,其次会处理变量声明

函数提升优先级高于变量提升

console.log(foo);
function foo(){
    console.log("foo");
}
​
var foo = 1;

会打印出函数[function:foo],而不是正常思维的foo变量提升的效果 undefined

这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

下面做一个小改变:

var foo = 1;
console.log(foo);
function foo(){
    console.log("foo");
};

这次打印结果就是“1”;

因为此时var foo = 1;先通过表达式进行赋值操作,当然这是已经执行了赋值操作,函数再怎么提升优先级也达不到执行的操作吧,所以打印的是“1”。

小练:

  1. var a=123;             
    function fun(){          
        alert(a);               
        var a=456;              
    }                           
    fun();                     
    alert(a);     
    // undefined;123
    

    等于

    var a
    a=123;             
    function fun(){   
        var a;
        alert(a);               
        a=456;              
    }                           
    fun();                     
    alert(a); 
    
  2. var a=123;             
    function fun(){          
        alert(a);               
        a=456;  //在函数中,不使用var声明的变量都会成为全局变量,必须使用!!      
    }                           
    fun();//执行fun()之后 全局变量a 变为456                     
    alert(a);    
    //undefined;456
    
  3. var a=123;            
    function fun(a){        
         alert(a);               
         a=456;                  
    }                       
    fun();                  
    alert(a);               
    // undefined;123 形参=var a;alert(a);a=456 即var a=456;alert(a);
    

参考文章: js变量提升函数提升