Skip to content

作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。

js
function outFun2() {
  var inVariable = '内层变量2'
}
outFun2() //要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable) // Uncaught ReferenceError: inVariable is not defined

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了 块级作用域,可通过新增命令 let 和 const 来体现。

作用域链

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链 。

全局作用域

  1. 最外层函数和在最外层定义的变量拥有全局作用域;
  2. 所有末定义直接赋值的变量自动声明为拥有全局作用域;
  3. 所有 window 对象的属性拥有全局作用域;

函数作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态作用域,通过它就能够预测代码在执行过程中如何查找标识符。动态作用域关注函数从何处调用,其作用域链是基于运行时的调用栈的,如确定函数 this。

指声明在函数内部的变量,并且采用词法作用域,也就是说,函数作用域在其申明时就确定了。

js
function fn() {
  let a = 1
  function fn1() {
    console.log(a)
  }
  fn1() // 1
  fn2() // Uncaught ReferenceError: a is not defined
}

function fn2() {
  console.log(a)
}

fn()

块级作用域

为了解决变量提升带来的一系列问题,ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有了块级作用域。

js
for (var i = 0; i < 10; i++) {}
console.log(i) // 10

for (let j = 0; j < 10; j++) {}
console.log(j) // ReferenceError: j is not defined

闭包

有权访问另一个函数作用域中的变量的函数。就是利用作用域链实现。

js
function fn() {
  let a = 1

  // 闭包函数
  function fb() {
    console.log(a)
  }

  fb()
}

fn()

闭包原理

考虑一下我们解决这个静态作用域链中的父作用域先于子作用域销毁怎么解决。父作用域要不要销毁? 是不是父作用域不销毁就行了?

父作用域中有很多东西与子函数无关,因为子函数没结束就一直常驻内存。这样肯定有性能问题,所以还是要销毁。 但是销毁了父作用域不能影响子函数,所以要再创建个对象,要把子函数内引用(refer)的父作用域的变量打包里来,给子函数打包带走。

怎么让子函数打包带走?

设计个独特的属性,比如 [[Scopes]] ,用这个来放函数打包带走的用到的环境。并且这个属性得是一个栈,因为函数有子函数、子函数可能还有子函数,每次打包都要放在这里一个包,所以就要设计成一个栈结构,这就是闭包的机制。

闭包是返回函数的时候扫描函数内的标识符引用,把用到的本作用域的变量打成 Closure 包,放到 [[Scopes]] 里。

闭包应用

  • 私有化变量,保护变量,例如累加器;
js
function fn() {
  let count = 0
  return function () {
    count++
    console.log(count)
  }
}
const counter = fn()
counter() // 1
counter() // 2
  • 模块化,避免全局污染,例如 vue3 中 hook;
js
function useToggle(options) {
  let _flag = options.flag || false

  const flag = Object.defineProperty({}, 'value', {
    get() {
      return _flag
    }
  })

  function open() {
    _flag = true
  }

  function close() {
    _flag = false
  }

  function toggle() {
    _flag = !_flag
  }

  return { flag, open, close, toggle }
}