Dieser Artikel bietet eine ausführliche Analyse (Codebeispiel) des Javascript-Bereichs. Ich hoffe, dass er für Freunde hilfreich ist.
Geltungsbereich
Der Geltungsbereich ist eine Reihe von Regeln, die bestimmen, wo und wie Variablen (Bezeichner) gefunden werden. Wenn der Zweck der Suche darin besteht, einer Variablen einen Wert zuzuweisen, wird die LHS-Abfrage verwendet. Wenn der Zweck darin besteht, den Wert der Variablen zu ermitteln, wird die RHS-Abfrage verwendet. Zuweisungsoperatoren verursachen LHS-Abfragen. Der =-Operator oder die Operation der Übergabe von Parametern beim Aufruf einer Funktion führt zu einer Zuweisungsoperation im zugehörigen Bereich.
Die JavaScript-Engine kompiliert zunächst den Code, bevor er ausgeführt wird. Während dieses Prozesses wird eine Anweisung wie var a = 2 in zwei separate Schritte unterteilt:
Zuerst , var a deklariert die neue Variable in ihrem Gültigkeitsbereich. Dies geschieht ganz am Anfang, bevor der Code ausgeführt wird.
Als nächstes fragt a = 2 die Variable a ab (LHS-Abfrage) und weist ihr einen Wert zu.
Sowohl LHS- als auch RHS-Abfragen starten im aktuellen Ausführungsbereich und suchen bei Bedarf (d. h. wenn sie den erforderlichen Bezeichner nicht finden) weiterhin im oberen Bereich nach . Die Zielkennung, sodass sie jedes Mal, wenn sie eine Ebene des Gültigkeitsbereichs (eine Etage) nach oben geht und schließlich die globale Ebene (oberste Ebene) erreicht, anhält, unabhängig davon, ob sie gefunden wird oder nicht.
Fehlgeschlagene RHS-Referenzen führen dazu, dass eine ReferenceError-Ausnahme ausgelöst wird. Eine fehlgeschlagene LHS-Referenz führt zur automatischen und impliziten Erstellung einer globalen Variablen (im nicht-strikten Modus), die das Ziel der LHS-Referenz als Bezeichner verwendet, oder zu einer ReferenceError-Ausnahme (im strikten Modus).
Lexikalischer Geltungsbereich
Lexikalischer Geltungsbereich bedeutet, dass der Geltungsbereich durch die Position der Funktionsdeklaration beim Schreiben des Codes bestimmt wird. Die lexikalische Analysephase der Kompilierung weiß grundsätzlich, wo und wie alle Bezeichner deklariert werden, sodass sie vorhersagen kann, wie sie während der Ausführung nachgeschlagen werden.
Es gibt in JavaScript zwei Mechanismen, um den lexikalischen Bereich zu „betrügen“: eval(..) und with . Ersteres wertet einen „Code“-String aus, der eine oder mehrere Deklarationen enthält, und verändert dadurch einen vorhandenen lexikalischen Bereich (zur Laufzeit). Letzteres erstellt im Wesentlichen einen neuen lexikalischen Bereich (erneut zur Laufzeit), indem es einen Verweis auf ein Objekt als Bereich und die Eigenschaften des Objekts als Bezeichner innerhalb des Bereichs behandelt.
Ein Nebeneffekt dieser beiden Mechanismen besteht darin, dass die Engine die Bereichssuche zur Kompilierungszeit nicht optimieren kann, da die Engine solche Optimierungen nur vorsichtig als ungültig betrachten kann. Die Verwendung eines dieser Mechanismen führt dazu, dass Ihr Code langsamer ausgeführt wird. Benutze sie nicht.
Da JavaScript den lexikalischen Bereich verwendet, wird der Umfang der Funktion bestimmt, wenn die Funktion definiert wird.
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope();//local scope
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()();//local scope
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo();//local scope
Funktionsdeklaration:Funktionsname (Parameter: optional) {Funktionskörper}
Funktionsausdruck:Funktion Funktionsname (optional) (Parameter: optional){Funktionskörper}
Identifikation:
Wenn der Funktionsname nicht deklariert ist, muss er sein ein Ausdruck.
Wenn die Funktion foo(){} Teil eines Zuweisungsausdrucks ist, handelt es sich um einen Funktionsausdruck, wenn die Funktion foo(){} in einem Funktionskörper enthalten ist Ganz oben im Programm handelt es sich um eine Funktionsdeklaration.
(Funktion foo(){}) ist in Klammern eingeschlossen. Der Grund, warum es sich um einen Ausdruck handelt, liegt darin, dass Klammern () ein Gruppierungsoperator sind und nur Ausdrücke darin enthalten können Die Funktionsdeklaration der Formel
function foo(){} // 声明,因为它是程序的一部分 (function(){ function bar(){} // 声明,因为它是函数体的一部分 })(); var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function foo(){}); // 表达式:包含在分组操作符内 try { (var x = 5); // 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句 } catch(err) { // SyntaxError }
kann in bedingten Anweisungen verwendet werden, sie wurde nicht standardisiert. Am besten verwenden Sie Funktionsausdrücke
Funktionsdeklaration überschreibt Variablendeklaration, aber nicht Variablenzuweisung
function value(){ return 1; } var value; alert(typeof value); //"function"
Funktion ist die häufigste Rolle in der JavaScript-Domäneneinheit . Im Wesentlichen ist eine innerhalb einer Funktion deklarierte Variable oder Funktion vor dem Bereich, in dem sie sich befindet, „versteckt“. Dies ist ein bewusstes und gutes Software-Designprinzip. Aber Funktionen sind nicht die einzige Umfangseinheit.
Blockbereich bedeutet, dass Variablen und Funktionen nicht nur zu dem Bereich gehören können, in dem sie sich befinden, sondern auch zu einem bestimmten Codeblock (normalerweise innerhalb von { .. }).
Ab ES3 haben Try/Catch-Konstrukte einen Blockbereich in der Catch-Klausel.
Das in ES6 eingeführte Schlüsselwort let (Cousin des Schlüsselworts var) wird zum Deklarieren von Variablen in jedem Codeblock verwendet.
if(..) { let a = 2; } deklariert eine Variable, die den Block { .. } von if übernimmt und die Variable diesem Block hinzufügt.
Manche Leute glauben, dass der Blockbereich nicht vollständig als Ersatz für den Funktionsbereich verwendet werden sollte. Beide Funktionen sollten gleichzeitig vorhanden sein. Entwickler können und sollten entsprechend ihren Anforderungen auswählen, welchen Bereich sie verwenden möchten, um guten Code zu erstellen, der lesbar und wartbar ist.
我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引
起很多危险的问题!
var a; if (!("a" in window)) { a = 1; } alert(a);
通常,程序员会错误的认为,只有匿名函数才是闭包。其实并非如此,正如我们所看到的 —— 正是因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包), 这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。 为了更好的澄清该问题,我们对ECMAScript中的闭包作两个定义(即两种闭包):
ECMAScript中,闭包指的是:
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })( i ); } for (var i=1; i<=5; i++) { let j = i; // 是的,闭包的块作用域! setTimeout( function timer() { console.log( j ); }, j*1000 ); } for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0]();//3 data[1]();//3 data[2]();//3
模块有两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回
值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭
包。
var MyModules = (function Manager() { var modules = {}; function define(name, deps, impl) { for (var i=0; i<deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply( impl, deps ); } function get(name) { return modules[name]; } return { define: define, get: get }; })();
//bar.js function hello(who) { return "Let me introduce: " + who; } export hello;
//foo.js // 仅从 "bar" 模块导入 hello() import hello from "bar"; var hungry = "hippo"; function awesome() { console.log( hello( hungry ).toUpperCase() ); } export awesome;
baz.js // 导入完整的 "foo" 和 "bar" 模块 module foo from "foo"; module bar from "bar"; console.log( bar.hello( "rhino" ) ); // Let me introduce: rhino foo.awesome(); // LET ME INTRODUCE: HIPPO
Google 维护着一个名为 Traceur 的项目,该项目正是用来将 ES6 代码转换成兼容 ES6 之前的环境(大部分是 ES5,但不是全部)。TC39 委员会依赖这个工具(也有其他工具)来测试他们指定的语义化相关的功能。
{ try { throw undefined; } catch (a) { a = 2; console.log( a ); } } console.log( a )
EC={ VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */}, this:{}, Scope:{ /* VO以及所有父执行上下文中的VO */} }
//ECS=[Window] A(//ECS=[Window,A] B(//ECS=[Window,A,B] //run B ) //ECS=[Window,A] ) //ECS=[Window]
var a = 10; function test(x) { var b = 20; }; test(30); /* VO(globalContext) a: 10, test: VO(test functionContext) x: 30 b: 20 */
function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function x() {}); } test(10); /* AO(test) = { a: 10, b: undefined, c: undefined, d: <reference to FunctionDeclaration "d"> e: undefined }; */
Scope = AO|VO + [[Scope]]
var x = 10; function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar(); } foo(); // 60
全局上下文的变量对象是:
globalContext.VO === Global = { x: 10 foo: <reference to function> };
在“foo”创建时,“foo”的[[scope]]属性是:
foo.[[Scope]] = [ globalContext.VO ];
在“foo”激活时(进入上下文),“foo”上下文的活动对象是:
fooContext.AO = { y: 20, bar: <reference to function> };
“foo”上下文的作用域链为:
fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: fooContext.Scope = [ fooContext.AO, globalContext.VO ];
内部函数“bar”创建时,其[[scope]]为:
bar.[[Scope]] = [ fooContext.AO, globalContext.VO ];
在“bar”激活时,“bar”上下文的活动对象为:
barContext.AO = { z: 30 };
“bar”上下文的作用域链为:
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.: barContext.Scope = [ barContext.AO, fooContext.AO, globalContext.VO ];
对“x”、“y”、“z”的标识符解析如下:
- "x" -- barContext.AO // not found -- fooContext.AO // not found globalContext.VO // found - 10 - "y" -- barContext.AO // not found fooContext.AO // found - 20 - "z" barContext.AO // found - 30
Das obige ist der detaillierte Inhalt vonEingehende Analyse des Javascript-Bereichs (Codebeispiel). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!