Heim  >  Artikel  >  Web-Frontend  >  Schließung der JavaScript-Zusammenfassung

Schließung der JavaScript-Zusammenfassung

WBOY
WBOYnach vorne
2022-11-07 16:31:081193Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über JavaScript, in dem hauptsächlich verwandte Themen zu Verschlüssen vorgestellt werden, einschließlich der Frage, was Verschlüsse sind, warum sie auf diese Weise gestaltet sind und wie sie verwendet werden können. Das Folgende ist: Werfen wir einen Blick darauf, hoffentlich hilft allen.

Schließung der JavaScript-Zusammenfassung

【Verwandte Empfehlungen: JavaScript-Video-Tutorial, Web-Frontend

Was ist ein Abschluss?

Für einen Wissenspunkt bin ich immer der Meinung, dass man, egal wo man beginnt, drei Fragen gründlich verstehen muss, um diesen Wissenspunkt wirklich zu verstehen, und ihn dann in der Praxis anwenden muss, um ihn meistern zu können. Diese drei Fragen lauten: Was ist

  • ?
  • Warum Design?
  • Wo kann es verwendet werden?

Beantworten Sie zunächst die Frage, was ein Verschluss ist. Die meisten Leute sollten viele verwandte Artikel gelesen haben, und viele Leute haben auch ihre eigenen Erklärungen abgegeben, daher werde ich zunächst eine Erklärung geben, die ich verstehe, nämlich: Es gibt zwei vorausgesetzte Konzepte:

  • Der Abschluss wurde während der lexikalischen Analyse bestimmt und hängt daher vom lexikalischen Umfang ab.

  • Die Voraussetzung für die Existenz von Abschlüssen ist, dass eine Programmiersprache Funktionen als erstklassige Bürger unterstützen muss, sodass sie mit Funktionen verknüpft sind.

Die endgültige Schlussfolgerung lautet also:

  • Der Abschluss ist zunächst eine Struktur. Die Komponenten dieser Struktur sind eine Funktion + der lexikalische Umfang der Funktion闭包首先是一个结构体,这个结构体的组成部分为  一个函数 + 该函数所处的词法作用域
  • 也就是闭包是由一个函数并且该函数能够记住声明自己的词法作用域所产生的结构体
  • 在内存中理解就是, 当一个函数被调用时,它所产生的函数执行上下文里的作用域链保存有其父词法作用域,所以父变量对象由于存在被引用而不会销毁,驻留在内存中供其使用。这样的情况就称为闭包。

上述的解释对于已经了解过闭包的人应该是一目了然的,但其实如果对于一个完全不知晓闭包的人来说,很可能是完全看不懂的。更甚至很多人其实仅仅只是记住了这种定义,而不是真的理解了这内涵。

所以我想用一个不一定精准的类比去帮助理解什么是闭包这东西,想象你写了一篇文章放在自己的服务器上,并且引用了自己的3篇文章作为参考。那么此时 一篇文章 + 服务器的环境 就类似于闭包。

在发表到网络上后被转载到其他的平台上,而其他平台上的读者点开你的文章阅读后想继续看你所引用的那些文章,就被准确无误的跳转到了你服务器里的文章中去。

在这个例子中,这篇文章保存了写这篇文章的服务器环境里的引用。  因而不论是在哪里读到文章,文章里所记得的参考文章引用指向永远是服务器里的地址。 这种情况叫做使用了闭包的特性。

可能例子还是不太好理解,毕竟它也没有很准确,闭包这概念就是有点抽象,没有想到现实中有什么具体的例子可以用来比喻。 如果有人想出更好的类比可以指出,我加以注释和描述。

为什么要设计出闭包?

对于为什么设计这点,仅以我自己粗浅的理解就是由于JavaScript是异步单线程的语言。对于异步编程来说,最大的问题就是当你编写了函数,而等到它真正调用的时机可能是之后任意的时间节点。

这对于内存管理来说是一个很大的问题,正常同步执行的代码,函数声明时和被调用时所需要的数据都还存留在内存中,可以无障碍的获取。而异步的代码,往往声明该函数的上下文可能已经销毁,等到在调用它时,如果内存中已经把它所需要的一些外部数据给清理了,这就是个很大的问题。

所以JavaScript解决的方案就是让函数能够记得自己之前所能获取数据的范围,统统都保存在内存里,只要该函数没有被内存回收,它自身以及所能记住的范围都不会被销毁

Das ist A Der Abschluss ist eine von einer Funktion generierte Struktur, die sich den lexikalischen Bereich ihrer Deklaration merkt.

Das Verständnis im Speicher besteht darin, dass beim Aufruf einer Funktion die Bereichskette im von ihr generierten Funktionsausführungskontext ihren übergeordneten lexikalischen Bereich speichert, sodass das übergeordnete Variablenobjekt nicht zerstört wird, da es existiert und referenziert wird im Speicher für seine Verwendung. Diese Situation wird Schließung genannt.

Die obige Erklärung sollte für Menschen klar sein, die Schließungen bereits verstanden haben. Wenn sie jedoch keine Ahnung von Schließungen haben, sind sie möglicherweise völlig unverständlich. Darüber hinaus erinnern sich viele Menschen nur an diese Definition, verstehen aber die Konnotation nicht wirklich.

