Avant ES6, les variables étaient déclarées à l'aide de var, et il y avait une pré-analyse des variables (les fonctions ont également une pré-analyse). Je crois que de nombreux étudiants étaient confus par la pré-analyse lorsqu'ils ont commencé à apprendre JavaScript. a été introduit dans ES6 et const, mais ES6 n'est pas complètement populaire à ce stade, et de nombreux codes plus anciens sont encore écrits selon les normes ES5 ou même ES3.
01 Affichage des variables et des fonctions en mémoire
Les types de variables en JavaScript sont les mêmes que dans les autres langages, y compris les types de données de base et les types de données de référence. Les types de données de base incluent : non défini, nul, booléen, chaîne, numéro ; les types de données de référence sont principalement des objets (y compris {}, [], /^$/, Date, Fonction, etc.).
var num = 24; var obj = {name:'iceman' , age:24}; function func() { console.log('hello world'); }
Lorsque le navigateur charge la page HTML, il fournit d'abord un environnement pour l'exécution globale du code JavaScript, appelé portée globale.
Les types de données de base fonctionnent sur des valeurs et les types de données de référence fonctionnent sur des adresses.
Selon les principes ci-dessus, le modèle de mémoire du code ci-dessus est :
Memory model.png
Le type de base est stockage direct Dans la mémoire pile, alors que les objets sont stockés dans la mémoire tas, les variables contiennent simplement l'adresse de l'objet. Ainsi obj contient l'adresse d'un objet oxff44, et la fonction func contient l'adresse oxff66.
Exécuter en fonction du code ci-dessus :
console.log(func); console.log(func());
La première ligne de sortie est la partie définition de la fonction entière (la fonction elle-même) :
La première ligne de code génère le résultat.png
Comme expliqué ci-dessus, func stocke une adresse qui pointe vers un morceau de mémoire tas, et la mémoire tas conserve la définition de la fonction .
La deuxième ligne de code affiche le résultat de retour de la fonction func :
La deuxième ligne de code affiche le résultat.png
En raison de la fonction func, il n'y a pas de valeur de retour, donc la sortie n'est pas définie.
Remarque : le résultat de retour de la fonction, quoi qu'il soit écrit après le retour, est la valeur de retour. S'il n'y a pas de retour, la valeur de retour par défaut n'est pas définie.
02 Pré-analyse
Après avoir compris le modèle de mémoire ci-dessus, vous pouvez mieux comprendre le mécanisme de pré-analyse. Ce qu'on appelle la pré-analyse est la suivante : dans la portée actuelle, avant que le code JavaScript ne soit exécuté, le navigateur déclarera ou définira d'abord toutes les variables avec les déclarations var et fonction à l'avance par défaut.
2.1. Déclaration et définition
var num = 24;
Cette simple ligne de code est en fait composée de deux étapes : déclaration et définition.
Déclaration : var num; indique au navigateur qu'il existe une variable num dans la portée globale. Si une variable est uniquement déclarée mais qu'aucune valeur n'est attribuée, la valeur par défaut n'est pas définie.
Définition : num = 12 ; La définition est d'attribuer une valeur à la variable.
2.2. La différence entre les variables déclarées par var et les fonctions déclarées par fonction lors de la pré-analyse
Il existe une différence entre les variables déclarées par var et les fonctions déclarées par fonction lors de la pré-analyse. déclarée par var are in Elle n'est déclarée à l'avance que lors de la pré-analyse. La fonction déclarée par function sera déclarée à l'avance et définie en même temps lors de la pré-analyse. En d'autres termes, la différence entre une variable déclarée par var et une fonction déclarée par function est de savoir si elle est définie en même temps qu'elle est déclarée.
2.3. La pré-analyse n'a lieu que dans la portée actuelle
Au début du programme, seules les variables et les fonctions sous la fenêtre sont pré-analysées, et seulement lorsque la fonction est exécutée. dans les fonctions sont préparées en tant que fonctions.
console.log(num); var num = 24; console.log(num); func(100 , 200); function func(num1 , num2) { var total = num1 + num2; console.log(total); }
Output result.png
Lorsque num est affiché pour la première fois, pour des raisons de pré-analyse, il est uniquement déclaré mais pas défini, donc Undefined sera affiché ; lorsque num est affiché pour la deuxième fois, il a été défini, donc 24 est affiché.
Étant donné que la déclaration et la définition de la fonction sont effectuées en même temps, bien que func() soit appelée avant l'instruction de définition de la fonction func, elle peut toujours être appelée normalement et 300 sera affiché normalement.
Memory model.png
03 Chaîne de portée
Comprenez d'abord les trois concepts suivants :
À l'intérieur de la fonction La portée devient une portée privée, et la portée où se trouve la fenêtre est appelée portée globale ;
les variables déclarées dans la portée globale sont des variables globales
est déclarée dans la "portée privée ; " "Variables" et "Paramètres formels de la fonction" sont tous deux des variables privées ;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到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中文网!