① JavaScript 是如何运行的?解释型语言和编译型语言的差异是什么?
-
JS代码-> 解析成 AST (先会有词法分析、语法分析) -> 生成字节码(V8)-> 生成机器码(编译器)
-
很多资料会说,JavaScript、Python、Ruby都是"解释型语言",是通过解释器来实现的。这么说其实很容易引起误解:语言一般只会定义其抽象语义,而不会强制性要求采用某种实现方式。
例如说C一般被认为是“编译型语言”,但C的解释器也是存在的,例如Ch。同样,C++也有解释器版本的实现,例如Cint。
实际上很多解释器内部是以 “编译器+虚拟机” 的方式来实现的,先通过编译器将源码转换为AST或者字节码,然后由虚拟机去完成实际的执行。
总结:
-
所谓“解释型语言”并不是不用编译,而只是不需要用户显式去使用编译器得到可执行代码而已。
-
实际上很多解释器内部是以 “编译器+虚拟机” 的方式来实现的。 大概流程是: 源码-> 编译器 -> AST或字节码 -> 虚拟机去完成实际的执行。
② 简单描述一下 Babel 的编译过程?
答: 首先,Babel的作用是 从一种源码到另一种源码,充当转换编译器的作用,可以简述为 解析(解析JS代码)->转换(解析和修改AST)->重建(将修改后的AST转换成另一种JS代码)
③ JavaScript 中的数组和函数在内存中是如何存储的?
答: ①数组,JS里的数组主要就是 以连续内存形式存储的FixedArray、以哈希表形式存储的HashTable。
②函数,函数属于引用数据类型,存储在堆中,在栈内存中只是存了一个地址来表示对堆内存中的引用。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
** tips: **
- 引用类型在内存中的存储【 栈内存中的地址 -> 堆内存中的实体 】原因是:引用类型实体较大,比较占用内存空间。
- 值类型在内存在的存储【栈内存中】
④ 浏览器中的事件循环机制是?
答: ①浏览器中的事件循环:
macrotasks(宏任务):
script(整体代码)setTimeoutsetIntervalsetImmediateI/OUI renderingevent listner
microtasks(微任务):
process.nextTickPromise.thenObject.observeMutationObserver
在浏览器里,每当一个被监听的事件发生时,事件监听器绑定的相关任务就会被添加进回调队列。通过事件产生的任务是异步任务,常见的事件任务包括:
- 用户交互事件产生的事件任务,比如输入操作;
- 计时器产生的事件任务,比如setTimeout;
- 异步请求产生的事件任务,比如 HTTP 请求。
主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。
- JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
- 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
- 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。
- 同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
- 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
- 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。
- 上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)。
总结
- Js 有一个【主线程和调用栈】,所有的任务最终都会被放到【调用栈】等待【主线程执行】。
- 宏任务 -> 微任务 - 宏任务 -> 微任务 循环往复执行
⑤ ES6 Modules 相对于 CommonJS 的优势是什么?
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。即ES6 Module只存只读,不能改变其值
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载
- import 的接口是 read-only(只读状态),不能修改其变量值。commonJS 可以重新赋值,但是对 ES6 Module 赋值会编译报错。
ES6 Modules 优势: CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 Modules不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
⑥ 【高级程序设计语言】是如何【编译】成【机器语言】的?
答: 高级语言代码->解析成 AST (期间伴随词法分析、语法分析)->生成字节码(V8)->生成机器码(编译器)
🀍 编译器一般由哪几个阶段组成?数据类型检查一般在什么阶段进行?
5个阶段:
- 【数据类型检查】词法分析, 语法分析
- parse阶段: V8引擎将js代码转成AST
- Ignition 阶段:解释器将 AST 转换为字节码,解析执行字节码也会为下一个阶段优化编译提供需要的信息;
- TurboFan 阶段:编译器利用上个阶段收集的信息,将字节码优化为可以执行的机器码;
- Orinoco 阶段:垃圾回收阶段,将程序中不再使用的内存空间进行回收。
流程:
js -> 词/语法检查 -> ast (通过v8引擎) -> 字节码(通过解释器)-> 机器码 (通过编译器)-> 垃圾回收阶段(将不再用的内存回收)
Ⅷ 发布 / 订阅模式和观察者模式的区别是什么?
答: 在观察者模式中,被观察者通常会维护一个观察者列表。当被观察者的状态发生改变时,就会通知观察者。
在发布订阅模式中,具体发布者会动态维护一个订阅者的列表:可在运行时根据程序需要开始或停止发布给对应订阅者的事件通知。
区别在于发布者本身并不维护订阅列表(它不会像观察者一样主动维护一个列表),它会将工作委派给具体发布者(相当于秘书,任何人想知道我的事情,直接问我的秘书就可以了);订阅者在接收到发布者的消息后,会委派具体的订阅者来进行相关的处理。
⑨ 装饰器模式一般会在什么场合使用?【👉🏻:扩展现有对象的功能】
答: 装饰器模式一般是指允许动态地向一个现有的对象添加新的功能,同时又不改变其结构,相当于对现有的对象进行了一个包装。
使用场景很多,比如以前写jQ项目,可以自己快速动态拓展jQ上面的方法,或者vue的自定义指令,主要是希望通过继承的方式扩展老旧功能。
十 列举你所了解的编程范式?
答:声明式、命令式(面向对象oop)、函数式fp
11. 什么是沙箱?浏览器的沙箱有什么作用?
答: 沙箱设计的目的是为了让不可信的代码运行在一定的环境中,从而限制这些代码访问隔离区之外的资源。
12. Hash 和 History 路由的区别和优缺点?
答: hash 路由模式的实现主要是基于下面几个特性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
- hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
- 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
- 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。在vue/react中监测到hash变化就加载不同的组件即可。
- hashchange事件触发时,事件对象会有hash改变前的URL(oldURL)和hash改变后的URL(newURL)两个属性:
demo
www.baidu.com/#a=0 当你修改#后的hash值时:
window.addEventListener('hashchange',function(e) { console.log(e.oldURL); console.log(e.newURL) },false);
history 路由模式的实现主要基于存在下面几个特性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
- 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
13.JavaScript 中的 const 数组可以进行 push 操作吗?为什么?
答: 可以,也可以进行splice()操作。
const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容。
14. 简单对比一下 Callback、Promise、Generator、Async 几个异步 API 的优劣?
答: 首先callback不是异步API,它是早年JS异步编程实现的一种手段。
Promise是社区为了解决回调地狱的问题在ES6版本提出的一种解决方案;
Generator也是一种异步编程解决方案,它最大的特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用 yield 语法来标注;
Async/await是 ES7 中提出的新的异步解决方案,async 是 Generator 函数的语法糖,async/await 的优点是代码清晰(不像使用 Promise 的时候需要写很多 then 的方法链)。async/await 不仅仅是 JS 的异步编程的一种方式,其可读性也接近于同步代码,让人更容易理解。
async/await 是自带了执行器,而 生成器Generator是没有执行器的。需要手动调用next()
15. Object.defineProperty 和 ES6 的 Proxy 有什么区别?
答: Proxy的优势如下
- Proxy可以直接监听整个对象而非属性。
- Proxy可以直接监听数组的变化。
- Proxy有13中拦截方法,如ownKeys、deleteProperty、has 等是 Object.defineProperty 不具备的。
- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;
- Proxy做为新标准将受到浏览器产商重点持续的性能优化,也就是传说中的新标准的性能红利。
Object.defineProperty 的优势如下
- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平。
Object.defineProperty 不足在于:
- Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
- Object.defineProperty不能监听数组。是通过重写数据的那7个可以改变数据的方法来对数组进行监听的。
- Object.defineProperty 也不能对 es6 新产生的 Map,Set 这些数据结构做出监听。
- Object.defineProperty也不能监听新增和删除操作,通过 Vue.set()和 Vue.delete来实现响应式的。