js基本概念
问题一:JavaScript和ECMAScript?
JavaScript是一种脚本语言,用于在Web浏览器中编写交互式前端应用程序。而ECMAScript是JavaScript的标准化版本,它规定了JavaScript的语法和行为,并且由ECMA国际组织负责维护和更新。
简单来说,JavaScript是一种具体的编程语言,而ECMAScript则是该语言的规范或标准。JavaScript实现了ECMAScript标准,并且还包括一些其他的API和特性,例如浏览器提供的DOM和BOM。
来自ChatGPT的总结。
问题二:js一般运行在哪里?(浏览器架构)
浏览器是多进程架构,一般包含主进程、渲染进程、GPU进程等,js就运行在渲染进程中。而渲染进程又包含多个线程,包含js引擎线程、gui渲染线程等多个线程,js就是运行在渲染进程的js引擎线程中。
问题三:js是一门什么样的语言?(js的关键语言特性或主要特点)
单线程:长时间同步任务会阻塞线程,因此必须有异步机制,通过定时器线程和事件触发线程的配合,通过事件循环机制,对异步任务进行处理。
动态、弱类型:动态指变量的数据类型可以更改,弱类型是指存在变量的自动类型转换。
面向对象和函数式:支持面向对象,同时支持函数式编程。
解释类语言、JIT:JIT,即Just-In-Time,即时编译器(JIT)将js代码转为机器码,并根据代码的实际情况进行优化和编译。
安全、性能差:性能差主要因为解释语言的限制、垃圾回收机制的影响、单线程的限制。
静态作用域:作用域由代码声明位置决定。
js执行过程
问题一:js的执行过程?
源代码经过词法、语法分析,生成AST,AST进一步编译成字节码。对于字节码,有两种处理方式,对于一般代码,将字节码逐行解释成机器码执行,对于频繁执行的代码,会将字节码编译成机器码,并存储起来,以备后续执行。
为什么不将所有字节码都编译成机器码?
因为机器码比字节码占用更多存储空间,需要时再编译比较节省空间。
问题二:一个包含多个函数,函数之间还有嵌套关系的js脚本的执行过程?
一个js脚本开始执行时,就创建了全局上下文,上下文中包括词法环境、变量环境、Outer和this。全局上下文每个生命周期只有一份。
其中,变量环境存储var定义的变量。var定义的变量有声明提升的特性,在代码并未执行到var声明语句前,变量环境中已经存在该变量,只不过该变量的值为undefined。
let和const定义的变量存储在词法环境中,let和const没有声明提升,但实际上,js引擎也会提前为这些变量开辟内存空间,只不过未执行到声明语句前,该部分内存无法访问,因此存在暂时性死区问题。词法环境是一个栈结构,每一个作用域块内的变量依次压入栈中。通过这种结构,js实现了块作用域。引用变量时,由词法环境栈从上至下寻找,然后再向变量环境中寻找。
Outer为一个指针,指向上下文的外部上下文,对于全局上下文来说,没有外部上下文。
当执行到某个函数调用时,编译器为该函数创建函数上下文,并将该上下文压入调用栈中。调用栈用一个指针,ESP,记录当前执行上下文的位置。调用栈有大小限制,因此递归次数太多的代码会出现call stack error。
函数上下文的Outer就会指向它的外部上下文,当函数内使用外部变量时,先寻找自身的词法环境和变量环境,如果没有,就去寻找Outer指向的外部上下文,依次寻找下去,直至找到或抛出reference error。
总结:
上下文分类:全局上下文、函数上下文、Eval上下文
上下文内容:词法环境、变量环境、Outer,this、可执行代码
创建上下文时做了什么:绑定this、创建词法环境、创建变量环境
js进阶
问题一:闭包?闭包的用途?
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。
用途:
- 允许将函数与其所操作的某些数据(环境)关联起来,通常使用只有一个方法的对象的地方,都可以使用闭包。
- 模拟私有方法 写一个立即执行函数,该函数返回一个对象,对象上有一些方法,数据则是立即执行函数中的局部变量,在函数销毁后通过闭包保存下来。
闭包回收
看引用闭包的函数是全局变量还是局部变量。
全局变量则直至页面关闭才会被回收,如果该闭包不经常使用,就会造成内存泄露。
问题二:this指向怎么判断?怎么修改?
指向判断
直接函数调用:window(严格模式:undefined)
对象方法调用:对象
new:永远绑定到了new出的对象
箭头函数:取决于上下文,没有办法改变this
改变this的函数:bind(永远由第一次绑定传入的this决定)
修改this
apply、call、bind
问题三:js垃圾回收机制?
代际假说:
- 大部分对象在内存中存活时间很短
- 不死的对象,存活得更久(一种推定思路,经历过几次回收都没有被回收的,可以认为它会存活更久)
新生区&副垃圾回收器
1~8M,小对象,Scavenge 算法
- 分为对象区域和空闲区域。新加入对象加入对象区域,对象区域快要满时进行垃圾回收。
- 对对象区域中的对象做标记,将标记存活的对象复制到空闲区,同时进行排序,因此副垃圾回收不会产生内存碎片。
- 复制完成后,翻转空闲区和对象区的角色,完成回收。
- 容量小,对象晋升策略,经过两次垃圾回收存活的的对象,移动到老生区。
标记——复制——翻转
老生区&主垃圾回收器
包括:
比较大的对象,直接进入老生区
新生区晋升来的
-
标记-清除
从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
-
标记-整理
标记过程一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
全停顿和增量标记
js和垃圾回收都需要主线程,垃圾回收会导致脚本执行暂停。新生代还行,老生代垃圾回收需要的时间比较久。
V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法。