Before ES6, variables were declared using var, and there would be pre-parsing of variables (functions also had pre-parsing). I believe that many students were confused by pre-parsing when they first started learning JavaScript. Although let was introduced in ES6 and const, but ES6 is not completely popular at this stage, and many older codes are still written according to ES5 standards or even ES3 standards.
01 Display of variables and functions in memory
The variable types in JavaScript are the same as those in other languages, including basic data types and reference data types. Basic data types include: undefined, null, boolean, String, Number; reference data types are mainly objects (including {}, [], /^$/, Date, Function, etc.).
var num = 24; var obj = {name:'iceman' , age:24}; function func() { console.log('hello world'); }
When the browser loads an html page, it will first provide an environment for global JavaScript code execution, which is called the global scope.
Basic data types operate according to values, and reference data types operate according to addresses.
According to the above principles, the model of the above code in memory is:
Memory model.png
The basic type is direct storage In stack memory, while objects are stored in heap memory, variables just hold the address of the object. So obj holds the address of an object oxff44, and the function func holds the address oxff66.
Execute based on the above code:
console.log(func); console.log(func());
The first line of output is the definition part of the entire function (the function itself):
The first line of code output result.png
As explained above, func stores an address, which points to a piece of heap memory, and the heap memory retains the definition of the function.
The second line of code outputs the return result of the func function:
The second line of code outputs the result.png
Because of the func function There is no return value, so the output is undefined.
Note: The return result of the function, whatever is written after return, is the return value. If there is no return, the default return value is undefined.
02 Pre-parsing
After understanding the above memory model, you can better understand the mechanism of pre-parsing. The so-called pre-parsing is: in the current scope, before the JavaScript code is executed, the browser will first declare or define all variables with var and function declarations in advance by default.
2.1. Declaration and Definition
var num = 24;
This simple line of code is actually two steps: declaration and definition.
Declaration: var num; tells the browser that there is a num variable in the global scope. If a variable is only declared but not assigned a value, the default value is undefined.
Definition: num = 12; The definition is to assign a value to the variable.
2.2. The difference between variables declared by var and functions declared by function during pre-parsing
There is a difference between variables declared by var and functions declared by function during pre-parsing. Variables declared by var are in It is only declared in advance during pre-parsing. The function declared by function will be declared in advance and defined at the same time during pre-parsing. In other words, the difference between a variable declared by var and a function declared by function is whether it is defined at the same time as it is declared.
2.3. Pre-parsing only occurs in the current scope
At the beginning of the program, only variables and functions under the window are pre-parsed, and only when the function is executed Variables in functions are preparsed as functions.
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
When num is output for the first time, due to pre-parsing reasons, it is only declared but not defined, so it will be output undefined; when num is output for the second time, it has been defined, so 24 is output.
Since the declaration and definition of the function are carried out at the same time, although func() is called before the func function definition statement, it can still be called normally and 300 will be output normally.
Memory model.png
03 Scope chain
First understand the following three concepts:
Inside the function The scope becomes a private scope, and the scope where the window is located is called the global scope;
Variables declared in the global scope are global variables;
Declared in the "private scope" "Variables" and "function parameters" are both private variables;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到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中文网!