본 글은 자바스크립트 스코프 체인(Scope Chain)의 사용법을 예시를 통해 분석한 것입니다. 참고하실 수 있도록 모든 사람과 공유하세요. 자세한 내용은 다음과 같습니다.
JS의 스코프 체인에 대해 오랫동안 들어왔고 여러 소개 블로그 게시물을 읽었지만 내 이해는 항상 모호했습니다. 최근에 『자바스크립트의 이해』라는 책을 읽어보니 아주 심오하게 쓰여졌다는 느낌이 들었습니다. 『코드의 공간과 시간』 부분에 몇 마디로 스코프 체인을 소개하는 부분이 있는데, 그 부분이 마음에 와 닿았습니다. 끝이 없는 뒷맛(사실 아직도 이해가 되네요^_^). 이제 독서 노트를 정리하고, 온라인 자료를 활용하여 적어보세요.
1. 간단한 질문부터 시작하겠습니다
페이지에서 다음 js 코드를 실행하면 어떤 결과가 표시되나요?
var arg = 1; function fucTest(arg) { alert(arg); var arg = 2; //alert(arg); } fucTest(10);
당신의 대답은 무엇입니까? 그렇군요 10개가 나오네요. 제가 이해한 바에 따르면, funTest 함수에는 형식 매개변수 arg가 있고, funTest 함수는 실제 매개변수 10을 전달하며, 경고 메서드는 10만 표시해 당황스럽습니다.
좋습니다. 질문이 다시 나옵니다.
var arg = 1; function funcTest() { alert(arg); var arg = 2; } arg = 10; funcTest();
답은 무엇인가요? 5년 전의 저였다면 절대 더 이상 생각하지 않았을 거에요. 여전히 10년일 거에요! 왜 이렇게 간단한 질문을 생각해야 할까요? 제가 이해한 바는 funTest 함수는 매개변수가 없는 함수라는 것입니다. 함수 내부에서 외부(전역) 변수 arg는 함수가 실행되기 전에 arg에 10이라는 값이 할당됩니다. 팝업되면 arg 값이 2. 로 변경되므로 팝업 값은 10입니다.
정말 10인가요? 예 아니면 아니오?
테스트 결과: "정의되지 않음"이 뜨고, 땀이 뻘뻘 납니다.
2. JavaScript 작동 메커니즘부터 시작하여 스코프 체인을 이해합니다
1. js의 실행 순서
문서 흐름에 여러 스크립트 코드 세그먼트(스크립트 태그로 구분된 js 코드 또는 도입된 js 파일)가 포함된 경우 실행 순서는 다음과 같습니다.
1단계. 첫 번째 코드 세그먼트 읽기(js 실행 엔진은 프로그램을 한 줄씩 실행하지 않고, 한 줄씩 분석하고 실행합니다)
2단계. 구문 분석을 수행합니다. 오류가 있는 경우 구문 오류(예: 대괄호 불일치 등)가 보고되고 5단계로 이동합니다.
3단계. var 변수 및 함수 정의의 "사전 구문 분석"을 수행합니다(올바른 선언만 구문 분석되므로 오류는 보고되지 않습니다)
Step 4. 코드 세그먼트를 실행하고 오류가 있으면 오류를 보고합니다(예: 변수가 정의되지 않음)
5단계. 다른 코드 세그먼트가 있으면 다음 코드 세그먼트를 읽고 2단계를 반복하세요
6단계. 종료
위의 분석은 충분히 명확합니다. 2, 3, 4단계의 빨간색 글꼴은 특히 3단계의 "사전 구문 분석"을 초보자가 이해하기에는 사각지대일 수 있습니다. -파싱이란, 항상 불안한 느낌이 들어요. "오류가 있으면 오류를 보고하라"는 네 번째 단계도 자주 접하게 된다. 예:
function funcTest() { alert(arg); var arg = 2; } funcTest();
上面这段代码执行时,弹出“undefined”,也就是说arg没有定义,js的变量不是不用定义也可以吗?
2、语法分析和“预解析”
(1)、从解释型语言的编译过程说起
众所周知,javascript是解释型语言,它不同于c#和java等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。
a、词法分析
简单地说,词法分析是将字符流(char stream)转换为记号流(token stream)。
但是这个转换过程并不是可以用一句话就可以概括的那么简单,我们可以试着用伪代码理解一段简单的程序:
代码var result=x-y;的转换大致可以表示如下:
NAME "result"
EQUALS
NAME "x"
MINUS
NAME "y"
SEMICOLON
b、语法分析
简单地说,语法分析就是为了构造合法的语法分析树,而语法分析树可以直观地表示出推导的过程。
那么什么是语法分析树?简单地说,就是程序推导过程的描述。但是到底什么是语法树,请参考专业文章,本篇略过。
c、其他
通过语法分析,构造出语法分析树后,接下来还可能需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配等等。
结论:通过上面的分析可以看出,对于javascript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。
(2)、执行过程
a、javascript的作用域机制
通过编译,javascript代码已经翻译成了语法树,然后会立刻按照语法树执行。
进一步的执行过程,需要理解javascript的作用域机制:词法作用域(lexcical scope)。通俗地讲,就是javascript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,所以只能说javascript的作用域机制非常接近词法作用域(lexical scope).
javascript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构(scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到scriptObject上)。
b、javascript作用域机制的实现方法
词法作用域(lexical scope)是javascript的作用域机制,还需要理解它的实现方法,就是作用域链(scope chain)。作用域链是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父scriptObject中寻找,一直lookup到全局调用对象(global object)。
现在回过头来分析第二个问题:
var arg = 1; function funcTest() { alert(arg); var arg = 2; } arg = 10; funcTest();
在执行funcTest函数时,也即进入了funcTest对应的作用域,js引擎在执行时,当遇到对变量名或者函数名的使用时,会首先在当前作用域(也即funcTest对应的作用域)查找变量或者函数(显然,arg变量在funcTest对应的作用域里被定义为var arg=2 所以alert方法的参数采用的是当前作用域的arg,但是因为arg被定义在alert方法后,所以arg变量默认值为undefined)。当然,如果没有找到就到上层作用域查找,依此类推(作用域范围可以持续到javascript运行环境的根:window对象)。
最后,让你看的更清楚,上面的代码其实可以等价于:
var arg = 1; function funcTest() { var arg; //默认值undefined alert(arg); arg = 2; } arg = 10; funcTest();
c, 종료
함수 인스턴스가 실행되면 클로저가 생성되거나 연결됩니다. (폐쇄에 관해서는 또 다른 스터디 노트를 작성할 예정입니다)
scriptObject는 함수와 관련된 변수 테이블을 정적으로 저장하는 데 사용되며 클로저는 실행 중에 이러한 변수 테이블과 실행 값을 동적으로 저장합니다.
클로저의 수명 주기는 함수 인스턴스의 수명 주기보다 길 수 있습니다. 활성 참조가 비어 있으면 함수 인스턴스가 자동으로 삭제됩니다.
클로저는 데이터 참조가 비어 있으면 JavaScript 엔진에 의해 재활용됩니다(경우에 따라 자동으로 재활용되지 않아 메모리 누수가 발생함).ps: "실행 프로세스"에 관한 이 섹션은 발음하기가 약간 어렵습니다. 명사가 많지만, 실행 컨텍스트를 이해하고 나면 객체 호출, 어휘 범위, 범위 등의 개념을 이해하게 됩니다. 체인과 클로저는 JavaScript의 많은 현상을 쉽게 해결할 수 있습니다.
3. 결론
두 번째 문단의 분석을 통해 첫 번째 문단에서 저자가 내린 판단과 비교해 보세요. (저자의 앞선 분석과 결론도 (때때로 결과가 맞을지라도!) 순진하다고 생각하시나요?) ?! 피상적이지 않습니다. ^_^) JavaScript에는 너무 많은 "미스터리"가 있지만 실제로 이해하고 마스터하는 것은 쉽지 않습니다. 먼저 "이해"한 다음 이야기해 봅시다.이 기사가 JavaScript 프로그래밍에 종사하는 모든 사람에게 도움이 되기를 바랍니다.