-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
1,027 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
--- | ||
title: 20240520积累面试 | ||
order: 37 | ||
group: | ||
order: 11 | ||
title: /interview/experience | ||
nav: | ||
order: 3 | ||
title: 'interview' | ||
path: /interview | ||
--- | ||
|
||
- 熟悉一兩种常见的前端构建工具(Webpack、rollup、vite 等),熟悉 babel 的使用 | ||
- 精通 Web 开发技术,对 JavaScript(含 ES2020)、HTML、CSS、DOM、安全等有较深入的理解,需要有微信小程序开发经验,必须熟悉 typrscript 开发。 | ||
- 5 年及以上开发经验,2 年以上 React 开发经验, | ||
- 熟悉基础的算法实现。链表/hash/回溯/深度/广度/dp 动态规划 | ||
|
||
- 宏任务和微任务 浏览器事件循环 | ||
- esmodule 和 commjs 区别 | ||
- H5 性能优化和小程序性能优化 | ||
|
||
## 宏任务和微任务 浏览器事件循环 | ||
|
||
- 整体的 script 作为第一个宏任务开始执行,执行的时候代码分为同步任务和异步任务 | ||
- 同步任务一次进入执行栈一次执行,然后出栈 | ||
- 异步任务又分为宏任务和微任务,首先说宏任务 | ||
- 宏任务进入到**event Table**中事件表中,并在里面注册回调函数,每当指定的事件完成时,event table 会将这个函数移入到 event queue 中 | ||
- 微任务注册要执行的函数, | ||
- 当主线程内的任务执行完毕,执行栈为空的时候,会优先检查微任务的 event queue 是否有事件回调,如果有事件回调则继续执行,没有则执行下一个宏任务 | ||
- 上述过程会不断重复,这就是 Event Loop,比较完整的事件循环 | ||
|
||
## webRTC 了解多少 | ||
|
||
- 实时通讯技术,允许网络应用建立点对点连接,实现视频流和音频流实时 点对点进行传输,直播业务比较合适 | ||
- 三部分组成,浏览器 API+音视频引擎+网络 I/O 协议还有一个就是信令服务器 | ||
- 交互过程: 1.音视频采集 2.信令交互,阔以使用 Node 搭建信令服务器 3.创建**RTCPeerConnection** 对象,进行媒体协商 | ||
|
||
RTCPeerConnection 是一个由本地计算机到远端的 WebRTC 连接,该接口提供**创建,保持,监控,关闭连接**的方法的实现,可以简单理解为功能强大的 **socket** 连接。通过**new RTCPeerConnection**即可创建一个 RTCPeerConnection 对象,此对象主要负责与各端建立连接(NAT 穿越),接收、发送音视频数据,并保障音视频的服务质量,接下来要说的端到端之间的媒体协商,也是基于 RTCPeerConnection 对象来实现的。 | ||
|
||
### 信令服务是干嘛用的 | ||
|
||
信令可以简单理解为消息,在协调通讯的过程中,为了建立一个 webRTC 的通讯过程,在通信双方彼此连接、传输媒体数据之前,它们要通过**信令服务器**交换一些信息,如加入房间、离开房间及媒体协商等,而这个过程在 webRTC 里面是没有实现的,需要自己搭建信令服务。 | ||
|
||
可以使用 **Socket.io** 来实现 WebRTC 信令服务器,Socket.io 已经内置了房间的概念,所以非常适合用于信令服务器的创建。 | ||
|
||
## esmodule 和 commjs 区别 | ||
|
||
- 值的拷贝和值的引用 | ||
|
||
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值 | ||
|
||
ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 | ||
|
||
- 静态编译时输出接口和运行时加载 | ||
|
||
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。 | ||
|
||
编译时加载: ES6 模块不是对象,而是通过 **export 命令显式指定输出的代码**,import 时采用静态命令的形式。即在 import 时可以指定加载某个输出值,而不是加载整个模块,这种加载称为**编译时加载**。 | ||
|
||
## H5 性能优化和小程序性能优化 | ||
|
||
### H5 性能优化 | ||
|
||
- 请求资源 CDN 和 dns 预解析 | ||
- http2 多路传输 | ||
- 减少不必要的 DOM 访问和操作,动画:请求动画帧 + requestIdleCallback | ||
- 路由懒加载 | ||
- 合理使用 defer+async 异步加载和解析 js 脚本 | ||
- 合理使用 preload/prefetch 预加载关键资源。 | ||
- 减少重绘和回流 | ||
|
||
- 框架层面,引入中间件简化和隔离这些基础设施与业务逻辑之间的细节,只关注业务本身 | ||
- 父组件更新,不波及子组件渲染,没有必要的渲染是对性能的极大浪费。合理使用 react.memo/useMemo/useCallback/shouldComponentUpdate/PureComponent | ||
- 遍历数组的时候,记得加上唯一的标记 key | ||
- 使用 Webpack 等打包工具进行代码分割,按需加载模块。 | ||
|
||
### webpack 性能优化 | ||
|
||
- 使用 externals 优化 cdn 静态资源 | ||
- tree shaking | ||
- 代码分割 | ||
- 图片压缩 | ||
- 多线程构建 | ||
- 按需引入模块 babel-plugin-import | ||
|
||
### 微信小程序性能优化 | ||
|
||
- 使用分包加载:利用微信小程序的分包加载机制,将不常用的页面放在单独的包中。 | ||
- 使用插槽 slot 组件抽离 | ||
- 减少 data 和 setData,避免频繁调用 setData,合并多次 setData 调用,减少界面重绘次数 | ||
- 使用合适的图片格式和压缩图片,利用图片懒加载。 | ||
- 合理使用缓存,利用本地缓存(如 wx.setStorageSync)来存储不常变动的数据,减少网络请求 | ||
- 合理使用生命周期函数:避免在 onLaunch、onShow 等生命周期函数中执行耗时操作。 | ||
- 减少页面的层级和组件嵌套,简化页面结构。 | ||
- 优化第三方库,评估和优化第三方库的使用,避免引入不必要的代码。 | ||
|
||
```js | ||
// 无重复字符的最长子串 | ||
function noDup(str) { | ||
if (str.length == 0) return ''; | ||
let arr = []; | ||
let max = 0; | ||
for (let i = 0; i < str.length; i++) { | ||
let cur = str[i]; | ||
const index = arr.indexOf(cur); | ||
if (index > -1) { | ||
arr.splice(0, index + 1); | ||
} | ||
arr.push(cur); | ||
max = Math.max(max, arr.length); | ||
} | ||
return max; | ||
} | ||
|
||
// 二分法 | ||
function two(arr, target) { | ||
let left = 0; | ||
let rignt = arr.length - 1; | ||
while (left <= right) { | ||
let mid = left + Math.floor(right - left) / 2; | ||
if (arr[mid] === target) { | ||
return mid; | ||
} else if (arr[mid] > target) { | ||
right = mid - 1; | ||
} else if (arr[mid] < target) { | ||
left = mid + 1; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
// 链表反转 | ||
function reverse(node) { | ||
let pre = null; | ||
let cur = node; | ||
while (cur) { | ||
let next = cur.next; | ||
cur.next = pre; | ||
pre = cur; | ||
cur = next; | ||
} | ||
return pre; | ||
} | ||
// 是否有环 | ||
function isCycle(head) { | ||
let m = new Set(); | ||
let cur = head; | ||
while (cur) { | ||
if (m.has(cur)) { | ||
return m.get(cur); | ||
} | ||
m.set(cur, cur); | ||
cur = cur.next; | ||
} | ||
return false; | ||
} | ||
|
||
// 盛最多水的容器 | ||
var maxArea = function (height) { | ||
// 长*宽 | ||
let max = 0; | ||
let left = 0; | ||
let right = height.length - 1; | ||
while (left <= right) { | ||
max = Math.max(max, Math.min(height[right], height[left]) * (right - left)); | ||
if (height[right] > height[left]) { | ||
left++; | ||
} else { | ||
right--; | ||
} | ||
} | ||
return max; | ||
}; | ||
|
||
// 有效的括号 | ||
// 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 | ||
function isValid(s) { | ||
let m = new Map([ | ||
[')', '('], | ||
[']', '['], | ||
['}', '{'], | ||
]); | ||
let arr = []; | ||
for (let item of s) { | ||
if (m.has(item) && m.get(item) == arr.slice(-1)) { | ||
arr.pop(); | ||
} else { | ||
arr.push(item); | ||
} | ||
} | ||
return arr.length === 0; | ||
} | ||
|
||
// 删除链表一个节点 | ||
var deleteNode = function (head, val) { | ||
let dummy = { | ||
next: head, | ||
val: 0, | ||
}; | ||
let cur = dummy; | ||
while (cur.next) { | ||
if (cur.next.val == val) { | ||
cur.next = cur.next.next; | ||
} | ||
cur = cur.next; | ||
} | ||
return dummy.next; | ||
}; | ||
// 二叉树前中后序 | ||
function inOrder(node) { | ||
if (node) { | ||
return null; | ||
} | ||
let arr = []; | ||
let dfs = (node) => { | ||
if (node) { | ||
// 根左右 | ||
arr.push(node.val); | ||
dfs(node.left); | ||
dfs(node.right); | ||
} | ||
}; | ||
return dfs(node); | ||
} | ||
// BFS 根左右 进右左 => 出 左右根 | ||
function BFSorder(node, res = []) { | ||
let queue = []; | ||
let cur = node; | ||
while (cur) { | ||
if (cur.left) { | ||
cur = cur.left; | ||
queue.push(cur.left); | ||
} else { | ||
cur = queue.pop(); | ||
res.push(cur.val); | ||
cur = cur.right; | ||
} | ||
} | ||
return res; | ||
} | ||
|
||
function preOrder(root, res = []) { | ||
if (!root) return res; | ||
var stack = [root]; | ||
while (stack.length) { | ||
let cur = stack.pop(); | ||
res.push(cur.val); | ||
if (cur.right) { | ||
stack.push(cur.right); | ||
} | ||
if (cur.left) { | ||
stack.push(cur.left); | ||
} | ||
} | ||
return res; | ||
} | ||
|
||
// 层序遍历 | ||
function BFS(node) { | ||
let queue = [node]; | ||
let res = []; | ||
while (queue.length) { | ||
let len = queue.length; | ||
let arr = []; | ||
for (let i = 0; i < len; i++) { | ||
let tree = queue.shift(); | ||
arr.push(tree.val); | ||
if (tree.left) { | ||
queue.push(tree.left); | ||
} | ||
if (tree.right) { | ||
queue.push(tree.right); | ||
} | ||
} | ||
res.push(arr); | ||
} | ||
return res; | ||
} | ||
|
||
var maxSubArray = function (nums) { | ||
let max = 0; | ||
let sum = 0; | ||
for (let n of nums) { | ||
if (sum > 0) { | ||
sum += n; | ||
} else { | ||
sum = n; | ||
} | ||
max = Math.max(max, sum); | ||
} | ||
return max; | ||
}; | ||
``` |
Oops, something went wrong.