Heim Web-Frontend js-Tutorial 浅谈Javascript嵌套函数及闭包_javascript技巧

浅谈Javascript嵌套函数及闭包_javascript技巧

May 16, 2016 pm 06:16 PM
嵌套函数 闭包

【嵌套函数】
  JavaScript允许嵌入的函数,允许函数用作数据,并且在函数词法作用域下面,可以产生与传统面向对象语言不同的惊人地方。
  首先,JavaScript的函数是通过词法来划分作用域的,而不是动态的划分作用域的,于是,函数的是在定义它们的作用域中运行,而不是在执行它们的作用域中运行,所以,当嵌套函数和它的外围函数定义在同一个词法作用域中的时候,是很容易理解的。比如下面很平淡无奇的代码:
复制代码 代码如下:

var x = 'global';
function f () {
var x = 'local';
function g() {
alert(x);
}
g();
}
f(); // 'local'

当f()调用的时候,作用域链可以理解为由两部分组成,包含f这一调用的调用对象,然后后面是全局对象。此时查找x的值,会先从f的调用对象中查找,如果没有,再查找后面全局对象中x。同理,g因为是f的一个嵌套函数,那么,g调用的时候,作用域链应该就是由三部分组成了,g的调用对象,f的调用对象,和全局对象。函数g是要输出x的值,所以会先在g的调用对象中查找x的值,g中没有定义,接下来查找外围f调用对象中x的定义,于是找到了x='local',那么就会输出x,而不会继续往下查找全局对象了。 如果f中也没定义x的值,那么就会继续查找作用域链后面的全局对象,结果就是global了。如果全局对象中也没定义,那么自然就是undefined。
  好了,我们对作用域链有了个初步的理解,同时我们知道,闭包有两个比较常用的用途,一个是可以利用它访问到局部变量,另一个是可以把它外围作用域中的变量值存储在内存中而不在函数调用完毕后就销毁。
  下面接着看一个平淡无奇的例子,或许可以帮助理解为什么闭包可以把外部变量值保存在内存中了。
复制代码 代码如下:

function makeFunc (x) {
return function () {return x++}
}
var a = [makeFunc(0), makeFunc(1), makeFunc(2)];
alert(a[0]());
alert(a[1]());
alert(a[2]());

执行结果为0,1,2 ;也没有什么特别的地方,这也是严格的词法作用域的正常表现。每次makeFunc调用完毕后,它的调用对象会从作用域链中移除,再没有任何对它的引用,最终通过垃圾收集而完结。说的详细一点,我们可以这样理解。
  makeFunc每次调用的时候,会为他创建一个调用对象放置到作用域链中。针对makeFunc这个函数而言,这个调用对象包含一个属性x(也就是函数的参数,因为函数参数可以看做调用对象的一个属性),makeFunc会返回一个匿名嵌套函数的引用,接下来这个匿名嵌套函数执行,又会创建一个调用对象,放置到作用域链中,匿名函数返回x的值,(注意:匿名函数的调用对象中是没有x的定义的,于是它会引用到它外围的函数makeFunc的调用对象,访问到x)然后x加1,至此,匿名函数执行完毕,它调用对象从作用域链中移除, 然后makeFunc也执行完毕,makeFunc调用对象也被移除。由于它的调用对象中包含x,所以x也随着它的销毁而销毁。不会保存下来。
  以上就是函数的详细的执行过程,请仔细理解后看看下面改动的代码:
复制代码 代码如下:

var x = 0;
function makeFunc () {
return function () {return x++}
}
var a = [makeFunc(), makeFunc(), makeFunc()];
alert(a[0]());
alert(a[1]());
alert(a[2]());

