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

相关推荐
zach1 小时前
前端落地实战:Next.js 项目 Nginx + PM2 生产部署全流程(附踩坑总结)
前端·pm2·next.js
overmind1 小时前
oeasy教h5前端001 修改的乐趣_直接修改在线网页
前端
只睡四小时1 小时前
JS 手写 D-pad 空间导航:电视端焦点引擎实战
android·开发语言·前端·javascript·ecmascript·hls·空间导航
恋猫de小郭1 小时前
Android 原生的 Compose A2UI 也来了,你还抱着 XML 养老吗?
android·前端·flutter
庄园特聘拆椅狂魔1 小时前
从连连看到粒子消散——前端小游戏与视觉特效的技术选型
前端
JudithHuang2 小时前
React 常用 Hooks
前端·react.js·前端框架
sycmancia3 小时前
Qml——Window元素使用
前端
每天都好困3 小时前
03 AR 投影与误差修正:把地图上的点贴到视频画面上
前端
每天都好困3 小时前
04 地图图层封装:像管理 Photoshop 图层一样管理地图元素
前端
去伪存真3 小时前
构建你的第一个 DevOps AI Agent:自动化捕获、分析并修复 CI 故障
前端·agent