>  기사  >  웹 프론트엔드  >  JavaScript의 범위에 대한 심층 분석

JavaScript의 범위에 대한 심층 분석

青灯夜游
青灯夜游앞으로
2021-05-31 17:28:372088검색

이 문서에서는 JavaScript 범위에 대한 심층적인 이해를 제공합니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.

JavaScript의 범위에 대한 심층 분석

이 글은 노트라고 부르는 것이 더 적절합니다. 내용은 "당신이 모르는 자바스크립트(1권)" 범위와 마무리의 첫 번째 부분에서 나옵니다. 매우 잘 설명되어 있고 볼만한 가치가 있습니다.

범위란 무엇입니까

범위는 이름으로 변수를 찾는 규칙 집합입니다.

범위 이해

먼저 몇 가지 기본 개념을 이해하세요.

  • 엔진: 처음부터 끝까지 전체 JavaScript 프로그램의 컴파일 및 실행 프로세스를 담당합니다.
  • 컴파일러: 구문 분석 및 코드 생성을 담당합니다. 이 부분도 볼 수 있습니다 JavaScript 코드가 실행되는 방식
  • 범위: 선언된 모든 식별자(변수)로 구성된 일련의 쿼리를 수집 및 유지 관리하고 현재 실행 코드의 액세스를 결정하기 위해 매우 엄격한 규칙 집합을 구현하는 역할을 담당합니다. 이러한 식별자에.

다음으로 다음 코드의 실행 과정을 살펴보겠습니다.

var a = 2;
  • var a를 만나면 compilerscopea 변수가 있는지 묻습니다. 동일한 범위가 중간에 설정됩니다. 존재하는 경우 컴파일러는 선언을 무시하고 컴파일을 계속합니다. 그렇지 않으면 범위에 현재 범위 컬렉션에서 새 변수를 선언하고 이름을 aa是否存在于同一个作用域集合中。如果存在,编译器会忽略声明,继续编译;否则,会要求作用域在当前作用域集合中声明一个新的变量,并命名为 a

  • 接下来 编译器 会为 引擎 生成运行时所需的代码,用来处理 a = 2 这个赋值操作。引擎运行时会先问作用域,当前作用域集中是否存在变量a。如果是,引擎就会使用该变量;如果不存在,引擎会继续查找该变量

  • 如果 引擎 找到了a 变量,就会将 2 赋值给它,否则引擎就抛出一个错误。

总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量,然后在运行时引擎就会会作用域中查找该变量,如果能够找到就对它赋值。

编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量 a来判断它是否已声明过。查找的过程中由作用域进行协助,但是引擎执行怎么样的查找,会影响最终的查找结果。

在我们的例子中,引擎会为变量 a 进行 LHS 查询,另外一个查找的类型叫做 RHS。 ”L“ 和 "R" 分别代表一个赋值操作左侧和右侧。当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。

LHS:试图找到变量的容器本身,从而可以对其赋值;RHS: 就是简单地查找某个变量的值。
console.log(a);

对 a 的引用是一个 RHS 引用,因为这里 a 并没有赋予任务值,相应地需要查找并取得 a 的值,这样才能将值传递给 console.log(...)

a = 2;

这里对 a 的引用是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为 = 2这个赋值操作找到目标。

funciton foo(a) {
    console.log(a)
}

foo(2);
  1. 最后一行 foo 函数的调用需要对 foo 进行 RHS 引用,去找 foo的值,并把它给我
  2. 代码中隐式的 a = 2 操作可能很容易被你忽略掉,这操作发生在 2 被当做参数传递给 foo 函数时,2 会被分配给参数 a,为了给参数 a (隐式地) 分配值,需要进行一次 LHS 查询。
  3. 这里还有对 a 进行的 RHS 引用,并且将得到的值传给了 console.log(...)console.log(...) 本身也需要一个引用才能执行,因此会对 console对象进行 RHS 查询,并且检查得到的值中是否有一个叫做 log

Next
로 지정하라는 메시지가 표시됩니다.

engine이 a = 2의 할당 작업을 처리하는 데 필요한 런타임 코드를 생성합니다. 엔진이 실행 중일 때 먼저 a 변수가 현재 범위 세트에 존재하는지 범위에 묻습니다. 그렇다면 엔진은 변수를 사용하고, 존재하지 않으면 엔진은 계속해서 변수를 찾습니다.