现在x是一个全局变量了,执行结果为0,1,2;但是这个结果就与上面的有些不同了。下面我们还是从作用域链的方向来理解这个结果产生的原因。
  同样,makeFunc每次调用的时候会创建一个调用对象到作用域链中,由于它返回内部嵌套函数的引用,所以内部嵌套函数开始执行,又创建一个嵌套函数的调用对象到作用域链。然后返回x的值,注意,这里就不同了,嵌套函数的调用对象中没有x,它外围的makeFunc的调用对象中也没有x,只能接着往下查找到全局对象中,在全局对象中找到了x的定义,于是正常执行,返回x的值,x加1,然后嵌套函数完毕,调用对象移除,接着makeFunc完毕,调用对象也移除,可是因为他们的调用对象中都没有x,他们的调用对象销毁根本不会影响到x。于是,全局变量x值的改变就这样被保存下来了。
  注意,上面说的访问外围的调用对象只是为了帮助理解而不严格的说法,JavaScript不会以任何方式直接访问调用对象,但是,它定义的属性作为调用对象中作用域链的一部分,还是“活的”。另外,如果一个外围函数包含了两个或多个嵌套函数都对全局对象有引用,那么这些嵌套函数都共享同一个全局调用对象,并且其中一个对全局对象的改变对其他的都是可见的。
  好了,在JavaScript里,函数是将要执行的代码以及执行这些代码的作用域构成的一个综合体,广义的说,我们就可以把这种代码和作用域的综合体叫做闭包。
  【闭包】
  我们偶尔需要写一个需要通过调用来记住一个变量值的函数。于是,如果我们了解了作用域,就会知道,局部变量是很难做到的,因为函数的调用对象不能在调用后一直维持。全局变量可以做到,就如上面的例子一样,但是这样很容易造成全局变量污染。既然调用对象不能维持,那么我们不把值保存在调用对象中不就行了?!所以,下面是实现的一种方法:用函数对象自身的属性来保存。
复制代码 代码如下:

uniqueID = function () {
if (!arguments.callee.id) arguments.callee.id = 0;
return arguments.callee.id ++;
}
alert(uniqueID()); //0
alert(uniqueID()); //1

如上,因为函数本身就是一个对象,所以,我们用它自身的一个属性来保存是可行的,但是这样做有一个问题,就是任何人在任何时候都可以通过unqueID.id强制访问到我们原本保存到的值并作出修改。这是我们不愿看到的。
  所以,通常,我们使用闭包来实现这件事。如下:
复制代码 代码如下:

_uniqueID = (function(){
var id = 0;
return function () {return id ++}
})();
alert(_uniqueID()); //0
alert(_uniqueID()); //1

同样,我们也用作用链域来解释下结果。注意到_uniqueID本身就是一个匿名函数,它内部又有个匿名嵌套函数,我们直接调用的是_uniqueID(),也就是说,我们直接调用的其实是_uniqueID内部的嵌套函数,而它本身的调用对象没有定义id,于是引用外围的调用对象中的id,并返回,id加1,执行完毕,内层嵌套函数调用对象移出作用域链。而外围的id并没有被销毁,于是就这样保存了下来。
  有人可能会疑惑,不是说调用对象在函数执行完毕后就移除了作用域链吗,外围匿名函数(function(){})();也是调用完毕了的,应该调用对象也没了才对。
  是的,调用对象是在当前函数执行完毕后就结束引用,但是这里不要误解了上面_uniqueID()的调用,他并不是直接调用的外围函数,而是调用的嵌套函数,嵌套函数的作用域链是包含外围函数的作用域链的。所以在它的调用对象移除作用域链的时候是能够访问到这条作用域链上其他对象的属性并改变的。
  闭包本身就是个难以理解但是又非常有用的东西,希望能对有需要的人一些帮助吧。此外,资历所限,本人理解也可能有误,如发现,敬请指正。
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Was bedeutet der Abschluss im C++-Lambda-Ausdruck? Was bedeutet der Abschluss im C++-Lambda-Ausdruck? Apr 17, 2024 pm 06:15 PM

In C++ ist ein Abschluss ein Lambda-Ausdruck, der auf externe Variablen zugreifen kann. Um einen Abschluss zu erstellen, erfassen Sie die äußere Variable im Lambda-Ausdruck. Abschlüsse bieten Vorteile wie Wiederverwendbarkeit, Ausblenden von Informationen und verzögerte Auswertung. Sie sind in realen Situationen nützlich, beispielsweise bei Ereignishandlern, bei denen der Abschluss auch dann noch auf die äußeren Variablen zugreifen kann, wenn diese zerstört werden.

Wie implementiert man einen Abschluss im C++-Lambda-Ausdruck? Wie implementiert man einen Abschluss im C++-Lambda-Ausdruck? Jun 01, 2024 pm 05:50 PM

C++-Lambda-Ausdrücke unterstützen Abschlüsse, die Funktionsbereichsvariablen speichern und sie für Funktionen zugänglich machen. Die Syntax lautet [capture-list](parameters)->return-type{function-body}. Capture-Liste definiert die zu erfassenden Variablen. Sie können [=] verwenden, um alle lokalen Variablen nach Wert zu erfassen, [&], um alle lokalen Variablen nach Referenz zu erfassen, oder [Variable1, Variable2,...], um bestimmte Variablen zu erfassen. Lambda-Ausdrücke können nur auf erfasste Variablen zugreifen, den ursprünglichen Wert jedoch nicht ändern.

