简单理解JS中的作用域和声明提升

282 阅读4分钟

前言

在讲作用域之前,我们先来聊聊js的编译和执行

JavaScript是弱类型编程语言,创建时不声明变量类型。如下所示

var a=5;          //在赋值前不知道变量类型 
var b='hello'     

强类型编程语言如Java、.net、Python、C++、C,创建时需要声明变量类型

int i=1;          //在赋值前通过'int'知道声明的变量是整型
char c='hello'

那Js在函数调用时怎么知道变量是什么类型呢,函数在执行后,Js会编译其作用域中的变量,并获取其数据类型。

在编译时,会通过找到某个域当中的有效标识符来获取数据类型。但是函数在没有调用的时候是不执行的,也就是不会进行编译。所以函数作用域并不是和全局作用域一起编译,而是先不编译,等要执行的时候再编译,编译永远只发生在执行的前一步.

而函数执行时,变量的查找会是从内到外的查找,即先查找执行函数的作用域,再去外层查找。

var a=1
function foo(){
    var a =2
        console.log(a);
}
foo()
// 打印 2
var a=1
function foo(){
    var a =2      
} 
console.log(a);
foo()
// 打印 1

不能从外到内的查找,即不能查找执行函数作用域内部的作用域变量。

var a = 1
 function foo(){
     var a = 2
     function bar(){
         console.log(a);
     }
 }
 foo()
 bar()
// undefined

上述代码报错原因是因为函数bar()未被定义,函数bar()是在函数foo() 的函数作用域内的,而bar()是在全局作用域中被调用执行的,又因为函数执行不能从外到内,所以上述代码执行不了

什么是作用域

作用域是可访问的变量的集合。它决定了代码区块中这些变量和其他资源的可见性(可访问性)。

作用域的类型

全局作用域、函数作用域、块级作用域、欺骗词法作用域

1.全局作用域

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

// 全局变量
var a = 1 
function foo(){
      console.log(a); 
} 
foo();
// 打印 1
foo();//调用函数执行编译

2.函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在此函数作用域内部。这些变量只能在函数内部访问,不能在函数以外去访问。

  var a = 1          // 全局编译
  function foo()     // 全局编译
  {
      console.log(a);// 函数编译   未调用时不编译
  }
  foo()              // 调用函数执行,编译器暂停,进行函数编译,函数编译完成后再重新执行全局编译
// 打印 1

声明提升

var 声明的变量存在声明的提升,提升到当前作用域的顶峰

// 表面上的代码
 console.log(a);
 var a = 1
// 实际上的代码 
 var a 
 console.log(a);
 a = 1
 
 // undefined

函数声明会整体提升

 //表面上的代码
 function foo(){
     console.log('123')
 }
 foo()
 
 //实际上的代码
 foo()
 function foo(){
     console.log('123')
 }

怎么避免声明提升呢

用let代替var定义可以避免变量提升

let虽然可以避免变量提升

 console.log(a)
 let a = 1
 // 报错

但是不能重复声明同一变量

 let a = 1
 let a = 2
 console.log(a)
 // 报错

var重复声明同一变量会覆盖

 var a = 1
 var a = 2
 console.log(a)
 // 打印 2

const 定义也不会发生声明提升 const虽然可以避免变量提升

 console.log(a)
 const a = 1
 // 报错

但是不能重复声明同一变量

 const a = 1
 const a = 2
 console.log(a)
 // 报错

而且const声明的变量值不能被修改

 const a = 1
 a = 'hello'
 // 报错

3.块级作用域

let + {} 会形成块级作用域
const + {} 也会形成块级作用域

 if(1){
     var a = 1
 }
 console.log(a);
 // 打印1

 if(1){
     let a = 1  // or const a = 1
 }
 console.log(a);
 //报错

 let a = 1
 if(true){
     console.log(a);     //暂时性死区
     let a = 2
 }
 //报错

4.欺骗词法作用域

eval( ) 让原本不属于这里的代码变成就是写在这里的

function foo(str){
    eval(str) //var b = 2
    var a= 1
    console.log(a,b);
}
foo('var b = 2');
// 打印 1,2

with 可以批量化修改对象的属性

 var obj = {
     a: 1,
     b: 2,
     c: 3,
 }
 // 使obj里面所有元素都加1
 // 直接定义
 obj.a = 2
 obj.b = 3
 obj.c = 4
 
 with(obj) {  // 批量修改obj内的值
     a = 2,
     b = 3,
     c = 4
 }

但当修改对象中不存在的属性时,该属性会泄漏到全局成为全局变量

function foo(obj){
    with(obj){
        a=2
    }
}
var o1={
    a:3
};
var o2={
    b:3
};
foo(o2);
console.log(a);// 此时a被泄漏到全局作用域上了
// 打印 2