面试复习计划JavaScript篇一

491 阅读8分钟

引言

面试去啊兄弟,面试,赶紧复习,不多说,复习!!!!

此系列将会针对面试中可能会遇到的知识点进行整理和复习,该篇包含以下知识点:执行上下文,作用域链,闭包。

Ⅰ执行上下文

——啥是执行上下文

执行上下文就是评估和执行JavaScript代码环境的抽象概念,按照我个人的理解来说,就是,由于代码从上往下执行,就叫做执行上下文 ( •̀ ω •́ )✧,可以简单理解为“当前代码的执行环境”。

执行上下文有三种类型:

  • 全局执行上下文,* 只存在一个,就是浏览器中的Window对象,在客户端中一般由浏览器创建。通过var定义的全局变量和函数都会成为window对象的属性和方法。全局执行上下文中的代码执行完毕(比如关闭网页,退出浏览器等)之后,就会被销毁,包括定义在它上面的所有变量和函数*
  • 函数执行上下文,* 存在于每个函数调用,即使同一个函数被多次调用,每次都会创建一个新的执行上下文,这些执行上下文被执行上下文栈管理和协调执行顺序*
  • Eval函数执行上下文,作用于eval函数内部,不常用,不说了。

创建执行上下文是包含三个部分,词法环境变量环境this值

——执行上下文栈

执行上下文栈简称执行栈,拥有其他语言中数组的push和pop方法相同的特性,也就是数据结构中的LIFO(后进先出)。当JavaScript最初解释你的代码时,首先会创建一个全局执行上下文并压入栈中,随后碰到每一个函数时都会创建一个函数执行上下文并压入栈中。最后来到执行阶段,引擎首先会执行后入栈中的函数,当函数执行完毕后,执行上下文从栈中弹出,控制流程到达当前栈中的下一个执行上下文。

Ⅱ作用域链

——什么是作用域

首先我们要了解作用域这个概念,作用域指的是声明变量和函数的代码区块,它决定了在该区块中声明的变量和函数等资源的可访问性。

在js中,作用域分为全局作用域函数作用域块级作用域

——全局作用域

在全局作用域内定义的资源在整个程序中都是可访问的,全局作用域存在于以下几种情形:

  • 在最外层代码中定义的变量拥有全局作用域

    image-20210827144511618

  • 所有未定义直接使用的变量拥有全局作用域

    image-20210827145143988

  • 所有window对象的属性拥有全局作用域

——函数作用域

函数作用域决定了在函数内部声明的变量或函数等资源的访问性,它不会接受函数外的访问

——块级作用域

使用var声明的变量,在如下图所示的代码块中,会拥有全局环境的作用域,很多时候会显得很不方便。

image-20210827155613181

为此,es6中新增了const、let关键字,使用这些关键字声明的变量具有块级作用域,如下图所示

image-20210827163454158

而且使用const、let声明的变量不具有变量提升,而具有“暂时性死区”特性,变量必须先声明后使用,否则报错。

那什么时变量提升呢,看图:

image-20210827172017205

那什么又是暂时性死区呢,还是看图:

image-20210827173326521

let和const的区别在于:

  • const声明的变量必须赋有初始值,否则报错
  • const声明的变量不可修改,否则报错
  • const声明的变量对于复杂类型来说可以更改,但不可更改该变量的内存地址,不懂?看图:

image-20210827174211613

——到底啥是作用域链呢?

前面讲了一大堆做铺垫,作用域链到底是什么玩意呢?

上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链

还是看一个例子吧,比起一大坨的解释省事多了

image-20210827181452941

在上图中,有一个全局作用域下的变量sayHi,b函数中有一个函数作用域下的变量sayHi,该程序以调用函数b为开端,随后函数b调用了函数a,函数a中要打印变量sayHi,但在a函数的函数作用域中找不到变量sayHi,随后开始查找a函数的上级作用域,最后返回了“Hello World”。

在这过程中,需要注意的是,在查找变量的过程中,要查找的对象不是调用了a函数的作用域,而是创建了a函数的作用域,也就是window的全局作用域。之所以会这样,是因为JavaScript采用的是静态作用域。

Ⅲ闭包

——什么是闭包

闭包就是指有权访问另一个函数作用域中变量的函数,可以简单理解为,定义在一个函数内部的函数就是闭包。

image-20210827190717938

在这段代码中,fun函数return返回的函数叫做闭包

——闭包的作用

  • 延申变量的作用范围

    闭包可以延申变量的作用范围,在上面的例子中,fun函数返回了一个闭包,函数外的变量fn进而可以通过这个闭包访问到fun函数内部的资源。在这个过程中,闭包延长了函数的生命周期,一般情况下,函数的环境周期开始于被调用,执行完毕后随即销毁,但闭包使得该函数的环境周期得到延长,因此我们可以在函数外通过闭包访问函数内的变量。

    image-20210828084338863

    如图所示,虽然闭包可以做到延申变量的作用范围,但由于延长了函数的生命周期,使得该函数中的变量一直占用内存,所以不能滥用闭包,否则可能会造成性能问题

  • 封装私有变量

    根据闭包的特性,我们可以利用闭包延申变量作用范围这一特性,通过构造函数,使其拥有封装私有变量或函数的功能

    image-20210828102621294

    在上面这个例子中,直接访问变量sayHi或fun函数都不会成功的,但我们借助闭包,开放了一些公共方法,让我们在私有资源的作用域外也可以成功修改和访问,这些公共方法就叫做特权方法

    但是还有点瑕疵,就是构造函数的老毛病了,每个实例都会重新创建一遍新方法。使用静态私有变量实现特权方法可以避免这个问题。

    image-20210828104734350

    这个模式与前一个模式的主要区别就是,私有变量和私有函数是由实例共享的。因为特权方法定义在原型上,所以同样是由实例共享的。像这样创建静态私有变量可以利用原型更好的重用代码。

    另外,还有一种封装私有变量的模式,叫做模块模式。模块模式是在单例对象(只有一个实例的对象)基础上加以拓展,使其通过作用域链来关联私有变量和特权方法。上代码:

    image-20210828150515610

模块模式使用了匿名函数,函数主体部分定义了私有变量和函数,最后返回一个对象。该对象包含特权方法或公有方法,是单例对象的公共接口。如果单例对象需要进行某种初始化,并且需要访问私有变量时,就可以采用这个模式。

结语

以上就是我在复习过程中,总结和整理的内容了,虽然我不是第一次写笔记,但却是第一次把我所写的笔记分享出来,有很多地方的编辑格式不太统一,所以可能显得有些乱,我会在此后的文章中不断改进的,这也正是我为什么一定要把笔记分享出来,平常记笔记的时候也就是随手一写,随便什么格式自己看的过去就好,但人总是得要进步的嘛,有时候我回头看自己写的笔记,啰里啰唆,很多地方都没有get到点,甚至由于写的太乱,想查找什么东西都无从下手。如果我要是把我的笔记分享出来的话,意识会告诉我,你写的这些东西不光你自己看,别人也能看得到,那可就得写好看一点,从而督促自己搞的更完美一点。ok,这就是本篇所有内容了,下一篇将会复习JavaScript中的this,call和apply,以及bind。