ES6 이전에는 var를 사용하여 변수를 선언했고, 변수에 대한 사전 파싱(함수에도 사전 파싱이 있습니다)이 있었을 텐데요. JavaScript를 처음 배울 때 많은 학생들이 사전 파싱으로 인해 혼란을 겪었을 것입니다. ES6 및 const에 도입되었지만 ES6은 현 단계에서는 완전히 대중적이지 않으며 많은 오래된 코드가 여전히 ES5 표준 또는 심지어 ES3 표준에 따라 작성됩니다.
01 메모리에 있는 변수와 함수 표시
자바스크립트의 변수 유형은 기본 데이터 유형, 참조 데이터 유형 등 다른 언어와 동일합니다. 기본 데이터 유형에는 정의되지 않음, null, 부울, 문자열, 숫자가 포함됩니다. 참조 데이터 유형은 주로 객체입니다({}, [], /^$/, 날짜, 함수 등 포함).
var num = 24; var obj = {name:'iceman' , age:24}; function func() { console.log('hello world'); }
브라우저가 HTML 페이지를 로드할 때 먼저 전역 범위라고 하는 전역 JavaScript 코드 실행을 위한 환경을 제공합니다.
기본 데이터 유형은 값에 대해 작동하고 참조 데이터 유형은 주소에 대해 작동합니다.
위 원칙에 따르면 위 코드의 메모리 모델은 다음과 같습니다.
Memory model.png
기본 유형은 다음과 같습니다. 직접 저장 스택 메모리에서 객체는 힙 메모리에 저장되지만 변수는 객체의 주소만 보유합니다. 따라서 obj는 객체 oxff44의 주소를 보유하고 함수 func는 주소 oxff66을 보유합니다.
위 코드를 기반으로 실행:
console.log(func); console.log(func());
출력의 첫 번째 줄은 전체 함수(함수 자체)의 정의 부분입니다:
첫 번째 코드 줄은 result.png를 출력합니다
위에서 설명했듯이 func는 힙 메모리 조각을 가리키는 주소를 저장하고 힙 메모리는 함수의 정의를 유지합니다.
코드의 두 번째 줄은 func 함수의 반환 결과를 출력합니다.
코드의 두 번째 줄은 result.png를 출력합니다.
func 함수 때문에 반환 값이 없으므로 출력이 정의되지 않습니다.
참고: return 뒤에 무엇을 작성하든 함수의 반환 결과는 반환 값입니다. 반환이 없는 경우 기본 반환 값은 정의되지 않습니다.
02 사전 파싱
위의 메모리 모델을 이해하고 나면 사전 파싱의 메커니즘을 더 잘 이해할 수 있습니다. 소위 사전 구문 분석은 현재 범위에서 JavaScript 코드가 실행되기 전에 브라우저가 먼저 기본적으로 var 및 함수 선언을 사용하여 모든 변수를 미리 선언하거나 정의하는 것입니다.
2.1. 선언 및 정의
var num = 24;
이 간단한 코드 줄은 실제로 선언과 정의의 두 단계로 구성됩니다.
선언: var num;은 전역 범위에 num 변수가 있음을 브라우저에 알려줍니다. 변수가 선언되기만 하고 값이 할당되지 않은 경우 기본값은 정의되지 않습니다.
정의: num = 12; 정의는 변수에 값을 할당하는 것입니다.
2.2. 사전 파싱 시 var로 선언된 변수와 함수로 선언된 함수의 차이점
변수로 선언된 변수와 사전 파싱 시 함수로 선언된 함수에는 차이가 있습니다. var에 의해 선언됨 사전 파싱 중에만 미리 선언됩니다. function에 의해 선언된 함수는 사전 파싱 중에 미리 선언되고 동시에 정의됩니다. 즉, var로 선언한 변수와 function으로 선언한 함수의 차이점은 선언과 동시에 정의되는지 여부입니다.
2.3. 현재 범위에서만 사전 파싱이 발생합니다
프로그램 시작시 창 아래의 변수와 함수만 사전 파싱되고, 함수가 실행될 때만 변수 in 함수는 함수로 준비됩니다.
console.log(num); var num = 24; console.log(num); func(100 , 200); function func(num1 , num2) { var total = num1 + num2; console.log(total); }
결과 출력.png
num이 처음 출력될 때 사전 파싱상의 이유로 선언만 하고 정의하지 않고, 따라서 정의되지 않은 상태로 출력됩니다. num이 두 번째로 출력되면 정의된 것이므로 24가 출력됩니다.
함수 선언과 정의가 동시에 이루어지므로 func 함수 정의문 이전에 func()를 호출하더라도 정상적으로 호출이 가능하며 300이 정상적으로 출력됩니다.
Memory model.png
03 Scope Chain
먼저 다음 세 가지 개념을 이해하세요.
함수 내부 범위는 개인 범위가 되며 창이 위치한 범위를 전역 범위라고 합니다.
전역 범위에 선언된 변수는 전역 변수입니다.
은 "개인 범위에 선언됩니다. ""변수"와 "함수의 형식 매개변수"는 모두 개인 변수입니다.
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到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中文网!