Home  >  Article  >  Web Front-end  >  In-depth analysis of Javascript scope (code example)

In-depth analysis of Javascript scope (code example)

不言
不言forward
2018-11-23 14:54:051470browse

The content of this article is an in-depth analysis (code example) of Javascript scope. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Scope

Scope is a set of rules that determine where and how to find variables (identifiers). If the purpose of the search is to assign a value to a variable, the LHS query will be used; if the purpose is to obtain the value of the variable, the RHS query will be used. Assignment operators cause LHS queries. The = operator or the operation of passing parameters when calling a function will result in an assignment operation in the associated scope.

The JavaScript engine first compiles the code before it is executed. During this process, a statement like var a = 2 is broken down into two separate steps:

  1. First, var a declares the new variable in its scope. This happens at the very beginning, before the code is executed.

  2. Next, a = 2 will query (LHS query) the variable a and assign a value to it.

LHS and RHS queries will start in the current execution scope, and if necessary (that is, they do not find the required identifier), they will continue to look up the upper scope. The target identifier, so that each time it goes up one level of scope (one floor), and finally reaches the global scope (top level), it will stop whether it is found or not found.

Unsuccessful RHS reference will cause a ReferenceError exception to be thrown. An unsuccessful LHS reference results in the automatic and implicit creation of a global variable (in non-strict mode) that uses the target of the LHS reference as an identifier, or a ReferenceError exception (in strict mode).

Lexical Scope

Lexical scope means that the scope is determined by the position of the function declaration when writing the code. The lexical analysis stage of compilation basically knows where and how all identifiers are declared, allowing it to predict how they will be looked up during execution.

There are two mechanisms in JavaScript to "cheat" lexical scope: eval(..) and with . The former evaluates a string of "code" containing one or more declarations and thereby modifies an existing lexical scope (at runtime). The latter essentially creates a new lexical scope (again at runtime) by treating a reference to an object as a scope and the object's properties as identifiers within the scope.

A side effect of these two mechanisms is that the engine cannot optimize the scope lookup at compile time, because the engine can only cautiously consider such optimization to be invalid. Using any of these mechanisms will cause your code to run slower. Don't use them.

Because JavaScript uses lexical scope, the scope of the function is determined when the function is defined.

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

Function expressions and function declarations

Function declaration:function function name (parameters: optional){function body}
Function expression :function function name (optional) (parameters: optional){function body}

Identification:

  • If the function name is not declared, It must be an expression.

  • If function foo(){} is part of an assignment expression, it is a function expression. If function foo(){} is included in a function body, Or if it's at the top of the program, it's a function declaration.

  • (function foo(){}) enclosed in parentheses, the reason why it is an expression is because parentheses () is a grouping operator, and it can only contain expressions inside it Formula

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

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

  var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
  new function bar(){}; // 表达式,因为它是new表达式
  (function foo(){}); // 表达式:包含在分组操作符内
  
  try {
    (var x = 5); // 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句
  } catch(err) {
    // SyntaxError
  }
  • Although function declaration can be used in conditional statements, it has not been standardized. It is best to use function expressions

  • Function declaration will override variable declaration, but not variable assignment

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

Function scope and block scope

Function is the most common role in JavaScript domain unit. In essence, a variable or function declared inside a function is "hidden" from the scope in which it is located. This is an intentional and good software design principle. But functions are not the only unit of scope.

Block scope means that variables and functions can not only belong to the scope in which they are located, but also belong to a certain code block (usually within { .. }).

Starting with ES3, the try/catch construct has block scope in the catch clause.

The let keyword (a cousin of the var keyword) was introduced in ES6 to declare variables in any block of code.
if(..) { let a = 2; } declares a variable that hijacks the if { .. } block and adds the variable to this block.

Some people believe that block scope should not be used entirely as a replacement for function scope. Both functions should exist at the same time. Developers can and should choose which scope to use according to their needs to create good code that is readable and maintainable.

提升

我们习惯将 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

The above is the detailed content of In-depth analysis of Javascript scope (code example). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete