js执行过程你了解多少?

ios 执行js的方法 js在哪执行_js变量提升

js是单线程语言:

在浏览器中只有一个线程在执行js脚本代码。

虽然js是单线程,但在js执行过程中并不是只有一个线程。其实有四个线程,包括:「JS引擎线程」「事件触发线程」「定时器触发线程」「HTTP异步请求线程」,但永远只有 「JS引擎线程」

js是单线程,但是代码确是异步执行的

通过事件循环( Event Loop)的方式实现。

js引擎执行分三个阶段

注:浏览器首先按顺序加载由 <script> 标签分割的js代码块,加载js代码块完毕后,立刻进入以下三个阶段,然后再按顺序查找下一个代码块,再继续执行以下三个阶段,无论是外部脚本文件(不异步加载)还是内部脚本代码块,都是一样的原理,并且都在同一个全局作用域中。

  1. 语法分析
  2. 预编译阶段
  3. 执行阶段

语法分析

分析脚本代码块语法是否正确,正确则进入「预编译阶段」,否则抛出 SyntaxError, 停止该代码块代码继续执行,然后继续查找下一个代码块。

预编译阶段

js的运行环境(执行上下文)主要有三种: - 全局环境(js代码加载完毕后,进入预编译阶段即进入了全局环境) - 函数环境(函数执行时进入该函数环境,不同函数的函数环境不同) - eval环境(不建议使用,会有安全,性能等问题)

执行栈,又称调用栈,用来存贮代码执行期间创建的所有执行上下文。是一个遵循后进先出(LIFO)的结构。栈底永远是全局执行上下文,栈顶是当前执行上下文。

执行上下文分两个阶段创建:1)创建执行上下文; 2)执行阶段

创建执行上下文

  1. 创建变量对象
  2. 创建作用域链
  3. 确定 this 指向

创建变量对象

主要过程如下:


ios 执行js的方法 js在哪执行_js同步执行方法_02


  1. 创建 Augments 对象:检查当前上下文中的参数,建立对象的属性与值,仅在函数环境(非箭头函数?)执行,全局环境没有这个过程。
  2. 检查 Funchtion 函数声明,创建属性:按代码顺序查找,将找到的函数提前声明,若函数不存在则新建立属性和属性值(指向该函数内存地址的引用);若存在,则直接覆盖原来的。
  3. 检查 var 变量,声明创建属性:按代码顺序查找,将找到的变量提前声明,如果变量不存在,则赋值为:undefined。若存在,则忽略该声明。

注:在全局环境中, window 对象就是全局执行上下文的变量对象,所有的变量和函数都是 window 对象的属性方法。

所以函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明。

变量提升:在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 letconst 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 letconst 定义的变量就会提示引用错误的原因。

「变量对象」转化为「活动对象」后才能进行访问

创建作用域链

作用域链由当前执行环境的变量对象和上层的一系列活动对象组成,保证了当前执行环境对符合访问权限的变量和函数的有序访问。

例:


var num = 30;

function test() {
    var a = 10;

    function innerTest() {
        var b = 20;

        return a + b
    }

    innerTest()
}

test()


在上面的例子中,当执行到调用 innerTest 函数,进入 innerTest 函数环境。全局执行上下文和 test 函数执行上下文已进入执行阶段,innerTest 函数执行上下文在预编译阶段创建变量对象,所以他们的活动对象和变量对象分别是 AO(global)AO(test)VO(innerTest) ,而 innerTest 的作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:


innerTestEC = {

    //变量对象
    VO: {b: undefined}, 

    //作用域链
    scopeChain: [VO(innerTest), AO(test), AO(global)],  

    //this指向
    this: window
}


  • 作用域链的第一项永远是当前作用域(当前上下文的变量对象或活动对象);
  • 最后一项永远是全局作用域(全局执行上下文的活动对象);
  • 作用域链保证了变量和函数的有序访问,查找方式是沿着作用域链从左至右查找变量或函数,找到则会停止查找,找不到则一直查找到全局作用域,再找不到则会抛出引用错误。

确定 this 指向

全局环境下指向window

函数环境下需根据执行环境和执行方法确定(==后续补充==)

执行阶段

js进入执行阶段后,代码执行顺序如下:

宏任务(同步任务) --> 微任务 --> 宏任务(异步任务)


ios 执行js的方法 js在哪执行_js变量提升_03


1.宏任务:宏任务又按执行顺序分为同步任务和异步任务

  • 同步任务: 在JS引擎主线程上按顺序执行的任务,只有前一个任务执行完毕后,才能执行后一个任务,形成一个执行栈(函数调用栈)
  • 异步任务: 不直接进入JS引擎主线程,而是满足触发条件时,相关的线程将该异步任务推进任务队列( task queue ),等待JS引擎主线程上的任务执行完毕,空闲时读取执行的任务,例如异步 AjaxDOM 事件,setTimeout 等。

理解宏任务中同步任务和异步任务的执行顺序,那么就相当于理解了JS异步执行机制–事件循环( Event Loop

事件循环可以理解为由三部分组成:主线程执行栈异步任务等待触发、**任务队列

在JS引擎主线程执行过程中:

  1. 首先执行宏任务的同步任务,在主线程上形成一个执行栈,可理解为函数调用栈;
  2. 当执行栈中的函数调用到一些异步执行的 API (例如异步 AjaxDOM 事件,setTimeoutAPI),则会开启对应的线程( Http 异步请求线程,事件触发线程和定时器触发线程)进行监控和控制
  3. 当异步任务的事件满足触发条件时,对应的线程则会把该事件的处理函数推进任务队列( task queue )中,等待主线程读取执行
  4. 当JS引擎主线程上的任务执行完毕,则会读取任务队列中的事件,将任务队列中的事件任务推进主线程中,按任务队列顺序执行
  5. 当JS引擎主线程上的任务执行完毕后,则会再次读取任务队列中的事件任务,如此循环,这就是事件循环( Event Loop )的过程

2.微任务:是在 es6node 环境中出现的一个任务类型.

微任务的 API 主要有: Promiseprocess.nextTick

示例:


console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');


输出结果:


script start
script end
promise1
promise2
setTimeout


以上就是js执行的过程

参考文章:

js引擎的执行过程(一)

js引擎的执行过程(二)

理解JavaScript 中的执行上下文和执行栈 | 木易杨前端进阶muyiy.vip

ios 执行js的方法 js在哪执行_js定时器一直开启会增加内存么_04