🎜Daher möchte ich eine Analogie verwenden, die nicht unbedingt korrekt ist, um zu verstehen, was Abschlüsse sind. Stellen Sie sich vor, Sie hätten einen Artikel geschrieben, ihn auf Ihren eigenen Server gestellt und drei Ihrer eigenen Artikel als Referenz zitiert. Zu diesem Zeitpunkt ähnelt eine Artikel- und Serverumgebung einem Abschluss. 🎜🎜Nachdem es im Internet veröffentlicht wurde, wurde es auf anderen Plattformen nachgedruckt, und Leser auf anderen Plattformen klickten auf Ihren Artikel, um ihn zu lesen und wollten die von Ihnen zitierten Artikel weiterlesen, und sie wurden genau zu Ihrem Server weitergeleitet . 🎜🎜In diesem Beispiel speichert dieser Artikel einen Verweis auf die Serverumgebung, in der dieser Artikel geschrieben wurde. Unabhängig davon, wo Sie den Artikel lesen, verweist die im Artikel gespeicherte Referenzartikelreferenz immer auf die Adresse auf dem Server. Diese Situation wird mit der Schließungsfunktion aufgerufen. 🎜🎜Vielleicht ist das Beispiel immer noch nicht leicht zu verstehen, schließlich ist es nicht sehr genau. Das Konzept des Abschlusses ist etwas abstrakt und mir sind keine konkreten Beispiele in der Realität eingefallen, die als Metapher verwendet werden könnten. Wenn jemand eine bessere Analogie hat, auf die ich hinweisen kann, werde ich sie kommentieren und beschreiben. 🎜

Warum sollten wir Abschlüsse entwerfen? 🎜🎜Was den Grund für dieses Design betrifft, so verstehe ich ganz einfach, dass JavaScript eine asynchrone Single-Threaded-Sprache ist. Das größte Problem bei der asynchronen Programmierung besteht darin, dass beim Schreiben einer Funktion der tatsächliche Aufrufzeitpunkt möglicherweise jederzeit später liegt. 🎜🎜Dies ist ein großes Problem für die Speicherverwaltung. Bei normalerweise synchron ausgeführtem Code bleiben die beim Deklarieren und Aufrufen der Funktion erforderlichen Daten weiterhin im Speicher und können ungehindert ausgeführt werden. Asynchroner Code gibt häufig an, dass der Kontext der Funktion möglicherweise zerstört wurde, wenn zum Zeitpunkt des Aufrufs einige externe Daten, die sie benötigt, im Speicher gelöscht wurden. Dies stellt ein großes Problem dar. 🎜🎜Die Lösung von JavaScript besteht also darin, der Funktion zu ermöglichen, sich an den Datenbereich zu erinnern, den sie zuvor abrufen kann, und sie alle im Speicher zu speichern, solange die Funktion nicht vom Speicher wiederverwendet wird nicht wird zerstört. 🎜🎜Der einprägsame Bereich bezieht sich hier auf den lexikalischen Bereich, der im Gedächtnis behalten werden muss, da er statisch ist. 🎜🎜Dies liegt wiederum am statischen Designumfang von JavaScript. Wenn es sich um einen dynamischen Bereich handelt, benötigt die Funktion beim Aufruf nur die Umgebung, wenn sie aufgerufen wird, und es ist nicht erforderlich, sich ihren eigenen Bereich zu merken. 🎜🎜Um es zusammenzufassen:🎜
  • 闭包是为了解决词法作用域引发的问题内存不好管理异步编程里数据获取所产生的。

经典题

原本我的想法是从最底层来解释闭包的情况,后来在查阅各种文章时发现, 有一篇文章已经写的很好了。 那就是JavaScript闭包的底层运行机制, 我觉得可以先看看这篇的讲解然后在看我之后所写的内容。

由于有非常多的文章都从下面这个非常经典的面试题入手,但似乎都没有人真正从最底层讲解过,所以我就打算将整个过程梳理一遍,来明白这其中的差异性。

