이 글에서는 JavaScript의 가장 기본이자 중요한 개념인 실행 컨텍스트에 대해 심도있게 설명하겠습니다. 이 글을 읽고 나면 코드를 실행하기 전에 JavaScript 엔진 내부에서 수행되는 작업, 특정 함수와 변수를 선언하기 전에 사용할 수 있는 이유, 최종 값이 어떻게 정의되는지 이해할 수 있을 것이라고 믿습니다.
Javascript의 코드 실행 환경은 다음 세 가지 유형으로 나뉩니다.
전역 수준 코드 – 코드가 로드되면 첫 번째 코드 실행 환경입니다. 엔진이 환경에 들어가는 것.
함수 수준 코드 – 함수가 실행되면 함수 본문의 코드가 실행됩니다.
Eval 코드 – Eval 함수 내에서 실행되는 코드입니다.
이 기사를 모든 사람이 더 쉽게 이해할 수 있도록 인터넷에서 범위를 설명하는 많은 리소스를 찾을 수 있습니다. "실행 컨텍스트"는 현재 코드의 실행 환경 또는 범위로 생각할 수 있습니다. 전역 및 함수 수준 실행 컨텍스트를 포함하는 아래 예를 살펴보겠습니다.
위 그림에서는 총 4개의 실행 컨텍스트가 사용됩니다. 보라색은 전역 컨텍스트를 나타내고, 녹색은 개인 함수 내의 컨텍스트를 나타내고, 파란색과 주황색은 개인 함수 내의 다른 두 기능의 컨텍스트를 나타냅니다. 상황이 어떠하든 다른 컨텍스트에서 액세스할 수 있는 전역 컨텍스트는 단 하나뿐입니다. 즉, person 컨텍스트의 전역 컨텍스트에서 sayHello 변수에 액세스할 수 있습니다. 물론 firstName 또는 lastName 함수에서도 변수에 액세스할 수 있습니다.
함수 컨텍스트 수에는 제한이 없습니다. 함수가 호출되고 실행될 때마다 엔진은 자동으로 새로운 함수 컨텍스트를 생성합니다. 즉, 여기서 사용할 수 있습니다. 로컬 범위 개인 변수 등을 선언할 때 로컬 범위 내의 요소는 외부 컨텍스트에서 직접 액세스할 수 없습니다. 위의 예에서 내부 함수는 외부 컨텍스트에 선언된 변수에 액세스할 수 있지만 그 반대는 불가능합니다. 그렇다면 그 이유는 무엇입니까? 엔진 내부에서는 어떻게 처리되나요?
브라우저에서 자바스크립트 엔진은 단일 스레드로 작동합니다. 즉, 특정 시간에 하나의 이벤트만 활성화되어 처리되고, 다른 이벤트는 대기열에 배치되어 처리를 기다리고 있습니다. 아래 예제 다이어그램은 이러한 스택을 설명합니다.
우리는 자바스크립트 코드 파일이 브라우저에 의해 로드될 때 기본적으로 전역 실행 컨텍스트가 먼저 입력된다는 것을 이미 알고 있습니다. 전역 컨텍스트에서 함수가 호출되고 실행되면 프로그램 흐름은 호출된 함수에 진입합니다. 이때 엔진은 함수에 대한 새로운 실행 컨텍스트를 생성하고 이를 실행 컨텍스트 스택의 맨 위로 푸시합니다. 브라우저는 항상 현재 스택 상단에 있는 컨텍스트를 실행합니다. 실행이 완료되면 해당 컨텍스트가 스택 상단에서 팝된 다음 그 아래 컨텍스트에서 코드를 실행합니다. 이러한 방식으로 스택의 컨텍스트는 순차적으로 실행되고 전역 컨텍스트로 돌아갈 때까지 스택에서 팝됩니다. 다음 예를 보세요:
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
위의 foo가 선언된 후 강제로 () 연산자를 통해 직접 실행됩니다. 함수 코드는 자신을 세 번 호출하고, 매번 지역 변수 i가 1씩 증가합니다. foo 함수가 호출될 때마다 새로운 실행 컨텍스트가 생성됩니다. 컨텍스트가 실행을 완료할 때마다 이전 컨텍스트는 스택에서 제거되고 다시 전역 컨텍스트로 돌아올 때까지 이전 컨텍스트로 반환됩니다. 전체 프로세스를 다음과 같이 추상화합니다.
실행 컨텍스트의 추상적인 개념을 다음과 같은 점으로 요약할 수 있음을 알 수 있습니다. 하나의 전역 컨텍스트
함수의 실행 컨텍스트 수에는 제한이 없습니다
함수가 호출될 때마다 호출 함수 자체인 경우에도 새로운 실행 컨텍스트가 생성됩니다.
실행 컨텍스트 설정 프로세스
实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:
(executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ }, scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ }, this: {} }
确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:
找到当前上下文中的调用函数的代码
在执行被调用的函数体中的代码以前,开始创建执行上下文
进入第一个阶段-建立阶段:
建立variableObject对象:
初始化作用域链
确定上下文中this的指向对象
建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
检查当前上下文中的函数声明:
每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
代码执行阶段:
执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。
下面来看个具体的代码示例:
function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22);
在调用foo(22)的时候,建立阶段如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } }
由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } }
我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:
(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = 'hello', bar = function() { return 'world'; }; function foo() { return 'hello'; } }());
上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。
为什么我们可以在声明foo变量以前就可以访问到foo呢?
因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。
为什么bar是undefined呢?
因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。
好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!
위 내용은 Javascript 실행문 컨텍스트에 대한 토론의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!