javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript执行机制

JavaScript在浏览器中的执行机制

作者:托儿所夜十三

既然说到了JavaScript,那么就会绕不过去执行上下文,只有理解了执行上下文才能更好的理解JavaScript本身,不过本部分不是专门讲解 JavaScript的,主要从JavaScript的执行顺序开始介绍一下JavaScript是怎样运行的

既然说到了 JavaScript,那么就会绕不过去执行上下文,只有理解了执行上下文才能更好的理解 JavaScript 本身,比如变量提升、栈溢出、作用域和闭包等。

不过本部分不是专门讲解 JavaScript 的,所有不会对 JavaScript 本身做过多介绍,主要从 JavaScript 的执行顺序开始介绍一下 JavaScript 是怎样运行的。

首先先看一下下面的代码:

showName()
console.log(myname)
var myname = '掘金'
function showName() {
  console.log('function showName run')
}

作为前端大家都知道上面的代码是同步的会顺序执行,那么输出应该是:

但是如下图执行结果跟我们想的并不一样:

第1行输出 “function showName run”,第2行输出 “undefined”,这和前面想象中的顺序执行有点不一样啊!

上面结果可以看出函数或者变量可以在定义之前使用,如果没有定义代码还能正常执行吗,可以参考下面代码去掉第三行 myName 的定义:

showName()
console.log(myname)
function showName() {
  console.log('function showName run')
}

使用了未定义的变量——执行报错

从上面两段代码的执行结果来看,我们可以得出如下三个结论。

第一个结论很好理解,因为变量没有定义,这样在执行JavaScript代码时,就找不到该变量,所以JavaScript会抛出错误。

但是对于第二个和第三个结论,就挺让人费解的,要解决这两个问题就要明白接下来我们要介绍的内容-变量提升。

变量提升

在正式了解变量提升之前,我们需要先理解两个 JavaScript 概念声明和赋值

var myName = "掘金"

这段代码可以这样拆解看:

var myName        //声明部分
myName = "掘金"   //赋值部分

上面说明了变量的声明和赋值,接下来说一下函数的声明和赋值:

function foo(){
  console.log('foo')
}
var bar = function(){
  console.log('bar')
}

第一个函数foo是一个完整的函数声明,也就是说没有涉及到赋值操作;第二个函数是先声明变量bar,再把function(){console.log('bar')}赋值给bar。为了直观理解,你可以参考下图:

通过上面我们了解了变量和函数的声明和赋值了,接下来说一说什么是变量提升。

所谓的变量提升就是指 JavaScript 代码在执行过程中, JavaScript 引擎把变量和函数的声明部分提升到代码开头的”行为“。变量提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined

具体变化如下代码:

/*
* 变量提升部分
*/
// 把变量 myname提升到开头,
// 同时给myname赋值为undefined
var myname = undefined
// 把函数showName提升到开头
function showName() {
    console.log('function showName run');
}
/*
* 可执行代码部分
*/
showName()
console.log(myname)
// 去掉var声明部分,保留赋值语句
myname = '掘金'

可以通过下图更直观的感受:

从图中可以看出,对原来的代码主要做了两处调整:

通过这两步,就可以实现变量提升的效果。你也可以执行这段模拟变量提升的代码,其输出结果和第一段代码应该是完全一样的。

通过这段模拟的变量提升代码,明白了可以在定义之前使用变量或者函数的原因——函数和变量在执行之前都提升到了代码开头。

let和const会变量提升吗?

我们执行下面代码进行尝试:

console.log(myName)
const myName = '掘金'

上图的报错为:ReferenceError: Cannot access 'variable' before initialization。和未定义的的报错不同,未定义报错如下

console.log(myName1)
const myName = '掘金'

由此可见,用 var 定义的变量可以在声明之前被访问而不报错,但是用 let 或 const 定义的变量却不行。

由 let 或 const 定义的变量提升时不会默认初始化,所以在声明之前访问会报错:ReferenceError: Cannot access 'variable' before initialization

然而由 var 定义的变量提升时被初始化为默认值 undefined,所以在声明之前访问会得到 undefined。 这里面就是涉及到了一个概念 暂时性死区,let/const 定义的变量被提升却无法正常访问,是因为存在暂时性死区(Temporal Dead Zone)

JavaScript 代码的执行顺序

通过上面介绍变量提升,你可能会觉得变量和函数会在物理层面中被移动到代码最前面,就是我们图中展示的那样,但是,实际上变量和函数声明在代码里的位置并不会改变,而是在编译阶段被 JavaScript 引擎放入到内存中,你没看错,一段 JavaScript 代码在被执行前需要 JavaScript 引擎编译,编译完成后才会被执行,如下图所示:

1.编译阶段

编译和变量提升有什么关系呢?我们可以从下面两部分代码来看:

第一部分:变量提升部分的代码。

var myName = undefined
function showName() {
    console.log('function showName run');
}

第二部分:执行部分的代码。

showName()
console.log(myName)
myName = '掘金'

可以把JavaScript的执行流程细化,如下图所示:

从上图可以看出,输入一段代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。

执行上下文是JavaScript执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如this、变量、对象以及函数等。

可以用下面的伪代码简单的理解环境变量对象:

VariableEnvironment:
     myname -> undefined, 
     showName ->function : {console.log(myname)

接下来看一下生成环境变量的过程:

showName()
console.log(myName)
var myName = '掘金'
function showName() {
    console.log('function showName run');
}

这样就生成了变量环境对象。接下来JavaScript引擎会把声明以外的代码编译为字节码,可以类比如下的伪代码:

showName()
console.log(myname)
myname = '掘金'

好了,现在有了执行上下文和可执行代码了,那么接下来就到了执行阶段了。

2. 执行阶段

JavaScript引擎开始执行“可执行代码”,按照顺序一行一行地执行。下面我们就来一行一行分析下这个执行过程:

VariableEnvironment:
     myname -> "掘金", 
     showName ->function : {console.log(myName)

代码中出现相同的变量或者函数怎么办?

现在你已经知道了,在执行一段JavaScript代码之前,会编译代码,并将代码中的函数和变量保存到执行上下文的变量环境中,那么如果代码中出现了重名的函数或者变量,JavaScript引擎会如何处理?

我们先看下面这样一段代码:

function showName() {
    console.log('沸点');
}
showName();
function showName() {
    console.log('掘金');
}
showName(); 

分析这两次调用打印出来的值是什么吗?

完整执行流程:

综上所述,一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。

总结

好了,今天就到这里,下面我来简单总结下今天的主要内容:

以上就是今天所讲的主要内容,当然,学习这些内容并不是让你掌握一些 JavaScript 小技巧,其主要目的是让你清楚JavaScript的执行机制:先编译,再执行

以上就是JavaScript在浏览器中的执行机制的详细内容,更多关于JavaScript执行机制的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文