Heim > Artikel > Web-Frontend > Vertiefendes Verständnis des JavaScript-Umfangs und -Abschlusses_Grundkenntnisse
Geltungsbereich
Der Gültigkeitsbereich ist der Gültigkeitsbereich einer Variablen und einer Funktion. Alle innerhalb einer Funktion in JavaScript deklarierten Variablen sind immer im Funktionskörper sichtbar. In JavaScript gibt es einen globalen Gültigkeitsbereich und einen lokalen Gültigkeitsbereich Variablen haben eine höhere Priorität als globale Variablen. Lassen Sie uns anhand einiger Beispiele die „versteckten Regeln“ des Gültigkeitsbereichs in JavaScript verstehen (dies sind auch Fragen, die häufig in Front-End-Interviews gestellt werden).
1. Variablen im Voraus deklarieren
Beispiel 1:
var scope="global"; function scopeTest(){ console.log(scope); var scope="local" } scopeTest(); //undefined
Die Ausgabe hier ist undefiniert und es wird kein Fehler gemeldet. Dies liegt daran, dass die zuvor erwähnte Deklaration innerhalb der Funktion immer im Funktionskörper sichtbar ist. Die obige Funktion entspricht:
var scope="global"; function scopeTest(){ var scope; console.log(scope); scope="local" } scopeTest(); //local
Beachten Sie, dass die Variable als globale Variable deklariert wird, wenn Sie var vergessen.
2. Kein Geltungsbereich auf Blockebene
Im Gegensatz zu anderen häufig verwendeten Sprachen gibt es in Javascript keinen Gültigkeitsbereich auf Blockebene:
function scopeTest() { var scope = {}; if (scope instanceof Object) { var j = 1; for (var i = 0; i < 10; i++) { //console.log(i); } console.log(i); //输出10 } console.log(j);//输出1 }
Der Umfang der Variablen in JavaScript ist auf Funktionsebene, das heißt, alle Variablen in der Funktion werden in der gesamten Funktion definiert. Dies bringt auch einige „versteckte Regeln“ mit sich, auf die wir stoßen, wenn wir nicht aufpassen:
var scope = "hello"; function scopeTest() { console.log(scope);//① var scope = "no"; console.log(scope);//② }
Der Wert, der bei ① ausgegeben wurde, war undefiniert, was wirklich verrückt ist. Wir haben den Wert der globalen Variablen bereits definiert. Sollte dieser Ort nicht „Hallo“ sein? Tatsächlich entspricht der obige Code:
var scope = "hello"; function scopeTest() { var scope; console.log(scope);//① scope = "no"; console.log(scope);//② }
Im Voraus deklarieren und globale Variablen haben eine niedrigere Priorität als lokale Variablen. Nach diesen beiden Regeln ist es nicht schwer zu verstehen, warum undefiniert ausgegeben wird.
Scope-Kette
In JavaScript hat jede Funktion ihren eigenen Ausführungskontext. Wenn der Code in dieser Umgebung ausgeführt wird, wird eine Bereichskette aus variablen Objekten erstellt, die einen geordneten Zugriff gewährleistet auf variable Objekte.
Das vordere Ende der Bereichskette ist das Variablenobjekt der aktuellen Codeausführungsumgebung, das oft als „aktives Objekt“ bezeichnet wird. Die Suche nach Variablen beginnt beim Objekt der ersten Kette. Wenn das Objekt Variablenattribute enthält Wenn nicht, wird die Suche in der oberen Bereichskette fortgesetzt, bis das globale Objekt gefunden wird:
Die schrittweise Suche in der Bereichskette wirkt sich auch auf die Leistung des Programms aus. Je länger die variable Bereichskette ist, desto größer ist die Auswirkung auf die Leistung. Dies ist einer der Hauptgründe, warum wir die Verwendung vermeiden globale Variablen.
Schließung
Grundkonzepte
Der Bereich ist eine Voraussetzung für das Verständnis von Abschlüssen. Abschlüsse bedeuten, dass auf Variablen im externen Bereich immer innerhalb des aktuellen Bereichs zugegriffen werden kann.
function createClosure(){ var name = "jack"; return { setStr:function(){ name = "rose"; }, getStr:function(){ return name + ":hello"; } } } var builder = new createClosure(); builder.setStr(); console.log(builder.getStr()); //rose:hello
Das obige Beispiel gibt zwei Abschlüsse in der Funktion zurück. Beide Abschlüsse behalten Verweise auf den externen Bereich bei, sodass auf Variablen in der externen Funktion immer zugegriffen werden kann, unabhängig davon, wo sie aufgerufen werden. Eine innerhalb einer Funktion definierte Funktion fügt das aktive Objekt der externen Funktion zu ihrer eigenen Bereichskette hinzu. Daher kann im obigen Beispiel über die interne Funktion auf die Eigenschaften der externen Funktion zugegriffen werden private Variablen simulieren.
Hinweis: Da Abschlüsse über zusätzliche Funktionsbereiche verfügen (interne anonyme Funktionen tragen den Umfang externer Funktionen), beanspruchen Abschlüsse mehr Speicherplatz als andere Funktionen.
Variablen in Verschlüssen
Bei Verwendung eines Verschlusses kann der Verschluss aufgrund des Einflusses des Bereichskettenmechanismus nur den letzten Wert der internen Funktion erhalten. Ein Nebeneffekt davon ist, dass, wenn sich die interne Funktion in einer Schleife befindet, der Wert von Die Variable ist immer der letzte a-Wert.
//该实例不太合理,有一定延迟因素,此处主要为了说明闭包循环中存在的问题 function timeManage() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); },1000) }; }
Das obige Programm gibt die Zahlen 1-5 nicht wie erwartet ein, sondern gibt alle 5 Mal 5 aus. Schauen wir uns ein anderes Beispiel an:
function createClosure(){ var result = []; for (var i = 0; i < 5; i++) { result[i] = function(){ return i; } } return result; }
Der Rückgabewert des Aufrufs von createClosure()[0]() ist 5, und der Rückgabewert von createClosure()[4]() ist immer noch 5. Anhand der beiden oben genannten Beispiele können wir die Probleme erkennen, die Abschlüsse bei der Verwendung interner Funktionen mit Schleifen haben: Da in der Bereichskette jeder Funktion aktive Objekte für externe Funktionen (timeManage, createClosure) gespeichert sind, verweisen sie alle auf dieselbe Variable i. Wenn die externe Funktion zurückkehrt, ist der Wert von i zu diesem Zeitpunkt 5, sodass der Wert jeder internen Funktion i ebenfalls 5 ist.
Wie kann man dieses Problem lösen? Wir können die Rückgabe des erwarteten Ergebnisses über einen anonymen Wrapper (anonymer selbstausführender Funktionsausdruck) erzwingen:
function timeManage() { for (var i = 0; i < 5; i++) { (function(num) { setTimeout(function() { console.log(num); }, 1000); })(i); } }
Oder geben Sie eine anonyme Funktionszuweisung in der anonymen Abschlussfunktion zurück:
function timeManage() { for (var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) } } //timeManager();输出1,2,3,4,5 function createClosure() { var result = []; for (var i = 0; i < 5; i++) { result[i] = function(num) { return function() { console.log(num); } }(i); } return result; } //createClosure()[1]()输出1;createClosure()[2]()输出2
无论是匿名包裹器还是通过嵌套匿名函数的方式,原理上都是由于函数是按值传递,因此会将变量i的值复制给实参num,在匿名函数的内部又创建了一个用于返回num的匿名函数,这样每个函数都有了一个num的副本,互不影响了。
闭包中的this
在闭包中使用this时要特别注意,稍微不慎可能会引起问题。通常我们理解this对象是运行时基于函数绑定的,全局函数中this对象就是window对象,而当函数作为对象中的一个方法调用时,this等于这个对象(TODO 关于this做一次整理)。由于匿名函数的作用域是全局性的,因此闭包的this通常指向全局对象window:
var scope = "global"; var object = { scope:"local", getScope:function(){ return function(){ return this.scope; } } }
调用object.getScope()()返回值为global而不是我们预期的local,前面我们说过闭包中内部匿名函数会携带外部函数的作用域,那为什么没有取得外部函数的this呢?每个函数在被调用时,都会自动创建this和arguments,内部匿名函数在查找时,搜索到活跃对象中存在我们想要的变量,因此停止向外部函数中的查找,也就永远不可能直接访问外部函数中的变量了。总之,在闭包中函数作为某个对象的方法调用时,要特别注意,该方法内部匿名函数的this指向的是全局变量。
幸运的是我们可以很简单的解决这个问题,只需要把外部函数作用域的this存放到一个闭包能访问的变量里面即可:
var scope = "global"; var object = { scope:"local", getScope:function(){ var that = this; return function(){ return that.scope; } } } object.getScope()()返回值为local。
内存与性能
由于闭包中包含与函数运行期上下文相同的作用域链引用,因此,会产生一定的负面作用,当函数中活跃对象和运行期上下文销毁时,由于必要仍存在对活跃对象的引用,导致活跃对象无法销毁,这意味着闭包比普通函数占用更多的内存空间,在IE浏览器下还可能会导致内存泄漏的问题,如下:
function bindEvent(){ var target = document.getElementById("elem"); target.onclick = function(){ console.log(target.name); } }
上面例子中匿名函数对外部对象target产生一个引用,只要是匿名函数存在,这个引用就不会消失,外部函数的target对象也不会被销毁,这就产生了一个循环引用。解决方案是通过创建target.name副本减少对外部变量的循环引用以及手动重置对象:
function bindEvent(){ var target = document.getElementById("elem"); var name = target.name; target.onclick = function(){ console.log(name); } target = null; }
闭包中如果存在对外部变量的访问,无疑增加了标识符的查找路径,在一定的情况下,这也会造成性能方面的损失。解决此类问题的办法我们前面也曾提到过:尽量将外部变量存入到局部变量中,减少作用域链的查找长度。
总结:闭包不是javascript独有的特性,但是在javascript中有其独特的表现形式,使用闭包我们可以在javascript中定义一些私有变量,甚至模仿出块级作用域,但闭包在使用过程中,存在的问题我们也需要了解,这样才能避免不必要问题的出现。