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

代码剖析:

首先声明的函数fnfoo会提升到头部即fnfoo函数声明前置 。查找函数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()所创建的作用域)查找变量afn1内部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函数则会执行函数fn3fn2函数体内部变量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

相关推荐
m0_748247551 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
m0_748255022 小时前
前端常用算法集合
前端·算法
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web130933203982 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
NiNg_1_2342 小时前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
如若1233 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~4 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语4 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport4 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg4 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全