Was sind die Vor- und Nachteile von Abschlüssen in C++-Funktionen? Was sind die Vor- und Nachteile von Abschlüssen in C++-Funktionen? Apr 25, 2024 pm 01:33 PM

Ein Abschluss ist eine verschachtelte Funktion, die auf Variablen im Bereich der äußeren Funktion zugreifen kann. Zu ihren Vorteilen gehören Datenkapselung, Zustandserhaltung und Flexibilität. Zu den Nachteilen gehören der Speicherverbrauch, die Auswirkungen auf die Leistung und die Komplexität des Debuggens. Darüber hinaus können Abschlüsse anonyme Funktionen erstellen und diese als Rückrufe oder Argumente an andere Funktionen übergeben.

Lösen Sie das durch Schließungen verursachte Speicherverlustproblem Lösen Sie das durch Schließungen verursachte Speicherverlustproblem Feb 18, 2024 pm 03:20 PM

Titel: Durch Abschlüsse und Lösungen verursachte Speicherlecks Einführung: Abschlüsse sind ein sehr verbreitetes Konzept in JavaScript, das internen Funktionen den Zugriff auf Variablen externer Funktionen ermöglicht. Allerdings können Schließungen bei falscher Verwendung zu Speicherverlusten führen. In diesem Artikel wird das durch Schließungen verursachte Speicherverlustproblem untersucht und Lösungen sowie spezifische Codebeispiele bereitgestellt. 1. Durch Schließungen verursachte Speicherlecks Das Merkmal von Schließungen besteht darin, dass interne Funktionen auf Variablen externer Funktionen zugreifen können, was bedeutet, dass in Schließungen referenzierte Variablen nicht durch Müll gesammelt werden. Bei unsachgemäßer Verwendung

Verkettete Aufrufe und Schließungen von PHP-Funktionen Verkettete Aufrufe und Schließungen von PHP-Funktionen Apr 13, 2024 am 11:18 AM

Ja, die Einfachheit und Lesbarkeit des Codes können durch verkettete Aufrufe und Abschlüsse optimiert werden: Verkettete Aufrufe verknüpfen Funktionsaufrufe in einer fließenden Schnittstelle. Abschlüsse erstellen wiederverwendbare Codeblöcke und greifen auf Variablen außerhalb von Funktionen zu.

Die Rolle des Golang-Funktionsschlusses beim Testen Die Rolle des Golang-Funktionsschlusses beim Testen Apr 24, 2024 am 08:54 AM

Funktionsabschlüsse der Go-Sprache spielen beim Unit-Testen eine wichtige Rolle: Werte erfassen: Abschlüsse können auf Variablen im äußeren Bereich zugreifen, sodass Testparameter erfasst und in verschachtelten Funktionen wiederverwendet werden können. Vereinfachen Sie den Testcode: Durch die Erfassung von Werten vereinfachen Abschlüsse den Testcode, indem sie die Notwendigkeit beseitigen, Parameter für jede Schleife wiederholt festzulegen. Verbessern Sie die Lesbarkeit: Verwenden Sie Abschlüsse, um die Testlogik zu organisieren und so den Testcode klarer und leichter lesbar zu machen.

Der Einfluss von Funktionszeigern und -abschlüssen auf die Golang-Leistung Der Einfluss von Funktionszeigern und -abschlüssen auf die Golang-Leistung Apr 15, 2024 am 10:36 AM

Die Auswirkungen von Funktionszeigern und -abschlüssen auf die Go-Leistung sind wie folgt: Funktionszeiger: Etwas langsamer als direkte Aufrufe, aber verbessert die Lesbarkeit und Wiederverwendbarkeit. Schließungen: Normalerweise langsamer, kapseln aber Daten und Verhalten. Praktischer Fall: Funktionszeiger können Sortieralgorithmen optimieren und Abschlüsse können Ereignishandler erstellen, aber sie bringen Leistungseinbußen mit sich.

Wie werden Schließungen in Java implementiert? Wie werden Schließungen in Java implementiert? May 03, 2024 pm 12:48 PM

Abschlüsse in Java ermöglichen es inneren Funktionen, auf äußere Bereichsvariablen zuzugreifen, selbst wenn die äußere Funktion beendet wurde. Durch anonyme innere Klassen implementiert, enthält die innere Klasse einen Verweis auf die äußere Klasse und hält die äußeren Variablen aktiv. Schließungen erhöhen die Codeflexibilität, Sie müssen sich jedoch des Risikos von Speicherverlusten bewusst sein, da Verweise auf externe Variablen durch anonyme innere Klassen diese Variablen am Leben halten.

See all articles