组件开发平台
1. 组件拆分的依据是什么?
总的来说,组件拆分的核心目的是提高代码的可维护性、扩展性和灵活性,从而更好地适应需求变化。关注点分离、复用性、可测试性是组件拆分的主要原则。
2. js 数据类型有哪些
原始数据类型Number、String、Boolean、null、undefined、Symbol、BigInt
引用数据类型 Object
3. String('123') 和 new String('123') 的区别
String('123') 和 new String('123') 之间也存在一些差异:
创建方式不同:
- `String('123')` 是使用构造函数 `String()` 创建的一个原始字符串。
- `new String('123')` 是使用 `String` 构造函数创建的一个 `String` 对象。
类型不同:
- `String('123')` 的结果是一个原始字符串类型。
- `new String('123')` 的结果是一个 `String` 对象类型。
内存占用不同:
- `String('123')` 创建的是一个原始字符串,占用的内存相对更少。
- `new String('123')` 创建的是一个 `String` 对象,占用的内存相对更多。
属性和方法不同:
- `String('123')` 虽然也能访问一些字符串的属性和方法,但它们是临时创建的 `String` 对象。
- `new String('123')` 创建的是一个完整的 `String` 对象,拥有丰富的属性和方法。
比较结果不同:
- `String('123') === '123'` 返回 `true`,因为它们是相同的原始字符串。
- `new String('123') === '123'` 返回 `false`,因为 `new String('123')` 是一个 `String` 对象,与原始字符串不相等。
总的来说,String('123') 创建的是一个原始字符串,而 new String('123') 创建的是一个 String 对象。在大多数情况下,使用字面量形式 '123' 就足够了,只有在需要使用 String 对象方法时,才考虑使用 new String('123') 的形式。
4. localStorage 可以设置过期时间嘛?
原生的 localStorage 并不提供直接设置过期时间的功能。localStorage 存储的数据是永久性的,除非手动清除。
但是我们可以通过一些技巧来模拟设置过期时间的效果。常见的方法有以下几种:
-
手动设置过期时间戳:
- 在存储数据时,同时存储一个过期时间戳。
- 读取数据时,先检查当前时间是否超过过期时间戳,如果超过则删除该数据。
javascript
复制
// 存储数据
const expirationTime = new Date().getTime() + 3600000; // 1小时后过期
localStorage.setItem('myData', JSON.stringify({ data: 'myData', expires: expirationTime }));
// 读取数据
const storedData = JSON.parse(localStorage.getItem('myData'));
if (storedData && storedData.expires > new Date().getTime()) {
console.log(storedData.data);
} else {
localStorage.removeItem('myData');
}
-
使用第三方库:
- 一些第三方库提供了对
localStorage进行过期时间管理的功能,比如js-cookie、localForage等。
- 一些第三方库提供了对
javascript
复制
// 使用 localForage 库
const storage = localForage.createInstance({
name: 'myApp',
version: 1.0,
storeName: 'keyValueStore',
description: 'Some description'
});
// 存储数据,设置过期时间为1小时
storage.setItem('myData', { data: 'myData' }, { expires: 3600 });
// 读取数据
storage.getItem('myData').then(data => {
if (data) {
console.log(data.data);
} else {
console.log('Data expired or not found');
}
});
-
使用 IndexedDB:
- IndexedDB 是一种浏览器原生的键值对数据库,可以设置过期时间。
总之,虽然原生的 localStorage 没有提供直接设置过期时间的功能,但我们可以通过一些技巧来模拟实现这一需求。根据具体情况选择合适的方法,既可以满足需求,又不会过度复杂化代码。
5. Redux 设计模式
Redux 是一种应用状态管理的设计模式,它主要包含以下几个核心概念:
-
Store:
- Redux 应用的全局状态存储中心,存储整个应用的状态。
- Store 是一个单一的、不可变的数据源。
-
Action:
- 描述应用状态变化的动作,是改变 Store 数据的唯一方式。
- Action 是一个普通的 JavaScript 对象,必须包含一个
type属性来标识动作类型。
-
Reducer:
- 纯函数,根据当前 State 和 Action 计算出新的 State。
- Reducer 接收
(state, action)作为参数,返回新的 State 对象。
-
Dispatch:
- 触发 Action 的方法,用于更新 Store 中的状态。
- Dispatch 接收一个 Action 对象作为参数,通知 Reducer 更新 Store。
-
Subscribe:
- 注册监听 Store 变化的回调函数。
- 当 Store 中的状态发生变化时,订阅的回调函数会被调用。
Redux 的设计模式主要体现在以下几个特点:
-
单一数据源:
- 整个应用的状态都存储在一个单一的 Store 中。
-
State 是只读的:
- 唯一改变 State 的方法是触发 Action,由 Reducer 返回新的 State。
-
使用纯函数进行状态更新:
- Reducer 是纯函数,不会产生任何副作用。
-
关注点分离:
- Action 描述状态变化,Reducer 执行状态更新,Store 管理状态。
这种设计模式有以下优点:
- 可预测性:应用状态的变化遵循严格的单向数据流。
- 可测试性:由于纯函数的特性,Reducer 很容易测试。
- 代码结构清晰:各组件职责明确,易于维护和扩展。
总之,Redux 提供了一种可预测、可测试、可维护的状态管理模式,被广泛应用于 React 等前端框架中。
6. nextTick 原理
7. vue diff 算法比较
开始和开始比较、结束和结束比较,开始和结束、结束和开始。如果tag和key一致,则认为是同一个节点,如果顺序有变化,就直接移动就可以了。
8.loader 和 plugin 相关问题
loader 中 css-loader 可以去除嘛
简单回答是,即使使用了 sass-loader,css-loader 也是必需的,不能完全去除。
原因如下:
-
Sass-loader 的作用:
Sass-loader 的主要作用是将 Sass/SCSS 语法转换为普通的 CSS 语法。它负责将你编写的 Sass/SCSS 代码编译成 CSS。 -
css-loader 的作用:
css-loader 的主要作用是解析 CSS 文件,处理 CSS 中的@import和url()等引用,并将其合并成一个 CSS 模块。即使使用了 sass-loader 将 Sass 编译成 CSS,css-loader 仍然需要进一步处理这些 CSS 文件,例如处理 CSS 中的
@import和url()等引用。 -
两者的协作:
通常情况下,我们会在 webpack 的 loader 链中同时使用 sass-loader 和 css-loader。sass-loader 负责将 Sass 编译成 CSS,css-loader 负责进一步处理生成的 CSS 文件。这样的组合可以确保 CSS 文件被正确地处理和打包。
因此,即使使用了 sass-loader,css-loader 仍然是必需的,不能完全去除。两者协同工作,可以确保 Sass/SCSS 代码被正确地编译和打包成最终的 CSS 文件。
webpack plugin中的实现( compiler 和 compilation)
Webpack 中的 Plugin 是通过一种插件架构来实现的。它提供了一个强大的 hooks 系统,允许开发者在 Webpack 构建过程的各个阶段注入自定义的逻辑。
下面是 Webpack 插件机制的基本原理:
-
插件定义:
Webpack 插件通常是一个具有apply方法的 JavaScript 对象。这个apply方法会在 Webpack compiler 实例上被调用,compiler 对象提供了对 Webpack 构建过程的访问。javascript
Copy
class MyPlugin { apply(compiler) { // 在这里注册自定义的 hooks 和逻辑 } } -
Hooks 注册:
插件通过在apply方法中注册自定义的 hooks 来扩展 Webpack 的功能。Webpack 内置了大量的 hooks,插件可以监听这些 hooks 并在特定构建阶段执行自定义逻辑。javascript
Copy
class MyPlugin { apply(compiler) { compiler.hooks.emit.tap('MyPlugin', (compilation) => { // 在 emit 阶段执行自定义逻辑 }); } } -
钩子类型:
Webpack 提供了不同类型的 hooks,比如compilation hooks、environment hooks、parser hooks等,覆盖了构建过程的各个阶段。插件可以根据需要选择合适的 hooks 来注入自定义逻辑。 -
Tapable 库:
Webpack 的 hooks 系统是基于 Tapable 库实现的。Tapable 提供了一系列 hook 类,如SyncHook、AsyncSeriesHook等,插件可以使用这些 hook 类来定义自己的 hooks。 -
插件配置:
在 Webpack 配置中,插件通常以数组的形式列出,Webpack 会自动实例化并调用这些插件的apply方法。javascript
Copy
module.exports = { // ... other Webpack config plugins: [ new MyPlugin() ] }
总的来说,Webpack 插件机制的核心思想是基于 hooks 系统,插件可以在 Webpack 构建过程的各个阶段注入自定义逻辑,从而扩展和增强 Webpack 的功能。这种灵活的插件架构使 Webpack 成为一个可高度定制的构建工具。
compiler 和 compilation
在 Webpack 插件中,compiler 和 compilation 是两个非常重要的概念,它们描述了 Webpack 构建过程中的两个关键对象。
-
Compiler 对象:
Compiler对象代表了整个 Webpack 环境配置。- 它在 Webpack 启动时被创建,并贯穿整个构建生命周期。
Compiler对象暴露了所有的 Webpack hooks,插件可以通过这些 hooks 来钩入并扩展 Webpack 的行为。Compiler对象封装了 Webpack 配置,并提供了许多有用的方法和属性,如compiler.options、compiler.inputFileSystem、compiler.outputFileSystem等。
-
Compilation 对象:
Compilation对象代表了一次单独的构建过程。- 每当检测到文件变更并需要重新构建时,就会创建一个新的
Compilation对象。 Compilation对象包含了当前模块资源、编译生成资源、变化的文件等。- 它暴露了许多 hooks,插件可以在这些 hooks 上注册自定义逻辑,例如
compilation.hooks.buildModule、compilation.hooks.afterOptimizeAssets等。 Compilation对象提供了许多用于处理模块、资源、依赖关系的方法,如compilation.addModule()、compilation.addEntry()、compilation.getModule()等。
在 Webpack 插件的 apply 方法中,我们通常会接受 compiler 对象作为参数,并在其上注册自定义的 hooks。在这些 hooks 的回调函数中,我们又可以访问 compilation 对象,以便在特定的构建阶段执行所需的自定义逻辑。
例如:
javascript
Copy
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
// 在 emit 阶段访问 compilation 对象,执行自定义逻辑
console.log(compilation.assets);
});
}
}
总的来说,compiler 对象代表了整个 Webpack 环境,而 compilation 对象则代表了单次构建过程。插件开发者需要理解并熟练使用这两个对象,才能更好地扩展和定制 Webpack 的行为。
插件钩子
Webpack 插件中有许多不同类型的 hooks 可以绑定,它们对应着 Webpack 构建过程的各个阶段。选择合适的 hook 来绑定自定义逻辑是非常重要的。
以下是一些常见的 Webpack 构建阶段以及相应的 hook 事件:
-
初始化阶段:
environment: Webpack 环境准备完成时触发。afterEnvironment: Webpack 环境准备完成后触发。
-
编译阶段:
compile: 一个新的 compilation 创建时触发。beforeCompile: compilation 开始编译前触发。compile: 编译器开始编译时触发。thisCompilation: 在 compilation 创建时触发。compilation: 在 compilation 完成时触发。
-
构建模块阶段:
normalModuleFactory: 在创建 normal module factory 时触发。contextModuleFactory: 在创建 context module factory 时触发。beforeModule: 在创建模块之前触发。module: 在创建模块时触发。afterModule: 在创建模块之后触发。
-
构建依赖图阶段:
beforeCompile: 在开始编译之前触发。afterCompile: 在完成编译之后触发。afterResolve: 在解析模块之后触发。
-
输出阶段:
emit: 在资源被放入 output 目录之前触发。afterEmit: 在资源被放入 output 目录之后触发。
-
其他阶段:
done: 在 Webpack 构建完成时触发。failed: 在 Webpack 构建失败时触发。
当编写 Webpack 插件时,我们需要根据插件的需求选择合适的 hook 来绑定自定义逻辑。例如,如果我们需要在输出资源之前进行某些修改,可以选择 emit 钩子;如果我们需要在构建完成时执行某些清理工作,可以选择 done 钩子。
通过熟练掌握这些 Webpack 构建阶段和相应的 hook 事件,我们就可以更好地编写出功能强大的 Webpack 插件。
钩子顺序
Webpack 的 hook 机制中有一套完整的管理策略,用于协调不同插件之间的执行顺序。这种机制有助于确保插件之间的协作性和可靠性。
主要有以下几种方式来管理 Webpack 的 hook 顺序:
-
钩子类型:
- Webpack 的钩子分为同步钩子和异步钩子两种类型。同步钩子中的回调函数会按照注册的先后顺序依次执行,而异步钩子中的回调函数则是并发执行。
- 同步钩子的执行顺序更容易控制,而异步钩子则更适合一些独立的、耗时的操作。
-
优先级:
- 对于同步钩子,Webpack 允许插件指定钩子的优先级。优先级分为
high、normal(默认)、low三种。 - 优先级高的插件会先于优先级低的插件执行。这样可以让重要的插件先执行,而不会被其他插件干扰。
- 对于同步钩子,Webpack 允许插件指定钩子的优先级。优先级分为
-
tapable.js 库:
- Webpack 基于
tapable.js库实现了钩子机制。tapable.js提供了丰富的 hook 类型和 API,供 Webpack 插件开发者使用。 - 通过
tapable.js提供的register、call、tap等方法,Webpack 可以灵活地管理钩子的注册和执行。
- Webpack 基于
-
钩子生命周期:
- Webpack 的钩子遵循一定的生命周期。比如
compile钩子会在编译开始前触发,而afterEmit钩子会在资源输出后触发。 - 插件开发者需要了解钩子的触发时机,以便在适当的阶段注册自己的逻辑。
- Webpack 的钩子遵循一定的生命周期。比如
-
钩子数据:
- Webpack 的钩子可以传递上下文数据。不同的钩子会传递不同的数据对象,如
compilation对象、module对象等。 - 插件开发者可以利用这些数据对象来获取构建过程的上下文信息,并进行相应的处理。
- Webpack 的钩子可以传递上下文数据。不同的钩子会传递不同的数据对象,如
总的来说,Webpack 通过钩子类型、优先级、tapable.js 库以及钩子生命周期和数据等机制,实现了对插件执行顺序的精细化管理。这确保了 Webpack 插件生态的健康发展,让不同插件可以安全、有序地协作
plugin demo
loader 和 plugin 的区别
Webpack 中的 Loader 和 Plugin 虽然都是用于扩展 Webpack 的功能,但它们在作用和使用方式上还是有一些区别的:
-
作用不同:
- Loader 主要用于对模块的源代码进行转换,比如将 ES6 转换为 ES5、Sass 转换为 CSS 等。
- Plugin 则更加广泛,可以在 Webpack 构建的各个阶段执行自定义的逻辑,如资源优化、环境变量替换、输出文件管理等。
-
使用方式不同:
- Loader 在 Webpack 配置的
module.rules中定义,用于处理特定类型的模块。 - Plugin 在 Webpack 配置的
plugins字段中定义,通过实例化插件类的方式添加到构建流程中。
- Loader 在 Webpack 配置的
-
生命周期不同:
- Loader 只参与模块转换这一个构建阶段,
- 而 Plugin 可以贯穿 Webpack 构建的全部生命周期,包括初始化、编译、输出等阶段。
-
复杂度不同:
- Loader 相对简单,通常只需要实现一个转换函数。
- 而 Plugin 则更加复杂,需要自定义 Webpack 构建流程中的钩子函数,实现更加灵活和强大的功能。
总的来说,Loader 和 Plugin 是 Webpack 提供的两种不同的扩展机制:
- Loader 用于模块转换,实现不同类型源代码到 JavaScript 的转换。
- Plugin 用于自定义 Webpack 构建过程的各个阶段,实现更加复杂和全面的功能扩展。
二者相互补充,共同为 Webpack 提供了强大的可扩展性,使得 Webpack 可以满足各种复杂的前端构建需求。
9.map 和 weakmap区别
WeakMap 和 Map 都是 JavaScript 中的数据结构,它们有一些相似和不同的地方:
相似点:
- 都是键值对的集合。
- 都可以使用任何类型的值作为键或值。
不同点:
-
键的类型:
Map的键可以是任意类型的值(包括对象)。WeakMap的键必须是对象类型。
-
值的类型:
Map的值可以是任意类型。WeakMap的值可以是任意类型。
-
垃圾回收:
Map中的键和值都是强引用,即使没有其他引用指向它们,它们也不会被垃圾回收器回收。WeakMap中的键是"弱"引用,如果除了WeakMap之外没有其他地方引用了该对象,那么该对象可能会被垃圾回收器回收。
-
可枚举性:
Map是可枚举的,你可以使用for...of循环、size属性等来访问和遍历它。WeakMap是不可枚举的,没有size属性,也无法被直接遍历。
-
性能:
Map的性能可能稍微差一点,因为它需要维护内部的迭代器。WeakMap的性能通常更好,因为它不需要维护内部的迭代器。
总的来说,Map 和 WeakMap 都是 JavaScript 中非常有用的数据结构,但它们的使用场景有所不同:
- 如果你需要存储任意类型的键值对,并且希望能够遍历和访问它们,那么
Map更适合。 - 如果你只需要存储对象到任意值的映射关系,并且希望能够自动回收不再需要的对象,那么
WeakMap更合适。
根据具体的业务需求,合理选择 Map 或 WeakMap 可以帮助你编写出更高效和内存友好的 JavaScript 代码。
10.脚手架做过哪些工作
command lerna 解析参数
template 如何加载自定义参数(title 属性不同)提供变量
11.Tree-shaking 摇树
ES6 为啥能支持tree-shaking,静态编译。 COMMONJS 动态编译
12.小程序框架
Taro React 系列 Uniapp Vue 系列
13. SSR 实现模式
14.JsBridge
H5 和 Native 之间如何相互通讯
H5 和 Native 之间的相互通讯有以下几种常用的方式:
-
WebView 与 Native 通讯
- 在 Native 端提供一个 WebView 组件,H5 页面可以通过 JavaScript 调用 Native 端的方法,反之亦然。
- 这种方式需要在 Native 端和 H5 端进行约定好的通讯协议,比较灵活,但需要双方都有开发能力。
-
URL Scheme
- H5 页面通过
window.location.href打开一个特定的 URL Scheme,Native 端监听到该 Scheme 后执行相应的操作。 - 这种方式简单易实现,但功能相对受限,且存在一定的安全隐患。
- H5 页面通过
-
Postmessage
- H5 页面通过
window.postMessage()发送消息,Native 端通过监听message事件接收消息。 - 这种方式跨域兼容性好,但需要双方都有开发能力,且存在一定的安全隐患。
- H5 页面通过
-
第三方 SDK 桥接
- 使用第三方 SDK (如 jsBridge) 提供的 API,在 H5 和 Native 之间建立通讯桥梁。
- 这种方式封装了通讯细节,使用简单,但需要依赖第三方 SDK,灵活性相对较低。
-
文件交互
- H5 页面生成一个文件,Native 端检测并读取该文件,执行相应操作。
- 这种方式简单易实现,但只能单向传递信息,且可能存在兼容性问题。
-
二维码/条形码
- H5 页面生成二维码/条形码,Native 端扫描并解析,执行相应操作。
- 这种方式适用于一些特定场景,如分享、支付等,但交互体验较差。
在实际项目中,可以根据具体需求选择合适的通讯方式。如果需要复杂的交互,通常会采用 WebView 与 Native 通讯的方式;如果需求相对简单,URL Scheme 或第三方 SDK 桥接可能会更合适。此外,还可以结合多种方式来满足不同的需求。
15.多主题变化
16.type 和 interface
TypeScript 中的 type 和 interface 是两种不同的类型定义方式,它们之间有一些区别和联系:
-
语法差异:
interface: 使用interface关键字定义接口。type: 使用type关键字定义类型别名。
-
功能差异:
interface: 主要用于定义对象的形状和结构。可以扩展、合并同名接口。type: 可以定义基本类型、联合类型、元组等更广泛的类型。不能直接扩展或合并同名type。
-
扩展差异:
interface: 可以使用extends关键字扩展接口。type: 可以使用交叉类型&来扩展类型别名。
-
兼容性差异:
interface: 可以被实现、扩展和合并。type: 不能被实现,但可以用于类型推断和约束。
-
重复定义差异:
interface: 同名接口会被合并,属性会被合并。type: 同名类型别名会报错,不会被合并。
-
表达能力差异:
interface: 更擅长描述对象的形状。type: 可以描述更复杂的类型,如联合类型、交叉类型、元组类型等。
总的来说,interface 和 type 都可以用于定义类型,但在使用场景和表达能力上有一些区别:
- 如果你需要定义对象的形状,使用
interface通常是更好的选择。 - 如果你需要定义更复杂的类型,如联合类型、交叉类型等,使用
type会更合适。 - 如果你需要扩展一个现有的类型,使用
interface的extends关键字会更方便。 - 如果你需要重复定义同名的类型,使用
interface会更加灵活。
在实际开发中,根据具体的需求选择合适的类型定义方式。通常情况下,interface 和 type 可以协同使用,发挥各自的优势。
17.如何监听动画结束
在浏览器中可以使用 animationend 事件监听 CSS 动画的结束。以下是具体的步骤:
-
添加 CSS 动画
在 CSS 中定义动画:css
Copy
@keyframes myAnimation { 0% { transform: translateX(0); } 100% { transform: translateX(100px); } } .animated-element { animation: myAnimation 2s linear; } -
添加事件监听器
在 JavaScript 中添加animationend事件监听器:javascript
Copy
const animatedElement = document.querySelector('.animated-element'); animatedElement.addEventListener('animationend', function(event) { console.log('Animation ended!'); });这里我们选择
.animated-element作为动画元素,并添加animationend事件监听器。当动画结束时,就会触发该事件,并执行回调函数。 -
获取动画信息
在回调函数中,可以获取更多关于动画的信息:javascript
Copy
animatedElement.addEventListener('animationend', function(event) { console.log('Animation ended!'); console.log('Animation name:', event.animationName); console.log('Elapsed time:', event.elapsedTime, 'seconds'); });event.animationName: 获取动画的名称event.elapsedTime: 获取动画持续的时间(单位为秒)
-
移除事件监听器
如果不再需要监听动画结束事件,可以使用removeEventListener移除事件监听器:javascript
Copy
animatedElement.removeEventListener('animationend', myAnimationEndHandler);
需要注意的是,animationend 事件只会在 CSS 动画结束时触发,如果使用 JavaScript 动画,则需要使用其他事件或方法来监听动画结束。
总之,使用 animationend 事件可以很方便地监听 CSS 动画的结束,并获取相关的动画信息。这在需要在动画结束时执行其他操作时非常有用。
18. css 居中
19. flex:1 ;
flex: 1 是 CSS Flexbox 布局中的一个非常常见且重要的属性值。它代表以下三个属性的简写形式:
flex-grow: 1flex-shrink: 1flex-basis: 0
让我们分别解释一下这三个属性的含义:
-
flex-grow:- 定义项目的放大比例。
- 默认值为 0,即如果存在剩余空间,也不放大。
- 设置为 1 后,若有剩余空间,该项目将平均分享剩余空间。
-
flex-shrink:- 定义了项目的缩小比例。
- 默认值为 1,即如果空间不足,该项目将缩小。
- 如果所有项目的
flex-shrink属性都为 1,当空间不足时,都将等比例缩小。
-
flex-basis:- 定义了在分配多余空间之前,项目占据的主轴空间。
- 浏览器根据这个属性计算主轴是否有多余空间。
- 它的默认值为
auto,即项目的本来大小。
综合起来,flex: 1 的含义就是:
- 当有剩余空间时,该项目将平分剩余空间(因为
flex-grow为 1)。 - 当空间不足时,该项目将等比例缩小(因为
flex-shrink为 1)。 - 在分配多余空间之前,该项目占据的主轴空间为 0(因为
flex-basis为 0)。
这是 Flexbox 布局中最常见且最有用的一种设置,可以让项目在容器中自动伸缩,以达到理想的布局效果。
20.兼容性会考虑了多少
兼容分级策略: A B S X
遇到哪些兼容性问题?怎么解决?
处理不同浏览器内核的兼容性问题,主要有以下几种常见的方法:
-
特性检测
- 使用 JavaScript 的特性检测机制,动态地根据浏览器支持情况加载不同的代码实现。
- 例如,可以检测
document.querySelector是否存在,以此判断浏览器是否支持 CSS 选择器 API。 - 基于特性检测的方式可以提供优雅的降级方案,确保核心功能在不同内核下都能正常工作。
-
浏览器嗅探
- 通过解析
navigator.userAgent字符串,识别浏览器的内核类型。 - 根据不同的内核类型,应用针对性的代码实现或样式调整。
- 这种方式相对简单,但需要维护一份较为完整的浏览器 UA 信息库。
- 通过解析
-
CSS hack
- 利用不同浏览器内核对 CSS 解析机制的差异,编写针对性的 CSS hack 代码。
- 常见的 hack 技巧包括条件注释、属性前缀、属性值重复等。
- 这种方式灵活性强,但可维护性较差,容易产生代码混乱。
-
CSS 预处理器
- 使用 Sass、Less 等 CSS 预处理器,通过条件判断或 mixin 等机制,生成针对不同内核的 CSS 代码。
- 预处理器提供了更好的可维护性和可读性,但需要额外的构建步骤。
-
前端框架/库
- 一些主流的前端框架/库,如 React、Vue 等,本身就内置了对不同浏览器内核的兼容性处理。
- 使用这些框架可以大大简化跨浏览器内核的兼容性工作。
-
Polyfill 和 Transpiler
- 使用 Polyfill 技术,为不支持的特性提供兼容性实现。
- 结合 Transpiler 工具(如 Babel)将新语法转换为兼容性更好的版本。
-
渐进增强
- 采用"渐进增强"的策略,先确保网页在最低要求的浏览器内核上能正常工作,然后逐步增强功能。
- 这种方式可以最大限度地保证核心功能的跨浏览器兼容性。
在实际项目中,通常需要综合使用上述几种方法,根据具体的业务需求和目标浏览器范围,制定合理的兼容性解决方案。同时,持续关注浏览器内核的发展动态,及时调整兼容性策略也很重要。
21.charles 为什么能劫持到请求
Charles 之所以可以劫持 HTTP/HTTPS 请求,主要有以下几个原因:
-
中间人攻击原理:
- Charles 作为一个 HTTP 代理服务器,可以位于客户端(如浏览器)和服务端之间,截获和监控两者之间的通信。
- 这种中间人攻击的方式可以让 Charles 获取并查看原本应该是"私密"的通信内容。
-
SSL/TLS 证书伪造:
- 对于 HTTPS 加密通信,Charles 可以生成一个自签名的 SSL/TLS 证书,并作为中间人的身份插入到客户端和服务端之间。
- 客户端会信任 Charles 伪造的证书,从而将原本加密的内容暴露给 Charles。
-
操作系统信任证书:
- Charles 会要求用户安装一个 Charles 根证书,并将其添加到操作系统的受信任证书中。
- 这样一来,操作系统会信任 Charles 生成的所有证书,从而允许 Charles 成为 HTTPS 通信的中间人。
-
动态 SSL 代理:
- Charles 会动态地为每个 HTTPS 连接生成一个新的证书,并将其注入到客户端和服务端之间。
- 这种方式可以让 Charles 实现对 HTTPS 通信的实时监控和修改。
-
浏览器配合:
- 现代浏览器一般都支持将 HTTP 代理的设置应用到整个浏览器会话。
- 用户可以在浏览器中配置 Charles 作为 HTTP 代理,从而让浏览器的所有 HTTP/HTTPS 请求都经由 Charles 代理。
总的来说,Charles 之所以能够劫持请求,是因为它巧妙地利用了中间人攻击的原理,并通过生成自签名证书以及操作系统信任证书等方式,成功地将自己插入到客户端和服务端之间的通信链路中。这种方式使得 Charles 能够完全控制和监控 HTTP/HTTPS 数据包的传输。
22.js 中 error 报错是怎么监控的
Sentry 是一个非常流行的前端和后端错误监控和报告工具。它可以通过以下几种方式检测前端应用中的错误:
-
全局错误捕获:
- Sentry 提供了全局错误捕获机制,可以监听
window.onerror、unhandledrejection(处理promise) 等事件,捕获应用中未被处理的异常。 - 这些未捕获的异常通常会导致应用崩溃或出现意外行为,Sentry 能够及时发现并上报这些重要的错误。
- Sentry 提供了全局错误捕获机制,可以监听
-
框架/库集成:
- Sentry 提供了对主流前端框架/库的集成支持,如 React、Angular、Vue.js 等。
- 通过这些集成,Sentry 能够更深入地了解应用的结构和运行状态,捕获各种类型的错误和异常。
-
手动 SDK 集成:
- Sentry 还提供了丰富的 SDK,开发者可以手动将其集成到应用中,以实现更细粒度的错误跟踪和监控。
- 通过 SDK 提供的 API,开发者可以手动上报错误信息,记录用户上下文数据等,帮助 Sentry 更好地分析问题。
-
错误采样:
- Sentry 支持错误采样,即只上报部分错误,以避免因大量错误上报而影响应用性能。
- 开发者可以根据错误发生频率、严重程度等因素设置采样策略,确保关键错误能够及时被发现和修复。
-
源码映射:
- Sentry 能够解析应用的源码映射(source map)信息,将错误堆栈信息映射回源代码位置。
- 这有助于开发者快速定位错误发生的具体位置,提高问题的修复效率。
-
上下文信息收集:
- Sentry 会自动收集错误发生时的上下文信息,如用户 ID、会话 ID、浏览器信息等。
- 这些信息有助于开发者更好地分析错误产生的原因和影响范围。
总的来说,Sentry 通过全局错误捕获、框架/库集成、手动 SDK 集成、错误采样、源码映射以及上下文信息收集等多种方式,实现了对前端应用的全面错误检测和监控。开发者可以根据具体需求选择合适的集成方式,以确保应用的稳定性和可靠性。
23.图片懒加载的几种方式 图片懒加载有以下几种常见的实现方式:
-
IntersectionObserver API:
- 使用
IntersectionObserver监听图片元素是否进入可视区域,当进入可视区域时才触发图片加载。 - 这是一种较新的方案,可以更精确地检测元素的可见状态,减少不必要的加载。
- 使用
-
Scroll 事件监听:
- 监听页面滚动事件,计算图片元素的位置是否在可视区域内,如果在则触发图片加载。
- 这种方式简单易实现,但需要频繁计算元素位置,可能会影响性能。
-
自定义 data- 属性*:
- 将图片的真实
src地址存放在自定义的data-src属性中。 - 当图片元素进入可视区域时,将
data-src的值赋给src属性,触发图片加载。
- 将图片的真实
-
Mutation Observer API:
- 使用
MutationObserver监听页面 DOM 结构的变化,当图片元素加入 DOM 时触发图片加载。 - 这种方式适用于动态插入图片的场景,能够及时捕获新图片的加载需求。
- 使用
-
Intersection Observer API + 自定义 data- 属性*:
- 结合使用
IntersectionObserver和自定义data-src属性的方式,可以实现更加可靠和高性能的懒加载。 - 先使用
IntersectionObserver监听图片是否进入可视区域,然后再触发data-src到src的赋值操作。
- 结合使用
-
Lazy Loading HTML 属性:
- HTML5 原生提供了
loading="lazy"属性,可以让浏览器自动实现图片的懒加载。 - 这种方式简单易用,但目前兼容性还不太好,需要关注浏览器的支持情况。
- HTML5 原生提供了
总的来说,这些方式各有优缺点,开发者可以根据具体需求和项目情况选择合适的懒加载实现方式。一般来说,使用 IntersectionObserver 结合自定义 data-src 属性的方式是较为推荐的做法,兼具性能和可靠性。
23.Promise 的机制
好的,让我给你解释一下 Promise 机制:
Promise 是 JavaScript 中用于处理异步操作的一种机制。它提供了一种更优雅和可控的方式来处理异步代码,相比于传统的回调函数方式更加清晰和可读。
Promise 对象有三种状态:
- Pending: 初始状态,既不是成功,也不是失败状态。
- Fulfilled: 操作成功完成。
- Rejected: 操作失败。
Promise 对象提供了以下方法:
then(onFulfilled, onRejected): 用于注册当 Promise 对象状态变为 Fulfilled 或 Rejected 时的回调函数。catch(onRejected): 用于注册当 Promise 对象状态变为 Rejected 时的回调函数。finally(onFinally): 用于注册当 Promise 对象状态变化完成时的回调函数,无论成功还是失败。
使用 Promise 的一个典型例子是发起一个 AJAX 请求:
javascript
复制
fetch('/data.json')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
在这个例子中,fetch() 方法返回一个 Promise 对象,我们使用 .then() 方法注册成功和失败的回调函数。
Promise 机制的优点包括:
- 更好的错误处理
- 更清晰的异步流程控制
- 支持链式调用
- 可以使用
async/await语法糖进一步简化异步代码
总之, Promise 是 JavaScript 处理异步操作的一种强大且灵活的机制,广泛应用于现代 Web 开发中。
24.Promise.all 和 Promise.race 的区别
Promise.all() 和 Promise.race() 是 Promise 的两个静态方法,它们都用于处理多个 Promise 对象,但是有以下区别:
-
返回值:
Promise.all()会返回一个新的 Promise 对象,当所有输入的 Promise 都 fulfilled 时,该 Promise 才会 fulfilled,返回值是一个包含所有传入 Promise resolve 值的数组。如果任何一个 Promise 被 reject,则整个Promise.all()会立即 reject。Promise.race()也会返回一个新的 Promise 对象,但是这个 Promise 的状态取决于第一个完成的 Promise,无论成功还是失败。
-
执行顺序:
Promise.all()会并行执行所有的 Promise,当所有 Promise 都完成后才会触发 resolved 状态。Promise.race()也是并行执行所有 Promise,但只要有一个 Promise 完成(无论成功或失败),就会立即触发 resolved 状态。
-
使用场景:
Promise.all()适用于需要获取多个异步操作的结果,并将它们组合成一个结果的场景。Promise.race()适用于需要尽快获得异步操作结果,并且不关心其他 Promise 结果的场景,比如设置超时。
总之,Promise.all() 用于等待所有异步操作完成,Promise.race() 用于尽快获取异步操作的结果。根据具体的业务需求来选择使用哪个方法。
25.小程序了冷启动
小程序冷启动优化是提升小程序整体性能的关键所在。以下是一些常见的小程序冷启动优化策略:
-
代码分包:
- 将小程序的代码按照功能模块进行分包,减少首次启动需要加载的代码量。
- 使用
preloadRule设置预加载规则,提升页面渲染速度。
-
资源优化:
- 将图片、样式等静态资源上传至 CDN,提升资源加载速度。
- 对资源进行压缩和适配处理,减小资源体积。
-
网络请求优化:
- 减少不必要的网络请求,对必要的请求进行缓存。
- 使用
wx.connectSocket()建立长连接,减少重复连接的开销。
-
启动优化:
- 将小程序首页设置为最小化页面,减少首屏渲染的复杂度。
- 使用
wx.preloadGame()实现游戏类小程序的预加载。
-
框架优化:
- 使用最新版本的小程序基础库,获得更好的性能表现。
- 根据业务需求选择合适的小程序框架,如 Taro、Chameleon 等。
-
监控与分析:
- 使用
wx.reportPerformance()收集用户的性能数据。 - 分析性能数据,找出性能瓶颈并进行针对性优化。
- 使用
-
开发工具优化:
- 在开发阶段使用小程序开发者工具的性能分析功能,发现并修复性能问题。
- 开启小程序开发者工具的 gzip 压缩功能,减小代码体积。
综上所述,小程序冷启动优化需要从多个角度入手,包括代码分包、资源优化、网络请求优化等。开发者需要根据小程序的具体情况,选择合适的优化策略,最大限度地提升小程序的冷启动性能。
26.小程序性能优化
小程序作为一种轻量级的移动端应用,性能优化是非常重要的。以下是一些常见的小程序性能优化技巧:
-
代码优化:
- 减少代码体积和网络请求
- 使用
wx.nextTick()优化 DOM 操作 - 合理使用
wx:if和wx:for减少无用渲染 - 使用
this.setData()更新数据,减少整页面的重新渲染
-
资源优化:
- 图片、视频等静态资源采用适当的压缩方式
- 使用
image标签的lazyLoad属性实现图片懒加载 - 将静态资源上传至 CDN 加速访问
-
网络请求优化:
- 尽量减少不必要的网络请求
- 使用缓存技术(如 Memcached、Redis)缓存接口数据
- 采用增量更新的方式获取数据,只更新变化部分
-
包体积优化:
- 使用 ES6 模块化管理代码,按需引入
- 开启小程序的 gzip 压缩功能
- 分包和独立分包,将代码按模块划分
-
页面优化:
- 使用
wx.createIntersectionObserver()实现页面懒加载 - 在页面切换时使用
wx.navigateTo()而非wx.redirectTo() - 合理使用
wx.hideLoading()隐藏加载框
- 使用
-
组件优化:
- 使用自定义组件提高复用性
- 在性能敏感的场景使用纯函数组件
- 使用
wx:if或hidden动态控制组件的显隐
-
运行时优化:
- 使用
wx.reportPerformance()监控页面性能 - 合理使用
wx.nextTick()延迟执行一些操作 - 检查并优化 JavaScript 代码的执行效率
- 使用
总的来说,小程序的性能优化需要从多个层面着手,包括代码优化、资源优化、网络优化等。开发者需要根据实际情况选择合适的优化方式,确保小程序的用户体验良好。
27.笔试题
promise
下面是一个使用 JavaScript Promise 实现 delay 方法的示例:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(200)
.then(() => {
console.log('Delayed for 200 milliseconds.');
})
.catch(error => {
console.error('Error:', error);
});
复制js 对象
在 JavaScript 中,有几种方法可以复制对象的所有属性,除了一个:
- 使用
Object.assign()方法:
const originalObj = { a: 1, b: 2, c: 3 };
const { c, ...newObj } = originalObj;
console.log(newObj); // { a: 1, b: 2 }
这里使用了对象解构赋值,将 c 属性分离出来,其余属性赋值给 newObj。
- 使用展开运算符
...和对象字面量
const originalObj = { a: 1, b: 2, c: 3 };
const newObj = { ...originalObj, c: undefined };
delete newObj.c;
console.log(newObj); // { a: 1, b: 2 }
这里先使用展开运算符复制了所有属性,然后手动删除了 c 属性。
- 使用
JSON.parse()和JSON.stringify():
const originalObj = { a: 1, b: 2, c: 3 };
const { c, ...newObj } = JSON.parse(JSON.stringify(originalObj));
console.log(newObj); // { a: 1, b: 2 }
这里先将对象转换为 JSON 字符串,然后再解析回对象,同时使用对象解构赋值排除 c 属性。
- 使用
for...in循环和对象字面量:
const originalObj = { a: 1, b: 2, c: 3 };
const newObj = {};
for (const key in originalObj) {
if (key !== 'c') {
newObj[key] = originalObj[key];
}
}
console.log(newObj); // { a: 1, b: 2 }
这里手动遍历对象的属性,并将除了 c 之外的属性复制到新对象中。
以上方法都可以实现复制对象的所有属性,除了一个。选择使用哪种方法取决于具体的需求和个人preference。
28.缓存
协商缓存和强制缓存是 HTTP 缓存机制中的两种不同的缓存策略:
-
强制缓存:
- 强制缓存是基于 HTTP 响应头中的缓存控制指令(
Cache-Control、Expires)来决定资源是否可以被缓存,以及缓存时间。 - 当浏览器再次请求该资源时,会先根据这些指令检查资源是否在缓存期内,如果在,则直接从缓存中获取,不会发送请求到服务器。
- 强制缓存的优点是速度快,没有网络开销,但缺点是缓存时间到期后就必须向服务器发送请求。
- 强制缓存是基于 HTTP 响应头中的缓存控制指令(
-
协商缓存:
- 协商缓存是基于 HTTP 响应头中的其他缓存验证指令(
Last-Modified、ETag)来决定资源是否有更新,以及是否需要从服务器重新获取。 - 当浏览器再次请求该资源时,会先向服务器发送带有这些验证指令的请求,服务器会根据这些指令判断资源是否有更新,如果没有更新,则返回 304 Not Modified 状态码,让浏览器继续使用缓存。
- 协商缓存的优点是可以动态判断资源是否更新,减少不必要的网络请求,但缺点是需要多一次网络请求来验证缓存。
- 协商缓存是基于 HTTP 响应头中的其他缓存验证指令(
强制缓存和协商缓存通常结合使用,优先使用强制缓存,当强制缓存失效时再使用协商缓存。这种混合策略可以最大化缓存的效率和时效性。
29. vue 相关面试题
setup 中为啥不能使用this
在 Vue.js 中,setup() 函数没有 this 的原因主要有以下几点:
-
基于 Composition API 的设计:
- Vue.js 2.x 版本使用的是基于 Options API 的设计模式,在组件选项中使用
this来访问组件实例。 - 而 Vue.js 3.x 引入了 Composition API,它摒弃了
this的使用,转而采用更加函数式编程的方式。
- Vue.js 2.x 版本使用的是基于 Options API 的设计模式,在组件选项中使用
-
更好的类型推导和智能提示:
- 在
setup()函数中,Vue.js 会自动推导出组件实例的类型,并将其作为函数参数提供。 - 这样可以在编辑器中获得更好的类型提示和自动补全,提高开发效率。
- 在
-
避免 this 的上下文问题:
this的上下文在 JavaScript 中一直是个头疼的问题,需要额外的绑定操作或箭头函数来解决。- 在
setup()函数中完全避免this,有助于消除这种上下文问题,使代码更加清晰和可维护。
-
更好的代码组织和复用:
- 在 Composition API 中,组件的逻辑可以被更好地拆分和组织,通过自定义 Hooks 实现高度的逻辑复用。
- 这种模式更加符合函数式编程的思想,提高了代码的可读性和可维护性。
-
更好的 TypeScript 支持:
- 在使用 TypeScript 的场景下,不依赖
this可以使得类型推导和检查更加准确和简单。 - 这也是 Composition API 设计的一个重要目标之一。
- 在使用 TypeScript 的场景下,不依赖
总的来说,setup() 函数没有 this 是 Composition API 设计的一个重要特点,它有助于提高代码的可读性、可维护性和类型安全性。开发者可以通过 setup() 函数的参数来访问组件实例的属性和方法,以及其他 Vue.js 提供的工具函数。
如何拿到for 循环里面的所有组件的实例
在 Vue.js 中,如果需要拿到 v-for 循环中所有组件实例,可以使用以下几种方法:
-
$refs 引用:
-
在组件模板中,给每个子组件添加
ref属性:html
Copy
<div v-for="item in items" :key="item.id" :ref="'child-' + item.id"> <!-- 子组件内容 --> </div> -
在
setup()函数或组件方法中,使用$refs访问所有子组件实例:javascript
Copy
import { ref, onMounted } from 'vue' export default { setup() { const childRefs = ref([]) onMounted(() => { childRefs.value = Object.values(this.$refs) }) return { childRefs } } }
-
-
provide/inject 依赖注入:
-
在父组件中,使用
provide提供子组件实例:javascript
Copy
import { provide, ref } from 'vue' export default { setup() { const childInstances = ref([]) provide('childInstances', childInstances) return { childInstances } } } -
在子组件中,使用
inject获取父组件提供的子组件实例:javascript
Copy
import { inject } from 'vue' export default { setup() { const childInstances = inject('childInstances') // 将自身实例添加到 childInstances 数组中 childInstances.value.push(/* 当前组件实例 */) return { // ... } } }
-
-
自定义指令:
-
在父组件中,定义一个自定义指令来收集子组件实例:
html
Copy
<div v-for="item in items" :key="item.id" v-my-directive="item"></div> -
在自定义指令的
mounted和unmounted钩子中,管理子组件实例数组:javascript
Copy
import { ref } from 'vue' export default { directives: { myDirective: { mounted(el, binding, vnode) { const childInstances = ref([]) childInstances.value.push(vnode.component) // 将 childInstances 绑定到父组件实例上 vnode.props.childInstances = childInstances }, unmounted(el, binding, vnode) { const childInstances = vnode.props.childInstances const index = childInstances.value.indexOf(vnode.component) childInstances.value.splice(index, 1) } } } }
-
以上三种方法都可以用于在 v-for 循环中获取所有子组件的实例。具体选择哪种方法,需要根据项目的具体需求和代码结构进行权衡。
30. 如何获取 dom 元素的坐标和行数
要获取 div 在页面中的坐标信息以及占用的行数,可以使用以下方法:
-
使用
getBoundingClientRect():-
这个方法返回一个
DOMRect对象,包含了元素的大小以及相对于视口的位置。 -
通过
top和bottom属性,我们可以计算出元素占用的行数。 -
示例代码:
javascript
Copy
const element = document.getElementById('myDiv'); const rect = element.getBoundingClientRect(); console.log('Left:', rect.left); console.log('Top:', rect.top); console.log('Width:', rect.width); console.log('Height:', rect.height); console.log('Lines:', Math.ceil(rect.height / 16)); // 每行约 16 像素
-
-
使用
offsetHeight和lineHeight:-
offsetHeight属性返回元素的像素高度,包括填充、边框和水平滚动条。 -
通过获取元素的
lineHeight属性,可以计算出元素占用的行数。 -
示例代码:
javascript
Copy
const element = document.getElementById('myDiv'); const height = element.offsetHeight; const lineHeight = window.getComputedStyle(element).lineHeight; console.log('Left:', element.offsetLeft); console.log('Top:', element.offsetTop); console.log('Width:', element.offsetWidth); console.log('Height:', height); console.log('Lines:', Math.ceil(height / parseFloat(lineHeight)));
-
-
使用
getClientRects():-
这个方法返回一个
DOMRectList对象,包含了元素中每一个内联子元素的大小和位置。 -
通过遍历
DOMRectList,可以获取元素占用的所有行的位置信息。 -
示例代码:
javascript
Copy
const element = document.getElementById('myDiv'); const rects = element.getClientRects(); console.log('Left:', rects[0].left); console.log('Top:', rects[0].top); console.log('Width:', rects[0].width); console.log('Height:', rects[0].height); console.log('Lines:', rects.length);
-
这三种方法各有优缺点,需要根据具体情况进行选择。getBoundingClientRect() 方法返回的是相对于视口的位置,offsetHeight 和 lineHeight 可以直接计算出行数,而 getClientRects() 方法可以获取元素中每一个内联子元素的位置信息。
31.了解浏览器的渲染过程嘛?
是的,我对浏览器的渲染过程有一定了解。下面简单介绍一下浏览器的主要渲染步骤:
-
HTML 解析:
- 浏览器首先会解析 HTML 文档,构建 DOM 树(文档对象模型)。
-
CSS 解析:
- 浏览器会解析 CSS 文件,构建样式表。
-
布局计算:
- 浏览器会根据 DOM 树和样式表,计算出各个元素在页面上的位置和大小。
-
绘制:
- 浏览器根据计算的布局信息,将各个元素绘制到屏幕上。
-
合成:
- 如果页面有复杂的动画或滚动效果,浏览器会将页面分成多个图层,并使用 GPU 进行合成,提高渲染性能。
-
重绘和重排:
- 当 DOM 或 CSS 发生变化时,会触发重绘(Repaint)或重排(Reflow)。重绘不会引起布局变化,而重排会引起布局变化,性能消耗较大。
-
优化:
- 为了提高渲染性能,可以采取一些优化措施,如减少 DOM 操作、使用 CSS3 动画、延迟加载等。
浏览器的渲染过程是一个复杂的过程,涉及 HTML 解析、CSS 解析、布局计算、绘制、合成等多个步骤。对于前端开发者来说,理解这个过程非常重要,可以帮助我们编写更高效的代码,提高页面的性能。
32. 浏览器怎么知道几何的位置?
浏览器是如何知道页面元素的几何位置信息的呢?主要包括以下几个步骤:
-
计算布局(Layout) :
- 浏览器会根据 HTML 结构和 CSS 样式,计算出每个元素在页面上的位置和大小。这个过程称为布局计算。
- 布局计算会考虑元素的宽高、边距、填充、边框等属性,并根据元素的显示模式(块级、行内、浮动等)来确定最终的位置。
-
建立渲染树(Render Tree) :
- 在完成布局计算之后,浏览器会建立一个渲染树(Render Tree),用来表示页面上实际可见的元素及其几何信息。
- 渲染树中的每个节点都包含了元素的位置、大小、颜色等信息。
-
获取几何信息:
- 通过 JavaScript 提供的一些 API,开发者可以获取元素的几何信息,比如
getBoundingClientRect()、offsetWidth/offsetHeight等。 - 这些 API 会查询渲染树,返回元素在页面上的尺寸和位置信息。
- 通过 JavaScript 提供的一些 API,开发者可以获取元素的几何信息,比如
-
动态更新:
- 当页面元素发生变化时,如添加新元素、修改样式等,浏览器会重新计算布局,更新渲染树,从而更新元素的几何信息。
总的来说,浏览器是通过解析 HTML 和 CSS,计算布局,建立渲染树,然后提供 API 供开发者查询元素几何信息的。这个过程看起来似乎简单,但实际上涉及到很多复杂的算法和优化策略,以确保页面能够快速、高效地渲染出来
33.常见的加密方法
在现代密码学中,常见的加密方法主要有以下几种:
-
对称加密:
- 也称为私钥加密或单密钥加密,加密和解密使用同一个密钥。常见算法有 AES、DES、Blowfish 等。
-
非对称加密:
- 也称为公钥加密,使用一对密钥:公钥和私钥。公钥用于加密,私钥用于解密。常见算法有 RSA、ECC 等。
-
哈希算法:
- 不可逆的数据摘要算法,将任意长度的输入映射为固定长度的输出。常见算法有 MD5、SHA-1、SHA-256 等。
-
数字签名:
- 利用非对称加密的原理,使用私钥对数据进行签名,公钥可以验证签名的合法性。常见算法有 RSA、ECDSA 等。
-
消息认证码(MAC) :
- 利用对称加密算法和哈希算法,为消息生成一个认证码,用于验证消息的完整性和来源。常见算法有 HMAC-SHA256 等。
-
密钥交换算法:
- 用于双方在不安全的网络上安全地协商出一个共享密钥,常见算法有 Diffie-Hellman、ECDH 等。
-
随机数生成器:
- 用于生成高质量的随机数,确保加密系统的安全性。常见算法有 CSPRNG、TRNG 等。
这些加密算法可以单独使用,也可以组合使用,构建出各种复杂的加密系统,满足不同场景下的安全需求。
加密算法的选择需要根据具体的应用场景、安全级别、性能要求等因素综合考虑。
对称加密和非对称加密的区别
对称加密和非对称加密(也称公钥加密)之间存在以下主要区别:
-
密钥管理:
- 对称加密使用单一的共享密钥进行加密和解密。密钥必须安全地在双方之间传递和管理。
- 非对称加密使用一对密钥:公钥和私钥。公钥可以公开分发,私钥由各自保管,密钥管理更加灵活。
-
加密/解密过程:
- 对称加密中,加密和解密使用同一个密钥。
- 非对称加密中,加密使用公钥,解密使用私钥。公钥和私钥是数学上相关的。
-
计算复杂度:
- 对称加密算法,如 AES,计算速度快,加解密效率高。
- 非对称加密算法,如 RSA,计算复杂度高,加解密速度相对较慢。
-
安全性:
- 对称加密的安全性完全依赖于密钥的保护。一旦密钥被泄露,系统就会被攻破。
- 非对称加密的安全性依赖于私钥的保护。即使公钥被泄露,系统也不会被破坏。
-
应用场景:
- 对称加密适用于大量数据的加密传输,如文件加密、网络通信加密等。
- 非对称加密适用于密钥分发、数字签名等场景,实现身份认证和数据完整性。
总的来说,对称加密和非对称加密各有优缺点,在实际应用中通常会结合使用,以发挥各自的优势。对称加密用于加密大量数据,非对称加密用于密钥分发和身份认证,两者结合可以构建出更加安全可靠的加密系统。
34.cookie 的问题
使用 Cookie 保存用户登录状态会存在以下一些潜在的问题:
-
安全性问题:
- Cookie 是以明文形式存储在客户端浏览器中的,如果 Cookie 被窃取,攻击者就可以冒充用户身份访问系统。
- 即使 Cookie 使用加密方式存储,如果密钥泄露,加密方式也可能被攻破。
-
跨域问题:
- Cookie 默认是同源限制的,即只能在设置 Cookie 的域名下访问。这会给跨域请求带来问题。
- 可以使用
domain和path属性放宽 Cookie 的作用域,但这可能带来新的安全隐患。
-
CSRF 攻击风险:
- 因为 Cookie 会自动携带在请求中,这使得 CSRF 攻击变得较为容易。攻击者可以伪造用户请求,利用 Cookie 进行攻击。
-
移动端问题:
- 移动设备上 Cookie 的容量和数量都有限制,无法满足复杂应用的需求。
-
退出登录问题:
- 使用 Cookie 存储登录状态,要实现退出登录需要删除 Cookie,但删除 Cookie 并不能保证用户真正退出登录。
为了解决这些问题,通常会采用以下一些解决方案:
- 使用 JWT(JSON Web Token)等无状态的身份验证机制,将用户信息加密存储在客户端,减少对服务器的依赖。
- 采用 Session 机制,将用户登录状态保存在服务器端,客户端仅保存 Session ID。
- 使用 Token 机制,通过 API 调用获取 Token,并在后续请求中携带 Token 进行身份验证。
- 采用双 Cookie 机制,一个 Cookie 存储登录 Session ID,另一个存储 CSRF 令牌。
总之,使用 Cookie 保存登录状态存在一些安全隐患,应该结合实际需求选择合适的身份验证方式,在安全性和可用性之间寻求平衡。
35.node 是单线程还是多线程
Node.js 是一个单线程的运行时环境。它使用事件驱动、非阻塞I/O模型来实现高并发,这意味着它可以在一个单线程上处理大量的并发请求。
具体来说:
- Node.js 的主线程是单线程的,它使用事件循环来处理事件和异步操作。
- 对于 CPU 密集型的任务,Node.js 可以使用 Worker Threads 来创建子线程进行并行计算。但这些子线程是由 Node.js 主线程管理的。
- 对于 I/O 密集型的任务,如网络请求、文件读写等,Node.js 可以通过非阻塞 I/O 操作来实现高并发,而不需要多线程。
- Node.js 还支持原生的 Cluster 模块,可以启动多个子进程来实现负载均衡。但这些子进程也是由主进程管理的。
总的来说,Node.js 的核心是单线程的,但通过事件驱动和异步 I/O 操作,它可以实现高并发的能力。对于 CPU 密集型的任务,则可以使用 Worker Threads 来创建子线程进行并行计算。
36.node 是怎么处理并发的
Node.js 通过以下几种方式实现并发:
- 事件循环(Event Loop) : Node.js 使用单线程的事件循环来处理并发。事件循环不断检查事件队列,当有新事件到达时就立即处理。这样可以避免长时间阻塞主线程。
- 非阻塞 I/O: Node.js 使用非阻塞 I/O 操作,对于 I/O 密集型的任务(如网络请求、文件 I/O 等)不会阻塞主线程。相反,它会将 I/O 操作交给操作系统内核处理,然后继续处理其他任务。当 I/O 操作完成时,会触发回调函数来处理结果。
- 回调函数: Node.js 大量使用回调函数来实现异步编程。当一个 I/O 操作完成时,会调用相应的回调函数来处理结果,从而避免阻塞主线程。
- Promise 和 async/await: 为了更好地管理异步代码,Node.js 支持 Promise 和 async/await 语法。这些语法糖可以帮助开发者编写更加直观和易读的异步代码。
- Worker Threads: 对于 CPU 密集型的任务,Node.js 提供了 Worker Threads 模块,允许开发者创建子线程来执行并行计算。子线程之间通过消息传递来交换数据。
- Cluster 模块: Cluster 模块允许 Node.js 应用程序利用多核 CPU 的能力。它可以创建多个子进程来分担工作负载,从而提高整体的吞吐量。
总的来说,Node.js 通过事件循环、非阻塞 I/O、回调函数、Promise/async-await 以及 Worker Threads 和 Cluster 模块等方式实现了并发处理能力。这些机制使 Node.js 能够在单线程上高效地处理大量并发请求。
37.node 事件循环机制
38.JS 的单线程和浏览器的多进程架构
概述 此外,JS 最初是为了解决⽹⻚交互的问题⽽诞⽣的,⽽⽹⻚交互的需求⼤部分是基于⽤户事件的,⽐如点击按钮、输⼊⽂本等。这些操作的响应速度要求很⾼,如果在响应事件的同时还要处理其他任务,可能会导致⽹⻚卡顿、响应变慢等⽤户体验不佳的问题。
为了利⽤多核 CPU 的计算能⼒,HTML5 提出 Web Worker 标准,允许 JS 脚本创建多个线程,但是⼦线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JS 单线程的本质。
浏览器的多进程架构 “JS 是单线程的” 指的是执⾏ JS 的线程只有⼀个,是浏览器提供的 JS 引擎线程(主线程)。
如今的主流浏览器都是多进程架构的,以 Chrome 为例,它包含了 1 个浏览器主进程、1个 GPU 进程、1 个⽹络进程、多个渲染进程或多个插件进程。默认情况下,Chrome 会为每个 Tab 标签创建⼀个渲染进程。⼀个渲染进程通常由以下线程组成:
JS 引擎线程(主线程): JavaScript 引擎,也称为 JS 内核,负责处理 JS 脚本,执⾏代码。当主线程空闲且任务队列不为空时,会依次取出任务执⾏。注意,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执⾏ JS 时间过⻓,将导致⻚⾯渲染的阻塞。 GUI 渲染线程: 主要负责⻚⾯的渲染,解析 HTML、CSS,构建DOM树,布局和绘制等。当界⾯需要重绘或者由于某种操作引发重排时,将执⾏该线程。注意:该线程与 JS 引擎线程互斥,当执⾏ JS 引擎线程时,GUI 线程会被挂起,当任务队列空闲时,主线程才会去执⾏ GUI 渲染。 事件触发线程: ⽤于控制事件循环,将准备好的事件交给 JS 引擎线程执⾏。当主线程遇到异步任务,如 setTimeOut(或 ajax 请求、⿏标点击事件),会将它们交由对应的线程处理,处理完毕后,事件触发线程会把对应的事件添加到任务队列的尾部,等待 JS 引擎的处理。注意:由于 JS 的单线程关系,队列中的待处理事件都得排队等待,只有在 JS 引擎空闲时才能被执⾏。 定时器触发线程: 负责执⾏定时器⼀类函数的线程,如 setTimeout,setInterval 等。主线程依次执⾏代码时,遇到定时器,会将定时器交由该线程进⾏计时,当计时结束,事件触发线程会将定时器的回调函数添加到任务队列的尾部,等待 JS 引擎空闲后执⾏。 异步 http 请求线程: 负责执⾏异步请求⼀类的函数的线程,如 Promise,axios,ajax 等。主线程依次执⾏代码时,遇到异步请求,会将函数交给该线程处理。当监听到状态码变更,如果设置有回调函数,事件触发线程会将相应的回调函数添加到任务队列的尾部,等待 JS 引擎空闲后执⾏。