c++ 花括号作用域_JavaScript 作用域

本文深入探讨了JavaScript和C++中的作用域链、闭包和块级作用域的概念。通过实例解析了如何在不同作用域中访问变量,强调了函数执行环境与变量对象的关系,以及闭包在保持变量状态方面的重要性。
摘要由CSDN通过智能技术生成

cb071851a20a565f809cc10f01891a0d.png

作用域链

举个栗子

作用域和变量有关,先看简单的代码:

var a = 'a'

function foo () {
  var b = 'b'
  console.log(a)
}

foo() // 'a'

console.log(b) // Uncaught ReferenceError: b is not defined

从上面代码的执行结果可以看出,foo 函数取到了它外部的变量 a, 而最外层的 console.log(b) 操作并没能取得 foo 函数里面的变量 b.

上面代码中,最外层的变量对象保存了变量 a 以及函数 foo,代码执行所需的变量和函数都从变量对象里面取得。 变量对象和当前的执行环境相关联,当前执行环境的代码执行结束后,该环境被丢弃,其中保存的变量和函数也随之被丢弃。

每个函数都拥有自己的执行环境,当函数执行时,执行环境被拾起,函数执行完毕,执行环境被丢弃。

上面代码最外层的 console.log(b) 试图取一个变量 b, 但是当前的变量对象,也就是最外层的变量对象,并没有保存变量 b, 所以代码报错。

foo 为什么能够取到外部的变量 a 呢?

分析一下 foo 取得 a 的过程。

foo 执行的时候, JavaScript 创建了一个活动对象。函数的活动对象是在函数执行时产生的,包含了函数的形参和函数内声明的变量等。 foo 声明的 b 在就 foo 的活动对象里面。在函数的执行环境中,活动对象被当做变量对象。

可是,foo 需要的是 a 啊,foo 自身的变量对象没有这个变量。

不要着急,在创建 foo 的时候,也创建了作用域链,里面包含了外一层的变量对象,保存在函数的 [[Scope]] 属性当中。

在调用 foo 的时候,创建 foo 的执行环境和活动对象,然后复制 [[Scope]] 属性构建作用域链,再把当前活动对象插队放到作用域链的最前面。这个时候,foo 自身的活动对象和外部的变量对象就在这个作用域里面排队。

foo 在作用域链的最前面,也就是自身的活动对象里面,找不到变量 b 的时候,就会去作用域链的下一个对象里面查找。

总结一下

作用域链依次保存了当前执行环境的变量对象,以及外层执行环境的变量对象。就像是变量对象在排队,越是内层的变量对象,越在前面。若无法在当前的变量对象找到一个变量,就会顺着作用域链,依次查找外部的变量对象。

闭包

闭包,看着这两个字,顾名思义也思不出什么来,不如先把它从脑中先抛开。

举个栗子

从文章的第一个例子,可以知道,在函数的外部是访问不了函数内部的变量的。

简单粗暴地去访问,当然访问不了,但也可以通过一系列操作,来访问函数内部变量。

看看简单的代码:

function bar () {
  var a = 0

  return function () {
    console.log(a++)
  }
}

var fn = bar()

fn() // 0
fn() // 1
fn() // 2

上面的 bar 定义变量 a0, 并函数返回一个函数,返回的函数打印变量 a, 然后对变量 a 进行递增操作。

执行 bar ,并将返回的函数赋值给 fn. 执行 fn, 从执行结果可以看出,这个全局变量的 fn 居然访问到了 bar 内的变量 a.

现在去采访一下 fn, 看看他是怎么做到的。

fn 的值是 bar 函数的返回值,也就是 bar 里面返回的匿名函数。

当匿名函数被创建的时候,和所有函数一样,也是将外部的变量对象保存到自身的 [[Scope]] 属性当中,匿名函数保存的,自然是 bar 的活动对象,里面包含了变量 b.

现在慢动作回放一下 var fn = bar() 这个操作:

  1. 初始化 bar 的活动对象 ,将 a 保存到活动对象
  2. 执行 bar 内代码,给 a 赋值为 0
  3. 执行 bar 内代码,创建匿名函数,将 bar 的活动对象保存到匿名函数 [[Scope]] 属性
  4. bar 返回匿名函数,bar 执行完毕,其执行环境被丢弃
  5. fn 接受 bar 返回的匿名函数

注意到步骤 4, 虽然 bar 的执行环境被丢弃,但是 bar的活动对象依然在内存中,因为匿名的 [[Scope]] 属性还在引用这个活动对象,而匿名函数赋值给了 fn, fn 仍然存在于内存中。

执行 fn, fn 自身的活动对象并没有变量 a, 所以顺着作用域链查找 a, 可在 bar 遗留的活动对象中找到。

总结一下

重新捡起闭包这两个字,《JavaScript 高级程序设计》这样定义闭包:

闭包是有权访问另一个函数作用域中变量的函数。

结合上面的例子,可以知道 fn 就是一个闭包,因为它有权访问另一个作用域 bar 内的变量。

块级作用域

举个栗子

看简单的代码:

for (var i = 0; i < 3; i ++) {
  if (i === 2) {
    var b = 200
  }
}

console.log(b) // 200

上面的代码输出了 200, 可不要被 forif 的花括号施了障眼法,误以为输出结果是 undefined.

上面的 forif 用花括号包起来的代码块,并没与自己的执行环境,所以上面的代码只有一个全局的执行环境,变量都保存在全局的变量对象里面。这就解释了外层为什么能访问花裤号内部的变量。

再举个栗子

看简单的代码:

for (let i = 0; i < 3; i ++) {
  if (i === 2) {
    const b = 200
  }
}

console.log(i) // Uncaught ReferenceError: i is not defined
console.log(b) // Uncaught ReferenceError: b is not defined

ES6 中的 letconst 能够形成块级作用域,用 letconst 声明的变量,只能在花括号内部访问。

总结一下

简单地用花括号包起来的代码块,是没有自己的执行环境,形成不了作用域的。而 letconst 命令声明的变量,只能在花括号内部访问。

总结

作用域和执行环境有关,函数拥有自己的执行环境,创建活动对象,作为自己的变量对象,就形成了作用域。

而简单的用花括号包起来的代码,没有自己的执行环境,当然也形成不了作用域,但是 letconst 命令声明的变量,只能在花括号内部访问。

作用域链将内部执行环境的变量对象与外部的变量对象依次保存,使得内部可以访问外部的变量。

当函数 a 的活动对象被函数外的另一个函数 b 引用,函数 b 就是闭包,可以访问函数 a 内的变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值