JavaScript中的上下文:从生活细节看编程艺术

理解执行上下文

在JavaScript的世界里,上下文是一个既熟悉又陌生的概念。熟悉的是它的名字,陌生的却是它的深邃含义。

很多人会问,什么是上下文?它就像是我们生活的环境,无处不在,却又容易被忽视。

全局执行上下文

概念

在JavaScript中,全局上下文(Global Context)是代码执行的默认上下文,它在整个代码中都是可访问的。

当你运行一个JavaScript程序时,就会创建一个全局上下文。全局上下文是代码执行的默认环境,它会在整个页面生存周期内存在。

想象你在厨房烹饪,厨房就是你做饭时的全局上下文。无论你在做什么菜,都需要在这个环境中操作,比如使用炉子、冰箱等。即使你在做不同的菜,这些基本的设备和条件都是一样的,这就是全局上下文的特点------它是持续存在的,并且对所有的代码都可用。

应用

在JavaScript中,全局上下文的应用非常广泛。例如,你可以在这个上下文中定义全局变量,这些变量可以在你的程序的任何地方被访问和修改。此外,全局上下文还可以包含全局函数,这些函数可以被程序中的任何其他函数调用。

总之,全局上下文是JavaScript程序的基础,它提供了一个共享的环境,使得程序中的所有部分都可以相互交互和通信。

下面是一个展示全局上下文的简单例子:

javascript 复制代码
// 全局上下文示例
var globalVar = '我是全局变量';

function globalContext() {
    var localVar = '我是局部变量';
    console.log(globalVar); // 输出全局变量
    console.log(localVar); // 输出局部变量
}

globalContext();

函数执行上下文

概念

在JavaScript中,函数上下文(Function Execution Context)是指当一个函数被调用时,JavaScript引擎为其创建的一个特定的环境。这个环境包括了函数内部的变量、函数声明、以及this关键字的指向等信息。

函数上下文是JavaScript中非常重要的一个概念,因为它决定了函数在执行时可以访问哪些数据,以及其行为。

想象你在厨房烹饪,你需要遵循特定的食谱来制作菜肴。食谱中的步骤和食材就是你的函数上下文,它们指导你如何操作。如果你在一个不同的厨房,使用不同的食材,即使食谱相同,结果也会不同。

这就好比在JavaScript中,相同的函数在不同上下文中可能有不同的行为,因为它依赖于当前的上下文环境。

应用

函数上下文的主要作用是确保函数内的变量和函数声明在函数执行时才被创建和销毁,而不影响函数外的变量和函数声明。换句话说,函数上下文提供了一个隔离的环境,使得函数内部的变量和函数声明不会干扰到函数外部的代码。

总之,函数上下文是JavaScript中的一个核心概念,它影响着函数的行为和可访问性。通过理解函数上下文,你可以编写更加灵活和强大的代码。

下面是一个展示函数上下文的简单例子:

javascript 复制代码
// 函数上下文示例
function functionContext() {
    var localVar = '我是局部变量';
    console.log(localVar); // 输出局部变量
    if (typeof localVar === 'undefined') {
        console.log('局部变量未定义');
    } else {
        console.log('局部变量已定义');
    }
}

functionContext();

生命周期

执行上下文的生命周期包括创建(Creation)、执行(Execution)和清理(Cleanup)三个阶段。

在执行阶段,会处理代码中的表达式和语句;

在清理阶段,会销毁执行上下文,释放资源。

Creation Phase - 创建阶段

在创建阶段,会创建变量对象(Variable Object)、构建作用域链(Scope Chain)、设置this关键字的值;

想象一下,你正在观看一场精彩的戏剧表演。在演员们登上舞台,台词脱口而出之前,有一系列复杂的准备工作正紧锣密鼓地进行着,它包括了一系列的布置工作,让演员们(也就是你的代码)能在舞台上正确地表演。这个过程就是"执行上下文"的创造。

首先,JavaScript引擎会与一个神秘的盒子------执行上下文对象(Execution Context Object, ECO)建立联系。这个盒子就像是戏剧的剧本,里面记录了所有的角色(变量和函数)以及他们的关系。在这个阶段,引擎会做三件事情:

1,创建变量对象(Variable Object, VO)

