js速记——作用域与闭包

88 阅读4分钟

一、作用域

1、什么是作用域

作用域就是在一个范围内查找变量的合集,规定了变量的访问范围;

2、作用域分类

js中作用域分为全局作用域、函数作用域、块作用域;

  • 全局作用域:作用于全局,所有地方都可以访问到;
  • 函数作用域:在函数预编译阶段确定,函数内部可访问;
  • 块级作用域:通过{}限制访问区域,可通过let或者const声明,使用var声明的变量会保存在全局或者函数作用域内;

(1)全局作用域

在浏览器环境中,全局作用域内的变量,都可以通过window访问到,创建全局作用域变量的方式如下:

  • 在全局作用域下定义的变量;
  • 在函数内,直接给未声明的变量赋值,这个变量会被定义成全局变量;
  • 直接在window上添加的变量;

(2)函数作用域

函数作用域内定义的变量,只能在当前函数内部被访问到,包括内部嵌套的函数,外部是无法访问的,创建函数作用域变量如下:

  • 在函数内部通过var声明的变量;
  • 函数的形参;

(3)块级作用域 块级作用域在es6有更新,声明块级作用域变量方式如下:

  • 通过letconst声明的变量;
  • try catch语句中的catch内
  • with语句内

3、作用域的创建

  • 全局作用域是在页面打开后创建,页面关闭后销毁,期间一直存在;
  • 函数作用域是在函数被调用时,预编译阶段创建,函数执行完成后销毁;

二、作用域链

1、什么是作用域链

当全局作用域、函数作用域、块级作用域嵌套使用的时候,内部是可以访问到外部作用域变量的,这就是通过作用域链进行查询的。

在创建一个函数后,通过查看函数属性,可以看到有一个内部属性[[Scopes]],这里按照嵌套顺序,保存着当前函数可以访问的所有外部作用域变量,当函数被调用执行时,会创建一个当前函数的活动对象OA,当前函数内部定义的所有变量、函数和形参,都被保存到这个OA对象上,预编译结束,这个活动对象会被插入[[Scopes]]中,执行函数阶段,遇到所有的变量查找,都会在这个[[Scopes]]内进行,这个就是作用域链

function fa() {
    var a = 1
    function fb() {
        var b = 2 + a
    }
    console.dir(fb)
}
fa()

运行代码,可以看到

l.png

三、闭包

函数作用域,在函数执行完后就会销毁,函数内的变量空间会被垃圾回收机制回收,但是在有些时候,由于函数内有变量存在外部引用,导致函数执行结束后,这些变量空间无法被回收,这时候,就形成了闭包。

1、什么是闭包

内部函数对外部函数变量的引用;

在前面作用域链章节说到,内部函数引用了外部函数可访问的变量,这种引用,就是闭包;那网上常说闭包会引起内存泄漏等等问题,那这么说不对啊,其实在一般嵌套中,由于外部函数执行完时,会将内部函数清理掉,那么内部函数对外部函数变量的引用也就没有了,这个时候可以放心清理变量内存,闭包确实不会引起任何问题,但是如果外部函数执行结束时,内部函数还没有结束,这个时候,外部函数内本该被垃圾回收处理的变量就不会被处理,所以有了闭包会引起变量常驻内存、内存泄漏这些问题;

网上大多数闭包的例子都喜欢外部函数return内部函数,会对一些同学造成误解

function fa() {
    var a = 1;
    var b = 2;
    function fb() {
        console.log(a)
    }
    fc = fb
}
var fc;
fa()

如上,内部函数fb被外部变量fc保存,也就是说这个fb函数不会被垃圾回收处理掉,而fb内部有对变量a的引用,这个时候,这个闭包就一直存在;

2、闭包的使用

  • 私有属性和方法;
  • 实现块级作用域;
  • 实现模块化;