🎜engine🎜이 변수를 찾으면 해당 변수에 2를 할당하고, 그렇지 않으면 엔진은 오류를 발생시킵니다. 🎜🎜🎜🎜요약: 변수 할당 작업은 두 가지 작업을 수행합니다. 먼저 컴파일러는 현재 범위에서 변수를 선언한 다음 엔진이 런타임 시 범위에서 변수를 검색합니다. , 할당이 사용됩니다. 🎜🎜컴파일러는 컴파일 프로세스의 두 번째 단계에서 코드를 생성합니다. 엔진이 코드를 실행할 때 a 변수를 찾아 코드가 선언되었는지 확인합니다. 검색 프로세스는 범위의 도움을 받지만 엔진이 검색을 수행하는 방식은 최종 검색 결과에 영향을 미칩니다. 🎜🎜이 예에서 엔진은 변수 a에 대해 LHS 쿼리를 수행하고 다른 유형의 쿼리를 RHS라고 합니다. "L"과 "R"은 각각 할당 작업의 왼쪽과 오른쪽을 나타냅니다. 할당문의 왼쪽에 변수가 나타날 때 LHS 질의가 수행되고, 오른쪽에 변수가 나타날 때 RHS 질의가 수행됩니다. 🎜🎜LHS: 값이 할당될 수 있도록 변수 자체의 컨테이너를 찾으려고 합니다. RHS: 단순히 변수의 값을 찾습니다. 🎜
var a = 1;
function foo() {

}
🎜a에 대한 참조는 RHS 참조입니다. 여기서 a에는 작업 값이 할당되지 않기 때문입니다. 따라서 해당 값을 console.log(.. .)🎜
var a = 1;
function foo() {
    b = 2;
}
foo();
console.log(b); // 2
🎜여기가 맞습니다. a의 참조는 LHS 참조입니다. 실제로는 현재 값이 무엇인지 신경 쓰지 않기 때문에 할당 작업 = 2의 대상을 찾고 싶습니다. 🎜
function foo() {
    var a = 1;
    console.log(a); // 1
}
foo();
console.log(a); // ReferenceError: a is not defined
    🎜마지막 줄에서 foo 함수를 호출하려면 foo에 대한 RHS 참조가 필요하며 foo의 값을 찾아 나에게 제공해야 합니다.🎜🎜코드의 암시적 a = 2 연산은 다음에서 쉽게 간과될 수 있습니다. you, this 이 작업은 2가 foo 함수에 매개 변수로 전달될 때 발생하며, 2a 매개 변수에 순서대로 할당됩니다. a 매개변수를 제공하려면 (암시적으로) 값을 할당하고 LHS 쿼리가 필요합니다. 🎜🎜a에 대한 RHS 참조도 있으며, 얻은 값은 console.log(...)로 전달됩니다. console.log(...) 자체도 실행하려면 참조가 필요하므로 콘솔 개체에 대해 RHS 쿼리를 수행하고 얻은 값 중 하나가 있는지 확인합니다. ​log 메소드라고 합니다. 🎜🎜🎜RHS 쿼리가 모든 중첩 범위에서 필요한 변수를 찾을 수 없는 경우 엔진은 ReferenceError 예외를 발생시킵니다. RHS 쿼리 중에 변수가 발견되었지만 이 변수의 값에 대해 비합리적인 작업을 수행하려고 합니다. 예를 들어 null 또는 정의되지 않은 유형 값의 속성을 참조하는 비함수 유형 값을 호출하려고 하면 엔진이 will 다른 유형의 TypeError 예외를 발생시킵니다. 🎜LHS 쿼리를 실행할 때 엔진이 변수를 찾을 수 없으면 전역 범위에 변수를 생성합니다. 하지만 엄격 모드에서는 전역 변수가 자동으로 생성되지 않지만 ReferenceError 예외가 발생합니다. 🎜🎜보충🎜JS의 몇 가지 일반적인 오류 유형🎜🎜🎜간단한 요약은 다음과 같습니다.🎜

    作用域是一套规则,用于确定在哪里找,怎么找到某个变量。如果查找的目的是对变量进行赋值,那么就会使用 LHS查询; 如果目的是获取变量的值,就会使用 RHS 查询;
    JavaScript 引擎执行代码前会对其进行编译,这个过程中,像 var a = 2 这样的声明会被分解成两个独立的步骤

  • var a 在其作用域中声明变量,这会在最开始的阶段,也就是代码执行前进行

  • 接下来,a = 2 会查询 (LHS查询)变量 a 并对其进行赋值。

词法作用域

词法作用域是你在写代码时将变量写在哪里来决定的。编译的词法分析阶段基本能够知道全局标识符在哪里以及是如何声明的,从而能够预测在执行过程中如果对他们查找。

有一些方法可以欺骗词法作用域,比如 eval, with, 这两种现在被禁止使用,1是严格模式和非严格模式下表现不同 2是有性能问题, JavaScript引擎在编译阶段会做很多性能优化,而其中很多优化手段都依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到识别符,eval, with会改变作用域,所以碰到它们,引擎将无法做优化处理。

全局作用域和函数作用域

全局作用域

  • 在最外层函数和最外层函数外面定义的变量拥有全局作用域
var a = 1;
function foo() {

}

变量a 和函数声明 foo 都是在全局作用域中的。

  • 所有未定义直接赋值的变量自动声明为拥有全局作用域

var a = 1;
function foo() {
    b = 2;
}
foo();
console.log(b); // 2
  • 所有 window 对象的属性拥有全局作用域

