>웹 프론트엔드 >JS 튜토리얼 >JavaScript 범위 체인, 실행 컨텍스트 및 closure_javascript 기술에 대한 간략한 분석

JavaScript 범위 체인, 실행 컨텍스트 및 closure_javascript 기술에 대한 간략한 분석

WBOY
WBOY원래의
2016-05-16 15:16:551014검색

클로저와 스코프 체인은 JavaScript에서 상대적으로 중요한 개념입니다. 지난 이틀 동안 몇 가지 정보를 읽고 관련 지식 포인트를 아래에 요약했습니다.

JavaScript는 함수 실행이 의존하는 변수의 범위는 함수가 실행될 때가 아니라 함수가 정의될 ​​때 결정됩니다. 다음 코드 조각을 예로 들어 보겠습니다. 일반적으로 foo가 호출된 후(C 언어와 같은 스택 기반 구현) 함수 내의 지역 변수 범위가 해제되지만 어휘적으로 말하면 포함된 익명 함수의 범위는 다음과 같습니다. foo는 foo의 지역 변수 범위를 참조해야 하며 실제로 코드의 실행 결과는 f가 호출된 후 지역 범위가 반환됩니다. 주요 함수 foo의 호출이 끝난 후에도 함수 객체 f는 여전히 foo 함수 본문의 범위 변수에 대한 참조를 유지합니다. 이것이 바로 클로저입니다.

var scope = 'global scope';
function foo() {
var scope = 'local scope';
return function () {
return scope;
}
}
var f = foo();
f(); // 返回 "local scope"

그럼 폐쇄는 어떻게 작동하나요? 클로저를 이해하려면 먼저 변수 범위와 범위 체인을 이해해야 합니다. 또 다른 중요한 개념은 실행 컨텍스트입니다.

가변 범위

JavaScript의 전역 변수에는 전역 범위가 있습니다. 함수 본문 내에 선언된 변수의 범위는 전체 함수 본문이며, 물론 함수 본문 내에 정의된 중첩 함수도 포함됩니다. 함수 본문에서 지역 변수의 우선순위는 전역 변수의 우선순위보다 높습니다. 지역 변수와 전역 변수의 이름이 같은 경우 전역 변수는 마찬가지로 지역 변수에 의해 보호됩니다. 중첩된 함수가 중첩된 변수가 있는 함수의 지역 변수보다 높습니다. 이것은 너무나 명백해서 거의 모든 사람이 이해할 수 있습니다.
다음으로, 누구에게나 생소할 수 있는 것에 대해 이야기해 보겠습니다.

함수 선언 프로모션

함수 선언 승격을 한 문장으로 설명하세요. 즉, 함수 본문 내에 선언된 변수가 전체 함수 내에서 유효하다는 의미입니다. 즉, 함수 본문 하단에 선언된 변수라도 상위로 승격됩니다. 예:

var scope = 'global scope';
function foo() {
console.log(scope); // 这里不会打印出 "global scope",而是 "undefined"
var scope = 'local scope'; 
console.log(scope); // 很显然,打印出 "local scope"
}
foo();

첫 번째 console.log(scope)는 지역 변수 선언이 승격되었지만 아직 값이 할당되지 않았기 때문에 전역 범위 대신 정의되지 않음을 인쇄합니다.

속성으로서의 변수

JavaScript에서는 다음 예제 코드에서 globalVal1, globalVal2, globalValue3와 같은 전역 변수를 정의하는 세 가지 방법이 있습니다. 흥미로운 현상은 실제로 전역 변수가 전역 개체 창/전역(브라우저의 창, node.js의 전역)의 속성일 뿐이라는 것입니다. 일반적인 의미의 변수 정의와 보다 일관성을 유지하기 위해 JavaScript는 var로 정의된 전역 변수를 삭제할 수 없는 전역 객체 속성으로 설계합니다. Object.getOwnPropertyDescriptor(this, 'globalVal1')를 통해 얻을 수 있으며, 구성 가능한 속성은 false입니다.

var globalVal1 = 1; // 不可删除的全局变量
globalVal2 = 2; // 可删除的全局变量
this.globalValue3 = 3; // 同 globalValue2
delete globalVal1; // => false 变量没有被删除
delete globalVal2; // => true 变量被删除
delete this.globalValue3; //=> true 变量被删除

那么问题来了,函数体内定义的局部变量是不是也作为某个对象的属性呢?答案是肯定的。这个对象是跟函数调用相关的,在 ECMAScript 3中称为“call object”、ECMAScript 5中称为“declaravite environment record”的对象。这个特殊的对象对我们来说是一种不可见的内部实现。

作用域链

从上一节我们知道,函数局部变量可与看做是某个不可见的对象的属性。那么 JavaScript 的词法作用域的实现可以这样描述:每一段 JavaScript 代码(全局或函数)都有一个跟它关联的作用域链,它可以是数组或链表结构;作用域链中的每一个元素定义了一组作用域内的变量;当我们要查找变量 x 的值,那么从作用域链的第一个元素中找这个变量,如果没有找到者找链表中的下一个元素中查找,直到找到或抵达链尾。了解作用域链的概念对理解闭包至关重要。

执行上下文

每段 JavaScript 代码的执行都与执行上下文绑定,运行的代码通过执行上下文获可用的变量、函数、数据等信息。全局的执行上下文是唯一的,与全局代码绑定,每执行一个函数都会创建一个执行上下文与其绑定。JavaScript 通过栈的数据结构维护执行上下文,全局执行上下文位于栈底,当执行一个函数的时候,新创建的函数执行上下文将会压入栈中,执行上下文指针指向栈顶,运行的代码即可获得当前执行的函数绑定的执行上下文。如果函数体执行嵌套的函数,也会创建执行上下文并压入栈,指针指向栈顶,当嵌套函数运行结束后,与它绑定的执行上下文被推出栈,指针重新指向函数绑定的执行上下文。同样,函数执行结束,指针会指向全局执行上下文。

执行上下文可以描述成式一个包含变量对象(对应全局)/活动对象(对应函数)、作用域链和 this 的数据结构。当一个函数执行时,活动对象被创建并绑定到执行上下文。活动对象包括函数体内申明的变量、函数、arguments 等。作用域链在上一节以及提到,是按词法作用域构建的。需要注意的是 this 不属于活动对象,在函数执行的那一刻就以及确定。
执行上下文的创建是有特定的次序和阶段的,不同阶段有不同的状态,具体的细节可以看一下参考资料,在结尾部分会列出。

闭包

了解了作用域链和执行上下文,回过头看篇首的那段代码,基本上就可以解释闭包式如何工作了。函数调用的时候创建的执行上下文以及词法作用域链保持函数调用所需要的信息, f 函数调用之后才可以返回local scope。

需要注意的是,函数内定义的多个函数使用的是同一个作用域链,在使用 for 循环赋值匿名函数对象的场景比较容易引起错误,举例如下:

var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: function() {
return i;
}
};
}
arr[0].func(); // 返回 10,而不是 0

arr[0].func()返回的是 10 而不是 0,跟感官上的语义有偏差。在 ECMAScript 6 引入 let 之前, 变量作用域范围是在整个函数体内而不是在代码区块之内,所以上面的例子中所有定义的 func 函数引用了同一个作用域链在 for 循环之后, i 的值已经变为 10 。

正确的做法是这样:

var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: getFunc(i)
};
}
function getFunc(i) {
return function() {
return i;
}
}
arr[0].func(); // 返回 0

以上内容给大家介绍了JavaScript作用域链、执行上下文与闭包的相关知识,希望对大家有所帮助。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.