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

相关推荐
汪子熙22 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ31 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试