Javascript执行上下文– JS的工作原理
所有javascript代码都需要有运行平台,它们可以在各种环境中运行。大多数情况下是在浏览器中运行的。
对于要在web浏览器中执行的任何一段JavaScript代码,会发生什么。在本文中,我们将了解JavaScript代码在web浏览器中运行的所有操作。
在深入讨论之前,请先了解一些知识点,它们经常在文本中被提及。
•解析器或语法解析器是一个逐行读取代码的程序。它将代码解析为浏览器理解并能运行的语言。
•Javascript引擎:JavaScript引擎只是一个接收JavaScript源代码并将其编译为CPU可以理解的二进制指令(机器代码)的计算机程序。JavaScript
引擎通常由web浏览器供应商开发,每个主要浏览器都有一个。例如,
Google chrome的V8引擎、Firefox的SpiderMonkey和Internet Explorer 的Chakra。
•函数声明:给函数指定一个名字
/
/here "doSomething" is the function's
name function doSomething() { statements;}
•函数表达式:匿名函数,也就是没有名字的函数,比如:
function () { statements }
它们通常用于语句中,比如将函数赋给变量:
let someValue = function () { statements }.
现在,我们已经了解了这些知识点,让我们开始吧。
Javascript代码是如何执行的
众所周知,由于javascript是高级语言,浏览器并不能理解我们平时在应用程序中用javascript写的代码。要被我们的电脑和浏览器理解,需要将javascript转换为它们能理解的机器语言。
当浏览器阅读HTML时,如果遇到了<script>标签或类似onClick这样的javascript 代码,就会将其发送给javascript引擎。
然后浏览器中的javascript引擎会创建一个特殊的环境来处理javascript代码的转换和执行。这种环境称为执行上下文。
执行上下文包含当前正在运行的代码以及辅助这些代码执行的所有内容。
在执行上下文运行期间,解析器解析特定代码,变量和函数储存在内存中,生成可执行字节码,然后执行代码。
Javascript中有2种执行上下文:
•全局执行上下文(GEC)
•函数执行上下文(FEC)
下面我们详细介绍两者。
全局执行上下文(GEC)
无论javascript引擎何时接收到javascript文件,它首先创建一个默认执行上下文,这时的这个执行上下文就是全局执行上下文(GEC)。GEC是基本/默认执行上下文,所有不在函数内部的JavaScript代码都在其中执行。
对于每个javascript文件,只能有一个GEC。
函数执行上下文(FEC)
无论什么时候调用函数,JavaScript引擎都会在GEC中创建一种不同类型的执行上下文,称为函数执行上下文(FEC),以评估和执行该函数中的代码。
因为每个函数调用都有自己的FEC,所以在一个脚本的运行时可以有多个FEC。
执行上下文是怎样被创建的?
现在我们已经知道了什么是执行上下文,并且知道了有2种不同种类的执行上下文,接下来就看一下它们是如何被创建的。
执行上下文的创建经历2个阶段:
1. 创建阶段
2. 执行阶段
创建阶段
在创建阶段,执行上下文首先创建一个执行上下文对象(ECO)。这个对象储存了大量重要的数据,这些数据在执行上下文运行期间会用到。
创建阶段分为3步,这个过程中执行上下文对象被定义和初始化。这几个步骤为:
1. 创建变量(VO)
2. 创建作用域链
3. 给this赋值
下面详细介绍这几个步骤。
创建变量(VO)
VO是在执行上下文中创建的一个类似对象的容器。它储存着执行上下文中定义的变量和函数。
在全局执行上下文中(GEC),对于使用var关键字声明的每个变量,将向VO添加一个属性,该属性指向该变量,并设置为“undefined”。
此外,对于每个函数声明,都会向VO添加一个指向该函数的属性,该属性存储在内存中。这意味着,
即使在代码开始运行之前,所有函数声明都将存储在VO中并可在VO中访问。
另一方面,函数执行上下文,即FEC不构造VO。相反,它会生成一个类似数组的对象,称为“argument”对象,其中包含提供给函数的所有参数。
在执行代码之前将变量和函数声明存储在内存中的过程称为提升。由于这是一个重要的概念,在进入下一阶段之前,我们将简要对它进行讨论。
javaScript中的提升
函数和变量的声明会在javaScript中进行提升。也就是说它们会被存储到当前的执行上下文的VO的内存中。甚至在代码开始执行前就可以在执行上下文中使用。
javascript全局数组函数提升
在创建应用程序的很多场景中,开发者会在script文件顶部定义函数,并且在之后调用,像下面这样:
然而,由于有函数提升的存在,如果函数在调用代码之后定义也能正常工作。
在上面的代码中,getAge函数的声明被存储到VO的内存中,使其在定义之前就可以被使用。
变量提升
使用var关键字初始化的变量作为属性存储在当前执行上下文的VO的内存中,并且初始化的值为undefined。这意味着,与函数不同,在定义变量之前尝试访问变量的值将是undefined。
提升的基本原则
提升只适用于函数声明,不适用于表达式。下面是一个函数表达式的示例,其中代码执行将中断。
getAge(1990);
var getAge=function(yearOfBirth) {
console.log(new Date().getFullYear -yearOfBirth)
};
代码执行中断,是因为对于函数表达式,getAge将作为变量而不是函数被提升。对于变量的提升,其值将设置为未定义。这就是我们得到错误的原因:
此外,变量提升不适用于使用let或const关键字初始化的变量。尝试在声明之前访问变量,并在以后使用let和const关键字声明它,将导致ReferenceError。
在这种情况下,它们将被提升,但不会被指定默认值undefined。
console.log(name);let name = "Victor";将抛出错误:
创建作用域链
在创建变量对象(VO)之后,创建作用域链成为执行上下文创建阶段的下一个阶段。
JavaScript中的作用域是一种机制,用于确定某段代码对代码库中其它部分的可用程度。
作用域回答了以下问题:
哪里可以访问这段代码?
哪里不能访问?
这段代码中什么可以使用,什么不能使用?
每个函数执行上下文都会创建自己的所用域:
通过某个过程可以访问定义的变量和声明的函数的空间/环境称为作用域。
这意味着某段代码在代码库中的位置关系着它的作用域。
当在一个函数中定义另一个函数时,这两个函数为父函数和子函数。
子的函数可以访问父函数的变量,还可以访问祖父函数的变量。这种行为称为词法作用域。
但是,父函数不能访问子函数的变量。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论