이 기사에서는 주로 JavaScript 범위와 범위 체인에 대해 설명합니다. 관심 있는 친구는
각 프로그래밍 언어에서 해당 변수가 이 범위를 초과하면 특정 유효 범위를 갖습니다. 변수가 유효하지 않게 됩니다. 이것이 변수의 범위입니다. 수학적 관점에서 보면 이는 독립변수의 영역입니다.
범위는 변수의 접근 가능한 범위입니다. 즉, 범위는 변수와 함수의 가시성과 수명 주기를 제어합니다. JavaScript에서는 객체와 함수도 변수입니다. 변수는 선언된 함수 본문과 이 함수 본문이 중첩된 함수 본문 내부에서 정의됩니다.
1. 정적 범위와 동적 범위
정적 범위
는 선언된 범위를 의미합니다. 어휘 범위라고도 알려진 프로그램 텍스트를 기반으로 컴파일 타임에 결정됩니다. 대부분의 최신 프로그래밍 언어는 정적 범위 지정 규칙을 사용하며 JavaScript는 이러한 유형의 범위 지정을 사용합니다.
정적 범위 지정을 사용하는 언어에서 가장 안쪽 중첩 범위의 규칙은 기본적으로 동일합니다. 선언에 의해 도입된 식별자는 선언이 있는 범위와 그 안에 있는 각 중첩 범위에서 볼 수 있습니다. 동일한 이름의 식별자에 대한 다른 중첩 선언으로 가려지지 않는 한 범위 내에서도 표시됩니다.
주어진 식별자가 참조하는 개체를 찾으려면 현재 가장 안쪽 범위에서 검색해야 합니다. 선언이 발견되면 식별자가 참조하는 객체도 찾을 수 있습니다. 그렇지 않으면 바로 외부 범위를 살펴보고 전역 개체가 선언된 범위인 프로그램의 가장 바깥쪽 중첩 수준에 도달할 때까지 바깥쪽으로 순차적으로 외부 범위를 계속 확인합니다. 어떤 수준에서도 해당 문을 찾을 수 없으면 프로그램에 오류가 있는 것입니다.
function cha(){ var name = "xiao;" function chb() { function chc() { console.log(name); } } }
먼저 chb()에서 name의 정의를 검색한 다음 계속해서 레이어별로 위쪽으로 검색합니다. 마지막으로 cha()에서 name의 정의를 찾습니다. 찾을 수 없으면 오류가 보고됩니다.
2. 동적 범위
동적 범위의 언어에서는 프로그램이 참조되는 개체가 프로그램에서 결정됩니다. 프로그램의 제어 흐름 정보를 기반으로 결정됩니다.
2. JavaScript의 범위
자바스크립트에는 전역 범위와 로컬 범위라는 두 가지 범위가 있습니다.
1. 전역 범위
는 코드 내 어디에서나 정의됩니다. 전역 변수가 html 페이지에 중첩된 js 코드 조각에 정의되어 있어도 참조된 js 파일에서 해당 변수에 계속 액세스할 수 있습니다. 이로 인해 전역 변수가 오염될 가능성이 높습니다.
다음 세 가지 상황의 변수는 전역 변수로 간주됩니다
(1) 가장 바깥쪽 함수와 가장 바깥쪽 변수는 전역 범위를 가집니다.
(2) 변수를 정의하고 직접 할당하지 않으면 자동으로 전역 범위를 갖도록 선언
(3) 창 객체의 모든 속성은 전역 범위를 갖습니다
2. 로컬 범위
로컬 범위 일반적으로 함수 내부 변수(함수 범위)와 같은 고정 코드 조각에서만 액세스할 수 있습니다.
var name = "xuxiaoping"; function echoName() { var firstname = "xu";//局部作用域 secondname = "xiao";//全局作用域 function echoFirstName() { console.log(first name);//xu } console.log(secondname); return echoFirstName; } console.log(name);//全局作用域 var f = echoName(); f(); console.log(firstname); console.log(secondname);
결과는 다음과 같습니다.
xuxiaoping
xiao
xu//내부 함수 외부 함수의 변수에 액세스할 수 있습니다
정의되지 않음 //함수의 내부 변수는 함수 외부에서 액세스할 수 없습니다
xiao
JavaScript에서는 전역 변수가 창 개체에 연결되어 창 개체의 속성이 됩니다. 창 개체.
3. 함수 범위
블록 수준 범위: 중괄호 쌍으로 묶인 모든 문 집합은 블록에 속합니다. 내부에 정의된 변수는 코드 블록 외부에서 보이지 않습니다. 대부분의 C 계열 언어에는 블록 수준 범위가 있습니다.
그러나 JavaScript의 중요한 특징은 블록 수준 범위가 없다는 것입니다.
function echoi() { for(var i = 0;i<10;i++){ ;//console.log(i); } if(true){ var str = "hello"; } console.log(i); console.log(str); } echoi();
출력 결과는 다음과 같습니다.
10
hello
for 문 외부에서 표시되며(if, while일 수도 있음) 정의된 변수 블록에서는 i에 계속 액세스할 수 있습니다. 즉, JavaScript는 블록 수준 범위를 지원하지 않고 함수 범위만 지원하며, 함수 내 어디에서나 정의된 변수는 함수 내 어디에서나 볼 수 있습니다. 프로그래밍을 시작할 때부터 C와 Java를 배워온 사람으로서는 적응하기가 조금 어렵습니다. 제가 테스트한 바에 따르면 PHP도 이와 같습니다.
물론 JavaScript의 클로저 기능을 사용하여 블록 수준 범위를 시뮬레이션할 수 있습니다
function echoi() { (function() { for(var i = 0;i<10;i++){ //console.log(i); } })(); if(true){ var str = "hello"; } console.log(i); console.log(str); } echoi();
结果为:i undefined
这样就隔离了变量的定义。在js中,为了防止命名冲突,应该尽量避免使用全局变量和全局函数,因此这种闭包的用的特别的多。
4、JavaScript 变量生命周期
JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。
三、JavaScript的作用域链
一看是链,大概就可以跟数据结构中的链表相结合起来
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:
function add(num1,num2) { var sum = num1 + num2; return sum; }
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):
函数add的作用域将会在执行时用到。例如执行如下代码:
var total = add(5,10);
执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
四、作用域链和代码优化
从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:
function changeColor(){ document.getElementById("btnChange").onclick=function() { document.getElementById("targetCanvas").style.backgroundColor="red"; }; }
这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:
function changeColor(){ var doc=document; doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。
五、with改变作用域链
数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。例如:
function initUI(){ with(document){ var bd=body, links=getElementsByTagName("a"), i=0, len=links.length; while(i < len){ update(links[i++]); } getElementById("btnInit").onclick=function(){ doSomething(); }; } }
여기서는 문서를 여러 번 작성하는 것을 피하기 위해 너비 문을 사용합니다. 이는 더 효율적인 것처럼 보이지만 실제로는 성능 문제를 유발합니다.
with 문까지 코드가 실행되면 런타임 컨텍스트의 범위 체인이 일시적으로 변경됩니다. 매개변수로 지정된 객체의 모든 속성을 포함하는 새로운 변경 가능한 객체가 생성됩니다. 이 개체는 범위 체인의 헤드로 푸시됩니다. 이는 함수의 모든 지역 변수가 이제 두 번째 범위 체인 개체에 있으므로 액세스 비용이 더 많이 든다는 것을 의미합니다. 아래 그림과 같이
따라서 프로그램에서는 with 문을 피해야 합니다. 이 예에서는 문서를 로컬 변수에 저장하는 것만으로도 성능이 향상될 수 있습니다. .
요약
1. 변수의 범위는 변수가 유효한 범위입니다.
2. 변수의 범위 체인은 생성된 범위에 있는 개체의 모음입니다.
이상 내용이 이 글의 전체 내용입니다. 자바스크립트 프로그래밍을 배우시는 모든 분들께 도움이 되었으면 좋겠습니다.