【deep JS】从面试题出发(一):深入理解闭包

706 阅读3分钟

1、什么是闭包?

  • closure vs scope

这两个概念的锚定,我们需要明确,主体,这两个概念分别在描述什么事情? 主体是函数变量。scope在描述函数内部的变量不能被外部访问到,函数形成了一个包裹隔绝掉了内外,看图理解: closure相对比较难,它描述是如果函数的内部如果想访问函数外部变量时所需要遵守的规则

  • 原理分析
function outer(){
  let n = 1;
  function inner(){
    console.log(n);
  }

  return inner;
}

const fn = outer();
fn();

这里面有一个问题outer执行完毕之后,应该消亡掉,fn再执行的时候n为什么等于1?

简单来说outer执行完毕后形成了闭包,闭包记录下来了outer的执行上下文。fn执行时依然可以访问到本来应该消亡掉了的outer里面的变量。看图理解:

理解closure需要一个前置只是,就是执行上下文execution context。如果对于我上面的描述理解不到的小伙伴,一定是少了这部分的只是拼图:www.youtube.com/watch?v=Nt-…

  • 用途

scope保证不污染global的变量池

closure可以实现变量的暂存,实际操作中我的话,会在写状态机的时候用的比较多。

2、 let和var的区别,let的产生背景?

  • 区别

这两个关键词的最关键的区别是scopevarfunction作为scopelet{} 认定为一个scope。 所以在for,if里面的let变量在外是读取不到的,而var则可以。

另外,如果在global下使用var,那么可以通过window来调用。而let则不可以。

var a = 1;
let b = 2;
console.log(windows.a);//1
console.log(windows.b);//undefined
  • 目的

有一些需要在函数块中记住变量属性的表达式,ES5需要加上花括号,这样子嵌套的层级会很多。ES6的时候就发明了let来简化代码。

3、 var的变量提升底层原理是什么?

  • JS编译代码分为两步:
  1. declare: 对声明进行编译——为varfunction分配地址内存。
  2. execute: 执行——进行正常的赋值。

例如var a = 2; 编译器先不会管a = 2这一赋值步骤。它最先要做的是把var a编译掉,而运算操作留在原地。声明在编译角度上被移动到了作用域的顶端。这就是提升的底层原理。

  • 拓展话题——TDZ暂时死区 aVar的值符合提升的原则。aLet就有点意思了,它抛出来的错误实际上就是暂时死区错误。简单来说,从声明到到let的运算步骤为止的这段时空被锁死,外界在此期间寻求访问,不被允许。

4、 模块化思想

  • 为什么要使用模块化?

模块化思想即最小暴露原则。隐藏掉别人不需要知道的代码细节,仅提供需要使用的API。保护代码的安全性和可维护性。

  • 如何模块化?

书写一段模块的要点有二:1、要使用闭包原理。2、函数至少被调用执行过一次。

方法一:工厂模式

function module(){
   let text = "baz";
   
   let publicAPI = {
       baz: fuction (){
           console.log(text);
       }
   }
   
   return publicAPI;
}

var foo = module();
foo.bar();

方法二:单例模式(IIFE)

var foo = (function module(){
    let text = "baz";
    let publicAPI = {
        baz: function(){
>      console.log(text);
}
return pubilcAPI;

} })(); foo.bar();

方法三:ES6的module模块
```JavaScript
function foo(){
 console.log("foo");
}
export default foo;
//在另外一个文件里的引用
import foo from "..."