五百字的闭包详解与一千字的使用场景

相关系列: 从零开始的前端筑基之旅(面试必备,持续更新~)javascript

在理解闭包以前,有个重要的概念须要先了解一下,就是 js 执行上下文前端

每当引擎遇到一个函数调用,它会为该函数建立一个新的执行上下文并压入栈的顶部。java

在建立阶段会发生三件事:

  1. This 绑定
  2. 建立词法环境组件。
  3. 建立变量环境组件。

当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。执行上下文中声明的全部变量都将被删除。面试

先看个栗子:闭包

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); // local scope
复制代码

依据词法做用域逻辑,查找变量的时候,会先从当前上下文的变量对象中查找,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象。函数

所以,内部函数f在执行时,找到定义位置的父级函数checkscope内的scope并将其返回,而后内部函数与外部函数的上下文依次弹出并销毁。post

再看另外一个栗子:ui

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
const f2 = checkscope();
f2(); // local scope
复制代码

与上个栗子不一样,这里并无在函数内部调用f,而是将f返回赋值给f2,所以,随着checkscope函数的结束,由于f2持有对函数f的引用,f没有被销毁,又由于f持有外部函数的做用域,因此scope也没有被销毁。spa

由此得出结论:.net

  • 闭包就是内部函数,咱们能够经过在一个函数内部或者 {} 块里面定义一个函数来建立闭包。闭包能够访问外部做用域,即便这个外部做用域已经执行结束。
  • 闭包在没有被外部使用的状况下,随执行结束销毁
  • 闭包的外部做用域是在其定义的时候已决定,而不是执行的时候。

下面是扩展阅读(偷懒直接复制的,连接在末尾,罪过~):

闭包与循环

闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。

function initEvents(){
  for(var i=1; i<=3; i++){
    setTimeout(function showNumber(){
     console.log(i)
    },10);
  }
}
initEvents(); // 4,4,4
复制代码

这个示例中,咱们建立了3个闭包,皆引用了同一个变量 i,因为变量 i 随着循环自增,所以最终输出的都是一样的值。

for 语句块中使用 let 变量声明,将在每次循环中为 for 语句块建立一个新的局部变量。

function initEvents(){
  for(let i=1; i<=3; i++){
    setTimeout(function showNumber(){
     console.log(i)
    },10);
  }
}
initEvents();
复制代码

函数与私有状态

经过闭包,咱们能够建立拥有私有状态的函数,闭包使得状态被封装起来。

自增生成器函数

经过闭包,咱们能够建立自增生成器函数。一样,内部状态是私有的。示例以下:

function createAGenerate(count, increment) {
  return function(){
    count += increment;
    return count;
  }
}
let generateNextNumber = createAGenerate(0, 1);
console.log(generateNextNumber()); //1
console.log(generateNextNumber()); //2
console.log(generateNextNumber()); //3
let generateMultipleOfTen = createAGenerate(0, 10);
console.log(generateMultipleOfTen()); //10
console.log(generateMultipleOfTen()); //20
console.log(generateMultipleOfTen()); //30
复制代码

对象与私有状态

以上示例中,咱们能够建立一个拥有私有状态的函数。同时,咱们也能够建立多个拥有同一私有状态的函数。基于此,咱们还能够建立一个拥有私有状态的对象。

function TodoStore(){
  let todos = [];
  
  function add(todo){
    todos.push(todo);
  }
  function get(){
    return todos.map(toTodoViewModel);
  }
  
  function toTodoViewModel(todo) {
     return { id : todo.id, title : todo.title };
  }
  
  return Object.freeze({
    add,
    get
  });
}
复制代码
复制代码

TodoStore() 函数返回了一个拥有私有状态的对象。在外部,咱们没法访问私有的 todos 变量,而且 add 和 get 这两个闭包拥有相同的私有状态。在这里,TodoStore() 是一个工厂函数。

闭包 vs 纯函数

闭包就是那些引用了外部做用域中变量的函数。

为了更好的理解,咱们将内部函数拆成闭包和纯函数两个方面:

  • 闭包是那些引用了外部做用域中变量的函数。
  • 纯函数是那些没有引用外部做用域中变量的函数,它们一般返回一个值而且没有反作用。

在上述例子中,add()get() 函数是闭包,而 isPriorityTodo()toTodoViewModel() 则是纯函数。

装饰器函数也使用了闭包的特性。

垃圾回收

Javascript 中,局部变量会随着函数的执行完毕而被销毁,除非还有指向他们的引用。当闭包自己也被垃圾回收以后,这些闭包中的私有状态随后也会被垃圾回收。一般咱们能够经过切断闭包的引用来达到这一目的。

避免全局变量

Javascript 中,咱们很容易建立出全局变量。任何定义在函数和 {} 块以外的变量都是全局的,定义在全局做用域中的函数也是全局的。

若是你收获了新知识,请给做者点个赞吧,在左侧边栏第一个按钮点一下~

参考文章:

[译]发现 JavaScript 中闭包的强大威力

相关文章
相关标签/搜索