函数作用域

函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。外部作用域无法访问函数内部的任何内容。

function foo() {
    var a = 1;
    console.log(a); // 1
}
foo();
console.log(a); // ReferenceError: a is not defined
只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域;

变量提升

提升是指声明会被视为存在与其所出现的作用域的整个范围内。

JavaScript编译阶段是找到找到所有声明,并用合适的作用域将他们关联起来(词法作用域核心内容),所以就是包含变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

每个作用域都会进行提升操作。

function foo() {
    var a;
    console.log(a); // undefined
    a = 2;
}
foo();
注意,函数声明会被提升,但是函数表达式不会被提升。

关于 块级作用域和变量提升的内容之前在 从JS底层理解var、let、const这边文章中详细介绍过,这里不再赘述。

块级作用域

我们来看下面这段代码

for(var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    })
}
console.log(`当前的i为${i}`); // 当前的i为5

上面这段代码我们希望是输出 0,1, 2, 3, 4 ,但是实际上输出的是 5,5, 5, 5, 5。我们在 for 循环的头部直接定义了变量 i,通常是因为只想在 for 循环内部的上下文中使用 i,但是实际上 此时的 i 被绑定在外部作用域(函数或全局)中。

,块级作用域是指在指定的块级作用域外无法访问。在ES6之前是没有块级作用域的概念的,ES6引入了 let 和 const。我们可以改写上面的代码,使它按照我们想要的方式运行。

for(let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    })
}
// 0 1 2 3 4
console.log(`当前的i为${i}`); // ReferenceError: i is not defined

此时 for 循环头部的 let 不仅将 i 绑定到了 for 循环的迭代中,事实上将它重新绑定到了循环的每一个迭代中,确保使用上一次循环迭代结束的值重新进行赋值。

let声明附属于一个新的作用域而不是当前的函数作用域(也不属于全局作用域)。但是其行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。
const也是可以用来创建块级作用域变量,但是创建的是固定值。

作用域链

JavaScript是基于词法作用域的语言,通过变量定义的位置就能知道变量的作用域。全局变量在程序中始终都有都定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。

每一段 JavaScript 代码都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表。当 JavaScript 需要查找变量 x 的时候(这个过程称为变量解析),它会从链中的第一个变量开始查找,如果这个对象上依然没有一个名为 x 的属性,则会继续查找链上的下一个对象,如果第二个对象依然没有名为 x 的属性,javaScript会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象包含属性 x, 那么就认为这段代码的作用域链上不存在 x, 并最终抛出一个引用错误 (Reference Error) 异常。

下面作用域中有三个嵌套的作用域。

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log(a, b, c)
    }
    bar( b * 3);
}
foo(2);

<img src="https://img.php.cn/upload/image/252/331/635/1622453204885970.png" title="1622453204885970.png" alt="JavaScript의 범위에 대한 심층 분석">

气泡1包含着整个全局作用域,其中只有一个标识符:foo;
气泡2包含着foo所创建的作用域,其中有三个标识符:a、bar 和 b;
气泡3包含着 bar所创建的作用域,其中只有一个标识符:c

执行 console.log(...),并查找 a,b,c三个变量的引用。下面我们来看看查找这几个变量的过程.
它首先从最内部的作用域,也就是 bar(..) 函数的作用域气泡开始找,引擎在这里无法找到 a,因此就会去上一级到所嵌套的 foo(...)的作用域中继续查找。在这里找到了a,因此就使用了这个引用。对b来说也一样,而对 c 来说,引擎在 bar(..) 中就找到了它。

如果 a,c都存在于 bar(...) 内部,console.log(...)就可以直接使用 bar(...) 中的变量,而无需到外面的 foo(..)中查找。作用域会在查找都第一个匹配的标识符时就停止。

在多层的嵌套作用域中可以定义同名的标识符,这叫”遮蔽效应“。

var a = &#39;外部的a&#39;;
function foo() {
    var a = &#39;foo内部的a&#39;;
    console.log(a); // foo内部的a
}
foo();

作用域与执行上下文

JavaScript的执行分为:解释和执行两个阶段

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

作用域在函数定义时就已经确定了,而不是在函数调用时确定,但执行上下文是函数执行之前创建的。

总结

  • 作用域就是一套规则,用于确定在哪里找以及怎么找到某个变量。

  • 词法作用域在你写代码的时候就确定了。JavaScript是基于词法作用域的语言,通过变量定义的位置就能知道变量的作用域。ES6引入的let和const声明的变量在块级作用域中。

  • 声明提升是指声明会被视为存在与其所出现的作用域的整个范围内。

  • 查找变量的时候会先从内部的作用域开始查找,如果没找到,就往上一级进行查找,依次类推。

  • 作用域在函数定义时就已经确定了,执行上下文是函数执行之前创建的。

更多编程相关知识,请访问:编程视频!!

위 내용은 JavaScript의 범위에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제