浏览器工作原理--浏览器中的JavaScript执行机制

一、变量提升

JavaScript 的执行机制:先编译,再执行

(1)JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。

(2)在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。

(3)如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。

(4)同名变量和函数的两点处理原则

1、如果是同名的函数,JavaScript编译阶段会选择最后声明的那个。

2、如果变量和函数同名,那么在编译阶段,变量的声明会被忽略

相当于:

函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖

二、调用栈

(1)每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码。

(2)如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。

(3)当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。

(4)当分配的调用栈空间被占满时,会引发“堆栈溢出”问题。

(5)用栈有两个指标,最大栈容量最大调用深度,满足其中任意一个就会栈溢出。

三、块级作用域

(1)作用域

 作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

(2)在 ES6 之前,ES 的作用域只有两种:全局作用域函数作用域

  • 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
  • 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。

(3)变量提升所带来的问题

1. 变量容易在不被察觉的情况下被覆盖掉

2. 本应销毁的变量没有被销毁

(4) ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有了块级作用域(作用域块内声明的变量不影响块外面的变量)

(5)JavaScript 是如何支持块级作用域的

function foo(){ 
    var a = 1 
    let b = 2 
    { 
        let b = 3 
        var c = 4 
        let d = 5 
        console.log(a) 
        console.log(b) 
} 
    console.log(b)
    console.log(c)
    console.log(d)
} 
foo()
  • 第一步是编译并创建执行上下文

  • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了。
  • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  • 在函数的作用域块内部,通过 let 声明的变量并没有被存放到词法环境中。
  • 第二步继续执行代码

 当执行到代码块里面时,变量环境中 a 的值已经被设置成了 1,词法环境中 b 的值已经被设置成了 2

  • 当进入函数的作用域块时,作用域块中通过 let 声明的变量,会被存放在词法环境的一个单独的区域中,这个区域中的变量并不影响作用域块外面的变量。
  • 在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。需要注意下,我这里所讲的变量是指通过 let 或者 const 声明的变量。
  • 再接下来,当执行到作用域块中的console.log(a)这行代码时,就需要在词法环境和变量环境中查找变量 a 的值了,具体查找方式是:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找。

  • 小结 

块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎也就同时支持了变量提升和块级作用域了

 (6)暂时性死区

1、执行函数时才有进行编译,抽象语法树(AST)在进入函数阶段就生成了,并且函数内部作用域是已经明确了,所以进入块级作用域不会有编译过程,只不过通过let或者const声明的变量会在进入块级作用域的时被创建,但是在该变量没有赋值之前,引用该变量JavaScript引擎会抛出错误---这就是“暂时性死区”

2、在块作用域内,let声明的变量被提升,但变量只是创建被提升,初始化并没有被提升,在初始化之前使用变量,就会形成一个暂时性死区

  • var的创建和初始化被提升,赋值不会被提升。
  • let的创建被提升,初始化和赋值不会被提升。
  • function的创建、初始化和赋值均会被提升。

(7) 第一个let声明的变量是在编译阶段就压入栈中的,但是后面的变量又感觉是在执行是压入栈中?

1、函数只会在第一次执行的时候被编译,所以编译时变量环境和词法环境最顶层数据已经确定了。

2、当执行到块级作用域的时候,块级作用域中通过let和const申明的变量会被追加到词法环境中,当这个块执行结束之后,追加到词法作用域的内容又会销毁掉。

四、作用域链和闭包

(1)作用域查找变量的链条称为作用域链,作用域链是由词法作用域决定的,而词法作用域反映了代码的结构。

(2)词法作用域

 词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

(3)词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

(4)闭包

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包

(5)如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

五、this

(1)当函数作为对象的方法调用时,函数中的 this 就是该对象;

(2)当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;

(3)嵌套函数中的 this 不会继承外层函数的 this 值。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值