【js基础】闭包

作用域

通过 var 创建的变量只有函数作用域,而通过 let 和 const 创建的变量既有函数作用域,也有块作用域。

嵌套作用域

闭包就是内部函数,我们可以通过在一个函数内部或者 {} 块里面定义一个函数来创建闭包。

外部函数作用域

内部函数可以访问外部函数中定义的变量,即使外部函数已经执行完毕

内部函数还可以访问外部函数中定义的形参

外部块作用域

内部函数可以访问外部块中定义的变量

词法作用域

内部函数在定义的时候就决定了其外部作用域

闭包的外部作用域是在其定义的时候已决定,而不是执行的时候。

因为是(词法)静态作用域链,不是动态的

作用域链

当我们使用一个变量的时候,Javascript引擎 会通过变量名在当前作用域查找,若没有查找到,会一直沿着作用域链一直向上查找,直到 global 全局作用域。

外部作用域执行完毕后

当外部作用域执行完毕后,内部函数还存活(仍在其他地方被引用)时,闭包才真正发挥其作用。譬如以下几种情况:

  • 在异步任务例如 timer 定时器,事件处理,Ajax 请求中被作为回调
  • 被外部函数作为返回结果返回,或者返回结果对象中引用该内部函数

除了 timer 定时器,事件处理,Ajax 请求等比较常见的异步任务,还有其他的一些异步 API 比如 HTML5 Geolocation,WebSockets , requestAnimationFrame()也将使用到闭包的这一特性。

变量的生命周期取决于闭包的生命周期。被闭包引用的外部作用域中的变量将一直存活直到闭包函数被销毁。如果一个变量被多个闭包所引用,那么直到所有的闭包被垃圾回收后,该变量才会被销毁。

闭包与循环

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

image.png

闭包与封装性

封装性意味着信息隐藏。

函数与私有状态

通过闭包,我们可以创建拥有私有状态的函数,闭包使得状态被封装起来。

工厂模式与私有原型对象

原型创建对象的常规方式:
object.create()
image.png

image.png

这里,Todo() 就是一个拥有私有状态的函数。

工厂模式与私有构造函数

image.png

翻译功能与私有map

image.png

自增生成器函数

image.png

对象与私有状态

image.png
这个例子中,add() 和 get() 函数是闭包,而 isPriorityTodo() 和 toTodoViewModel() 则是纯函数。

闭包 vs 纯函数

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

闭包在函数式编程中的应用

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

垃圾回收

在 Javascript 中,局部变量会随着函数的执行完毕而被销毁,除非还有指向他们的引用。当闭包本身也被垃圾回收之后,这些闭包中的私有状态随后也会被垃圾回收。通常我们可以通过切断闭包的引用来达到这一目的。比如给它的值设为null,就清空了。

避免全局变量

1.为了避免将所有的工厂函数都放在全局作用域下,最简单的方法就是将他们挂在 app 全局变量下

2.可以通过传参的方式访问工厂函数

image.png

loader 对象

function Loader(){
  let modules = Object.create(null);// 这里不用{}新建空对象而用Object.create(null),是因为Object.create(null)返回的是一个没有原型没有继承关系的空对象
  let started = false;
  function getNamespaceModule(modulesText){
  // 一个闭包
    let parent = modules;
    if(modulesText){
      let parts = modulesText.split('.');
      for(let i=0; i<parts.length; i++){
        let part = parts[i];
        if (typeof parent[part] === "undefined") {
          parent[part] = Object.create(null);
        }
        parent = parent[part];
      }
    }
    return parent;
  }
  //
  
  function addFunction(namespace, fn){
    if(typeof(fn) !== "function") {
      throw "Only functions can be added";
    }
       
    let module = getNamespaceModule(namespace);
    let fnName = fn.name;    
    module[fnName] = fn;
  }
  //
  function addNamespace(namespace){
    return function(fn){
      addFunction(namespace, fn)
    }
  }
  //
  function factory(){
    if(typeof(arguments[0]) === "string"){
      return addNamespace(arguments[0]);
    } else {
      return addFunction(null, arguments[0]);
    }
  }
  
  function start(startApplication){
    if(started){
      throw "App can be started only once";
    }
     
    startApplication(Object.freeze(modules));
    started = true;
  }
  
  return Object.freeze({
    factory,
    start
  });
};
let app = Loader();


复制代码

其实以上代码,我看的不是很懂。。。。。。感觉似乎执行不下去的。。。调用了 Loader()后, Loader内部的getNamespaceModule()没有调用的地方,,,所以感觉没法走下去。。。有人理解这里面吗?
Object.create(null) 和 {}的区别:
image.png

总结

闭包是一个可以访问外部作用域中变量的内部函数。

这些被引用的变量直到闭包被销毁时才会被销毁。

闭包使得 timer 定时器,事件处理,AJAX 请求等异步任务更加容易。

可以通过闭包来达到封装性。

例子

以下也是一个闭包,函数返回函数的例子:

image.png

原文链接:

juejin.cn/post/684490…

zhuanlan.zhihu.com/p/56490498

blog.leapoahead.com/2015/09/15/…

如有侵权,请联系作者删除

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享