八股文
讲讲异步组件和路由懒加载?异步加载的过程是怎么样的?为什么在router中使用回调的方式引入组件就可以实现异步加载?假如一个页面有特别多的异步组件会带来什么问题?
其实异步组件和异步路由是一样的东西,异步路由引入的也还是对应的组件。
异步加载的过程涉及到JavaScript运行时、事件循环机制以及打包工具的配合工作。过程:
- 代码分割 :当我们在代码中使用
import()函数异步加载组件时,打包工具(如Webpack或Rollup)会将这些组件作为单独的JavaScript文件(通常被称为"chunks")进行打包。 - 初始加载:在页面初始加载时,只有主bundle会被下载。主bundle中包含了应用的主要代码,但不包括异步组件的代码。
- 异步请求:当用户触发了需要异步组件的操作(如访问一个使用了路由懒加载的路由),JavaScript运行时会发送一个网络请求,请求异步组件对应的JavaScript文件。
- 下载和执行:浏览器下载异步组件对应的JavaScript文件,并执行其中的代码。这个过程可能会涉及到一些延迟,因为需要等待网络请求完成,并且JavaScript是单线程的,不能同时执行多段代码。
- 渲染组件:异步组件的代码被执行后,组件会被注册到Vue.js应用中,并触发视图的更新。Vue.js会创建一个新的组件实例,并将其渲染到页面上。
在这个过程中,打包工具起到了关键作用。它负责将代码分割成多个小文件,并在构建时生成一份映射表,这样JavaScript运行时就知道每个异步组件对应哪个文件。
同时,JavaScript的事件循环机制也在这个过程中发挥了作用。由于JavaScript是单线程的,它需要使用事件循环来处理异步操作。当我们使用import()函数加载一个组件时,这个操作会被放入微任务队列中。只有当当前的同步代码执行完毕后,才会执行微任务队列中的操作。这也是为什么异步组件不会立即被加载和渲染的原因。
过多的异步组件可能会导致的问题:
- 网络请求过多导致阻塞、请求失败
- 加载异步组件导致页面闪烁、抖动,影响用户体验
- 每个异步组件都可能下载失败,一一提供错误处理比较复杂
Vuex 的底层原理是什么?有了解过设计模式吗?发布订阅模式?
笔者对 vuex 也仅限于使用所以答得并不太好,在掘金上找到一篇讲的比较好的文章Vuex核心源码解析之手写Vuex:
使用方式:
vuex是个状态管理模式,其基本工作流程是:当用户修改状态的时候,如果是同步修改状态,会先提交(commit)触发mutations方法,改变Store实例中的状态,这也是唯一修改状态的途径,如果是异步的修改状态的方法,比如发起数据请求等,会提交dispatch触发actions方法,然后在actions方法中通过commit触发mutations方法进而更新Store实例的状态,然后再将状态更新到Vue组件中。如果我们的项目是大型的单页面应用,那么我们就需要使用vuex在组件外部管理状态,通过这个状态管理模式我们可以很轻松的实现组件之间的通信,这也是选择vuex的原因之一,当然vuex也有自己的缺陷,就是状态不能够持久化。
实现原理:
Vuex实现响应式原理通过在install方法内通过 vue的mixin方法将Store实例注入到每个组件内,在resetStoreVm方法内部主要是借助vue本身的响应式原理来实现状态的响应式,借助computed来实现getters的缓存效果,然后通过发布订阅的思想将用户定义的mutations和actions方法存储起来,当用户触发commit、dispatch的时候就去订阅mutations和actions找出对应的方法。如果存在模块嵌套的情况下,首先,vuex内部会通过一个moduleCollection类将所有模块格式化为一个树形结构,其次,通过installModule方法将格式化好的树形结构安装到Store实例上,最后,通过resetStoreVm方法借助vue内部响应式的原理将所有模块的状态都置为响应式。vuex是响应式的同时还支持插件的使用,Store内部有subscribe方法和replaceState方法,通过这两个方法我们实现vuex的状态的持久化,以上说明了 vuex是高度依赖vue响应式和插件系统的。
哪里跌倒在哪里爬起来之手写发布订阅模式:
美团一面:你了解发布-订阅模式吗?「每天搞透一道JS手写题💪Day8」 - 掘金 (juejin.cn)
HTTP和HTTPS的区别是什么?SSL协议的加密方式是什么?
HTTP (端口号80)和HTTPS(端口号443)的主要区别在于安全性和数据传输方式。
HTTP(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。它以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息。
HTTPS(超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议,需要CA证书。HTTPS经由HTTP进行通信,但利用SSL/TLS来加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
SSL (Secure Sockets Layer)安全套接层协议,目的是保证数据传输的安全和完整。SSL协议既用到了对称加密也用到了非对称加密 (公钥加密),在建立传输链路时,SSL首先对对称加密的密钥 使用公钥进行非对称加密 ,链路建立好之后,SSL对传输 内容使用对称加密。这样,既利用了非对称加密在公开环境下安全传输密钥的优点,又利用了对称加密算法在大量数据加密时的效率优势。
TCP 的三次握手?
TCP(传输控制协议)是一种面向连接的协议,它在数据传输之前需要在客户端和服务器之间建立连接。这个连接的建立过程就是我们所说的"三次握手"。以下是三次握手的具体步骤:
第一次握手:客户端将标志位SYN设置为1,随机产生一个值seq=J,并将该数据包发送给服务器,然后客户端进入SYN_SENT状态。
第二次握手:服务器收到客户端的SYN包,必须确认客户端的SYN,将标志位SYN和ACK都置为1,ack=J+1,同时为自己也要发送SYN请求信息,随机产生一个值seq=K,并将该数据包发送给客户端,此时服务器进入SYN_RCVD状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=K+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手后,客户端与服务器就正式建立了连接,在这个状态下,双方都可以发送数据。
了解事件循环吗?微任务有哪些?
事件循环是JavaScript的执行机制。由于JavaScript是单线程的,事件循环机制可以解决JavaScript在处理高IO任务时可能出现的阻塞问题。
事件循环包括两种任务:宏任务和微任务。宏任务由宿主(浏览器/Node)发起,微任务由JavaScript自身发起。
微任务包括:
Promise.then()process.nextTick(仅在Node.js环境中)Object.observe(已废弃;被Proxy对象替代)MutationObserver
执行顺序是:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中。当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
代码输出题
结合刚才说的事件循环,判断下面的代码输出顺序
js
console.log('start');
setTimeout(() => {
console.log('Timeout1');
}, 1000);
Promise.resolve().then(() => {
console.log('Promise1');
});
Promise.resolve().then(() => {
console.log('Promise2');
setTimeout(() => {
Promise.resolve().then(() => {
console.log('Promise3');
})
console.log('Timeout2');
}, 0);
});
console.log('end');
打印顺序: start end Promise1 Promise2 Timeout2 Promise3 Timeout1
说明:
- 首先按顺序执行同步代码,打印 start 和 end,并且把异步任务分别放入宏任务和微任务队列中;
- 其次执行微任务队列中的回调,打印 Promise1 和 Promise2;
- 打印完 Promise2 之后,又向宏任务队列中推入了一个计时器;
- 此时宏任务队列中有两个计时器,优先执行延迟时间短的那个,打印了 Timeout2,并且向微任务队列中加入了要一个回调函数;
- 再次优先执行微任务队列中的回调,打印 Promise3。最后,打印 Timeout1。
这段代码输出什么?为什么会输出这样的结果?如何修改使其正常工作?
js
for(var i = 0; i < 3; i++){
setTimeout(function(){
console.log(i)
})
}
这段代码会输出三次 "3",因为 setTimeout 函数是异步的,当计时器执行时,for 循环已经结束,变量 i 的值为 3,并且 var 不存在块级作用域,因此三个计时器中的回调函数都会打印 3。
想要让这段代码顺序打印0,1,2,就要保证计时器中的 i 是当次循环的 i 值,这需要将这个 i 和计数器固定到一个作用域中。
有两种方法:一是直接将 var 修改为 let,let 关键字会创建块级作用域,在每次循环中 let 都会创建一个新的变量 i,并初始化其为当前的值;二是使用闭包给每一次循环创建一个新的作用域,代码如下:
js
for(var i = 0; i < 3; i++){
(function(i){
setTimeout(function(){
console.log(i)
})
})(i)
}
算法题
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function (root) {
if (!root) return [];
let queue = [];
let res = [];
queue.push(root);
while (queue.length) {
let arr = [];
let len = queue.length;
for (let i = 0; i < len; i++) {
let node = queue.shift();
arr.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
res.push(arr);
}
return res;
};
写完之后会让你结合代码讲一下思路。