看到一篇文章,写的很好,我自己又打了一遍加深记忆:
文章链接:https://blog.csdn.net/qq_39669807/article/details/89399276
js是单线程的,一个页面是由一个线程在执行代码
js是异步执行的,通过事件循环的方式实现
js为什么是单线程的
- 为了防止两个线程操作同一个DOM
- 为了利用多核CPU的能力,html5提出了webWork标准,允许JavaScript创建多个线程,但是子线程完全受主线程的控制,且不得操作DOM
- js是单线程,但是浏览器开了四个线程参与js的执行,永远只有js引擎在执行脚本,其他三个线程只负责将满足触发条件的处理函数推进任务队列,等待js引擎执行
- js引擎线程(主线程,只有这个线程负责解析和执行代码)
- 事件触发线程
- 定时器触发线程
- http异步请求线程
js为什么需要异步操作
因为js是单线程的,一次只能执行一个任务,这些任务形成一个任务队列排队执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样排队等待执行执行效率会非常低,甚至导致页面的假死,所以浏览器为这些耗时任务开辟了另外的线程,挂起处于等待中的任务,先运行排在后边的任务,当等待中的任务返回结果后再有主线程执行。
js是怎么通过单线程实现异步的
js通过回调函数,setTimeout实现异步,通过事件循环机制执行异步操作。
js引擎的执行过程
- 语法分析:分别对加载完成的代码进行语法检查,语法正确进入与编译阶段,不正确则停止该代码块的执行,查找下一个代码块并进行加载,加载完成后再次进入该代码块进行分析
- 预编译阶段:语法分析阶段完成后,进入预编译阶段,创建变量对象(创建arguments对象,函数生命提前解析,变量生命提升)
- 执行阶段
注意:浏览器首先按顺序加载由<script>标签分割的js代码块,加载js代码完毕后,立级进入以上上三个阶段,再按顺序查找下一个代码块,继续执行以上三个阶段,无论是外部脚本还是内部脚本块都是一样的原理,并且都在同一个局部作用域中(后边的代码块可以访问前面的变量,而前面的不能访问后面的)
语法分析阶段
分析该js代码的代码块是否正确,如果不正确则向外抛出语法错误,组织该代码块的执行,然后继续查找下一个代码块,语法正确,则进入预编译阶段。
预编译阶段
js代码块通过语法分析阶段后,进入预编译阶段。
js的运行环境:
- 全局环境(js代码加载完毕后,进入代码预编译即进入全局环境)
- 函数环境(函数调用执行时,进入函数环境,不同函数的函数环境不同)
- eval
每进入一个不同的运行环境即创建一个相应的执行上下文,在一段js程序中会创建多个执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈,栈底永远是全局执行上下文,栈顶永远是当前执行上下文
函数调用栈:以栈的方式管理运行环境,后进先出
创建执行上下文
创建执行上下文只要做了三件事:
- 创建变量对象
- 建立作用域链
- 确定this指向
创建变量对象
- 创建arguments对象:检查当前上下文中的参数,建立该对象的属性和属性值(箭头函数和全局环境没有这一步)
- 检查当前上下文的函数声明,找到函数声明将其值指向函数所在堆地址的引用中
- 检查当前上下文的变量声明
函数声明和变量提升是在创建变量对象中进行的,且函数声明优先级高于变量声明
创建的变量对象发生在预编译极端,尚未执行,改变量对象是无法访问的,值仍未undefined,只有进入执行阶段,变量对象转换为活动对象后才能进行访问这就是VO(变量对象)----》AO(活动对象)的过程
建立作用域链
作用域链由当前执行环境的变量对象与上层环境的一系列活动对象组成,保证了当前执行环境符合访问权限的变量和函数的有序访问
作用域链相当于一个数组,第一项是当前作用域,最后一项是全局作用域,作用域链保证了变量和函数的有序访问,沿着作用域从左向右查找变量或函数,找到就会停止查找,找不到就一直查找直到全局作用域,再找不到就会抛出错误。
闭包
- 函数嵌套函数
- 内部函数可以访问外部函数的局部变量
- 外层函数的局部变量不会被垃圾回收机制自动回收
确定this指向
在全局执行上下文中this指向window;函数环境中的this根据执行环境和执行方法确定