for (var i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

基本所有有基础的人一眼就能看出输出的是三个3。

然后让修改成按顺序输出,通常只需要修改var成let:

for (let i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

这样就成了输出为0,1,2.并且是同时间输出,而不是每间隔一秒输出一次。

那么问题来了,为什么?

这里可以先不看下面,先写写自己的解释,看看是否跟我写的一样。

1. 先来探讨变量i是var的情况。

当代码开始执行时,此时执行上下文栈和内存里的情况是这样: 其中全局对象里的变量i和全局执行上下文里变量环境里的变量i是同一个变量。

然后开始进行循环, 当 i = 0时,第一个定时器被丢入宏任务队列,关于宏任务相关的内容属于事件循环范畴,暂时只需要理解setTimeout会被丢入队列里,等之后执行。 此时在堆内存中会创建它的回调函数cb,并且函数创建时会创建[[scope]],在实际ECMA的规则中,[[scope]]会指向该函数的父作用域,也就是当前的全局对象(作用域是概念上的东西,实际体现在内存中就是保存数据的一种结构,可能是对象也可能是其他)。 但是在V8引擎的实现中,其实并不会指向全局对象,而是去分析该函数使用了父作用域中的哪些变量,将这些变量存储到Closure中,然后由scope指向。每个函数都有且只有一个Closure对象。


这里先插入一下关于Closure对象可以在Chrome中哪看到的情况: 可以看到,创建bar函数时,它只有引用了父作用域的name变量,所以在闭包对象中只会存储变量name, 而不会存在变量age。


同理之后的 i = 1, 和 i = 2 都是一样的,最终结果会变成:

最终因为 i++导致 i = 3, 循环结束,全局代码执行完毕。此时的结果为:

然后开始进入定时器回调函数执行的过程, 开始执行第一个定时器里的回调函数,压入了执行上下文栈中,执行输出i, 但是在词法环境和变量环境中找不到这个变量i,所以去自身[[scope]]向上寻找,在Closure对象中找到了 i 等于3,输出结果3。

同理对于后面两个定时器也是一样的流程,并且实际上定时器开启的时间都是在循环中就立即执行的,导致实际上三个函数的定时1秒时间是一致的,最终输出的结果是几乎同时输出3个3。而不是每间隔1秒后输出3, 当然这是定时器相关的知识了。

2. 然后探讨通过var修改成let之后实际上变了什么

同样是刚创建时,所展示的情况为:

之后进入循环体,当i = 0时:

之后进入 i = 1时的情况:

最后进入到 i = 2的情况,与 i = 1基本类似:

最终 i++,变成i值为3,循环结束。开启定时器工作:

当执行第一个定时器的回调函数时,创建了函数执行上下文,此时执行输出语句i时,会先从自己的词法环境里寻找变量i的值,也就是在 record环境记录里搜索,但是不存在。因而通过自己外部环境引用outer找到原先创建的块级作用域里 i = 0的情况, 输出了i值为0的结果。

对于之后的定时器也都是一样的情况,原先的块级作用域由于被回调函数所引用到了,因而就产生了闭包的情况,不会在内存中被销毁,而是一直留着。

等到它们都执行完毕后,最终内存回收会将之全部都销毁。

其实以上画的图并不是很严谨,与实际在内存中的表现肯定是有差异的,但是对于理解闭包在内存里的情况还是不影响的。

闭包能用在哪?

首先需要先明确一点,那就是在JavaScript中,只要创建了函数,其实就产生了闭包。这是广义上的闭包,因为在全局作用域下声明的函数,也会记着全局作用域。而不是只有在函数内部声明的函数才叫做闭包。

通常意义上所讨论的闭包,是使用了闭包的特性

1. 函数作为返回值

let a = 1function outer() {  let a = 2

  function inside() {
    a += 1
    console.log(a)
  }  return inside
}const foo = outer()foo()

此处outer函数调用完时,返回了一个inside函数,在执行上下文栈中表示的既是outer函数执行上下文被销毁,但有一个返回值是一个函数。 该函数在内存中创建了一个空间,其[[scope]]指向着outer函数的作用域。因而outer函数的环境不会被销毁。

当foo函数开始调用时,调用的就是inside函数,所以它在执行时,先询问自身作用域是否存在变量a, 不存在则向上询问自己的父作用域outer,存在变量a且值为2,最终输出3。

2. 函数作为参数

var name = &#39;xavier&#39;function foo() {  var name = &#39;parker&#39;
  function bar() {    console.log(name)
  } console.log(name)  return bar
}function baz(fn) {  var name = &#39;coin&#39;
  fn()
}baz(foo())baz(foo)

对于第一个baz函数调用,输出的结果为两个'parker'。 对于第二个baz函数的调用,输出为一个'parker'。

具体的理解其实跟上面一致,只要函数被其他函数调用,都会存在闭包。

3. 私有属性

闭包可以实现对于一些属性的隐藏,外部只能获取到属性,但是无法对属性进行操作。

function foo(name) {  let _name = name  return {    get: function() {      return _name
    }
  }
}let obj = foo(&#39;xavier&#39;)
obj.get()

4. 高阶函数,科里化,节流防抖等

对于一些需要存在状态的函数,都是使用到了闭包的特性。

// 节流function throttle(fn, timeout) {  let timer = null
  return function (...arg) {    if(timer) return
    timer = setTimeout(() => {
    fn.apply(this, arg)
    timer = null
    }, timeout)
  }
}// 防抖function debounce(fn, timeout){  let timer = null
  return function(...arg){    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arg)
    }, timeout)
  }
}

5. 模块化

在没有模块之前,对于不同地方声明的变量,可能会产生冲突。而闭包能够创造出一个封闭的私有空间,为模块化提供了可能性。 可以使用IIFE+闭包实现模块。

var moduleA = (function (global, doc) {  var methodA = function() {};  var dataA = {};  return {    methodA: methodA,    dataA: dataA
  };
})(this, document);

【相关推荐:JavaScript视频教程web前端

Das obige ist der detaillierte Inhalt vonSchließung der JavaScript-Zusammenfassung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen