Vor ES6 wurden Variablen mit var deklariert, und es gab ein Vorparsen von Variablen (auch Funktionen hatten ein Vorparsen), als sie mit dem Erlernen von JavaScript begannen wurde in ES6 und const eingeführt, aber ES6 ist zu diesem Zeitpunkt noch nicht ganz beliebt und viele ältere Codes werden immer noch nach ES5-Standards oder sogar ES3-Standards geschrieben.
01 Anzeige von Variablen und Funktionen im Speicher
Variablentypen in JavaScript sind die gleichen wie in anderen Sprachen, einschließlich Basisdatentypen und Referenzdatentypen. Zu den grundlegenden Datentypen gehören: undefiniert, null, boolesch, Zeichenfolge, Zahl; Referenzdatentypen sind hauptsächlich Objekte (einschließlich {}, [], /^$/, Datum, Funktion usw.).
var num = 24; var obj = {name:'iceman' , age:24}; function func() { console.log('hello world'); }
Wenn der Browser die HTML-Seite lädt, stellt er zunächst eine Umgebung für die globale Ausführung von JavaScript-Code bereit, die als globaler Bereich bezeichnet wird.
Basisdatentypen operieren mit Werten und Referenzdatentypen operieren mit Adressen.
Gemäß den oben genannten Prinzipien ist das Speichermodell des obigen Codes:
Memory model.png
Der Grundtyp ist Direkte Speicherung Im Stapelspeicher werden Objekte im Heap-Speicher gespeichert, Variablen enthalten lediglich die Adresse des Objekts. Obj enthält also die Adresse eines Objekts oxff44 und die Funktion func enthält die Adresse oxff66.
Auf der Grundlage des obigen Codes ausführen:
console.log(func); console.log(func());
Die erste Ausgabezeile ist der Definitionsteil der gesamten Funktion (der Funktion selbst):
Die erste Codezeile gibt die Datei result.png aus
Wie oben erklärt, speichert func eine Adresse, die auf einen Teil des Heap-Speichers verweist, und der Heap-Speicher behält die Definition der Funktion bei .
Die zweite Codezeile gibt das Rückgabeergebnis der Funktion func aus:
Die zweite Codezeile gibt das result.png aus
Aufgrund der Funktion func gibt es keinen Rückgabewert, daher ist die Ausgabe undefiniert.
Hinweis: Das Rückgabeergebnis der Funktion, was auch immer nach der Rückgabe geschrieben wird, ist der Rückgabewert. Wenn es keine Rückgabe gibt, ist der Standardrückgabewert undefiniert.
02 Vorparsing
Nachdem Sie das obige Speichermodell verstanden haben, können Sie den Mechanismus des Vorparsings besser verstehen. Das sogenannte Vorparsen ist: Im aktuellen Bereich deklariert oder definiert der Browser vor der Ausführung des JavaScript-Codes standardmäßig alle Variablen mit var- und Funktionsdeklarationen im Voraus.
2.1. Deklaration und Definition
var num = 24;
Diese einfache Codezeile besteht eigentlich aus zwei Schritten: Deklaration und Definition.
Deklaration: var num; teilt dem Browser mit, dass es eine num-Variable im globalen Bereich gibt. Wenn eine Variable nur deklariert, aber kein Wert zugewiesen wird, ist der Standardwert undefiniert.
Definition: num = 12; Die Definition besteht darin, der Variablen einen Wert zuzuweisen.
2.2. Der Unterschied zwischen durch var deklarierten Variablen und durch die Funktion beim Vorparsen deklarierten Funktionen
Es gibt einen Unterschied zwischen durch var deklarierten Variablen und durch die Funktion beim Vorparsen deklarierten Funktionen Die von var deklarierte Funktion wird nur während der Voranalyse im Voraus deklariert. Die von der Funktion deklarierte Funktion wird im Voraus deklariert und gleichzeitig während der Voranalyse definiert. Mit anderen Worten: Der Unterschied zwischen einer durch var deklarierten Variablen und einer durch function deklarierten Funktion besteht darin, ob sie gleichzeitig mit der Deklaration definiert wird.
2.3. Die Voranalyse erfolgt nur im aktuellen Bereich.
Zu Beginn des Programms werden nur Variablen und Funktionen unter dem Fenster vorab analysiert, und zwar nur, wenn die Funktion ausgeführt wird in-Funktionen werden als Funktionen vorbereitet.
console.log(num); var num = 24; console.log(num); func(100 , 200); function func(num1 , num2) { var total = num1 + num2; console.log(total); }
Ausgabe result.png
Wenn num zum ersten Mal ausgegeben wird, wird es aus Vorbereitungsgründen nur deklariert, aber nicht definiert, also wird Undefiniert ausgegeben; wenn num zum zweiten Mal ausgegeben wird, wurde es definiert, also wird 24 ausgegeben.
Da die Deklaration und Definition der Funktion gleichzeitig ausgeführt werden, kann func() zwar vor der Funktionsdefinitionsanweisung von func aufgerufen werden, kann jedoch weiterhin normal aufgerufen werden und 300 wird normal ausgegeben.
Memory model.png
03 Scope-Kette
Verstehen Sie zunächst die folgenden drei Konzepte:
Innerhalb der Funktion Der Bereich wird zu einem privaten Bereich, und der Bereich, in dem sich das Fenster befindet, wird als globaler Bereich bezeichnet.
Im globalen Bereich deklarierte Variablen sind globale Variablen.
werden im „privaten Bereich“ deklariert „ „Variablen“ und „Formale Funktionsparameter“ sind beide private Variablen;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到window为止,这就是作用域链。
当函数执行的时候,首先会形成一个新的私有作用域,然后按照如下的步骤执行:
如果有形参,先给形参赋值;
进行私有作用域中的预解析;
私有作用域中的代码从上到下执行
函数形成一个新的私有的作用域,保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的),这也就是闭包的概念。
console.log(total); var total = 0; function func(num1, num2) { console.log(total); var total = num1 + num2; console.log(total); } func(100 , 200); console.log(total);
以上代码执行的时候,第一次输出total的时候会输出undefined(因为预解析),当执行func(100,200)的时候,会执行函数体里的内容,此时func函数会形成一个新的私有作用域,按照之前描述的步骤:
先给形参num1、num2赋值,分别为100、200;
func中的代码进行预解析;
执行func中的代码
因为在func函数内进行了预解析,所以func函数里面的total变量会被预解析,在函数内第一次输出total的时候,会输出undefined,接着为total赋值了,第二次输出total的时候就输出300。 因为函数体内有var声明的变量total,函数体内的输出total并不是全局作用域中的total。
最后一次输出total的时候,输出0,这里输出的是全局作用域中的total。
console.log(total); var total = 0; function func(num1, num2) { console.log(total); total = num1 + num2; console.log(total); } func(100 , 200); console.log(total);
将代码作小小的变形之后,func函数体内的total并没有使用var声明,所以total不是私有的,会到全局作用域中寻找total,也就说说这里出现的所有total其实都是全局作用域下的。
04全局作用域下带var和不带var的区别
在全局作用域中声明变量带var可以进行预解析,所以在赋值的前面执行不会报错;声明变量的时候不带var的时候,不能进行预解析,所以在赋值的前面执行会报错。
console.log(num1); var num1 = 12; console.log(num2); num2 = 12;
输出结果.png
num2 = 12; 相当于给window增加了一个num2的属性名,属性值是12;
var num1 = 12; 相当于给全局作用域增加了一个全局变量num1,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12;
问题:在私有作用域中出现一个变量,不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window也没有呢?
获取值:console.log(total); --> 报错 Uncaught ReferenceError: total is not defined
设置值:total= 100; --> 相当于给window增加了一个属性名total,属性值是100
function fn() { // console.log(total); // Uncaught ReferenceError: total is not defined total = 100; } fn(); console.log(total);
注意:JS中,如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不再执行了
05预解析中的一些变态机制
5.1 不管条件是否成立,都要把带var的进行提前的声明
if (!('num' in window)) { var num = 12; } console.log(num); // undefined
JavaScript进行预解析的时候,会忽略所有if条件,因为在ES6之前并没有块级作用域的概念。本例中会先将num预解析,而预解析会将该变量添加到window中,作为window的一个属性。那么 'num' in window 就返回true,取反之后为false,这时代码执行不会进入if块里面,num也就没有被赋值,最后console.log(num)输出为undefined。
5.2 只预解析“=”左边的,右边的是指,不参与预解析
fn(); // -> undefined(); // Uncaught TypeError: fn is not a function var fn = function () { console.log('ok'); } fn(); -> 'ok' function fn() { console.log('ok'); } fn(); -> 'ok'
建议:声明变量的时候尽量使用var fn = ...的方式。
5.3 自执行函数:定义和执行一起完成
(function (num) { console.log(num); })(100);
自治性函数定义的那个function在全局作用域下不进行预解析,当代码执行到这个位置的时候,定义和执行一起完成了。
补充:其他定义自执行函数的方式
~ function (num) {}(100) + function (num) {}(100) - function (num) {}(100) ! function (num) {}(100)
5.4 return下的代码依然会进行预解析
function fn() { console.log(num); // -> undefined return function () { }; var num = 100; } fn();
函数体中return下面的代码,虽然不再执行了,但是需要进行预解析,return中的代码,都是我们的返回值,所以不进行预解析。
5.5 名字已经声明过了,不需要重新的声明,但是需要重新的赋值
var fn = 13; function fn() { console.log('ok'); } fn(); // Uncaught TypeError: fn is not a function
经典题目
fn(); // -> 2 function fn() {console.log(1);} fn(); // -> 2 var fn = 10; // -> fn = 10 fn(); // -> 10() Uncaught TypeError: fn is not a function function fn() {console.log(2);} fn();
更多JavaScript 中的预解析相关文章请关注PHP中文网!