dialog置为顶层的好处? 减少样式污染
一个小细节: Array.prototype.sort()默认是把元素转成字符串,然后按 UTF-16 码元值升序排序.
想按数字大小排序的话,记得写成:arr.sort((a, b) => a - b)
sort会改变原数组,不想改变记得用toSorted
| compareFn(a, b)返回值 | 排序顺序 |
|---|---|
| > 0 | a 在 b 后 如 [b, a] |
| < 0 | a 在 b 前 如 [a, b] |
| === 0 | 保持 a 和 b 原来的顺序 |
node的调试,可以看看ndb
关于v-for和v-if
- 在 vue 2.x 中,在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用, 要避免这俩在同一个标签使用。(但是如果要根据某个特定属性来决定是否渲染,使用计算属性computed)
- 在 vue 3.x 中,v-if 总是优先于 v-for 生效。
TLS SSL加密算法
TLS 1.0 靠RSA密钥交换算法,现在TLS 1.2使用ECDHE算法
RSA 和 ECDHE 握手过程的区别:
- RSA 密钥协商算法「不支持」前向保密,ECDHE 密钥协商算法「支持」前向保密;
- 使用了 RSA 密钥协商算法,TLS 完成四次握手后,才能进行应用数据传输,而对于 ECDHE 算法,客户端可以不用等服务端的最后一次 TLS 握手,就可以提前发出加密的 HTTP 数据,节省了一个消息的往返时间;
- 使用 ECDHE, 在 TLS 第 2 次握手中,会出现服务器端发出的「Server Key Exchange」消息,而 RSA 握手过程没有该消息;
前向保密:
Forward secrecy的大致意思是:用来产生会话密钥(session key)的长期密钥(long-term key)泄露出去,不会造成之前通讯时使用的会话密钥(session key)的泄露,也就不会暴漏以前的通讯内容。 简单的说,当你丢了这个long-term key之后,你以后的行为的安全性无法保证,但是你之前的行为是保证安全的。
HTTP 3.0 Quic协议的TLS 1.3
可以把 QUIC 看成是集成了“TCP+HTTP/2 的多路复用 +TLS 等功能”的一套协议。
简化握手过程中的操作,使得握手过程从2-RTT变为1-RTT,同时有效提高安全性和性能。
与缓存相关的HTTP请求头
强缓存:
- Expires
- Cache-Control
协商缓存:
- Etag, If-None-Match
- Last-Modified, If-Modified-Since
Cache-Control: no-cache和no-store的区别
no-cache
在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证 (协商缓存验证)。
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。
TCP 序列号和确认号的变化
- 公式一:序列号 = 上一次发送的序列号 + len(数据长度)。 特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为上一次发送的序列号 + 1。
- 公式二:确认号 = 上一次收到的报文中的序列号 + len(数据长度)。 特殊情况,如果收到的是 SYN 报文或者 FIN 报文,则改为上一次收到的报文中的序列号 + 1。
Promise和V8引擎
Promise A+规范大家都熟,但是在V8引擎里面实际运行时,Promise的表现却和我们平时认知的不太一样
先看两道输出题
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4)
}).then(res => {
console.log(res);
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
// 0 1 2 3 4 5 6
ECMA标准对于NewPromiseResolveThenableJob的解释
microtask(() => {
resolution.then((value) => {
ReslovePromise(promise, value)
})
})
这个任务中会调用 resolution.then ,然后同步到 promsie。但是这个整体的过程需要加入 microtask 队列中等待运行,当这个任务运行时,如果 resolution 也是一个 Promise 的话,则 (value) => {ReslovePromise(promise, value) } 又会被作为一个 microtask 加入 microtask 队列中等待运行。
为了预防下面的这种情况:
new Promise((resolve, reject) => {
Promise.resolve().then(() => {
resolve({
then: (resolve, reject) => resolve(1)
});
Promise.resolve().then(() => console.log(2));
});
}).then(v => console.log(v));
// 2 1
node 事件循环
事件循环流程
- 在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为:Tick
- 每个Tick过程就是查看是否有事件待处理,如果有就取出事件及其相关的回调函数。然后进入下一个循环,如果不再有事件处理,就退出进程。
与浏览器事件循环的区别
Node中宏任务分成了几种类型,并且放在了不同的task queue(事件队列)里,不同的task queue在执行顺序上也有区别,微任务放在了每个task queue的末尾:
setTimeout/setInterval: timers类型setImmediate: check类型- socket 的close事件属于 close callbacks类型
- 其他MacroTask属于poll类型
process.nextTick本质上属于MicroTask类型,但优先于其他所有的MicroTask执行- 所有MicroTask的执行时机在不同类型的MacroTask切换后
- idle/prepare 仅供内部调用,我们可以忽略
- pending callbacks 不常见,暂时忽略
宏任务(macrotask), 微任务(microtask)
每个Tick中,如何判断是否有事件要处理
- 每个事件循环中有一个或多个观察者,判断是否有事件要处理就是向这些观察者询问是否有要处理的事件
- 在Node中,这些事件主要来源于网络请求,文件I/O等,这些时间有对应的网络I/O观察者和文件I/O观察者。
- 事件循环是一个典型的生产者/消费者模型。异步I/O,网络请求等是事件的生产者,为Node提供不同类型的事件,这些事件被传递到观察者那里,事件循环则从观察者那里取出事件并处理。
- 在windows中,这个循环基于IOCP创建,在Linux中,通过 epoll 轮询实现。
浏览器事件循环中,何时进行渲染?
- 每个 eventloop 由三个阶段构成:执行一个 task,执行 microtask 队列,可选的 ui render 阶段,requestAnimationFrame callback 在 render 阶段执行。我们平时写的逻辑代码会被分类为不同的 task 和 microtask。
- microtask 中注册的 microtask 事件会直接加入到当前 microtask 队列。
- microtask 执行时机『尽可能早』,只要 javascript 执行栈为空,就会执行。一轮 eventloop 中,可能执行多次 microtask。
- requestAnimationFrame callback 的执行时机与浏览器的 render 策略有关,是黑箱的。
- 一些低优先级的任务可使用 requestIdleCallback 等浏览器不忙的时候来执行,同时因为时间有限,它所执行的任务应该尽量是能够量化,细分的微任务(micro task)。
- 因为它发生在一帧的最后,此时页面布局已经完成,所以不建议在 requestIdleCallback 里再操作 DOM,这样会导致页面再次重绘。
useRef Hook
- 除了可以获取DOM以外
- 更重要的作用:在多次渲染之间共享数据:比如用来停止定时器
TS 相关
never和void
- void 表示没有任何类型,void 类型的变量,只能赋予 undefined 和 null
- never 表示永远不存在的值的类型
LocalStorage 的 5M
满了会发生什么?
- 不存储数据, 也不会覆盖现有数据
- 浏览器Uncaught QuotaExceededError错误(localStorage超出限额)(quota_exceeded_err)
满了怎么办?
- postmessage通信往其他域上存取 参考
- 询问用户保留哪些
- LRU ?
flex 布局
对前端工程化的理解?
- 模块化:模块化就是把一个大的文件,拆分成多个相互依赖的小文件,按一个个模块来划分。
- 组件化:页面各个部分进行拆分
- 规范化:项目结构, eslint和prettier, 接口规范, git规范,定期codeReview
- 自动化:自动化构建,CI/CD
前端不刷新修改URL(Vue router相关)
WindowEventHandlers.onpopstate
样式的上下左右
margin为例
/* 应用于所有边 */
margin: 1em;
margin: -3px;
/* 上边下边 | 左边右边 */
margin: 5% auto;
/* 上边 | 左边右边 | 下边 */
margin: 1em auto 2em;
/* 上边 | 右边 | 下边 | 左边 */
margin: 2px 1em 0 auto;
错误监听
- window.onerror
- 异步错误用window.addEventListener('unhandledrejection'
- Vue的errorCaptured和errorHandler(
Vue.config.errorHandler) - 如果一个 class 组件中定义了
static getDerivedStateFromError()或componentDidCatch()这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用static getDerivedStateFromError()渲染备用 UI ,使用componentDidCatch()打印错误信息。
error传播规则(划重点)
- 默认情况下,如果定义了全局的errorHandler,所有的error都将最终汇总到errorHandler中做统一处理
- 如果一个组件的继承链或父链存在多个errorCaptured钩子,则这些钩子将会被相同的错误逐级唤起。
- 如果当前组件的errorCaptured钩子本身继续抛出错误,那么这些新的错误和原本的错误都将上传到父级组件的errorCaptured钩子,以及汇总到errorHandler
如果一个errorCaptured钩子返回了false,则会阻止此error的继续向上传播,也就是说这个error到此就已经处理完毕了。这个会阻止其他任何被这个错误唤起的errorCaptured钩子以及全局的errorHandler。
tips: 如果errorCaptured本身抛出error,return false也就不会执行了。
上报数据
上报时机有三种:
- 采用 requestIdleCallback/setTimeout 延时上报。
- 在 beforeunload 回调函数里上报。Window: beforeunload event
- 缓存上报数据,达到一定数量后再上报。
建议将三种方式结合一起上报:
- 先缓存上报数据,缓存到一定数量后,利用 requestIdleCallback/setTimeout 延时上报。
- 在页面离开时统一将未上报的数据进行上报。
对promise的理解?
Vue.mixin原理(极简述)
mixin 的本质还是对象之间的合并,但是对不同对象和方法右不同的处理方式,对于普通对象,就是简单的对象合并类似于Object.assign,对于基础类型就是后面的覆盖前面的,而对于生命周期上的方法,相同的则是合并到一个数组中,调用的时候依次调用。
Axios 原理(极简述)
Axios特点:
- 从浏览器中创建 XMLHttpRequests,从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
Axios实例化跟request函数做了什么事情。
- 首先对默认的config和自定义的config进行了合并处理。
- 接着定义了promise链chain数组,并且通过promise.resolve(config)生成一个新的promise,接着对请求拦截器和相应拦截器插入到chain数组中。
- 判断chain数组长度,如果length不等于0,不断的更新promise的值,最后返回最终的promise。
拦截器
拦截器的原理就是通过chain的Promise链,在请求之前加入请求拦截器,请求结束之后加入响应拦截器,循环遍历chain,从而达到链式调用的作用。在dispatchRequest的前后,传递的参数由request变成response。
取消
取消请求的是一个异步分离的设计方案,利用promise的异步效果,通过切换promise的状态,从而达到异步取消请求的实现。
浏览器的队列们
Vue组件的data为什么是个函数?
- vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。
- 当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
- 当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。
源码层面
通过vue.extend方法创建一个子类,创建子类之后会把自己的选项和父类的选项使用mergeOptions方法做一个合并,自己的选项就包含data。在mergeOptions中会调用strats.data对子类的data进行合并,这个方法中首先会判断子类的data进行判断,要求data必须是一个函数,如果不是会报错告诉它这个data应该是一个函数定义,因为每一个组件都会返回一个实例,如果是data就会调用 mergeDataOrFn方法进行合并。然后会合并父类的extend、minin、use方法,最后extend返回的就是这个子类的方法。
补充:
为什么要合并?
因为子组件也要有父组件的属性,extend方法是通过一个对象创建了一个构造函数,但是这个构造函数并没有父类的属性,因为它是一个新函数,和之前的Vue构造函数是没有关系的。通过extend产生了一个子函数,这个子函数需要拥有vue实例上的所以东西,它就要做一次合并。
为什么new Vue这个里面的data可以放一个对象?
因为这个类创建的实例不会被复用。它只会new一次,不用考虑复用。
JS为什么是单线程的?
- 简单性:单线程使得 JavaScript 的并发模型相对简单,不需要考虑多线程之间的同步和竞态条件等复杂问题。这使得开发者更容易编写和调试代码。
- 安全性:由于 JavaScript 运行在浏览器环境中,多线程可能导致安全问题,例如数据竞争和死锁。通过限制为单线程,可以避免这些潜在的问题。
- 前端交互:JavaScript 主要用于前端开发,处理用户界面的交互和响应。单线程模型确保了事件的顺序执行,避免了多个事件同时修改页面状态导致的混乱。
Vue2和3的区别?
- 响应式原理不同
- 选项式API和组合式API
- Diff算法不同
- 因为fragment导致的根标签个数
- 生命周期不同
Vue.nextTick 原理
- vue 里面用到了观察者模式,默认组件渲染的时候,会创建一个 watcher,并且渲染视图
- 当渲染视图的时候,会取 data 中的数据,触发属性的 get 方法,就让这个属性的 dep 记录watcher(注意:每一个data属性都对应一个dep)
- 同时让 watcher 也记住 dep ,dep 和 watcher 是多对多的关系,因为一个属性可能对应多个视图,一个视图对应多个数据
- 如果数据发生变化,也就是在 set 的时候,会触发 dep.notify() ,通知 dep 中存放的 watcher 去更新
- 每次更新数据都会同步调用 watcher 中 update 方法,此时就可以将更新的逻辑缓存起来,等会同步更新数据的逻辑执行完毕后,依次调用 (去重的逻辑)
自己用过的vite插件
- 按需引入组件库
- viteMockServe
- svgLoader
- scss相关
vite打包?
Vite在开发环境中使用即时编译(esbuild等),而在生产环境中使用Rollup进行打包。
一些其他的部分
如何切换盒模型模式?
box-sizing: content-box // 标准
box-sizing: border-box // 怪异
图片懒加载?如何检测图片在不在可视区域?
- JS事件触发:三种事件都可能导致图片的可视数量发生变化:scroll,resize和oritentionChange。
oritentionChange: 移动端设备旋转 - intersectionObserver
GPU
比如想要改变元素位置 left ,top 值,可以换一种思路通过改变 transform: translate,transform 是由 GPU 直接控制渲染的,所以不会造成浏览器的重排。
因为: transform: translate是直接合成,跳过了前面的重排重绘。