JavaScript作用域、作用域链及案例分析

什么是作用域?

作用域是一套规则,用于确定在何处以及如何查询变量(标识符)。

作用域链

什么是作用域链

当一个函数或代码块嵌套在另一个函数或代码块中,就形成了作用域链。这么说可能不够直观,参考《你不知道的JavaScript》的插图如下
整个建筑表示嵌套的作用域链,第一层表示当前作用域,顶层表示全局作用域。

如何在作用域链中查找变量

要准确查找作用域中的变量首先必须明白的三点:

  • 函数声明与变量声明都会提升,但是函数声明会首先被提升,然后才是变量声明提升。
  • 作用域查找都是从内部作用域开始 ,然后往上级作用域 查找,直到遇到第一个匹配的标识符为止 ,如果没有遇到标识符则会一直查找到最外层的全局作用域。
  • 无论函数在哪里被调用,以及如何被调用,函数的词法作用域都只是由函数声明所处的位置决定。而不是函数调用是的位置所决定。

当然也需要掌握JavaScript提升的相关知识点JavaScript之var变量声明提升及函数声明提升,下面通过示例剖析。

示例剖析

示例1
javascript 复制代码
var x = 10
fn() 
function foo() {
  console.log(x)
}
function fn(){
  var x = 30
  foo()
}
// 等价于👇🏻的代码
/*
function foo() {
  console.log(x)
}
function fn(){
  var x = 30
  foo()
}
var x;
x = 10;
fn() 
*/

控制台打印的值为:10

代码剖析:

首先声明的函数fn、foo会提升到头部即fn、foo函数声明前置 。查找函数foo内部的标识符x,函数foo内部的作用域并没有匹配到该标识符,往其上层作用域(全局作用域)匹配到一个对应的标识符即是全局变量 var x = 10,则执行fn()输出在控制台的结果值为10。

示例2
javascript 复制代码
var a = 1
function fn1(){
  // var a = 2;语句声明并赋值的变量a会提升到函数fn1内部的顶层
  function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  // 被fn1执行过后,a会被赋值为2
  var a = 2
  return fn3
}

var fn = fn1()
fn() //输出多少

控制台打印的值为:2

大致划分出四个作用域:
1️⃣ 全局作用域包含 变量`a`、函数`fn1`、函数`fn`
2️⃣ 函数`fn1`所创造的的作用域,包含`fn2`、`fn3`、变量`a`
3️⃣ 函数fn3内部作用域,包含一个变量`a`

代码剖析:

执行fn函数输出的值,即是执行函数fn2内部语句console.log(a)在控制台打印的值。函数fn2变量a对应的作用域,首先从函数fn2()内部作用域找是否能匹配的标识符。当前fn2内部无法匹配,则往上层作用域(fn1()所创建的作用域)查找变量a。fn1内部var a = 2;变量a提升到函数fn1作用域的顶部 ,则fn2内部变量a匹配的正是fn1作用域声明的变量a。在var fn = fn1() 函数fn1被调用了,return fn3前面的语句a = 2给变量a赋值。最终console.log(a)首先打印变量a,且被赋值为2,所以控制台打印的值为2。

示例3
javascript 复制代码
var a = 1
function fn1(){
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2
  return fn3
}
function fn2(){
  console.log(a)
}
var fn = fn1()
fn() //输出多少

控制台打印的值为:1

代码剖析:
查找作用域中的变量要记住记住上面三条规则,其中一条就是"函数运行在它们被定义的作用域,而非被执行的作用域"。函数 _fn2_在函数 _fn3_被调用,但是函数 _fn2_是被声明在全局作用域中的。从函数 _fn2()_内部查找匹配变量 _a__,内部没有声明的该变量,则_往上层查找即是全局变量 a (a = 1)。 如果注释掉上例中全局作用域中的var a = 1,则fn()控制台会报错"Uncaught ReferenceError: a is not defined"

示例4
javascript 复制代码
var a = 1
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
    // fn2() 被调用之后,才会给a赋值为4
    var a = 4 // 该语句放置在fn2()前面,则打印的结果值是4
  }
  var a = 2
  return fn3
}
var fn = fn1()
fn() //输出多少

控制台打印的值为:undefined

代码剖析:

返回fn1内部声明的fn3函数,执行fn函数则会执行函数fn3。fn2函数体内部变量a匹配的作用域为它的上级作用域fn3函数所创建的作用域。

javascript 复制代码
 function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
    var a = 4
  }

fn2()的的输出结果是undefined,这是由于在fn3()所创建的作用域变量a 提升到函数内顶部,但是变量a赋值 是在调用 fn2()之后,所以console.log(a)中的a,并没有被赋值,既输出结果是undefined。 如果把 var a = 4语句放在调用fn2()之前,则输出的结果是4

相关推荐
白山编程大哥2 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt2 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn3 小时前
bundle优化
前端
slongzhang_4 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
北冥有鱼4 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚4 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
用户37203749260184 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能4 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师4 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端
alsmile4 小时前
AI友好的可视化图形引擎自我革命:Meta2d.js 2.0 历时一年全新重写,性能与多人协作全面升级
前端·开源·数据可视化