![](https://s2.loli.net/2022/10/09/31kvp8HRJuoBCfc.jpg)
模拟题八
1.行内元素、块级元素有哪些,区别是什么
考察点:行内元素、块级元素
行内元素:a、b、br、code、i、label、q、small、span、strong、sub、sup、textarea、tt
块级元素:div、h1-h6、dl、dd、dt、ul、o、li、form、p、pre、table、tbody、td、tfood、th、thead、tr
2. 实现发布订阅
考察点:手写订阅发布
发布订阅模式无需知道对方,通过监听数据
javascript
class EventEmitter {
constructor() {
this.event = {};
}
on(type, callback) {
if (!this.event[type]) {
this.event[type] = [callback];
} else {
this.event[type].push(callback);
}
}
off(type, callback) {
if (!this.event[type]) {
return;
}
this.event[type] = this.event[type].filter((item) => {
return item != callback;
});
}
emit(type, ...args) {
if (!this.event[type]) {
return;
}
this.event[type].forEach((callback) => {
callback.apply(this, args);
});
}
once(type, callback) {
function f() {
callback();
this.off(type, f);
}
this.on(type, f);
}
}
使用示例:
javascript
const eventEmitter = new EventEmitter();
// 订阅事件
eventEmitter.on("公众号", (name) => {
console.log(`我订阅了${name}`)
})
// 发布事件
eventEmitter.emit('公众号', '随朱波流')
衍生问题:你还知道哪些设计模式、Redux
你还知道哪些设计模式
单例模式:保证一个类只有一个实例,并提供一个全局访问点。
redux,vuex 等唯一状态数据库,JQuery 只有一个$
- 实现唯一性
- 减少内存空间
工厂模式:定义一个创建对象的类
观察者模式:就是发布订阅的代表
装饰器模式:不改变原对象的基础上,通过对其进行包装扩展,使原有对象满足用户的复杂需求
适配器模式:通过将一个类的接口换成客户端所期待的接口,可以帮我们剞劂不兼容问题
Redux
Redux 是状态管理库,它由数据驱动,发起 action,会引发 reducer 的数据更新,从而更新到最新的 store
Redux 用到了很多设计模式,例如发布订阅模式,单例模式,装饰器模式
发布订阅模式
- 先订阅,
单例模式
- 全局只有一个 store,Redux 像一个状态机,你传入 state 和 action,返回新的 state
装饰器模式
- 它的enhancer、中间件都是装饰器模式,就在 Redux 基础上再包了一层
3.垃圾回收机制
考察点:v8 垃圾回收
详见原理:垃圾回收机制
V8 进程的内存由以下部分组成:
- 新生代内存区
- 老生代内存区
- 其他(大对象区、代码区、map 区)
新生代存在生存时间短的对象,老生代存放生存时间久的对象
新生代通常只支持1-8M的容量,而老生代区会支持更大的容量,而针对这两块区域,V8 分别使用两个不同的垃圾回收器
- 主垃圾回收器,负责老生代的垃圾回收
- 副垃圾回收器,负责新生代的垃圾回收
新生代内存回收:Scavenge 算法
- 将空间分成两半,一半是 from 空间,一半是 to 空间,新加入的对象会放在 from 空间,当空间快满时,执行垃圾清理;再角色调换,当调换完后的 from 空间快满时,再执行垃圾清理,如此反复
老生代内存回收:标记-清除-整理(Mark - Sweep),此为两个算法,「标记-清理」算法和 「标记-整理」算法
- 标记-清理:标记用不到的变量,清理掉
- 标记-整理:整理完内存后,会产生不连续的内容空间,为节省空间,整理算法会将内容排序到一处空间,空间就变大了
引用计数:引擎中有张“引用表”,保存了内存里面的资源的引用次数,如果一个值的引用次数是0,就表示这个只不再用到,因此可以将这块内存释放
- 缺点:循环引用,从而导致内容泄露
4.ES6特性:扩展运算符和剩余运算符
考察点:扩展运算符(...)
以不同的语法形式出现在 函数调用中,数组字面量中以及对象字面量中
5.react中一个父组件,两个子组件,一个传props,一个不传props,父组件重新渲染,两个子组件会不会都重新渲染
6.React17、18、19 更新以及什么是服务端组件
React17、18、19 更新以及服务端组件
React17
全新的 JSX 转换
React 17之前必须引入 import React from 'react';
,打包工具才能正常编译
React 17 之后,React 引入了 react/jsx-runtime
,编译工具会自动识别 React 组件并将代码编译为 React.createElement
事件委托的变更
React v17 中,React 不会再将事件处理添加到 document
上,而是将事件处理添加到渲染 React 树的根 DOM 容器中
jsx
const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);
![image-20240513105744250](D:\Documents\PicGo Files\image-20240513105744250.png)
在 React 16 和更早的版本中,React 将对大多数事件执行document.addEventListener()。
React 17 将在后调用rootNode.addEventListener()
React 18 更新
并发模式
v18的新特性是使用现代浏览器的特性构建的,彻底放弃对 IE 的支持。
v17 和 v18 的区别就是:从同步不可中断更新变成了异步可中断更新,v17可以通过一些试验性的API开启并发模式,而v18则全面开启并发模式
V16就提出了Fiber架构,React 并发(concurrent)模式还在构建中(处于实验阶段),到V18才正式投入
更新 render API
v18 使用 ReactDOM.createRoot() 创建一个新的根元素进行渲染,使用该 API,会自动启用并发模式。
tsx
// v17
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
// v18
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
自动批处理
批处理是指 React 将多个状态更新,聚合到一次 render 中执行,以提升性能
在v17的批处理只会在事件处理函数中实现,而在Promise链、异步代码、原生事件处理函数中失效。而v18则所有的更新都会自动进行批处理。
tsx
// v17
const handleBatching = () => {
// re-render 一次,这就是批处理的作用
setCount((c) => c + 1)
setFlag((f) => !f)
}
// re-render两次
setTimeout(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
}, 0)
// v18
const handleBatching = () => {
// re-render 一次
setCount((c) => c + 1)
setFlag((f) => !f)
}
// 自动批处理:re-render 一次
setTimeout(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
}, 0)
如果在某些场景不想使用批处理,可以使用 flushSync
退出批处理,强制同步执行更新。
tsx
// flushSync 会以函数为作用域,函数内部的多个 setState 仍然是批量更新
const handleAutoBatching = () => {
// 退出批处理
flushSync(() => {
setCount((c) => c + 1)
})
flushSync(() => {
setFlag((f) => !f)
})
}
Suspense 支持 SSR
SSR 一次页面渲染的流程:
- 服务器获取页面所需数据
- 将组件渲染成 HTML 形式作为响应返回
- 客户端加载资源
- (hydrate)执行 JS,并生成页面最终内容
新功能(新API):过渡(startTransition)
新增 useId、useTransition 和 useDeferredValue
- useTransition
- useDeferredValue
放弃 IE
服务端组件
7. Interface 和 Type 的区别
Interface 和 Type 的核心区别是 Type 不可在定义后重新添加内容,而 Interface 则总是可以扩展新内容(可扩展)。相比 Interface,Type 并没有实际创建一个新的类型,而是创建一个引用某个类型的名字
8.项目构建工具有考虑换vite吗?vite有什么优势?
9. 浏览器:跨域
考察点:跨域
具体可看跨域的十种解决方案
同源策略
一个安全策略
同源:同一协议(protocol)、域名(domain)、端口(port)
跨域解决方案
JSONP
- 最古老的彼岸者,利用
script
标签没有跨域限制这个特点 - 仅支持 GET 方法
- 步骤
- 定义jsonp回调函数方法jsonpCallback
- script 请求接口(后端)时带上cb=jsonpCallback 参数,如
/api?callback=jsonpCallback
- 后端 response 返回 jsonpCallback ({a: 'b'}),前端执行
jsonpCallback
,就拿到注入的数据
CORS(跨域资源共享)
在服务端/后端的相应头中添加Access-Control-Allow-*
头,告知浏览器端通过此请求只需要服务器端/后端支持即可,不涉及前端改动
window.postMessage
postMessage是HTML5引入的一种跨文档通信的机制。a 页面通过iframe.contentWindow.postMessage({msg: 'hello'}, 'b.html(目标页面)')
发送消息,b 页面通过 window.addEventListener('message', function(e){})
接受信息,通讯就完成了
WebSocket
双向数据通信,不是 http 协议,所以不受跨域限制,服务端直接传数据给前端
Nginx 代理
Nginx.conf 设置反向代理
Node 代理
本地开发时,即用 webpack 或者 vite 构建工具时,配置方向代理