跳到主要内容

执行上下文与作用域

执行上下文与作用域(红宝书4)

JavaScript Execution Context

  • When the JavaScript engine executes the JavaScript code, it creates execution contexts.

  • Each execution context has two phases: the creation phase and the execution phase. 创建阶段 和 执行阶段

1. The creation phase 创建阶段

tip

When the JavaScript engine executes a script for the first time, it creates the global execution context. During this phase, the JavaScript engine performs the following tasks:

  • Create the global object i.e., window in the web browser or global in Node.js.

  • Create the this object and bind it to the global object.

  • Setup a memory heap for storing variables and function references.

  • Store the function declarations in the memory heap and variables within the global execution context with the initial values as undefined.

let x = 10;

function timesTen(a) {
return a * 10;
}

let y = timesTen(x);

console.log(y); // 100

When the JavaScript engine executes the code example above, it does the following in the creation phase:

  • First, store the variables x and y and function declaration timesTen() in the global execution context.

  • Second, initialize the variables x and y to undefined.

global execution context

2. The execution phase 执行阶段

During the execution phase, the JavaScript engine executes the code line by line, assigns the values to variables, and executes the function calls.

global execution context

For each function call, the JavaScript engine creates a new function execution context. JavaScript引擎对每一个函数调用 创建一个新的 函数执行上下文。

The function execution context is similar to the global execution context. But instead of creating the global object, the JavaScript engine creates the arguments object that is a reference to all the parameters of the function.

function timesTen(a) {
return a * 10;
}

The function execution context creates the arguments object that references all parameters passed into the function, sets this value to the global object, and initializes the a parameter to undefined.

function execution context

During the execution phase of the function execution context, the JavaScript engine assigns 10 to the parameter a and returns the result (100) to the global execution context.

function execution context

tip

To keep track of all the execution contexts, including the global execution context and function execution contexts, the JavaScript engine uses the call stack.

JavaScript Variable Scopes

tip

Scope determines the visibility and accessibility of a variable. JavaScript has three scopes:

  • The global scope
  • Local scope
  • Block scope (started from ES6)

1. The global scope

When the JavaScript engine executes a script, it creates a global execution context. Also, it also assigns variables that you declare outside of functions to the global execution context. These variables are in the global scope. They are also known as global variables. 当 JavaScript 引擎执行脚本时,它会创建一个全局执行上下文。此外,它还将您在函数外部声明的变量分配给全局执行上下文。这些变量在全局作用域内。它们也称为全局变量。

global scope

2. Local scope

The variables that you declare inside a function are local to the function. They are called local variables. local scope

When the JavaScript engine executes the say() function, it creates a function execution context. The variable message declared inside the say() function is bound to the function execution context of the function, not the global execution context.

3. Scope chain

The way that JavaScript resolves a variable is by looking at it in its current scope, if it cannot find the variable, it goes up to the outer scope, which is called the scope chain. JavaScript 解析变量的方式是在其当前作用域中查找变量,如果找不到该变量,则向上查找外部作用域,这称为作用域链。 scope chain

var y = 20;

function bar() {
var y = 200;

function baz() {
console.log(y);
}

baz();
}

bar();
  • First, the JavaScript engine finds the variable y in the scope of the baz() function. It cannot find any. So it goes out of this scope.
  • Then, the JavaScript engine finds the variable y in the bar() function. It can find the variable y in the scope of the bar() function so it stops searching.

4. Global variable leaks: the weird part of JavaScript

全局变量泄漏:JavaScript 的奇怪部分

function getCounter() {
counter = 10;
return counter;
}

console.log(getCounter());

In this example, we assigned 10 to the counter variable without the var, let, or const keyword and then returned it. 能正常输出 10,这个问题被称为全局变量的泄漏(the leaks of the global variables)。

  • the JavaScript engine first looks up the counter variable in the local scope of the getCounter() function. Because there is no var, let, or const keyword, the counter variable is not available in the local scope. It hasn’t been created. 由于没用var, let, 或 const关键字声明,counter变量在局部作用域不可用,它还没有创建。

  • Then, the JavaScript engine follows the scope chain and looks up the counter variable in the global scope. The global scope also doesn’t have the counter variable, so the JavaScript engine creates the counter variable in the global scope. 然后,JavaScript 引擎沿着作用域链在全局作用域中查找counter变量。全局作用域也没有counter变量,因此 JavaScript 引擎在全局作用域内创建counter变量。

tip

使用 'use strict' 可以避免全局变量泄漏

'use strict'

function getCounter() {
counter = 10;
return counter;
}

console.log(getCounter()); // Uncaught ReferenceError: counter is not defined
function getCounter() {
'use strict'
counter = 10;
return counter;
}

console.log(getCounter()); // Uncaught ReferenceError: counter is not defined

5. Block scope

Generally, whenever you see curly brackets {}, it is a block. It can be the area within the if, else, switch conditions or for, do while, and while loops.

ES6 provides the let and const keywords that allow you to declare variables in block scope.

function say(message) {
if(!message) {
let greeting = 'Hello'; // block scope
console.log(greeting);
}

console.log(greeting); // ReferenceError
}

say();

In this example, we reference the variable greeting outside the if block that results in an error.

Lexical Scope in JavaScript – What Exactly Is Scope in JS?

1. Scope

tip
  • Scope refers to the area where an item (such as a function or variable) is visible and accessible to other code.
  • JavaScript scope is all about space.
const fullName = 'Oluwatobi Sofela';

function profile() {
function sayName() {
function writeName() {
return fullName;
}
return writeName();
}
return sayName();
}

console.log(profile());

