关于前端:夯实基础中篇图解作用域链和闭包

25次阅读

共计 3012 个字符,预计需要花费 8 分钟才能阅读完成。

前言

本文承接上篇 夯实根底上篇 - 图解 JavaScript 执行机制,请 先浏览上篇~

讲根底不容易,本文通过 7 个 demo 和 6 张图,和大家一起学习温故作用域链和闭包,本文纲要:

  1. 什么是作用域链
  2. 什么是词法作用域
  3. 什么是闭包
  4. 闭包的理论应用案例

夯实根底系列,将继续更新,有什么想一起学习探讨的可在评论区留言~

什么是作用域链

注释开始~

请思考上面 demo 的 name 打印什么

    function test() {console.log(name)
    }
    function test1() {
      const name = 'test1 的 name'
      test()}
    const name = 'global 的 name'
    test1()

通过执行上下文来剖析代码的执行流程,执行到 test 函数时:

那 test 函数里的 name 是哪个呢?这就波及到了作用域链的定义:变量和函数的查找 链条就是作用域链。它决定了各级上下文中的代码在拜访变量和函数时的程序:查找变量和函数时,先在以后执行上下文找,以后没有,到下一个执行上下文找,没有再到下一个,直到全局执行上下文,都没有就报错 应用未定义的变量或函数

而在每个执行上下文的变量环境中,都蕴含了一个内部援用 outer,用来指向内部的执行上下文,链条构造是 以后执行上下文 > 蕴含以后上下文的上下文 1 > 蕴含上下文 1 的上下文 2 ...

而这个 demo 会打印global 的 name,起因是 test 执行上下文的 outer 指向全局执行上下文,包含 test1 的 outer 也是指向全局执行上下文:

兴许会有同学纳闷,为什么 test 的 outer 指向全局执行上下文,而不是 test1,这是因为在 JavaScript 执行过程中,其作用域链是由词法作用域决定的。

什么是词法作用域

词法作用域就是作用域是由代码中 函数申明的地位 来决定的,它是动态的作用域,通过它就可能预测代码在执行过程中如何查找标识符,它 与函数是怎么调用的没有关系。所以方才的例子打印的是global 的 name

看个具体例子:

    const count = 0
    function test() {
      const count = 1

      function test1() {
        const count = 2

        function test2() {const count = 3}
      }
    }

其蕴含关系和作用域链:

事实上在 Global Scope 全局作用域(Window)之前,还有一个 Script Scope 脚本作用域,它寄存的是以后 Script 内可拜访的 let 变量和 const 变量,而 var 变量寄存在 Global 上的就不在 Script Scope,它相似于是 脚本范畴内 的全局作用域。在上面的 demo 中再举例。

什么是闭包

闭包指的是那些 援用了另一个函数作用域中变量 的函数,通常是在嵌套函数中实现的。

比方这个例子:

    var globalVariable = 1
    const scriptVariable = 2
    
    function test() {
      let name = 'Jaychou'

      return {getName() {
          const count = 1
          return name
        },
        setName(newValue) {name = newValue}
      }
    }

    const testFun = test()
    console.log(testFun.getName()) // Jaychou
    testFun.setName('小明')
    console.log(testFun.getName()) // 小明

大家能够依据作用域链的常识,思考一下执行到 console.log(testFun.getName()) 的 getName 外面的时候作用域链是怎么的~

咱们用浏览器的开发者工具看一下:


作用域链是 以后作用域》test 函数的闭包》Script 作用域》Global 作用域

  1. 为什么叫 test 函数的闭包?因为当 const testFun = test() 的 test 函数执行完之后,test 的函数执行上下文曾经被销毁了,但它返回的 {getName(){}, setName(){}} 对象被 testFun 援用着,而 getName 和 setName 援用着 test 函数内定义的 name 变量,所以这些被援用的变量仍然须要被保留在内存中,而这些变量的汇合称为闭包 Closure;
  2. 目前闭包内的 name 变量就只能通过 getName 和 setName 去拜访和设置,而这也是闭包的作用之一:封装公有变量;
  3. 方才说的 Script Scope 中保留着 scriptVariable 变量,globalVariable 变量是 var 申明的,所以在 Global Scope(Window)中。

再看 1 个具体案例了解闭包:

    const globalCount = 0

    function test() {
      const count = 0
      return test1

      function test1() {
        const count1 = 1
        return test2

        function test2() {
          const count2 = 2
          console.log('test2', globalCount + count + count1 + count2)
        }
      }
    }
    test()()()


执行到 test2 外部的 console.log 那一行时,其作用域链是 以后作用域》test1 的闭包》test 的闭包》Script Scope》Global Scope

闭包应用倡议:当不须要应用了之后,留神要解除援用着闭包的变量,这样闭包才会被开释。比方第 1 个案例的 testFun 如果不须要用了,就把它开释 testFun = null。

闭包的理论应用案例

封装公有变量

就是方才的 getName、setName 案例,通过 getName 获取 name,通过 setName 设置 name

封装单例

    const Single = (function () {
      let instance = null

      return function () {if (!instance) {
          instance = {
            name: 'jaychou',
            age: 40
          }
        }
        return instance
      }
    })()
    const obj1 = new Single()
    const obj2 = new Single()
    console.log(obj1 === obj2) // true

这里只是举个例子,具体的 instance 是什么类型,反对什么性能要看理论我的项目。

防抖和节流

防抖:

    function debounce(fn, delay) {
      let timer = null;

      return function () {
        let context = this;
        let args = arguments;

        timer && clearTimeout(timer);

        timer = setTimeout(function () {fn.apply(context, args);
        }, delay);
      }
    }

节流:

    function throttle(fn, interval) {
      let last = 0;

      return function () {let now = +new Date()
        if (now - last >= interval) {fn.apply(this, arguments);
          last = now;
        }
      }
    }

更残缺的防抖和节流的实现可参考 Lodash,这里次要是演示闭包的应用

总结

闭包的应用场景很多,性能很弱小,能够说在前端我的项目中常常可见例如 React Hooks 等等,这里只列举了几个很简略的很实用的利用场景。

总结

本文次要介绍了作用域链和闭包,沿着 夯实根底上篇 - 图解 JavaScript 执行机制 来一起看的话应该比拟容易了解,若对大家有所帮忙,请不吝点赞关注~

正文完
 0