javaScript的执行顺序
前言
突然发现很久没有学习,基本上都是一些很无聊的业务代码和无止境的加班.最近项目进入正轨,也能正常上下班了,就又开始试图去学习下React.突然在执行代码的时候发现了一个问题,为什么React中定义的函数组件,能在组件渲染的时候访问到我在render方法后面定义的变量呢?大概如下图
我们都知道js的执行顺序是从上到下依次执行,这里如果是普通的函数会因为拿不到ac的值而报错,但是这是React.render函数会进入异步,在Board组件渲染的时候,ac已经被加入到全局上下文中了.
js的执行机制
首先我们知道js是单线程语言,在js执行前会有一个预解析的过程.这里也涉及到js一些面试题,比如js之前是解释性语言,但是后续V8引擎给了优化,一部分热函数(被分析经常使用的)会直接编译.大大提高了js的执行效率.一部分则会边解释边执行.
js预解析
js预解析干了什么呢?首先将var声明的变量的声明提升到了作用域的最顶端,注意是声明而不是赋值.函数在js是一等公民,函数整体都会被提升到当前作用域的最顶端.这也是为什么function定义的函数我们可以不用考虑写在哪里,但是let 或者const 声明的函数就要注意书写的位置.以防止函数执行的时候根本在执行上下文中全局执行环境中根本找不到变量的地址而发生报错的问题.
作用域
js是有作用域的,全局是一个作用域.函数是一个作用域,代码块也是一个作用域.js在执行代码的时候变量可以存放到全局中也就是全局作用域中,也可以声明在局部代码块中或者函数中.作用域遵循就近原则,因为每个函数都是一个执行上下文,内部都存储了对外部环境的一个引用.在内部找不到变量的时候会向外找,如果一直找不到就会报错.
调用栈和词法环境
js是一个单线程语言,每个函数都要经过一个入栈出栈的逻辑来执行.先进后出,执行完当前的的函数就会出栈,如果当前函数中还有函数嵌套,那么被嵌套的函数会继续入栈,执行完之后依次出栈.比如下面这部分代码.
- 首先调用栈会执行fn,fn会生成一个执行上下文和一个词法环境.(注意执行上下文先创建再入栈,词法环境会记录该执行上下文需要用到的变量和对外部环境的一个记录,这可以帮助我们沿着链条向上查找).这个词法环境中会存储一个当前词法环境的全局变量和一个局部变量.(整个script可以理解为一个全局词法环境,这个词法环境的局部变量就是全局变量).注意这个全局变量是一个引用,每个执行上下文(函数)都有这个东西.
- fn执行后呢会返回fn2的这个函数,但是这个时候函数还没有调用所以不做处理,全局fn上下文结束出栈.
- fn2开始调用,这个时候要创建一个fn2的词法环境,注意词法环境在定义的时候就已经确定好了!不管是在哪调用都不影响这个词法环境的外部变量的位置.比如这个fn2函数永远先找fn环境下的变量.这也是闭包的底层原理!
fn2执行的时候会发现自己没有name,那么这个时候要通过全局变量这个通道去向外找,发现了fn有这个name,随后打印'猪八戒',fn2函数出栈,引用关系消失,fn也就释放了内存.由此执行结束.
事件循环 和WebApi
- 以上讲的主要是一些同步代码的情况,异步的简单过一下.首先js的异步代码有两种情况,一个是任务(宏任务),微任务.比如请求方法fetch,fetch在调用的时候会由webapi去处理网络相关的问题.
- 在.then方法执行的时候会将内部的代码推送到微任务队列.再比如计时器,会在webapi完成时间的等待后推送到宏任务队列.
- 事件循环器会一直在执行完同步代码后询问微任务队列有没有任务,假如有那么就执行微任务,微任务会产生新的微任务和宏任务!微任务和宏任务继续到自己的队列去排队,执行完所有的微任务之后会去询问宏任务队列有没有任务(在询问之前可能会渲染一次页面),宏任务又可能除了同步代码又产生更多的微任务和宏任务,以此类推直到执行完所有的任务.
- 总之顺序就是宏任务(script)->微任务(清空)->宏任务.这里要注意定时器推送到任务队列的时候可能调用栈还在执行代码,也就是说这个定时器也许会更慢哦.