作用域
作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用的是
词法作用域
, 也就是 静态作用域。静态作用域:
函数的作用域在函数定义的时候就决定了
动态作用域:
函数的作用域是在函数调用的时候才决定的
var str = '前端自学社区'
function getValue(){
console.log(str)
}
function getNewValue(){
var str = '海军'
// 当调用 getValue 时, 它的作用域环境已经确定了
// 它会寻函数自身内部是否有 str 局部变量,没有的话,它会指向最外层寻找该变量。
getValue()
}
getNewValue()
// 前端自学社区
因为JavaScript
的作用域是 静态的,所以创建函数时,它的作用域已经确定了。如果某语言是采用的是
动态作用域
的话,这道题的答案是 输出海军
.解析:
当 调用
getNewValue
函数时,它内部又调用的getValue
,getValue
内部访问了str
变量,这时会优先看getValue
内部是否有str
变量,没有的话,它会查找getNewValue
内部是否有 局部变量str
, 所以最终结果为海军
.
执行上下文和执行栈
什么是执行上下文
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
执行上下文 三种类型
- 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置
this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文。- 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
- Eval 函数执行上下文 — 执行在
eval
函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用eval
,所以在这里我不会讨论它。
执行栈
执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
上述代码的执行上下文栈。当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到
first()
函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。当从
first()
函数内部调用second()
函数时,JavaScript 引擎为second()
函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当second()
函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即first()
函数的执行上下文。当
first()
执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。
怎么创建执行上下文?
现在让我们了解 JavaScript 引擎是怎样创建执行上下文的。创建执行上下文有两个阶段:1) 创建阶段 和 2) 执行阶段。
1.创建阶段
在 JavaScript 代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:
- this 值的决定,即我们所熟知的 This 绑定。
- 创建词法环境组件。
- 创建变量环境组件。
this
绑定
在全局执行上下文中,this
的值指向全局对象。(在浏览器中,this
引用 Window 对象)。在函数执行上下文中,
this
的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么this
会被设置成那个对象,否则this
的值被设置为全局对象或者undefined
(在严格模式下)。
let foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // 'this' 引用 'foo', 因为 'baz' 被
// 对象 'foo' 调用
let bar = foo.baz;
bar(); // 'this' 指向全局 window 对象,因为
// 没有指定引用对象
2. 执行阶段
在此阶段,完成对所有这些变量的分配,最后执行代码。