💭局部作用域

  • 函数作用域:函数内部声明的变量只能在函数内部访问
    • 函数的参数也是局部变量
    • 不同函数内部的同名变量互不影响
    • 函数执行完毕后,局部变量会被销毁
  • 块级作用域:letconst声明的变量只能在块级作用域内访问(ES6新增
    • 块级作用域:{}包裹的代码块,比如ifforwhile
    • 代码块内部声明的比那两外部有可能无法访问(var声明的变量可以访问)

💭全局作用域

  • 全局作用域:在函数外部声明的变量,所有函数内部都可以访问
    • 为window对象动态添加的属性也是全局变量(不推荐)
    • 函数中未使用varletconst声明的变量也是全局变量(不推荐)
    • 尽可能避免全局变量,会造成全局变量污染

💭作用域链

本质是底层的变量查找机制,当访问一个变量时,会先在当前作用域查找,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域。

  • 嵌套关系的作用域串联起来形成了作用域链
  • 相同作用域链中按着从小到大的规则查找变量
  • 子作用域能够访问父作用域的变量,反之不行

💭JS垃圾回收机制GC(Garbage Collection)

JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

  • 内存的生命周期:
    • 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
    • 内存使用:即读写内存,也就是使用变量、函数等。
    • 内存回收,使用完毕,由垃圾回收器自动回收不再使用的内存。
  • 全局变量一般不会回收(关闭页面回收)
  • 一般情况下局部变量的值会被自动回收。
  • 内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏。
  • 💻 算法说明
    • 栈、堆

      • 栈:由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
      • 堆:一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里面。
    • 引用计数法(不常使用)

      • 定义内存不再使用就是看一个对象是否有指向它的引用,没有引用了就会被回收。
        1. 跟踪记录被引用的次数
        2. 如果被引用了一次,那么记录次数为1,多次引用会累加,减少引用次数会减少。
        3. 如果引用次数为0,则释放内存。
      • 缺点:嵌套引用(循环引用),如果两个对象相互引用,尽管他们已经不再被使用,但是引用次数不为0,导致无法回收,也就是内存泄漏。
    • 标记清除法

      • 定义不再使用的对象为无法达到的对象,即从根部(在js中是全局对象)出发定时扫描内存中的对象。凡是能从根部到达
        的对象,都是还需要使用的。
      • 无法从根部出发触及到的对象标记为不再使用,稍后进行回收。

闭包(Closure)

一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。

  • 闭包 = 内层函数 + 外层函数的变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function outer() {
    let a = 1 // a定义在outer函数内部,外部无法访问
    function inner() { // inner函数是闭包,如果外部想访问a,就可以通过inner函数
    console.log(a)
    }
    return inner
    }
    let fn = outer()
    fn() // 1
  • 作用:封闭数据,实现数据私有,外部也可以访问函数内部的变量。允许将函数与其所操作的某些数据关联起来,但可能引起内存泄漏。

  1. 统计函数调用次数。
1
2
3
4
5
6
7
8
9
10
11
  function count() {
let num = 0
return function () {
num++
console.log(num)
}
}

let fn = count()
fn() // 1
fn() // 2

变量提升

允许在声明变量之前使用变量,仅存在于var声明的变量。

当代码在执行之前,会把在当前作用域下所有用var声明的变量和函数声明提升到当前作用域的最前面。只提升声明,不提升赋值。