>  기사  >  웹 프론트엔드  >  Javascript 범위 심층 분석(코드 예시)

Javascript 범위 심층 분석(코드 예시)

不言
不言앞으로
2018-11-23 14:54:051473검색

이 글은 Javascript 범위에 대한 심층적인 분석(코드 예제)을 제공합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

범위

범위는 변수(식별자)를 찾을 위치와 방법을 결정하는 규칙 집합입니다. 검색 목적이 변수에 값을 할당하는 것이라면 LHS 쿼리가 사용되며, 변수 값을 얻는 것이 목적이라면 RHS 쿼리가 사용됩니다. 할당 연산자는 LHS 쿼리를 발생시킵니다. = 연산자 또는 함수 호출 시 매개변수 전달 작업은 연결된 범위에서 할당 작업을 수행합니다.

JavaScript 엔진은 실행되기 전에 먼저 코드를 컴파일합니다. 이 프로세스에서 var a = 2와 같은 문은 두 개의 별도 단계로 나뉩니다.

  1. 먼저 var a가 해당 범위에 있습니다. 에서 새 변수를 선언합니다. 이는 코드가 실행되기 전 맨 처음에 발생합니다.

  2. 다음으로 a = 2는 변수 a를 쿼리(LHS 쿼리)하고 여기에 값을 할당합니다.

LHS 및 RHS 쿼리는 모두 현재 실행 범위에서 시작됩니다. 필요한 경우(즉, 필요한 식별자를 찾지 못하는 경우) 상위 범위에서 대상 식별자를 계속 검색하므로 매번 1단계 범위(1층)로 올라가고 최종적으로 전역 범위(최상위 수준)에 도달하면 발견 여부에 관계없이 중지됩니다.

RHS 참조에 실패하면 ReferenceError 예외가 발생합니다. 실패한 LHS 참조로 인해 LHS 참조 대상을 식별자로 사용하는 전역 변수(비엄격 모드)가 자동으로 암시적으로 생성되거나 ReferenceError 예외(엄격 모드)가 발생합니다.

어휘 범위

어휘 범위는 코드 작성 시 함수 선언 위치에 따라 범위가 결정된다는 의미입니다. 컴파일의 어휘 분석 단계에서는 기본적으로 모든 식별자가 선언된 위치와 방법을 알고 있으므로 실행 중에 식별자가 어떻게 검색될지 예측할 수 있습니다.

JavaScript에는 어휘 범위를 "속이는" 두 가지 메커니즘이 있습니다: eval(..) 및 with . 전자는 하나 이상의 선언을 포함하는 "코드" 문자열을 평가하여 기존 어휘 범위를 수정합니다(런타임 시). 후자는 본질적으로 객체에 대한 참조를 범위로 처리하고 객체의 속성을 범위 내 식별자로 처리하여 런타임에 다시 새로운 어휘 범위를 생성합니다.

이 두 메커니즘의 부작용은 엔진이 컴파일 타임에 범위 조회를 최적화할 수 없다는 것입니다. 왜냐하면 엔진은 이러한 최적화가 유효하지 않은 것으로 조심스럽게 고려할 수 있기 때문입니다. 이러한 메커니즘을 사용하면 코드 실행 속도가 느려집니다. 사용하지 마십시오.

자바스크립트는 어휘 범위를 사용하기 때문에 함수가 정의될 ​​때 함수의 범위가 결정됩니다.

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();//local scope
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();//local scope
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();//local scope

함수 표현식 및 함수 선언

함수 선언:함수 함수 이름(매개변수: 선택 사항) {함수 본문}
함수 표현식:함수 함수 이름(선택 사항)(매개 변수: 선택 사항){ 함수 본문}

식별:

  • 함수 이름이 선언되지 않은 경우 표현식이어야 합니다.

  • 함수 foo(){}가 할당 표현식의 일부로 사용되면 함수 표현식입니다. function foo(){}가 함수 본문이나 프로그램 상단에 포함되어 있으면 함수 선언.

  • (function foo(){})를 괄호로 묶은 이유는 괄호()가 그룹화 연산자이고

  function foo(){} // 声明,因为它是程序的一部分

  (function(){
    function bar(){} // 声明,因为它是函数体的一部分
  })();

  var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
  new function bar(){}; // 表达式,因为它是new表达式
  (function foo(){}); // 表达式:包含在分组操作符内
  
  try {
    (var x = 5); // 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句
  } catch(err) {
    // SyntaxError
  }
  • 안에만 표현식을 포함할 수 있기 때문입니다. 조건문 내에서 사용할 수 있지만 표준화되지 않았습니다. 함수 표현식을 사용하는 것이 가장 좋습니다

  • 함수 선언은 변수 선언을 재정의하지만 변수 할당은 재정의되지 않습니다.

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"

함수 범위 및 블록 범위

