js执行上下文

js的执行上下文

执行上下文是当前js代码被解析和执行时所在的环境的抽象概念,js中的运行任何代码都是在执行上下文中运行。

执行上下文的的三种类型

全局执行上下文:这是默认的,最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文。它做了两件事:

  • 创建一个全局对象,在浏览器中这个全局对象就是window对象。
  • 将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。

函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在被调用时才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,她都会按照特定的顺序执行一系列步骤。

Eval函数执行上下文:运行在eval函数中的代码也获得自己的执行上下文。

执行上下文的生命周期

执行上下文的生命周期包括三个阶段:创建阶段——执行阶段——回收阶段

创建阶段

当函数被调用时,但未被执行其内部任意代码前,会做以下三件事:

  1. 创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。
  2. 创建作用域链(scope chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,js始终从代码嵌套的最内层开始,如果最内层没有该变量,就往上一层父级作用域中找,直到找到变量。
  3. 确定this指向

在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。

另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出this arguments和函数的参数。

以上过程实则为代码的预处理阶段,详细过程如下:

  1. 读取分析整个源代码
  2. 先扫描函数声明,之后扫描变量(var声明)
    • 处理函数声明时有冲突,会覆盖。
    • 处理变量声明有冲突,会忽略。
  3. 将扫描到的函数和变量保存到一个对象中(全局的会保存在window对象中)
  4. 变量的值是undefined,函数的值则是指向该函数(是一个函数字符串)
    • 形式如:window = { x : undefined , f : ‘function(){console.log(1);}’ }

注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。

function声明的优先级比var声明高,也就意味着当两个同名变量同时被function和var声明时,function声明会覆盖var声明

执行阶段

执行变量赋值、代码执行。

该过程为:

  1. 将变量的值从undefined改为给定值。

  2. 调用函数f(),以便函数得到执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    上面函数(f)内,代码的处理过程:

    1、预处理阶段

    a、将函数的参数添加到一个对象(暂定为:词法对象)

    b、扫描函数声明,之后扫描变量(var声明)

    d、将扫描到的函数和方法添加到词法对象里面

    c、变量的值是undefined,函数的值则指向该函数(与全局的一样)

    2、运行阶段

    与全局的的运行原理一样

回收阶段

执行上下文出栈等待虚拟机回收执行上下文

看看一下例子就知道变量提升是怎么回事了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test(arg){
// 1. 形参 arg 是 "hi"
// 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
console.log(arg);
var arg = 'hello'; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
function arg(){
console.log('hello world')
}
console.log(arg);
}
test('hi');
/* 输出:
function arg(){
console.log('hello world')
}
hello
*/

执行上下文栈

函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?

JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

我们需要记住几个关键点:

  • JavaScript执行在单线程上,所有的代码都是排队执行。
  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
  • 浏览器的JS执行引擎总是访问栈顶的执行上下文。
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

推荐:

https://www.cnblogs.com/lark-/p/7954047.html

执行环境及作用域

执行环境定义了变量或函数有权访问的其他函数,决定了各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

全局执行环境时最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行的对象也不一样,在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境就会被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁)。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在环境执行之后,栈将其环境弹出,把控制权发回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果这个环境是函数,则其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即argument对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自外部环境,而在下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级搜索标识符的过程,搜索过程始终从作用域链的前端开始,然后逐级向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。

加长作用域链
  1. try-catch语句的catch块
  2. with语句
1
2
3
4
5
6
7
with(location){
var url = href + qs;
}
// 相当于
function ***(location){
var url = location.href + location.qs;
}
垃圾回收机制

JavaScript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存。而在C和C++之类的语言中,开发人员的以像基本任务就是手工跟踪内存的使用情况,这是造成许多问题的一个根源。在编写JavaScript程序时,开发人员不再关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾回收机制的原理其实很简单:找出那些不在继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔或代码执行中预定的收集时间周期性的执行这一操作。

局部变量的生命周期

局部变量只在函数执行过程中存在,而在这个过程中,会为局部变量在栈或堆内存上分配相应的空间,以便存储它们的值。然后再函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放他们的内存以供将来使用。在这种情况下,很容易【判断变量是否还有存在的比亚;但并非所有情况下都这么容易就能得出结论。垃圾收集器必须跟踪哪个变量有用哪个没用,对于不再有用的变量打上标记以便将来回收其占用的内存。用于表示无用变量的策略可能也因实现而异,但具体到浏览器中的实现则通常由两个策略。

  1. 标记清除

    垃圾收集器在运行时会给存储在内存中的所有变量加上标记。然后他会去掉环境中的变量以及被环境中的变量引用的变量标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些代表带标记的值并回收他们所占用的内存空间。

  2. 引用计数

    引用计数的含义是跟踪记录每个值被引用的次数。当这个值的引用次数变为0,则说明没法再访问这个值了,就会将其占用的内存空间回收。这样当垃圾收集器下次再运行时,他就会释放那些引用为0的值所占的空间

管理内存

分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的是出于安全方面考虑,目的是防止JavaScript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式是为执行中的代码只保存必要的数据,一旦数据不再有用,最好通过将其值设置为null来释放其引用——这种做法叫做结束引用。这一做法适用于大多数全局变量和全局变量的属性。局部变量会在它们离开执行环境时自动被解除引用。

不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

总结

数据类型

基本类型只在内存中占据固定大小的空间,因此会被保存在栈内存中,从一个变量向另一个变量复制,会创建该值的副本。

引用类型的值是对象,保存在堆内存中,包含引用类型值的变量并非对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

确定一个值是哪种基本类型可用typeof,而确定是哪种引用用类型使用instanceof。

文章目录
  1. 1. js的执行上下文
    1. 1.0.1. 执行上下文的的三种类型
    2. 1.0.2. 执行上下文的生命周期
      1. 1.0.2.0.1. 创建阶段
  2. 1.0.3. 执行阶段
  3. 1.0.4. 回收阶段
  4. 1.0.5. 执行上下文栈
  5. 1.0.6. 执行环境及作用域
    1. 1.0.6.0.1. 加长作用域链
    2. 1.0.6.0.2. 垃圾回收机制
    3. 1.0.6.0.3. 局部变量的生命周期
    4. 1.0.6.0.4. 管理内存
  • 1.0.7. 总结
    1. 1.0.7.0.1. 数据类型
  • |