查缺补漏(一)

155 阅读14分钟

dialog置为顶层的好处? 减少样式污染
一个小细节: Array.prototype.sort()默认是把元素转成字符串,然后按 UTF-16 码元值升序排序.
想按数字大小排序的话,记得写成:arr.sort((a, b) => a - b)
sort会改变原数组,不想改变记得用toSorted

compareFn(a, b)返回值排序顺序
> 0a 在 b 后 如 [b, a]
< 0a 在 b 前 如 [a, b]
=== 0保持 a 和 b 原来的顺序

eval() 可以把传入的字符串当js运行

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 布局

flex属性看这个

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也就不会执行了。

上报数据

上报时机有三种:

  1. 采用 requestIdleCallback/setTimeout 延时上报。
  2. 在 beforeunload 回调函数里上报。Window: beforeunload event
  3. 缓存上报数据,达到一定数量后再上报。

建议将三种方式结合一起上报:

  • 先缓存上报数据,缓存到一定数量后,利用 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是直接合成,跳过了前面的重排重绘。