这就像是把剧本中的角色名单列出来,给他们分配角色。对于那些用var声明的角色(变量),他们会被标记为"未定义",就像是演员们还没有穿上戏服;而对于那些函数角色,则是直接准备好,让他们随时待命。这就是所谓的"提升",因为它们在代码还没开始正式演出(执行)之前就已经准备好了。

javascript 复制代码
// 这段代码就像是一场戏剧的开场白
var a; // 剧本中的角色a,还未登场
function greetings() { // 剧本中的角色greetings,已经准备好
  console.log('Hello, World!');
}

2,构建作用域链(Scope Chain)

这个链就像是连接不同层级的舞台幕布,它确定了演员们能否互相看见对方,也就是变量和函数能否在不同的作用域中被访问。每个幕布代表了一个层级,从当前的舞台(VO)到更高级别的舞台(如果有的话),这样演员们就能找到彼此,协同表演。

javascript 复制代码
// 假设我们有两个舞台,一个大的(全局)和一个小的(局部)
var a = '全球'; // 全局舞台上的角色
function greet() {
  var b = '本地'; // 局部舞台上的角色
  // 在这里,b可以直接被看见,而a也可以通过幕布(作用域链)被看见
}

3,初始化this关键字

在全局舞台上,this指向的是整个剧院(window对象),而在局部舞台上,它指向的是当前的小舞台(调用它的环境)。这就像是演员知道他们在哪个剧场表演,以及谁是他们的观众。

现在,让我们回到我们的例子,看看这一切是如何运作的。假设我们有一个全局的变量a和一个函数greetings,还有一个函数addExtra,它在一个局部舞台上被调用。在创建阶段,JavaScript引擎会这样做:

javascript 复制代码
// 全局执行上下文(GEC)
{
  VO: {
    a: undefined, // 全局角色a,尚未登台
    greetings: function greetings() { ... } // 全局角色greetings,已经准备好
  },
  scope: [...GEC.VO] // 只有一个全局舞台
}

// 局部执行上下文(FEC),比如由greetings函数创建
{
  VO: {
    word: 'nothing', // 局部角色word,已经准备好
    addExtra: function addExtra() { ... } // 局部角色addExtra,也已经准备好
  },
  scope: [GEC.VO, ...greetings.VO] // 可以通过幕布看到全局舞台和局部舞台
}

当戏剧(代码)开始上演时,所有的角色都已经准备就绪,幕布(作用域链)已经拉好,演员们(变量和函数)都知道自己的位置,观众(this)也已经坐好。

这就是执行上下文的魔法,它让一切变得井然有序,让代码能够在正确的环境中运行。

这就是JavaScript引擎在你写下代码之前所做的幕后工作。虽然你看不见它,但它确保了每一段代码都能够按照预期那样运行。

当你点击按钮,触发一个函数,或是简单地在控制台输入一些代码时,这个过程就开始了。这就是为什么当你写下console.log(a)时,尽管你还没有给a一个具体的值,但JavaScript仍然知道你在说什么,因为它已经在幕后做了很多准备工作。

Execution Phase - 执行阶段

在执行阶段,代码开始执行,并在执行完成后从执行栈中移除。

当帷幕拉开,演员们开始表演...

如果说创建阶段是幕后的紧张筹备,那么执行阶段就是演员们真正走上舞台,表演开始的时候。在这个阶段,JavaScript引擎将启动它的解析器,将之前创建好的脚本转化为可以执行的字节码,并且逐行执行。这就好比导演将剧本翻译成舞台上的动作,灯光师调整灯光,音效师准备音乐,一切都准备就绪,只等观众入场。

在执行阶段,变量对象(VO)中的那些标记为"undefined"的变量和函数终于迎来了它们的首秀。它们被赋予了生命,变量不再是空缺,而是填充了实际的值,函数也开始活跃起来,响应调用。

如果你之前声明了一个变量a,现在你可以给它赋值,让它从一个默默无闻的配角变成剧中的主角。

如果你之前定义了一个函数greetings,现在你可以调用它,让它在舞台上发出声音。

javascript 复制代码
// 当执行阶段开始,我们的变量和函数终于有了生命
a = 'Hello, World!'; // 给变量a赋予生命,让它说出第一句台词
greetings(); // 让函数greetings走上舞台,开始它的表演