함수가 가장 많습니다. JavaScript의 공통 범위 단위. 본질적으로 함수 내부에 선언된 변수나 함수는 해당 변수나 함수가 위치한 범위에서 "숨겨집니다". 이는 의도적이고 좋은 소프트웨어 설계 원칙입니다. 그러나 함수가 범위의 유일한 단위는 아닙니다.

블록 범위는 변수와 함수가 해당 범위에 속할 뿐만 아니라 특정 코드 블록(보통 { .. } 내)에도 속할 수 있음을 의미합니다.

ES3부터 try/catch 구문은 catch 절에 블록 범위를 갖습니다.

let 키워드(var 키워드의 사촌)는 모든 코드 블록에서 변수를 선언하기 위해 ES6에 도입되었습니다.
if(..) { let a = 2; }는 if { .. } 블록을 하이재킹하는 변수를 선언하고 해당 변수를 이 블록에 추가합니다.

일부 사람들은 블록 범위가 함수 범위의 대체 수단으로 완전히 사용되어서는 안 된다고 생각합니다. 두 기능은 동시에 존재해야 합니다. 개발자는 읽기 쉽고 유지 관리가 가능한 좋은 코드를 작성하기 위해 필요에 따라 사용할 범위를 선택할 수 있고 선택해야 합니다.

提升

我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引
起很多危险的问题!

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);

作用域闭包

通常,程序员会错误的认为,只有匿名函数才是闭包。其实并非如此,正如我们所看到的 —— 正是因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包), 这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。 为了更好的澄清该问题,我们对ECMAScript中的闭包作两个定义(即两种闭包):

ECMAScript中,闭包指的是:

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量

循环闭包

for (var i=1; i<=5; i++) {
    (function(j) {
        setTimeout( function timer() {
        console.log( j );
        }, j*1000 );
    })( i );
}

for (var i=1; i<=5; i++) {
    let j = i; // 是的,闭包的块作用域!
    setTimeout( function timer() {
    console.log( j );
    }, j*1000 );
}

for (let i=1; i<=5; i++) {
    setTimeout( function timer() {
    console.log( i );
    }, i*1000 );
}
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();//3
data[1]();//3
data[2]();//3

模块

模块有两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回
值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭
包。

现代模块机制

var MyModules = (function Manager() {
    var modules = {};
    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply( impl, deps );
    }
    function get(name) {
        return modules[name];
    }
    return {
        define: define,
        get: get
    };
})();

未来模块机制

//bar.js
function hello(who) {
    return "Let me introduce: " + who;
}
export hello;
//foo.js
// 仅从 "bar" 模块导入 hello()
import hello from "bar";
var hungry = "hippo";
function awesome() {
    console.log(
        hello( hungry ).toUpperCase()
    );
}
export awesome;
baz.js
// 导入完整的 "foo" 和 "bar" 模块
module foo from "foo";
module bar from "bar";
console.log(
    bar.hello( "rhino" )
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO

块作用域替代方案

Google 维护着一个名为 Traceur 的项目,该项目正是用来将 ES6 代码转换成兼容 ES6 之前的环境(大部分是 ES5,但不是全部)。TC39 委员会依赖这个工具(也有其他工具)来测试他们指定的语义化相关的功能。

{
    try {
        throw undefined;
    } catch (a) {
        a = 2;
        console.log( a );
    }
}
console.log( a )

上下文

EC(执行环境或者执行上下文,Execution Context)

EC={
    VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */},
    this:{},
    Scope:{ /* VO以及所有父执行上下文中的VO */}
}

ECS(执行环境栈Execution Context Stack)

//ECS=[Window]
A(//ECS=[Window,A]
    B(//ECS=[Window,A,B]
        //run B 
    )
    //ECS=[Window,A]
)
//ECS=[Window]

VO(变量对象,Variable Object)

var a = 10;
function test(x) {
  var b = 20;
};
test(30);
/*
VO(globalContext)
  a: 10,
  test: 
VO(test functionContext)
  x: 30
  b: 20
*/

AO(活动对象,Active Object)

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
} 
test(10);
/*
AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};
*/

scope chain(作用域链)和[[scope]]属性

Scope = AO|VO + [[Scope]]

例子

var x = 10;
 
function foo() {
  var y = 20;
 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
 
  bar();
}
 
foo(); // 60
  • 全局上下文的变量对象是:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};
  • 在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [
  globalContext.VO
];
  • 在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};
  • “foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];
  • 内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];
  • 在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {
  z: 30
};
  • “bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
 
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];
  • 对“x”、“y”、“z”的标识符解析如下:

- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
   globalContext.VO // found - 10

- "y"
-- barContext.AO // not found
   fooContext.AO // found - 20

- "z"
   barContext.AO // found - 30

위 내용은 Javascript 범위 심층 분석(코드 예시)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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