作用域和作用域链

作用域

  • 所有未定义的变量直接赋值会自动声明为全局作用域的变量(隐式全局变量可以用delete删除,var定义的则不行)
  1. a=1 // 隐式全局变量 严格模式报错
  2. var b=2 // 显式全局变量
  3. console.log(a,b) //1 2
  4. delete a // 严格模式报错
  5. delete b // 严格模式报错
  6. console.log(b,a) // 2 a is not defined
  • window对象的所有属性拥有全局作用域

  • 内层作用域可以访问外层作用域,反之不行

  • var声明的变量,在除了函数作用域之外,在其他块语句中不会创建独立作用域

  • let和const声明的变量存在块语句作用域,且不会变量提升

  • 同作用域下不能重复使用let、const声明同名变量,var可以,后者覆盖前者

  • for循环的条件语句的作用域与其循环体的作用域不同,条件语句块属于循环体的父级作用域

  1. // 以下语句使用let声明不报错,说明为不同作用域
  2. for (let i = 0; i < 5; i++) {
  3. let i = 5
  4. }
  5. --------------------------------------------
  6. // 此语句报错,说明循环体为条件语句块的子作用域
  7. // for循环执行顺序为:条件语句块1->条件语句块2->循环体->条件语句块3->条件语句块2 依次类推
  8. for (let i = 0; i < 5; i=x) { // x is not defined
  9. let x = 5
  10. }

作用域链

  • 作用域链也就是所谓的变量查找的范围

  • 在当前作用域引用变量时,如果没有此变量,则会一路往父级作用域查找此变量,直到全局作用域,如果都没有,在非严格情况下会自动声明,所以是undefined,在严格条件下则会报错

  • 变量的查找路径依据的是在创建这个作用域的地方向上查找,并非是在执行时的作用域,如下 b变量的值为2。可以看出当执行到需要b变量时,当前作用域下并没有b,所以要到定义这个b变量的静态作用域中寻找,即创建时候的作用域链上查找b的值

  1. b = 1
  2. function a() {
  3. // 定义b,找到
  4. const b = 2
  5. function s() {
  6. // 使用到b,当前作用域并没有,向上找
  7. console.log(b);
  8. }
  9. return s
  10. }
  11. const s = a()
  12. var b = 3
  13. s() // 2

执行上下文

  • 执行上下文在运行时确定,随时可能改变

  • 调用栈中存放多个执行上下文,按照后进先出的规则进行创建和销毁,最底部的执行上下文,也就是栈低的执行上下文为全局上下文,最早被压入栈中,其上下文中的this指向window,严格模式下为undefined

  • 创建执行上下文时,会绑定当前this,确定词法环境,存储当前环境下函数声明内容,变量let与const绑定但未关联任何值,确认变量环境时,绑定var的初始值为undefined

  • 在var声明之前,调用var声明的变量时值为undefined,因为创建了执行上下文,var声明的变量已经绑定初始undefined,而在let和const声明之前调用其声明的变量时,由于只绑定在了执行上下文中,但并未初始任何值,所以在声明之前调用则会抛出引用错误(即TDZ暂时性死区),这也就是函数声明与var声明在执行上下文中的提升

变量提升

  1. console.dir(foo) // foo(){}
  2. function foo() {}
  3. var foo = 5
  4. /*
  5. console.dir(foo) // undefined
  6. var foo = 5
  7. */
  8. ------------------------------
  9. var foo = 5
  10. function foo() {}
  11. console.dir(foo) // 5

从以上代码结果可以得出结论:

  • 上面代码块能够体现,在解析阶段会将函数与变量提升,且函数的优先级比var声明的变量高,因为打印的是函数声明,如果var声明的优先级高,那么应该是undefined
  • 从下面的代码块中可以看出foo在代码执行的时候被赋值为5,而函数声明在解析阶段已经结束,在执行阶段没有效果
  • 还有一点 个人认为在解析阶段,函数声明与变量声明提升之后在代码块中的位置顺序没什么关系

闭包 详解

  • 所谓闭包就是函数与其词法环境(创建当前作用时的任何局部变量)的引用。闭包可以使内部函数访问到外部函数的作用域,当函数被创建时即生成闭包
  1. function fn1() {
  2. var name = 'hi';
  3. function fn2() {
  4. console.log(name);
  5. }
  6. return fn2
  7. }
  8. fn1()() // hi
  • 当你从函数内部返回一个内部函数时,返回的函数将会保留当前闭包,即当前词法环境

  • 闭包只会保留环境中任何变量的最后一个值,这是因为闭包所保存的是整个变量的对象

  • 闭包的作用域链包含着它自己的作用域,以及包含它父级函数的作用域和全局作用域

  • 当返回一个闭包时,保留此闭包下的所有被外部引用的对象

  • 闭包之间是独立的,在闭包环境下可以创建多个不同的闭包环境暴露给外部,从而实现不同的效果

  • 暴露闭包的方式不止返回内部函数一种,还可以使用回调函数产生闭包环境,或者把内部函数赋值给其他外部对象使用

  • 闭包在没有被外部使用的情况下,随执行结束销毁,如何产生闭包并且保留闭包环境的关键就在于不让其环境被垃圾回收系统自动清除,那么就要使内部环境中的引用被外部保留,这样才能保留闭包

  • 闭包虽然方便我们操作和保留内部环境,但是闭包在处理速度和内存消耗方面对脚本性能具有负面影响,除非在特定的情况下使用