前端进击的巨人(一):执行上下文与执行栈,变量对象

写在开篇

已经不敢自称前端小白,曾经吹过的牛逼总要一点点去实现。

正如前领导说的,自己喝酒吹过的牛皮,跪着都得含着泪去实现。

那么没有年终完美总结,来个新年莽撞开始可好。

进击巨人系列开篇,不忘初心,砥砺前行。



理解执行上下文

执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境)

运行JavaScript代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建局部变量对象等。

具体做了什么先按下不表,先来看下JavaScript执行环境有哪些?

JavaScript中执行环境

  1. 全局环境
  2. 函数环境
  3. eval函数环境 (已不推荐使用)

那么与之对应的执行上下文类型同样有3种:

执行上下文的类型

  1. 全局执行上下文
  2. 函数执行上下文
  3. eval函数执行上下文

JavaScript运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行上下文。

先插播一个知识点:"JS是单线程"! "单线程"! "单线程"!

简单理解下单线程,就是同个时间段只能做一件任务,完成之后才可以继续下一个任务。正如女朋友只有一个,各位面向对象的小伙伴们你们说对不对?有女票的必须说没毛病。

既然是这样,必须要有一个排队机制,不然就会出现几个流氓霸着车道不让过,"还有王法么?"

JS中管理多个执行上下文

函数编程中,代码中会声明多个函数,对应的执行上下文也会存在多个。在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。

在说明执行栈前,先来补下"栈数据结构"知识点。

栈数据结构

借助前端大神的例子,用乒乓球盒子来理解栈的存取方式。(这个例子让我彻底记住了栈数据结构)

栈遵循"先进后出,后进先出"的规则,或称LIFO ("Last In First Out") 规则。

如图所示,我们只能从栈顶取出或放入乒乓球,最先放进盒子的总是最后才能取出。

栈中"放入/取出",也可称为"入栈/出栈"

总结栈数据结构的特点:

  1. 后进先出,先进后出
  2. 出口在顶部,且仅有一个

执行栈(函数调用栈)

理解完栈的存取方式,我们接着分析JavaScript中如何通过栈来管理多个执行上下文。

程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);

程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。

因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文",当函数调用完成后,它就会从栈顶被推出(理想的情况下,闭包会阻止该操作,闭包后续文章深入详解)。

"全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底"

文字太多不如上代码系列 ——》代码 + 图,一览无遗:

function foo () {
    function bar () {
        return ‘I am bar‘;
    }
    return bar();
}
foo();

执行上下文的生命周期

