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

前言

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

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

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

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

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

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

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

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

js 复制代码
var a=1
function foo(){
    var a =2
        console.log(a);
}
foo()
// 打印 2
js 复制代码
var a=1
function foo(){
    var a =2      
} 
console.log(a);
foo()
// 打印 1

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

js 复制代码
var a = 1
 function foo(){
     var a = 2
     function bar(){
         console.log(a);
     }
 }
 foo()
 bar()
// undefined

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

什么是作用域

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

作用域的类型

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

1.全局作用域

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

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

2.函数作用域

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

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

声明提升

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

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

函数声明会整体提升

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

怎么避免声明提升呢

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

let虽然可以避免变量提升

js 复制代码
 console.log(a)
 let a = 1
 // 报错

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

js 复制代码
 let a = 1
 let a = 2
 console.log(a)
 // 报错

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

js 复制代码
 var a = 1
 var a = 2
 console.log(a)
 // 打印 2

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

js 复制代码
 console.log(a)
 const a = 1
 // 报错

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

js 复制代码
 const a = 1
 const a = 2
 console.log(a)
 // 报错

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

js 复制代码
 const a = 1
 a = 'hello'
 // 报错

3.块级作用域

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

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

js 复制代码
 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( ) 让原本不属于这里的代码变成就是写在这里的

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

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

js 复制代码
 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
 }

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

js 复制代码
function foo(obj){
    with(obj){
        a=2
    }
}
var o1={
    a:3
};
var o2={
    b:3
};
foo(o2);
console.log(a);// 此时a被泄漏到全局作用域上了
// 打印 2
相关推荐
ss.li1 分钟前
TripGenie:畅游济南旅行规划助手:个人工作纪实(二十二)
javascript·人工智能·python
海的诗篇_40 分钟前
前端开发面试题总结-JavaScript篇(二)
开发语言·前端·javascript·typescript
琹箐1 小时前
ant-design4.xx实现数字输入框; 某些输入法数字需要连续输入两次才显示
前端·javascript·anti-design-vue
程序员-小李1 小时前
VuePress完美整合Toast消息提示
前端·javascript·vue.js
Dontla5 小时前
为什么React列表项需要key?(React key)(稳定的唯一标识key有助于React虚拟DOM优化重绘大型列表)
javascript·react.js·ecmascript
德育处主任Pro7 小时前
『React』Fragment的用法及简写形式
前端·javascript·react.js
CodeBlossom8 小时前
javaweb -html -CSS
前端·javascript·html
CodeCraft Studio8 小时前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
打小就很皮...8 小时前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
dancing99911 小时前
cocos3.X的oops框架oops-plugin-excel-to-json改进兼容多表单导出功能
前端·javascript·typescript·游戏程序