Keep in mind that whenever the writeName() function gets invoked, the computer will not go straight to the global scope to call the fullName variable. Instead, it must sequentially go through the scope chain to look for fullName. 请记住,无论何时调用 writeName() 函数,计算机都不会直接进入全局作用域来调用 fullName 变量。相反,它必须按顺序通过作用域链来查找 fullName。

2. Scope Chain

tip

A scope chain refers to the unique spaces that exist from the scope where a variable got called to the global scope. 作用域链指的是 从调用变量的作用域到全局作用域 存在的唯一空间。

const fullName = 'Oluwatobi Sofela';

function profile() {
function sayName() {
function writeName() {
return fullName;
}
return writeName();
}
return sayName();
}

console.log(profile());

In the snippet above, observe that the fullName variable got called from the writeName() function's scope.

Therefore, the scope chain that exists from the variable’s call to the global scope is: writeName() scope ---> sayName() scope ---> profile() scope ---> global scope

In other words, there are four (4) spaces from fullName’s invocation scope to its lexical scope (the global scope in this instance). 从它的调用作用域到它的词法作用域

tip

Note: The global scope is the last link in JavaScript's scope chain. 注意:全局作用域是 JavaScript 作用域链中的最后一环。

3. How Does the Scope Chain Work?

tip

JavaScript's scope chain determines the hierarchy of places the computer must go through — one after the other — to find the lexical scope (origin) of the specific variable that got called. JavaScript 的作用域链决定了计算机必须经过的层次结构——一个接一个——以找到被调用的特定变量的词法作用域(起点)。

const fullName = 'Oluwatobi Sofela';

function profile() {
function sayName() {
function writeName() {
return fullName;
}
return writeName();
}
return sayName();
}

console.log(profile());

here are the sequential steps the computer must take to locate fullName's lexical scope 以下是计算机必须采取的顺序步骤来定位 fullName 的词法作用域:

  1. Firstly, the computer will check if fullName got defined locally within the writeName() function. But it will find no fullName definition there, so it moves up to the next scope to continue its quest. 首先,计算机将检查 fullName 是否在 writeName() 函数中本地定义。但是它在那里找不到 fullName 定义,因此它向上移动到下一个作用域以继续其探索。

  2. Secondly, the computer will search for fullName's definition in sayName() (the next space in the scope chain). Still, it doesn't find it there, so it climbs up the ladder to the next scope.

  3. Thirdly, the computer will search for fullName's definition in the profile() function. Yet still, fullName is not found there. So the computer goes forward to seek fullName's lexical scope in the next region of the scope chain.

  4. Fourthly, the computer goes to the global scope (the following scope after profile()). Fortunately, it finds fullName's definition there! Therefore, it gets its content ("Oluwatobi Sofela") and returns it.

tip
  • Suppose the computer did not find fullName's definition in any of the scopes. In such a case, the computer will return Uncaught ReferenceError: fullName is not defined.

  • The global scope is always the last scope of any JavaScript scope chain. In other words, the global scope is where all searches will end. 全局作用域始终是任何 JavaScript 作用域链的最后一个作用域。换句话说,全局作用域是所有搜索结束的地方。

  • An inner (child) scope has access to its parent (outer) scope, but an outer scope does not have access to its child scope. 内部(子)作用域可以访问其父(外部)作用域,但外部作用域不能访问其子作用域。例如,在上面的代码片段中,writeName() 可以访问其任何父作用域(sayName()、profile() 或全局作用域)内的代码。但是,无论是 sayName()、profile() 还是全局作用域都不能访问 writeName() 的任何代码。

4. Lexical Scope

tip
  • Lexical refers to the definition of things. 词法是指事物的定义。

  • Lexical scope is the definition area of an expression. 词法作用域是表达式的定义区域。

  • In other words, an item's lexical scope is the place in which the item got created. xx的词法作用域 是 xx被创建的地方。

  • Another name for lexical scope(or lexical scoping) is static scope(or static scoping). 词法作用域的另一个名称是静态作用域。

  • The place an item got invoked (or called) is not necessarily the item's lexical scope. Instead, an item's definition space is its lexical scope.

  • only code within an item's lexical scope can access the item.

例1:

// Define a variable in the global scope:
const myName = "Oluwatobi";

// Call myName variable from a function:
function getName() {
return myName;
}

In the snippet above, notice that we defined the myName variable in the global scope and called it in the getName() function. Remember that lexical scope means definition space — not invocation space. Therefore, myName’s lexical scope is the global scope because we defined myName in the global environment.

例2:

function getName() {
const myName = "Oluwatobi";
return myName;
}

Notice that we created and called myName within getName(). Therefore, myName’s lexical scope is getName()’s local environment because getName() is myName’s definition space.

例3:

// Define a function:
function showLastName() {
const lastName = "Sofela";
return lastName;
}

// Define another function:
function displayFullName() {
const fullName = "Oluwatobi " + lastName;
return fullName;
}

// Invoke displayFullName():
console.log(displayFullName());

// The invocation above will return: Uncaught ReferenceError: lastName is not defined

lastName’s definition space is showLastName() while displayFullName()’s lexical scope is the global environment.

例4:

function showLastName() {
const lastName = "Sofela";
return lastName;
}

// Define another function:
function displayFullName() {
const fullName = "Oluwatobi " + showLastName();
return fullName;
}

// Invoke displayFullName():
console.log(displayFullName());

// The invocation above will return: "Oluwatobi Sofela"

displayFullName() 无法访问 showLastName() 的 lastName 变量。displayFullName() 调用了 showLastName(), 然后返回其 lastName 变量的内容。

displayFullName() could invoke showLastName() because the two functions are both defined in the global scope. displayFullName() and showLastName() are in the same lexical scope.