2023.11.20
1、对项目进行了哪些优化?
-
webpack 打包构建优化
-
优化 webpack 构建速度,主要从几个方面入手,分别是定向查找、减少执行构建的模块、并行构建以提升总体的速度、并行压缩提高构建效率、合理使用缓存等,具体的配置如下:
- 定向查找:定向查找主要是配置
resolve属性,通过指定路径和配置路径别名来优化查找路径等,缩短查找时间,配置如下:
module.exports = { // ...其他配置 resolve: { // 指定第三方模块的搜索路径,减少搜索步骤 // 如果没有指定,则会从当前路径逐层向上查找 modules: [path.resolve(__dirname, 'node_modules')], // 路径别名,可以减少书写繁琐的路径前缀,优化查找路径 alias: { "@": path.resolve(__dirname, "src"), "_c": path.resolve(__dirname, "src/components") }, // 在导入语句没有带文件后缀时,可以按照配置的列表,自动补上后缀 // 可以把高频率的后缀放在最前,减少没有必要的匹配 extensions: ['ts', 'tsx', 'js', 'jsx', 'scss'] } }- 减少执行构建的模块:
noParse忽略没有模块化的文件(例如 JQuery、lodash)、合理配置loader的include、exclude属性等
module.exports = { // ...其他配置 module: { // 忽略没有模块化的文件 noParse: /jquery|lodash/, rules: [ { test: /.jsx?$/, include: [path.resolve(__dirname, 'src')], exclude: /node_modules/, use: ['babel-loader'] } ] } }- 并行构建以提升总体的速度:可以使用
thread-loader开启多个线程池进行并发执行构建,只有在代码量很多的时候开启多进程构建才会有明显的提升,因为官方也有说明,每一个 worker 需要耗费 600ms 的启动;
module.exports = { // ...其他配置 module: { rules: [ { test: /.jsx?$/, use: [ // 开启多线程 { loader: 'thread-loader', options: { worker: 2 } }, { loader: 'babel-loader' } ] } ] } }- 并行压缩提高构建效率:并行压缩主要是用了
terser-webpack-plugin,在webpack5中自带最新的terser-webpack-plugin,在mode:'production'时会自动开启,如果想要自定义配置的话,仍然需要安装依赖npm i terser-webpack-plugin --save-dev;
const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ // test 匹配文件的正则表达式 // include 包括哪些文件 // exclude 排除哪些文件 // parallel 使用多进程并行提高速度,默认是 os.cpus().length - 1 // terserOptions 配置选项 // extractComments 是否保留注释 }), ], }, };- 合理使用缓存: 利用 webpack5 的新特性
cache,首次构建会增加耗时,但是之后再次构建会大幅度提高构建速度;babel-loader增加缓存等
module.exports = { // ... 其他配置 module: { rules: [ { test: /.jsx?/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true, } } ] } ], }, cache: { type: 'filesystem' } } - 定向查找:定向查找主要是配置
-
优化 webpack 打包体积,主要从几个方面入手,分别是代码压缩、按需加载、提前加载、Code Spliting、Tree Shaking、Gzip、作用提升等,具体配置如下:
- 代码压缩:代码压缩主要压缩 html、css、js、image 文件,具体如下配置:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, // 是否需要压缩 minimizer: [ new CssMinimizerPlugin({}), // 配置 css 压缩插件 new TerserPlugin(), // 压缩 js 文件,生产环境默认开启,自定义选项时需要下载依赖重新配置 ], }, module: { rules: [ // ... 其他配置 { test: /.(png|jpg|gif|jpeg|webp|svg)$/, use: [ "file-loader", { // 压缩图片的loader loader: "image-webpack-loader", options: { mozjpeg: { progressive: true, }, optipng: { enabled: false, }, pngquant: { quality: [0.65, 0.9], speed: 4, }, gifsicle: { interlaced: false, }, }, }, ], }, ] }, plugins: [ new HtmlWebpackPlugin({ template: "./index.html", // 动态生成 html 文件 minify: { removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 删除空⽩符与换⾏符 minifyCSS: true // 压缩内联css }, }) ] }- 按需加载:按需加载主要依赖于
import语法,主要分为配置第三方UI库按需加载、路由懒加载、import('xx').then()语法等来实现按需加载; - Code Splitting:代码分割主要是将公共的代码进行抽离,放到一个公共文件,这样不仅可以减少文件体积,也可以做到只要文件内容不变,就可以从浏览器缓存中读取,这里主要是用
SplitChunksPlugin进行分割,具体配置如下:
module.exports = { //...其他配置 optimization: { splitChunks: { chunks: 'all', // 值有 all/async/initial/function,表示选择哪些 chunk 进行优化 minSize: 20000, // 生成 chunk 的最小体积。 minRemainingSize: 0, minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。 maxAsyncRequests: 30, // 按需加载时的最大并行请求数。 maxInitialRequests: 30, // 入口点的最大并行请求数。 enforceSizeThreshold: 50000, // 强制执行拆分的阈值 cacheGroups: { defaultVendors: { test: /[/]node_modules[/]/, // 匹配的路径文件 priority: -10, // 缓存的优先级 reuseExistingChunk: true, // 复用当前 chunk 包含已从主 bundle 中拆分出的模块 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, }, }, };-
Tree Shaking
- 清除没有用到的 js、css 代码,主要依赖于
esmodule的特性,可以在package.json中配置sideEffects:false选项,表示对所有文件进行tree shaking,但是会有一定的影响,比如导入polyfill包,这种会被处理掉,导致样式丢失和失去polyfill的作用,这明显不是我们要的结果,那么基于这种情况,我们可以进一步对sideEffects做配置,举个例子,以下代码表示遇到数组中的模块,就跳过,不进行tree shaking操作;
{ "sideEffects": ["@babel/poly-fill", "*.less"] }- css 代码进行
tree shaking需要借助purgecss-webpack-plugin插件,配置如下
const PurgecssPlugin = require("purgecss-webpack-plugin"); const glob = require("glob"); module.exports = { // ...其他配置 plugins: [ new PurgecssPlugin({ // 这里我的样式在根目录下的index.html里面使用,所以配置这个路径 paths: glob.sync(`${path.join(__dirname)}/**/*.css`, { nodir: true }), }) ] } - 清除没有用到的 js、css 代码,主要依赖于
-
Gzip:Gzip 是对资源进行进一步的压缩,当浏览器支持 gzip 时,服务器返回 gzip 包,浏览器接收到 gzip 包后就进行解压操作,我们可以借助
compression-webpack-plugin插件来打包;const CompressionWebpackPlugin = require("compression-webpack-plugin"); module.exports = { // ...其他配置 plugins: [ new CompressionWebpackPlugin() ] }
-
-
代码优化
代码优化包括路由懒加载、封装虚拟列表、拆分组件减少渲染、React.memo 缓存组件。
2、react 路由懒加载是如何实现的?
react 实现路由懒加载主要是使用 lazy 方法和 import 配合,并结合 Suspense 来完成路由懒加载的
// 路由文件
const Home = lazy(() => import("@/pages/Home.js"))
const routes = [
{
path: '/home',
element: <Supsense fallback={<>loading</>}>
<Home />
</Supsense>
}
]
3、为什么 import 能实现异步加载?
import 是 ECMAScript模块系统的一部分,ECMAScript 模块系统是基于异步加载而设计的,当用 import() 导入模块时,会异步去加载这个模块,不会阻塞后续的代码提高了应用程序的性能和响应性。
4、import 一定要放在文件顶部吗?
这里分两种情况,一种是 esmodule的 import 和 import() 函数,两者的结果是不一样的:
esmodule 的 import:正如官方所言,esmodule 是静态的,不能有条件地导入或导出,实际开发中,都是将import语句放在文件的顶部,这样有助于更清晰地看到模块的依赖关系,提高代码的可读性;
import():import()是异步加载,它允许我们可以在任何位置进行导入,所以它不一定要放在文件顶部;
5、esmodule 和 commonjs 的区别
不同点:
commonjs是使用require和module.exports/exports来导入导出对象;esmodule则是使用import和export导入导出;commonjs主要用于Node环境,如果要在浏览器中使用需要借助webpack等工具进行转换,esmodule原生支持浏览器和Node环境;commonjs主要是同步加载,异步加载需要额外处理,esmodule支持同步和异步加载,可以通过import()动态导入模块,属于异步加载,import ... from 'xxx'是同步加载;commonjs是对模块的一个拷贝,esmodule是对模块的一个引用;commonjs的值可以重新赋值,而esmodule不能重新赋值,类似于const定义的变量;
//------ lib.js ------
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
//------ main1.js ------
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
counter++;
console.log(counter); // 4
// ESModule
//------ lib.js ------
export let counter = 3;
export function incCounter() {
counter++;
}
//------ main1.js ------
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
counter++; // Error
相同点:
commonjs 和 esmodule 都可以对引入的对象的内部属性进行修改;
6、commonjs 是异步还是同步?
commonjs 是同步加载模块,使用require导入模块时,会阻塞后续代码的执行, 直到模块加载完成并返回导出的内容;
7、import 可以动态导入吗?
答案:可以。当用 import xxx from 'xxx' 导入模块时,属于同步加载,使用 import() 函数导入时是异步加载,所以我们可以通过 import() 函数来动态加载模块;
8、Vue3 相对于 Vue2 有什么优化或改进,有哪些新特性?
优化:
Vue3的源码管理使用monorepo进行维护,根据功能划分不同的模块,使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性;Vue3是基于typescript编写的,提供了更好的类型检查,能支持复杂的类型推导;- 可以通过
webpack进行tree-shaking,减少打包体积; - 重写虚拟 dom,编译模板优化,使用静态提升,编译速度更快;
- 使用
composition API更加灵活的逻辑组合与复用; - 优化底层响应式的实现,使用
Proxy替代Object.defineProperty;
改进:
- 生命周期函数重命名;
v-model用法更改,支持绑定多个数据,需要指定变量名,否则默认为modelValue;v-if和v-for的优先级更改;- 移除过滤器;
新特性:
fragments:单文件组件可以由多个根节点组成;Teleport:相当于传送门,可以把Teleport的内容移动到指定的容器里;Composition API:更灵活的逻辑组合与复用;createRenderer:自定义渲染器,能够将Vue的开发模型扩展到其他平台;
9、响应式 reactive 和 ref 的区别,平时更偏向于用哪种
区别:
ref可以声明基本数据类型和对象,reactive只能声明对象;ref使用需要通过.value去获取值,在模板可以省略,而reactive可以直接使用;ref可以用来获取 DOM 实例;
reactive 的局限性:
- 有限的值类型:
reactive只能声明对象类型,不能声明原始类型; - 不能替换整个对象:
Vue的响应式是根据属性来访问实现的,所以必须保证对响应式对象的相同引用,如果替换掉原有对象,会失去对第一个引用的响应式连接; - 对解构不友好: 当将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,将丢失响应性连接;
10、reactive 代理对象是浅拷贝还是深拷贝
reactive 返回一个对象的响应式代理,它是深层次的进行响应式处理,使用 Proxy 进行代理,当第一层的属性是个对象时,会再用 reactive 生成一个响应式对象,从而达到深层次的转换,如果要避免深层次的转换,可以使用 shallowReactive 进行声明;
11、有用过 toRef 和 toRefs 吗,他们有什么区别
toRef: 基于响应式对象上的一个属性,创建一个对应的ref,这样创建的ref与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然;toRefs: 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的ref;
toRefs 内部创建是基于 toRef 来创建 ref,在用法上,toRefs 传入的参数是源对象,toRef 是传入源对象和属性名,两者都可以使解构/展开返回的对象不会失去响应性。
12、js 中有哪些继承方式
js 中的继承包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承;
- 原型链继承:所有实例共享同一个原型对象,内存是共享的
function Parent() {
this.name = "parent";
}
function Child() {
this.sex = "男";
}
Child.prototype = new Parent();
- 构造函数继承:不能继承父类原型属性和方法;
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function () {
return this.name;
}
function Child() {
Parent.call(this)
this.sex = "男";
}
let child = new Child()
child.getName() // 报错
- 组合继承:造成性能开销;
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function () {
return this.name;
}
function Child() {
Parent.call(this)
this.sex = "男";
}
Child.prototype = new Parent()
Child.prototype.constructor = Child;
- 原型式继承:多个实例的引用类型属性指向相同的内存地址,存在篡改的现象;
let parent = {
name: "parent",
children: ["Atom", "Btom"]
}
let parent1 = Object.create(parent);
parent1.children.push("Ctom");
let parent2 = Object.create(parent);
parent2.children.push("Dtom");
console.log(parent1.children) // ["Atom", "Btom", "Ctom", "Dtom"]
console.log(parent2.children) // ["Atom", "Btom", "Ctom", "Dtom"]
- 寄生式继承:类似于工厂函数,封装继承的过程,缺点跟原型式继承一样;
let parent = {
name: "parent",
children: ["Atom", "Btom"]
}
function cloneNode(origin) {
let obj = Object.create(origin);
obj.selfName = 'self';
return obj;
}
let parent1 = cloneNode(parent);
- 寄生组合式继承:
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function () {
return this.name;
}
function Child() {
Parent.call(this)
this.sex = "男";
}
function extendFunciton(p, c) {
c.prototype = Object.create(p.prototype);
c.prototype.constructor = c;
}
extendFunciton(Parent, Child)
13、js 中类的定义、以及类的转化
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
// 转化为函数
'use strict'
function Person(name) {
if (!this instanceof Person) {
return new TypeError("实例需要通过 new 实现,不能直接调用")
}
this.name = name;
}
Object.defineProperty(Person.prototype, 'getName', {
value: function () {
if (!this instanceof Person) {
return new TypeError("实例需要通过 new 实现,不能直接调用")
}
return this.name;
},
enumerable: false, // 防止 for-in 遍历原型
})
14、Suspense 的用法以及应用场景
Suspense 是 Vue3 中的一个内置组件,用来在组件树中协调对异步依赖的处理,它可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态;
<template>
<Suspense>
<template #default>
<async-component></async-component>
</template>
<!-- 占位符 -->
<template #fallback>
loading...
</template>
</Suspense>
</template>
15、new 关键字的实现
function myNew(target, ...args) {
let obj = Object.create(null)
obj.__proto__ = target.prototype
let result = target.call(obj, ...args)
return result && (typeof result == 'object' || typeof result == 'function') ? result : obj;
}
16、css 实现垂直居中的方式
/* flex 布局 */
.parent {
display: flex;
align-items: center;
justify-content: center;
}
/* 绝对定位 */
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 绝对定位 + margin */
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
/* margin */
.parent {
width: 200px;
height: 200px;
}
.child {
height: 160px;
margin: 20px auto;
}
17、什么是文档流,对 BFC 的理解,如何触发?
文档流是指网页文档中可显示对象在排列时所占用的位置,元素按照文档中的出现顺序从上到下、从左到右依次排列;
BFC 是指块级格式上下文,它是 web 中的一种渲染区域,有一定的规则指定子元素如何布局以及和其他元素之间的关系,BFC 的特征如下:
- 内部块级元素在垂直方向上,由上到下逐个排列;
- 上下相邻的两个容器
margin会重叠; - BFC 区域不会和浮动元素重叠;
- BFC 是一个独立的容器,不会受外部影响;
- 计算高度时,需要计算浮动元素的高度;
- 每个元素的左外边距和容器的左边框相接触;
BFC 的触发条件:
- 根元素
body - 设置浮动
- 绝对定位
- overflow 为
hidden、auto、scroll; - display 值为
flex、inline-block、table-cell等;
18、commonjs 中 module.exports 和 exports 的区别,可以同时存在吗?
exports 实际上是 module.exports的引用,两者可以同时存在,但是导出的结果会以 module.exports 导出的结果为主;
let module = {
exports: {}
}
let exports = module.exports;
19、Proxy 和 Object.defineProperty 的区别?
Proxy 和 Object.defineProperty 分别用于对象的代理和属性拦截,他们的区别如下:
proxy是对目标对象进行代理,包括对象的读写、设置、删除等;defineProperty是对目标对象的属性进行拦截;proxy可以监听到目标对象属性的新增与删除,而defineProperty不行;defineProperty兼容性比proxy好;
20、项目中是如何通过网络、渲染进行优化的?
网络优化
- 利用 CDN 可以将静态资源分发到离用户更近的服务器节点,减少网络延迟,加速资源加载;
- 压缩文件,减少文件体积,减少网络传输时间;
- 懒加载,在页面需要的时候再加载数据,减少初次加载的请求;
渲染优化
- 利用虚拟列表实现懒加载;
- 采用图片懒加载的形式加载图片;
21、ts 中 interface 和 type 的区别?
- 接口名相同的声明最后会进行合并,而 type 不允许重复声明;
interface继承使用 extends 关键字,type继承使用 & 符号;type可以声明复杂对象的结构类型、基础数据类型、元组、联合类型等,interface只能声明对象;
2023.11.28
1、Vue 和 React 的比较?
相同点:
- 都有虚拟 DOM,Vue2 才开始引入的虚拟 DOM;
- 都支持服务端渲染;
- 组件化思想,推崇将应用拆分成一个个功能明确的模块,提高复用性;
- 数据驱动视图;
- 都可以通过
props进行父子组件通信;
不同点:
Vue采用template结构化分离编写模板,React采用jsx结构表现融合,即all in js;Vue采用双向数据绑定,数据更新自动更新视图,React采用单向数据流,需要手动更新;- 高阶函数
Vue使用mixins实现,React使用HOC实现; diff算法不同,Vue使用双端队列,边对比边更新,React主要用diff队列保存哪些 DOM 需要更新,获得patch树,再统一操作批量更新 DOM;- 跨平台实现方案不同,
Vue对应Weex,React对应React Native;
2、Vue 的 diff 算法和 React 的 diff 算法有什么区别?
Vue2是同层比较新旧vnode,新的存在旧的不存在则新建,新的不存在旧的存在则删除,子节点采用双向指针头对尾进行对比;Vue3采用动静结合的方式,在编译阶段对静态节点进行标记,diff过程中直接跳过静态节点, 在子节点对比过程中记录节点位置以及最长递增子序列优化对比过程;React是递归同层比较,标识需要更新的DOM,保存到队列中,得到patch树,再统一操作批量更新 DOM;在diff过程中,主要是移动、删除、增加三个操作,如果结构发生改变,则直接卸载重新创建,如果没有则在旧集合和新集合进行节点比较,判断是否需要移动,如果遍历过程中新集合没有,旧集合有,则执行删除操作;
3、Vue2 和 Vue3 的区别;
- 响应式设计的实现,
Vue3使用Proxy代替Vue2的defineProperty; Vue3使用compositionAPI代替Vue2的optionsAPI,使逻辑组合更加灵活;- 生命周期重命名,例如
mounted --> onMounted; - 源码管理采用
monorepo的形式进行管理,根据功能进行划分不同的模块,使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,同时也使用rollup进行打包,可以通过 tree-shaking 来减少打包的体积; diff算法的优化,Vue3采用动态结合的形式,在编译阶段对静态节点进行标记,在diff过程中跳过静态节点,并通过最长递增子序列的算法优化对比过程;Vue3支持单个文件有多个根节点,另外新增Teleport标签和Suspense标签;
4、Vue 的 composition API 和 React 的 hook 有什么联系?
从 React Hook 的实现角度看,React Hook 需要根据 useState 调用的顺序来确定下一次渲染时的 state 来源,所以出现了以下限制:
- 不能在条件、循环、嵌套函数中调用;
- 必须确保在函数最顶层调用;
useEffect、useMemo等函数必须手动确定依赖关系;
而 CompositionAPI 是基于 Vue 的响应式系统实现,与 React Hook 相比:
CompositionAPI是在setup函数内声明调用,只需要一次调用,而React Hook每次渲染都需要调用,在 GC 方面相对于Vue更有压力,性能相对Vue来说也比较慢;CompositionAPI可以在条件、循环、嵌套函数中调用;- 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由
Vue内部自己完成,而React Hook需要手动传入依赖,而且必须保证依赖的顺序,让useEffect、useMemo等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降;
5、React Hook 有什么限制条件,为什么?
- 不能在条件、循环或者嵌套函数中调用
hook; - 需要确保在函数的最顶层调用他们;
- 只在函数组件调用,类组件不调用;
原因: hooks 的设计是基于链表的结构进行存储,如果在条件、循环或嵌套函数中调用 hook,会导致链表的位置发生错乱,导致更新的值发生错误;
6、React 中 useEffect 和 useLayoutEffect 的区别,以及对应的应用场景;
useEffect 和 useLayoutEffect的区别在于他们的执行时机不同,useEffect 是在组件渲染完成之后执行,而 useLayoutEffect 是在 DOM 更新完成,浏览器重新绘制之前执行,需要注意的是在 useLayoutEffect中,不能执行耗时的操作,因为它会阻塞浏览器的渲染;
useEffect:
- 用于处理副作用,例如网络请求、订阅事件等;
- 适用于大部分的情况,因为它不会阻塞浏览器渲染,有更好的性能和响应;
useLayoutEffect:
- 用于需要在DOM更新之后立即执行的副作用操作,例如获取DOM节点的尺寸或执行需要依赖已更新的布局的UI操作;
- 适用于需要同步执行的情况,因为它会在所有DOM更新后、浏览器绘制前立即执行,可以避免屏幕闪烁或布局抖动的问题;
7、React 中的 useMemo 和 useCallback 以及 memo;
useMemo: 缓存需要计算的值,当传入的依赖项发生变化时重新计算,否则返回缓存的值;
useCallback: 缓存不会变化的函数,当依赖项变化时重新生成;
memo: 对组件进行缓存,在 props 没有变化的时候,跳过重新渲染,可以自定义传入对比的方法;
三者都是用于优化的手段,当性能达到瓶颈时可以尝试使用这些 hook 进行优化;
8、React 中 useRef 和 forwardRef 的区别;
9、Redux 的三大原则;
单一数据源:redux的所有状态应该存储在一个单一的数据源中,这个数据源是唯一的,需要通过特定的形式进行改变,即action触发变化,这样可以方便我们追踪和调试应用的状态,使得状态的管理和更新变得可预测;状态是只读的:不能直接修改store的值,需要通过dispatch一个action操作来修改状态,这样做可以让我们更好的追踪状态变化;使用纯函数处理状态:在修改store的状态时,需要用纯函数来处理,也就是reducer,reducer接收一个旧的状态和一个action操作,返回一个新的状态,纯函数的特性是相同的输入始终返回相同的输出,这样的特性可以让确保状态变化的可预测性和可控性;
10、Redux 中间件,redux-thunk 和 redux-saga 有什么区别, redux-toolkit 有用过吗?
11、了解自动批处理和并发渲染吗,分别有什么应用场景?
自动批处理是React18新增的一个特性,在 18 之前,批处理只能在 React 事件处理程序中进行,原生事件、setTimeout、promise等不会进行批处理,在 18 版本之后,这些都会被自动批处理,举个例子:
// 18 之前,定时器执行回调后,会渲染两次,每次更新一个状态
// 18 之后,定时器执行回调后,只会渲染一次
setTimeout(() => {
setValue(v => v + 1)
setCount(c => c + 1)
}, 1000)
并发渲染是指 React 将渲染过程分割成多个可中断的任务,以实现高效的页面渲染,在引入并发之前,更新内容的渲染是单一且不可中断的同步事务处理,一旦开始渲染就无法中断,无法响应用户的交互,出现卡顿的现象,而在并发渲染中,React 可能开始渲染一个更新,然后中途挂起,稍后又继续,它甚至可能完全放弃一个正在进行的渲染,React 保证即使渲染被中断,UI 也会保持一致。
12、聊一聊 TS 中的泛型;
泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在typescript中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。
13、有了解过可选链吗,说一说可选链;null ?? 1、null || 1、0 ?? 1、0 || 1 的返回值
- 在 js 中可选链的标识符是
?.,用于简化访问可能为null或undefined的对象属性或方法的操作,如果该属性不存在或为null/undefined时,表达式返回undefined; - 在 ts 中可选链的标识符是
?:,用于标识属性在声明的类型里面是否可选,通过类型推断给出更精确的类型,避免了潜在的类型错误;
null ?? 1 // 输出 1
null || 1 // 输出 1
0 ?? 1 // 输出 0
0 || 1 // 输出 1
14、TS 有什么优点?
15、TS 中 void、never、null 的区别;
16、TS void 的应用场景,函数没有返回值,可不可以省略 void?
17、在项目中是如何进行代码规范,有哪些代码优化?
18、如果给到一个项目,会如何设计?
19、假设现在后端所有的接口进行了改变,包括接口名、返回数据字段,怎么在不改变现有逻辑的情况下,如何去做兼容?
20、在 react 中你是如何引入 css 的?
21、styled-component 有用过吗?与 css 模块相比,哪种更容易维护?
22、说一下弹性布局的主轴和侧轴;
23、简单说一下 XSS、CSRF;
-
XSS:跨站脚本攻击,是一种代码注入攻击,通过在网站上注入恶意脚本,达到攻击的目的;主要有三种类型:反射型、存储型、DOM型;反射型:将恶意代码放到 URL 中,服务端接收数据处理之后,将带有恶意代码的数据返回给客户端,客户端把带有恶意代码的数据当作脚本执行,完成XSS攻击;存储型:攻击者将恶意代码存储在目标服务器上,当浏览器请求数据时,恶意代码从服务器返回并执行;DOM型:通过修改网站的DOM节点来完成XSS攻击;
防范的措施有以下几种:
- 建立白名单
CSP,告知浏览器哪些资源可以执行、从而防止恶意代码的注入; - 对需要插入到 HTML 中的代码做好充分的转义;
- 对一些敏感信息进行保护,比如
cookie使用http-only,使得脚本无法获取,也可以使用验证码,避免脚本伪装成用户执行一些操作;
-
CSRF:跨站请求伪造攻击,攻击者通过诱导用户点击第三方网站,然后该网站向被攻击网站发送跨站请求,本质是利用cookie会在同源请求中携带发给服务器的特点,冒充用户;常见的攻击手段包括如下:GET类型的攻击,比如在网站中的img构建一个请求,当用户打开网站时会自动发起请求;POST类型的攻击,比如构建一个表单,在用户打开网站时自动提交;- 链接类型的攻击,比如在
a标签构建一个请求,然后诱导用户去点击;
防范的措施有以下几种:
- 同源检测:服务器根据 http 请求头中
origin或者referer信息来判断请求是否为允许访问的站点,从而对请求进行过滤; - token 验证:服务器向客户端发送一个
token,客户端在每次请求时带上token,然后服务器对其进行验证; - 在设置 cookie 属性的时候设置
Samesite,限制cookie不能作为被第三方使用;
24、在项目中有做过网络安全相关的措施吗?
25、在项目中遇到问题是怎么解决的?
26、浏览器的渲染原理;
27、MVC 对应 react 中的哪部分?
28、对于 List 的改变,Vue 和 React 分别是怎么处理的?
用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略,如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。
29、React18 有什么新特性?
- 自动批处理:在 18 之前,原生事件、setTimeout、Promise 等事件不会自动进行批处理操作,在 18 之后,这些事件全部变成自动批处理;
- 过渡更新: transition,对应 hook 是
useTransition,使用startTransition开启过渡
import { startTransition } from 'react';
// 紧急更新: 显示输入的内容
setInputValue(input);
// 将任何内部的状态更新都标记为过渡更新
startTransition(() => {
// 过渡更新: 展示结果
setSearchQuery(input);
});
-
新的
Suspense特性,在 18 之前,Suspense和React.lazy配合完成代码拆分,但是不支持服务端渲染,在 18 之后,支持在服务端使用,并且基于并发渲染的特性对其功能进行扩展; -
新的 APIs 替换,
createRoot->render、hydrateRoot=>hydrate; -
新的服务端渲染API
renderToPipeableStream:用于 Node 环境中的流式渲染;renderToReadableStream:对新式的非主流运行时环境;
-
新的严格模式: React 18 为严格模式下的开发环境引入了一个新的检查机制。每当组件第一次挂载时,这个检查机制将自动卸载又重新挂载每个组件,并在第二次挂载时复用先前的状态;
-
新的
hooks:useId:生成唯一的 id,保证客户端和服务端 id 的一致性;useTransition:标记状态为过渡更新;useDeferredValue: 允许推迟渲染树的非紧急更新,延迟渲染是可中断的,不会阻塞用户输入;useSyncExternalStore:允许使用第三方状态管理来支持并发模式,并且能通过对store进行强制更新实现数据同步;useInsertionEffect:主要解决CSS-in-JS库在渲染中注入样式的性能问题,在 DOM 变更发生后,在 layout effect 获取新布局之前运行;