JS高级三

49 阅读6分钟

变量提升和函数提升

//面试题
var a = 3
function fn (){
conlole.log(a) 
var a = 4
}
fn()//undifined
//相当于
var a = 3
function fn (){
var a //变量提升
conlole.log(a) 
a = 4
}
fn()//undifined

函数提升是指通过function声明(必须采用声明的方式,采用函数表达是遵循变量提升)的函数,可以在之前调用。

fn()
function fn(){
console.log('......')
}

执行上下文和执行上下文栈

执行上下文

  1. 代码分类(位置)
  • 全局代码
  • 函数代码
  1. 全局执行上下文
  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    • var定义的全局变量==>undefined, 添加为window的属性
    • function声明的全局函数==>赋值(fun), 添加为window的方法
    • this==>赋值(window)
  • 开始执行全局代码
  1. 函数执行上下文
  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
  • 对局部数据进行预处理
    • 形参变量==>赋值(实参)==>添加为执行上下文的属性
    • arguments==>赋值(实参列表), 添加为执行上下文的属性
    • var定义的局部变量==>undefined, 添加为执行上下文的属性
    • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    • this==>赋值(调用函数的对象)
  • 开始执行函数体代码

执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window

image.png

  • 理解

    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:

    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期

    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:

    • 全局 :

      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数

      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程

    • 全局:

      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:

      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

image.png

image.png

作用域与作用域链

  • 理解:

    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量 image.png
  • 分类:

    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用

    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文

    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的
  1. 区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时

  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建

  • 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建

  1. 区别2
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化

  • 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放

  1. 联系
  • 上下文环境(对象)是从属于所在的作用域

  • 全局上下文环境==>全局作用域

  • 函数上下文环境==>对应的函数使用域

image.png image.png

闭包

1. 理解:

  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包

  • 使用chrome调试查看

  • 理解一: 闭包是嵌套的内部函数(绝大部分人)

  • 理解二: 包含被引用变量(函数)的对象(极少数人)

  • 注意: 闭包存在于嵌套的内部函数中

2.产生闭包的条件?

  • 函数嵌套

  • 内部函数引用了外部函数的数据(变量/函数)

3. 作用:

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

  1. 函数执行完后, 函数内部声明的局部变量是否还存在?

  2. 在函数外部能直接访问函数内部的局部变量吗?

写一个闭包程序

```
function fn1() {
  var a = 2;
  function fn2() {
    a++;
    console.log(a);
  }
  return fn2;
}
var f = fn1();
f();
f();
```

4.常见闭包

  • 将函数作为另一个函数的返回值

image.png

  • 将函数作为实参传递给另一个函数调用

image.png

闭包的生命周期

  1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)

  2. 死亡: 在嵌套的内部函数成为垃圾对象时

闭包的应用

闭包的应用 : 定义JS模块

  • 具有特定功能的js文件

  • 将所有的数据和功能都封装在一个函数内部(私有的)

  • 只向外暴露一个包含n个方法的对象或函数

  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能

缺点

    1. 缺点
  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长

  • 容易造成内存泄露

  1. 解决
  • 能不用闭包就不用

  • 及时释放

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放

  • 内存泄露积累多了就容易导致内存溢出

  • 常见的内存泄露:

    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包