在执行阶段,作用域链也发挥了它的作用。它就像是一个个连接不同层级舞台的通道,确保演员们能够找到彼此,无论他们是同台演出还是在不同的场景中。这样,即使在局部舞台上定义的函数也能通过这些通道找到全局舞台上的角色,反之亦然。

javascript 复制代码
// 使用作用域链,我们可以轻松地在不同的舞台之间切换视角
function addExtra() {
  console.log(a); // 尽管我们在局部舞台,但依然可以通过通道找到全局舞台上的角色a
}

最后,当我们谈论this关键字时,它就像是指向观众席的指示牌。在全局舞台上,它指向整个剧院,而在局部舞台上,它指向的是当前的小舞台。这样,演员们就知道他们是在为谁表演,他们是在全局的大厅里还是在局部的小房间里。

javascript 复制代码
// 在全局舞台上,this指向整个剧院
console.log(this === window); // 真的,我们在这里!

// 在局部舞台上,this指向的是我们当前的小房间
function greet() {
  console.log(this === window); // 错误,我们现在在一个小房间里!
}

总的来说,执行阶段是JavaScript引擎将之前的准备工作转化为实际表演的时刻。它不仅仅是代码的执行,更是JavaScript魔法展现的时刻。

当引擎逐行读取并执行代码时,它确保了每一个变量和函数都在其适当的位置和时间被访问和调用,就像是确保每个演员都在正确的时间走上舞台,说出台词,做出动作。

这就是为什么当你在控制台输入代码时,它总能给你一个准确的回答,因为执行上下文已经为这一刻做好了充分的准备。

执行上下文栈

执行上下文栈(ECS)就像是剧场的节目单,它记录着所有即将上演的场景------也就是所有的执行上下文。每当一个新的执行上下文被创建,无论是全局的还是函数的,它都会被添加到这个栈中。随着剧情的发展,新的上下文不断加入,旧的上下文则按照"后进先出"的原则被处理。这意味着最后一个进入的执行上下文将是第一个被执行的,就像最后上场的演员将首先开始他的表演。

当脚本开始执行时,全局执行上下文首先被推入栈中。它是整个故事的背景,提供了一个宏大的舞台,所有的角色和情节都将在这里展开。然后,当你的脚本遇到一个函数调用时,一个新的执行上下文就会被创建,并被推入栈中。这个新上下文会暂时占据舞台中心,直到这个函数完成它的表演,然后才会退场,把舞台让给之前的角色。

javascript 复制代码
// 全局执行上下文首先上场
console.log('Hello, theatergoer!'); // 全局执行上下文的开场白

// 当我们调用一个函数时,一个新的执行上下文占据了舞台中心
function sceneOne() {
  console.log('This is Scene One.'); // 新的执行上下文上场
}

sceneOne(); // 函数执行上下文被推入栈中,成为焦点

在整个剧本中,无论何时何地,执行上下文栈都保持着对所有上下文的跟踪。它就像是剧场的导演,确保每个场景都能按时登台,每个角色都能在适当的时刻发言。即使是最复杂的情节,也不会有任何混乱,因为栈总是知道下一个该是谁。

javascript 复制代码
// 当我们调用另一个函数时,又有一个新的执行上下文加入了舞台
function sceneTwo() {
  console.log('And now, Scene Two.');
}

sceneTwo(); // 又一个执行上下文加入栈中,成为焦点

这种机制确保了即使在多任务的环境中,JavaScript也能保持秩序。每次只有一个执行上下文被执行,但栈在那里等待着,记下所有的上下文,确保每个都有机会在聚光灯下展示自己。这就是为什么,当你在网页上点击一个按钮,触发一个事件处理器时,那个事件的执行上下文会被推入栈中,即使其他的脚本还在等待,它也会立即开始执行。一旦它的表演结束,它就会离开舞台,让位给下一个上下文。

总结

总结起来,JavaScript的执行上下文就像是剧场的运营方式,它管理着每一个细节,确保每场戏都能有序地展开。通过掌握这个概念,我们可以写出更加精确和高效的代码,就像导演精心策划每一幕一样。

相关推荐
zhougl99637 分钟前
html处理Base文件流
linux·前端·html
花花鱼41 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_44 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)3 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法