执行上下文的生命周期有两个阶段:

  1. 创建阶段(进入执行上下文)
  2. 执行阶段(代码执行

创建阶段:函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段

执行阶段:执行函数中代码时,此时执行上下文进入执行阶段

创建阶段的操作

  1. 创建变量对象

    • 函数环境会初始化创建Arguments对象(并赋值
    • 函数声明(并赋值
    • 变量声明,函数表达式声明(未赋值
  2. 确定this指向(this由调用者确定
  3. 确定作用域(词法环境决定,哪里声明定义,就在哪里确定

执行阶段的操作

  1. 变量对象赋值

    • 变量赋值
    • 函数表达式赋值
  2. 调用函数
  3. 顺序执行其它代码

看到这里,我们不经会问变量对象是什么鬼,它与代码中常见的函数声明,变量声明有神马关系???

变量对象和活动对象的区别:

当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

"创建阶段对函数声明做赋值,变量及函数表达式仅做声明,真正的赋值操作要等到执行上下文代码执行阶段"

代码例子1:变量提升

function foo() {
  console.log(a);         // 输出undefined
  var a = ‘I am here‘;    // 赋值
}
foo();

// 实际执行过程
function foo() {
  var a;                // 变量声明,var初始化undefined
  console.log(a);
  a = ‘I am here‘;     // 变量重新赋值
}

代码例子2:函数声明优先级

function foo() {
    console.log(bar);
    var bar = 20;
    function bar() {
      return 10;
    }
    var bar = function() {
        return 30;
    }
}
foo();  // 输出bar()整个函数声明

函数声明,变量声明,函数表达式的优先级

  1. 函数声明,如果有同名属性,会替换掉
  2. 变量,函数表达式
  3. 函数声明优先 > 变量,函数表达式

执行上下文的数量限制(堆栈溢出)

执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。

// 递归调用自身
function foo() {
  foo();
}
foo();

// 报错: Uncaught RangeError: Maximum call stack size exceeded

文末总结

  1. JavaScript是单线程
  2. 栈顶的执行上下文处于执行中,其它需要排队
  3. 全局上下文只有一个处于栈底,页面关闭时出栈
  4. 函数执行上下文可存在多个,但应避免递归时堆栈溢出
  5. 函数调用时就会创建新的上下文,即使调用自身,也会创建不同的执行上下文


参考文档:

作者:以乐之名

本文原创,有不当的地方欢迎指出。转载请指明出处。

原文地址:https://www.cnblogs.com/kenz520/p/10269170.html

时间: 2024-11-10 22:04:08

前端进击的巨人(一):执行上下文与执行栈,变量对象的相关文章

278 执行上下文、执行上下文栈:变量提升与函数提升,执行上下文,执行上下文栈,全局执行上下文,函数执行上下文,练习题

变量提升与函数提升 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined) 函数提升: 在函数定义语句之前, 就执行该函数 先有变量提升, 再有函数提升 变量声明提升.函数声明提升 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>01_变量声明提升.函数声明提升</title> &l

Javascript核心原理(一)执行环境、执行环境栈、变量对象、活动对象

注:此文是在一个前端网站上看到的一篇文章转载而来的.

前端进击的巨人(三):从作用域走进闭包

进击的巨人第三篇,本篇就作用域.作用域链.闭包等知识点,一一击破. 作用域 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符(变量)的访问权限 --<你不知道的JavaScript上卷> 作用域有点像圈地盘,大家划好区域,然后各自经营管理,井水不犯河水. var globaValue = '我是全局作用域'; function foo() { var fooValue = '我是foo作用域'; function bar

你不知道的JavaScript--Item19 执行上下文(execution context)

在这篇文章里,我将深入研究JavaScript中最基本的部分--执行上下文(execution context).读完本文后,你应该清楚了解释器做了什么,为什么函数和变量能在声明前使用以及他们的值是如何决定的. 1.EC-执行环境或者执行上下文 每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文(好高大上的概念啊). javascript中,EC分为三种: 全局级别的代码 –– 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码

javascript 执行环境,变量对象,作用域链

前言 这几天在看<javascript高级程序设计>,看到执行环境和作用域链的时候,就有些模糊了.书中还是讲的不够具体. 通过上网查资料,特来总结,以备回顾和修正. 要讲的依次为: EC(执行环境或者执行上下文,Execution Context) ECS(执行环境栈Execution Context Stack) VO(变量对象,Variable Object)|AO(活动对象,Active Object) scope chain(作用域链)和[[scope]]属性 EC 每当控制器到达EC

JavaScript学习--Item19 执行上下文(execution context)

在这篇文章里,我将深入研究JavaScript中最基本的部分--执行上下文(execution context).读完本文后,你应该清楚了解释器做了什么,为什么函数和变量能在声明前使用以及他们的值是如何决定的. 1.EC-执行环境或者执行上下文 每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文(好高大上的概念啊). javascript中,EC分为三种: 全局级别的代码 –– 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码

声明函数 执行上下文 匿名函数

方法一: var foo = function () { //code } 方法二: function foo () { //code } 方法一:foo在进入函数执行上下文开始执行代码的时候,foo是undefined的,执行完var foo = function () { }这句,foo才会得到赋值: 方法二:在进入函数执行上下文开始执行代码的时候,foo已经是一个function了. 一旦进入执行上下文(在执行代码之前),VO(变量对象)就会被一些属性填充: 函数的形参(当进入函数执行上下

JavaScript内部原理系列-变量对象(Variable object)

概要 我们总是会在程序中定义一些函数和变量,之后会使用这些函数和变量来构建我们的系统.然而,对于解释器来说,它又是如何以及从哪里找到这些数据的(函数,变量)?当引用一个对象的时候,在解释器内部又发生了什么? 许多ECMA脚本程序员都知道,变量和执行上下文是密切相关的: var a = 10; // 全局上下文中的变量 (function () { var b = 20; // 函数上下文中的局部变量 })(); alert(a); // 10 alert(b); // "b" is n

深入理解JavaScript系列(12):变量对象(Variable Object)

介绍 JavaScript编程的时候总避免不了声明函数和变量,以成功构建我们的系统,但是解释器是如何并且在什么地方去查找这些函数和变量呢?我们引用这些对象的时候究竟发生了什么? 原始发布:Dmitry A. Soshnikov 发布时间:2009-06-27 俄文地址:http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/ 英文翻译:Dmitry A. Soshnikov 发布时间:2010-03-15 英文地址:h