likes
comments
collection
share

浏览器中的JS执行机制2

作者站长头像
站长
· 阅读数 31

今天我们来复习巩固一下这几天学习的内容整合一下JS的执行机制

一,预编译

首先必然是我们的预编译的规则方法,这是需要多加练习收悉的

  • 当预编译发生在全局时
  1. 创建GO对象
  2. 找变量声明,将函数名作为GO的属性名,值为 undefined
  3. 在全局找函数声明,将函数名作为GO的属性名,值为该函数体
  • 当预编译发生在函数体内
  1. 创建一个AO对象
  2. 找形参和变量声明,将形参和变量名作为AO的属性名,值为 undefined
  3. 形参和实参统一
  4. 在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体

当我们熟悉了规则后才能无忧无虑的编写代码

二,声明

我们在前面两篇文章中已经对比过函数声明和var声明提升问题,再次做个总结

  1. js 引擎在执行js的过程中把变量声明部分提升到代码的头部,默认赋值为undefined

  2. 声明提升是发生在编译阶段

我们看下面这个例子:

showName();
console.log(myName);
var myName = 'Xu';
function showName() {
    console.log('函数showName被执行');
}

实际上的处理:

var myName = undefined;

function showName(){
    console.log('函数showName被执行');
}

showName();
console.log(myName);
myName = Xu;

这样的处理也和预编译的过程有所出入,在编译过程中,我们要时常注意到这个问题, 记住:变量声明,声明提升;函数声明,函数体整体提升 此外,还有一道经典考题值得我们去思考

var arr = []

for (var i = 0; i < 10; i++) {
    
    arr[i] = function () {
        console.log(i);
    }
}

for (var j = 0; j < arr.length; j++) {
    arr[j]()   
//此处输出的结果是十个10,为什么?
}

这还是因为var的声明提升问题,for循环语句不是作用域,所以var的声明相当于在全局声明,这导致i的值一直不变,i = 0 进入函数循环后等于10输出。如果想输出1234...应将var改为let,形成块级作用域,这个块级作用域作为了function的下一级作用域链(这个function的词法作用域是这个块级作用域)。这两者的区别是一个在循环体外,值不变;一个在循环体内,值依次增加。

三,执行上下文

我们在上一篇文章中详细的解释了什么是上下文对象 一段JS代码被V8引擎读取时第一时间进行预编译,随即创造上下文对象,大家还记得上下文对象吗?把他当成存储属性数据的集合就好噜~ 只要是域,就都会产生一个执行上下文出来,每调用一个函数JS引擎就会为它创造一个上下文对象,并将这个上下文对象压入到栈里面去 执行上下文分为变量环境词法环境。变量环境用来放var声明的变量,词法环境方let,const 声明的变量。代码在被编译时产生执行上下文和可执行的代码放在词法环境和变量环境中;原始类型的数据全部存放在栈里面,引用(也称对象类型,复杂类型)类型的数据全部存放在堆里面,如图

浏览器中的JS执行机制2 当函数执行上下文被执行完就会从上到下依次被销毁只留下闭包

浏览器中的JS执行机制2

四,调用栈

关于栈我们也在前文详细讲过喔~不知道各位友友们还记得么,这里我们来简单概括一下:

  • 栈结构:特殊的数组,先进后出
  • 调用栈:JS引擎用来追踪函数调用关系的
  • 栈溢出:调用栈的内存超过限制

数组顶部底部中间都可以增加删除值,栈只能在顶部增加push删除pop 装词法环境的盒子也是栈喔~

五,作用域链

在上一篇的基础上,我们得知了噢原来执行上下文是放在栈里执行的,先放全局作用域在向上放函数执行上下文,在上一篇文章中我们知道了作用域链的简单指定向--先指向自己再指向外部作用域,今天我们再看来看看这快编程码的打印结构还是不是简单的指向:

function bar(){
    console.log(myName);
}

function foo() {
    var myName = 'Tom'
    bar();
}

var myName = 'Jerry'

foo()

从全局上看,这里存在两个并列函数且外部就是全局作用域,在这里我们必须知道的是outer的作用:引入外部作用域。

浏览器中的JS执行机制2

通过这块代码我们看到并不能简单的就靠 “ 就近原则”来判断最后打印的结果,事实证明bar是可以访问全局作用域的,两个并列的函数不存在覆盖问题。所以我们要记住下面两点

  • 并不是在调用栈中从上到下查找,而是看当前指向上下文变量环境中的outer指向来定, 而outer指向的规则是,我的词法作用域在哪,outer就指向哪里

  • 词法作用域:在函数定义时所在的作用域,由函数声明的位置来决定,跟函数在哪里调用没有关系(全局声明还是在其他函数体内声明)

六,练习

请打印出以下代码块的结果

function foo() {
    var a = 1
    let b = 2
    {
        let b = 3
        var c = 4
        let d = 5
        console.log(a);//1
        console.log(b);//3
    }
    console.log(b);//2
    console.log(c);//4
    console.log(d);//error
}
foo()

我们发现其中出现了块级作用域,还记得块级作用域的概念吗?在含有let const {}生成块级作用域,但是生成的块级作用域依然不影响var 的声明提升。在这道题中有一个全局作用域中的函数foo(),并且他被调用了;所以我们创建了一个AO对象,也叫foo执行上下文。执行完成后,变量查询从词法环境开始查询,经变量 环境,全局执行上下文,最后没找到就会报错,如图,思路我就帮大家准备好啦

浏览器中的JS执行机制2

结语

今天我们学的新知识不多,主要为复习,下一章我们将迎来浏览器中的JS执行机制的最后一片,带你轻松搞定闭包,冲击大厂!

转载自:https://juejin.cn/post/7366948087130112036
评论
请登录