工具链
Eslint 代码检查的过程是啥?
ESLint 是一个插件化的静态代码分析工具,用于识别 JavaScript 代码中的问题。它在代码质量和编码风格方面有助于保持一致性。代码检查的过程通常如下:
-
配置: 首先需要为 ESLint 提供一套规则,这些规则可以在
.eslintrc
配置文件中定义,或者在项目的package.json
文件中的eslintConfig
字段里指定。规则可以继承自一套已有的规则集,如eslint:recommended
,或者可以是一个流行的样式指南,如airbnb
。也可以是自定义的规则集。 -
解析: 当运行 ESLint 时,它会使用一个解析器(如
espree
,默认的解析器)来解析代码,将代码转换成一个抽象语法树(AST)。AST 是代码结构的一个树状表示,能让 ESLint 理解代码的语义结构。 -
遍历: 一旦代码被转换成 AST,ESLint 则会遍历该树。它会查找树的每个节点,检查是否有任何规则适用于该节点。在遍历过程中,如果发现违反了某项规则,ESLint 将记录一个问题(通常称为“lint 错误”)。
-
报告: 在遍历完整个 AST 之后,ESLint 会生成一份报告。这份报告详细说明了它在代码中找到的任何问题。这些问题会被分类为错误或警告,根据配置设置的不同,某些问题可能会阻止构建过程或者被忽略。
-
修复: 对于某些类型的问题,ESLint 提供了自动修复的功能。这意味着你可以让 ESLint 尝试自动修复它所发现的问题,不需人工干预。
-
集成: ESLint 可以集成到 IDE 中,这样就可以在代码编写过程中即时提供反馈。它也可以被集成到构建工具如 Webpack 或任务运行器 Grunt、Gulp 中,作为构建过程或提交代码到版本控制系统前的一个步骤。
通过以上步骤,ESLint 帮助开发者在编码过程中遵循一致的风格和避免出现潜在的错误。
模块化加载?
Webpack 支持以下几种模块化标准:
-
ESM (ECMAScript Modules): 这是 JavaScript ES6 中引入的官方标准模块系统。使用
import
和export
语句来导入和导出模块。 -
CommonJS: 主要用于 Node.js,允许使用
require()
来加载模块和module.exports
来导出模块。 -
AMD (Asynchronous Module Definition): 用于异步加载模块,并使用
define
方法来定义模块。 -
UMD (Universal Module Definition): 结合了 AMD 和 CommonJS 的特点,并支持全局变量定义的方式,使得模块可以在客户端和服务端上运行。
除此之外,Webpack 还可以处理非 JavaScript 文件并将它们视为模块,例如 CSS, LESS, SASS, 图像文件(PNG, JPG, GIF, SVG 等), 字体(OTF, TTF, WOFF, WOFF2, EOT), HTML 以及任何其他类型的文件。这通过使用相应的 loader 来实现,如 style-loader
, css-loader
, file-loader
等。这些 loader 会将非 JavaScript 文件转换为可以被 Webpack 处理的模块。
参考文档
前端模块化是指在前端开发中,通过模块化的方式组织代码,将代码按照一定规则分割成不同的模块,便于管理和维护。
前端模块化的发展历程如下:
-
早期,前端开发采用的是全局变量的方式进行开发,即将所有代码都放在一个文件中,通过全局变量进行交互。这种方式的问题在于,代码量较大,代码耦合度高,不易维护。
-
后来,前端开发采用了命名空间的方式进行组织代码,即将代码放在一个命名空间下,通过命名空间进行交互。这种方式解决了全局变量带来的问题,但是在开发大型应用时,仍然存在代码耦合度高、依赖管理不便等问题。
-
2009年,CommonJS提出了一种新的模块化规范,即将每个模块封装在一个独立的文件中,通过require和exports进行模块之间的依赖管理和导出。这种方式解决了前两种方式带来的问题,但是由于该规范是同步加载模块,不适用于浏览器环境。
-
2011年,AMD规范提出,即异步模块定义规范,采用异步的方式加载模块,可以在浏览器环境下使用。该规范主要是通过require和define方法进行模块之间的依赖管理和导出。
-
2013年,CommonJS和AMD的创始人合并了两种规范,提出了新的规范——CommonJS 2.0规范。该规范在CommonJS 1.0的基础上,增加了异步加载的功能,使其可以在浏览器环境下使用。
-
2014年,ES6(即ECMAScript 2015)正式发布,引入了模块化的支持,即通过import和export语句进行模块之间的依赖管理和导出。ES6的模块化规范具有更好的可读性、可维护性和性能优势,已成为前端开发的主流方式。
-
同时,还有一些第三方库,如RequireJS、SeaJS等,提供了更加灵活和可扩展的模块化方式,使得前端开发的模块化更加便捷和高效。
1 函数作为块
最开始的时候,是以函数为块来编程,因为函数有自己的作用域,相对比较独立
function add (a, b) {
// ...
}
function add1 (a, b, c) {
// ...
}
这种形式中,add和add1都是定义在全局作用域中的,会造成很多问题:
- 污染全局作用域,容易造成命名冲突
- 定义在全局作用域,数据不安全
2 namespace模式
使用对象作为独立块编程
const myModule = {
a: 1,
b: 2,
add: function (m, n) {
// ...
}
}
优点:减少了全局变量,有效解决了命名冲突
缺点:
- 没有私有变量,使用起来很繁琐
- 数据不安全,模块外面可以随意修改内部的数据
3 IIFE模式
使用立即执行函数来创建块,可以形成独立的作用域,外面无法访问,借助window对象来向外暴露接口
(function ($) {
const a = 1
const b = 2
function add (m, n) {
// ...
}
$('#id').addClass('.hehe')
window.myModule = {
a,
b,
add
}
})()
优点:
- 减少了全局变量,解决了命名冲突
- 创建了独立的作用域,外部无法轻易修改内部数据
缺点:
如果多个模块分布在多个js文件中,那么在html文件中就需要引入多个js文件
- 会增加多个http请求,增加首屏的时候,降低用户体验
- 模块之间的引用关系很不明显,难以维护
4 CommonJS
最开始出现的模块化方案是在node.js中实现的。node中的模块化方案是根据CommonJS规范实现的。
CommonJS规定每个文件就是一个模块,以同步的方式引入其他模块
// a.js
function add (m, n) {
return m + n
}
module.exports = { add }
// b.js
// eslint-disable-next-line
const { add : add1 } = require('./a.js');
console.log(add1(1, 2)) // 3
这种方式是node端独有的,浏览器端如果想要使用,需要使用 Browserify 工具来解析。
5 AMD和Require.js
CommonJS模块之前是同步引入的,这在服务端是没有什么问题的,因为文件都是保存在硬盘中,读取文件的速度是非常快的,同步加载带来的阻塞基本可以忽略不计。
但是如果在浏览器中使用CommonJS的话,因为js文件是存在服务端需要请求获取,所以同步的方式加载会极大的阻塞页面,显然是不可取的。
于是诞生了AMD(Asynchronous Module Definition)规范,一种异步加载的模块方案,使用回调函数来实现。require.js实现了AMD的规范。
// 定义没有依赖的模块
// a.js
define(function () {
function add (m, n) {
return m + n
}
return { add }
})
// 定义有依赖的模块
// b.js
define(['a'], function (a) {
const sum = a.add(1, 2)
return { sum }
})
// 引用模块
require(['b'], function (b) {
console.log(b.sum) // 3
})
由上面代码分析Require.js的特点
- 依赖模块的代码都是放在回调函数中,等待模块都加载完成才执行这个回调函数,执行顺序可以保证
- 内部加载其他模块的时候,使用的是动态添加script标签的方式来实现动态加载
- 内部需要缓存模块暴露出来的接口,避免多次执行
AMD推崇的是依赖前置,提前执行。
从上面代码可以看出,**在声明一个模块的时候,会在第一时间就将其依赖模块的内部代码执行完毕。而不是在真正使用的地方再去执行。**因此会带来一些资源浪费
define(['a', 'b'], function (a, b) {
let sum = a.add(1, 2)
// eslint-disable-next-line
if (false) {
sum = b.add(1, 2) // b模块是没有被使用的,应该是不需要执行模块内部代码的
}
return sum
})
6 CMD和Sea.js
由于require.js自身的一些问题存在,所以后来在国内(玉伯)诞生了CMD(Common Module Definition)和Sea.js。
CMD结合了CommonJS和AMD的特点,也是一种异步模块的方案,提倡就近依赖,延迟执行。
需要用到某个模块的时候,才用require引入,模块内部的代码也是在被引入的时候才会执行,声明的时候并没有执行。
语法设计上比较像CommonJS
// 定义模块 math.js
define(function (require, exports, module) {
const a = require('./a.js') // 引入模块
function add (m, n) {
return m + n
}
module.exports = {
add
}
})
// 加载模块
seajs.use(['math.js'], function (math) {
const sum = math.add(1, 2)
})
看上面的代码可能会有疑问,模块是异步加载的,但是使用的时候require是同步使用的,没有回调函数,如何能够保证执行的顺序呢?这就不得不提sea.js中的静态依赖分析机制了。
6.1 Sea.js中的静态依赖分析机制
Sea.js中模块加载的入口方法是use()方法,执行这个方法会开始加载所有的依赖模块。然后sea.js中是就近依赖的,它是如何获取依赖模块的呢?
在define的方法中,如果传入的参数factory是一个函数,内部会执行函数的toString方法,转化成字符串,然后通过正则表达式分析字符串,获取require方法中的参数,通过路径分析去加载依赖的模块。以此链式分析下去,边分析边加载模块,等待所有的依赖都加载完成之后,才开始调用use的回调函数,正式执行模块内代码。
所以在require方法执行之前,对应的模块已经加载完成了,所以可以直接传入参数,执行模块函数体。
6.2 Sea.js的特点
- 就近依赖,延时执行
- 内部拥有静态依赖分析机制,保证require之前,模块已经加载完毕,但是函数还没有执行
- 也是一种异步的模块化方案
- 内部也有缓存机制,缓存模块暴露的接口
- 内部加载模块的时候,和require.js一样,也是通过动态增加script标签来完成的
7 ES Module
ES6开始,在语法标准上实现了模块化功能。简称ES Module
ES Module是一种静态依赖的模块化方案,模块与模块之间的依赖关系是在编译期完成连接的。
前面所说的三种方案都是动态模块化方案,依赖模块都是动态引入的,而且模块都是一个对象。而ES Module中,模块不是一个对象,模块与模块之间也不是动态引入的,而且编译期间静态引入的,所以无法实现条件加载
// a.js
function add (m, n) {
return m + n
}
export { add }
// b.js
// eslint-disable-next-line
// import { add } from './a.js';
// console.log(add(1,2)); //3
axios 有哪些特性?
直接可以参考官网链接: 资料
特点
- 从浏览器创建XMLHttpRequest
- 从node.js生成http请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 超时时间
- 支持嵌套项的查询参数序列化
- 自动请求体序列化为:
- JSON (应用程序/ison)
- 多部分/表格数据 (多部分/表格数据)
- URL编码形式 (申请书/x-www-form-urlencoded )
- 以JSON格式发布HTML表单
- 响应中的自动JSON数据处理
- 为浏览器和node.js捕获进度,附带额外信息 (速度、剩余时间)
- 设置node.is的带宽限制
- 兼容符合规范的FormData和Blob (包括节点) js)
- 客户端对XSRF的保护支持
axios 是如何区分是 nodejs 环境还是 浏览器环境 的?
Axios 是一个跨平台的 HTTP 客户端库,可以在浏览器和 Node.js 中使用。Axios 通过判断当前环境来确定是在浏览器还是在 Node.js 环境中运行。
在浏览器环境中,Axios 默认会使用浏览器提供的 XMLHttpRequest 对象来发送 HTTP 请求。
在 Node.js 环境中,Axios 会检查是否存在 process
全局对象,以及 process
对象中是否存在 nextTick
方法。如果存在以上两个条件,Axios 就默认在 Node.js 环境中运行,并使用 Node.js 内置的 http
模块发送 HTTP 请求。
如果需要明确指定运行环境,可以使用 axios.defaults.adapter
属性来设置适配器(adapter),以便在需要时手动选择使用 XMLHttpRequest 或 Node.js 内置的 http
模块。
例如,在 Node.js 环境中可以这样设置适配器:
const axios = require('axios')
const httpAdapter = require('axios/lib/adapters/http')
axios.defaults.adapter = httpAdapter
通过上述方式,Axios 可以根据环境自动选择适当的底层实现来发送 HTTP 请求,使其在不同的环境中都能正常工作。
axios interceptor
Axios 是一个基于 Promise 的 HTTP 客户端库,可以用于浏览器和 Node.js 环境中发送 HTTP 请求。Axios 提供了拦截器机制,可以在请求发送前和响应返回后对请求和响应进行拦截和处理,从而实现一些通用的功能,例如:添加请求头、添加认证信息、显示 loading 状态、错误处理等。
Axios 的拦截器机制主要是通过 interceptors
属性来实现的,该属性包含了 request
和 response
两个对象,分别代表请求拦截器和响应拦截器。每个对象都包含 use
方法,该方法用于注册拦截器回调函数,拦截器回调函数会在请求发送前或响应返回后被调用。
下面是一个示例代码,展示了如何使用 Axios 的拦截器:
import axios from 'axios'
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('请求拦截器')
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('响应拦截器')
return response
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
// 发送请求
axios.get('/api/user')
.then(function (response) {
// 处理响应数据
})
// eslint-disable-next-line
.catch(function (error) {
// 处理请求错误
})
在上面的代码中,我们首先通过 import
语句引入了 Axios 库。然后,我们调用 axios.interceptors.request.use
方法注册了一个请求拦截器回调函数,该函数会在发送请求前被调用,可以在该函数中进行一些通用的操作,例如添加请求头、添加认证信息等。接着,我们调用 axios.interceptors.response.use
方法注册了一个响应拦截器回调函数,该函数会在响应返回后被调用,可以在该函数中进行一些通用的操作,例如显示 loading 状态、错误处理等。
最后,我们使用 axios.get
方法发送请求,并通过 then
和 catch
方法处理响应数据和请求错误。在请求发送前和响应返回后,我们注册的拦截器回调函数会被自动调用,可以对请求和响应进行拦截和处理。
Axios 的拦截器机制非常强大,可以用于实现一些通用的功能,例如添加请求头、添加认证信息、显示 loading 状态、错误处理等。在实际开发中,我们经常会使用 Axios 的拦截器来提高代码的复用性和可维护性。
axios 拦截器原理
Axios 的拦截器机制是通过 interceptors
属性来实现的,该属性包含了 request
和 response
两个对象,分别代表请求拦截器和响应拦截器。每个对象都包含 use
方法,该方法用于注册拦截器回调函数,拦截器回调函数会在请求发送前或响应返回后被调用。
具体来说,当我们使用 axios
发送请求时,会先调用请求拦截器的回调函数,该函数会在请求发送前被调用,可以在该函数中进行一些通用的操作,例如添加请求头、添加认证信息等。如果请求拦截器返回的不是一个 Promise 对象,则会自动将其封装为一个 Promise 对象。
接着,Axios 会使用 XMLHTTPRequest 对象发送请求,并监听其状态变化事件。当响应返回后,Axios 会调用响应拦截器的回调函数,该函数会在响应返回后被调用,可以在该函数中进行一些通用的操作,例如显示 loading 状态、错误处理等。如果响应拦截器返回的不是一个 Promise 对象,则会自动将其封装为一个 Promise 对象。
需要注意的是,Axios 的拦截器是按照添加顺序依次执行的,也就是说,先添加的拦截器回调函数先执行,后添加的拦截器回调函数后执行。如果一个拦截器回调函数中没有调用 next
方法,则后面的拦截器回调函数将不会被执行。
下面是一个示例代码,展示了如何使用 Axios 的拦截器:
import axios from 'axios'
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('请求拦截器')
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('响应拦截器')
return response
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
// 发送请求
axios.get('/api/user')
.then(function (response) {
// 处理响应数据
})
// eslint-disable-next-line
.catch(function (error) {
// 处理请求错误
})
在上面的代码中,我们首先通过 import
语句引入了 Axios 库。然后,我们调用 axios.interceptors.request.use
方法注册了一个请求拦截器回调函数,该函数会在发送请求前被调用,可以在该函数中进行一些通用的操作,例如添加请求头、添加认证信息等。接着,我们调用 axios.interceptors.response.use
方法注册了一个响应拦
axios 拦截器核心逻辑代码实现
下面是一个简单实现 Axios 拦截器核心逻辑的示例代码:
class Axios {
constructor () {
// 请求拦截器
this.requestInterceptors = []
// 响应拦截器
this.responseInterceptors = []
}
// 注册请求拦截器
useRequestInterceptor (callback) {
this.requestInterceptors.push(callback)
}
// 注册响应拦截器
useResponseInterceptor (callback) {
this.responseInterceptors.push(callback)
}
// 发送请求
async request (config) {
// 执行请求拦截器
for (const interceptor of this.requestInterceptors) {
config = await interceptor(config)
}
// 发送请求
const response = await fetch(config.url, {
method: config.method,
headers: config.headers,
body: config.data
})
// 执行响应拦截器
for (const interceptor of this.responseInterceptors) {
// eslint-disable-next-line
response = await interceptor(response)
}
return response
}
}
// 创建 Axios 实例
const axios = new Axios()
// 注册请求拦截器
axios.useRequestInterceptor(config => {
// 在请求头中添加认证信息
config.headers.Authorization = 'Bearer xxx'
return config
})
// 注册响应拦截器
axios.useResponseInterceptor(response => {
// 处理响应数据
return response.json()
})
// 发送请求
axios.request({
url: '/api/user',
method: 'GET'
}).then(data => {
// 处理响应数据
console.log(data)
}).catch(error => {
// 处理请求错误
console.error(error)
})
在上面的代码中,我们首先定义了一个 Axios
类,该类包含了请求拦截器和响应拦截器两个属性,分别用于保存注册的拦截器回调函数。然后,我们定义了 useRequestInterceptor
和 useResponseInterceptor
两个方法,用于注册请求拦截器和响应拦截器回调函数。在这两个方法中,我们将回调函数保存到对应的属性中。
接着,我们定义了 request
方法,该方法用于发送请求。在 request
方法中,我们首先执行请求拦截器回调函数,将请求配置传递给回调函数,并将回调函数返回的结果赋值给请求配置。接着,我们使用 fetch
函数发送请求,并将响应保存到 response
变量中。然后,我们执行响应拦截器回调函数,将响应对象传递给回调函数,并将回调函数返回的结果赋值给响应对象。最后,我们返回响应对象。
在最后几行代码中,我们创建了一个 axios
实例,并使用 useRequestInterceptor
方法和 useResponseInterceptor
方法注册了请求拦截器和响应拦截器回调函数。然后,我们调用 request
方法发送请求,并使用 then
方法处理响应数据,使用 catch
方法处理请求错误。
axios 如何取消请求
取消请求
timeout
在 axios 调用中设置属性可处理响应相关的超时。
在某些情况下(例如网络连接不可用),提前取消连接对 axios调用大有裨益。如果不取消连接,axios 调用可能会挂起,直到父代码/堆栈超时(在服务器端应用程序中可能需要几分钟)。
要终止 axios 调用,您可以使用以下方法:
signal
cancelToken
(已弃用)
组合timeout
和取消方法(例如signal
)应该涵盖响应相关的超时和连接相关的超时。
signal
:中止控制器
从v0.22.0
Axios 开始支持AbortController
以 fetch API 方式取消请求:
const controller = new AbortController()
axios
.get('/foo/bar', {
signal: controller.signal
})
.then(function (response) {
// ...
})
// cancel the request
controller.abort()
使用最新 API nodejs 17.3+
. 的超时示例AbortSignal.timeout()
:
axios
.get('/foo/bar', {
signal: AbortSignal.timeout(5000) // Aborts request after 5 seconds
})
.then(function (response) {
// ...
})
具有超时辅助函数的示例:
function newAbortSignal (timeoutMs) {
const abortController = new AbortController()
setTimeout(() => abortController.abort(), timeoutMs || 0)
return abortController.signal
}
axios
.get('/foo/bar', {
signal: newAbortSignal(5000) // Aborts request after 5 seconds
})
.then(function (response) {
// ...
})
取消令牌deprecated
还可以使用 CancelToken_取消请求。
Axios 取消令牌 API 基于已撤回的可取消承诺提案。
此 API 已弃用
v0.22.0
,不应在新项目中使用
您可以使用工厂创建取消令牌CancelToken.source
,如下所示:
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios
.get('/user/12345', {
cancelToken: source.token
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message)
} else {
// handle error
}
})
axios.post(
'/user/12345',
{
name: 'new name'
},
{
cancelToken: source.token
}
)
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.')
您还可以通过将执行器函数传递给构造函数来创建取消令牌CancelToken
:
const CancelToken = axios.CancelToken
let cancel
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor (c) {
// An executor function receives a cancel function as a parameter
cancel = c
})
})
// cancel the request
cancel()
注意:您可以使用相同的取消令牌/信号取消多个请求。
在过渡期间,你可以使用这两个取消 API,即使对于同一个请求也是如此:
const controller = new AbortController()
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios
.get('/user/12345', {
cancelToken: source.token,
signal: controller.signal
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message)
} else {
// handle error
}
})
axios.post(
'/user/12345',
{
name: 'new name'
},
{
cancelToken: source.token
}
)
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.')
// OR
controller.abort() // the message parameter is not supported
949 axios 如何注销拦截器
一、为拦截器分配一个引用
- 创建拦截器时保存引用:
- 当创建一个 Axios 请求或响应拦截器时,可以将其分配给一个变量,以便后续可以引用并注销它。
const requestInterceptor = axios.interceptors.request.use((config) => {
// 请求拦截器逻辑
return config
})
const responseInterceptor = axios.interceptors.response.use((response) => {
// 响应拦截器逻辑
return response
})
二、使用Eject
方法注销拦截器
- 注销单个拦截器:
- 使用拦截器的引用和
axios.interceptors.request.eject()
或axios.interceptors.response.eject()
方法来注销特定的拦截器。
axios.interceptors.request.eject(requestInterceptor)
axios.interceptors.response.eject(responseInterceptor)
- 注销所有拦截器:
- 如果需要注销所有的请求或响应拦截器,可以使用
axios.interceptors.request.clear()
或axios.interceptors.response.clear()
方法。
axios.interceptors.request.clear()
axios.interceptors.response.clear()
以下是一个完整的示例:
import axios from 'axios'
const requestInterceptor = axios.interceptors.request.use((config) => {
// 请求拦截器逻辑
return config
})
const responseInterceptor = axios.interceptors.response.use((response) => {
// 响应拦截器逻辑
return response
})
// 注销特定拦截器
axios.interceptors.request.eject(requestInterceptor)
axios.interceptors.response.eject(responseInterceptor)
// 或者注销所有拦截器
// axios.interceptors.request.clear();
// axios.interceptors.response.clear();
通过这些方法,可以在需要的时候注销特定的拦截器或所有拦截器,以灵活地管理 Axios 的拦截器。
解释下 axios withCredentials 配置
在 Axios 中,withCredentials
是一个配置选项,用于处理跨源请求时是否携带用户凭证(cookies、HTTP 认证信息等)。
一、作用
- 默认行为:
- 默认情况下,
withCredentials
的值为false
。这意味着在跨源请求中,浏览器不会自动发送用户凭证。 - 例如,当你使用 Axios 向不同域名的服务器发送请求时,如果
withCredentials
为false
,浏览器不会在请求中包含任何用户凭证信息。
- 启用凭证发送:
- 如果将
withCredentials
设置为true
,则在跨源请求中,浏览器会自动包含用户凭证信息,如 cookies、HTTP 认证信息等。 - 这对于需要在不同域名之间共享用户认证状态的应用非常有用。例如,一个单页应用(SPA)可能需要与不同的后端服务进行交互,并且希望在这些服务之间共享用户登录状态。
二、注意事项
- 服务器端配置:
- 要使
withCredentials
生效,服务器端也需要进行相应的配置,以允许接收跨源请求中的凭证信息。 - 服务器需要设置适当的 CORS(跨源资源共享)响应头,如
Access-Control-Allow-Credentials: true
,并且指定允许的源Access-Control-Allow-Origin
不能为通配符*
,而必须是具体的源地址。
- 安全考虑:
- 启用
withCredentials
可能会带来安全风险,因为用户凭证可能会被发送到不受信任的服务器。因此,在使用时需要谨慎考虑安全问题,并确保只向可信任的服务器发送凭证信息。
例如:
axios
.get('http://another-domain.com/api/data', {
withCredentials: true
})
.then((response) => {
console.log(response.data)
})
.catch((error) => {
console.error(error)
})
在这个例子中,Axios 发送一个跨源请求,并将withCredentials
设置为true
,以尝试在请求中包含用户凭证信息。
eslint 如何设置只校验本次 MR 变更的文件内容
要让 ESLint 只校验在 Merge Request (MR)、Pull Request (PR)或代码提交中变更的文件,可以采用几种方法。下面是几个可能的方案:
- 命令行 Git 和 ESLint 组合使用
通过组合git
命令和eslint
命令来实现。首先,使用git diff
获取变更的文件列表,然后将这些文件传递给eslint
进行校验。
获取master分支与当前分支变更的文件列表,然后对这些文件执行eslint校验
git diff --name-only --diff-filter=d master | grep '\.js$' | xargs eslint
这里的命令解释:
git diff --name-only --diff-filter=d master
:获取相对于master
分支变更的文件列表,--diff-filter=d
表示排除已删除的文件。grep '\.js$'
:过滤出.js
结尾的文件。xargs eslint
:将过滤后的文件列表作为参数传递给eslint
命令。
注意:这个命令以master
分支作为对比对象,如果你需要对比其他分支,请将master
替换为相应的分支名。
- 使用 lint-staged 运行 ESLint
lint-staged 是一个在 git 暂存文件上运行 linters 的工具,它非常适合与 pre-commit 钩子结合使用,确保只有符合代码规范的代码才能被提交。
首先,安装lint-staged
和husky
(用于管理 git 钩子的工具):
npm install lint-staged husky --save-dev
然后,你可以在项目的package.json
文件中配置lint-staged
:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": ["eslint --fix", "git add"]
}
}
这样配置后,每次执行git commit
操作时,husky
会触发pre-commit
钩子,运行lint-staged
,再由lint-staged
运行 ESLint 检查所有暂存的.js
文件。通过这种方式,只有变更的并且被 git track 的文件会被 ESLint 校验。
- CI/CD 中集成 ESLint
在持续集成/持续部署 (CI/CD) 流程中,你也可以配置脚本使用类似于第一个方案的命令,只校验在 MR/PR 中变更的文件。具体实现方式会依赖于你使用的 CI/CD 工具(如 GitLab CI、GitHub Actions、Jenkins 等)。
通过在 CI/CD 流程中加入这一步,可以确保只有通过 ESLint 校验的代码变更才能合并到主分支。
请求数量过多,该如何治理
1. 常量请求做本地内存存储
不是使用 https 缓存, 而是直接存一个 promise 在浏览器内存里面。 保证整个系统里面, 请求只调用一次。
对于一些数据不经常变化的请求,例如用户信息、配置数据等,可以将请求的结果缓存起来。下一次请求相同的资源时,先从缓存中读取数据,如果缓存有效,则无需再发起新的网络请求。
思路类似于下面这张图
要达到这样的效果,可以设计一个请求缓存管理器,来管理并发的请求。如果有相同的请求(URL、参数、方法相同)时,只发起一次网络调用,然后将结果分发给所有等待的请求。这种模式通常可以通过一个简单的缓存对象来实现,该对象将请求的唯一标识作为键,对应的 Promise 作为值。
以下是一个基本实现的示例:
class RequestCache {
constructor () {
this.cache = new Map()
}
// 生成请求的唯一标识符,这里仅以 URL 和 Method 为例,实际可能需要包括请求体等
generateKey (url, method) {
return `${method}:${url}`
}
// 执行请求的方法,接受 fetch 的所有参数
request (url, options = {}) {
const { method = 'GET' } = options
const key = this.generateKey(url, method)
// 检查缓存中是否有相同的请求
if (this.cache.has(key)) {
return this.cache.get(key)
}
// 没有相同的请求,发起新的请求
const requestPromise = fetch(url, options)
.then((response) => response.json())
.then((data) => {
// 请求成功后,将其从缓存中移除
this.cache.delete(key)
return data
})
.catch((error) => {
// 请求失败也应该从缓存中移除
this.cache.delete(key)
throw error
})
// 将新的请求 Promise 保存在缓存中
this.cache.set(key, requestPromise)
return requestPromise
}
}
// 使用示例
const cache = new RequestCache()
const URL = 'https://api.example.com/data'
// 假设这三个请求几乎同时发起
cache.request(URL).then((data) => console.log('请求1:', data))
cache.request(URL).then((data) => console.log('请求2:', data))
cache.request(URL).then((data) => console.log('请求3:', data))
这个简单的 RequestCache
类通过一个内部的 Map
对象管理缓存的请求。当一个新的请求发起时,它会首先检查是否已经有相同的请求存在。如果已存在,那么它只返回先前请求的 Promise;如果不存在,它会发起一个新的网络请求,并将请求的 Promise 存储在缓存中,直到请求完成(无论是成功还是失败)之后,再将其从缓存中移除。
请注意,这里的示例非常基础,且主要用于说明如何缓存并复用请求的结果。在实际应用中,你可能还需要考虑更多细节,比如如何更精细地处理 POST 请求的请求体内容、如何设置缓存的过期时间、错误处理策略、缓存大小限制等。
推荐参考文档: 资料
2. 合并请求
对于多个小请求,特别是对同一个服务器或 API 的调用,考虑将它们合并为一个较大的请求。例如,如果有多个 API 分别获取用户信息、用户订单、用户地址等,可以考虑后端提供一个合并接口,一次性返回所有所需数据。
3. 使用 Web 缓存
- 浏览器缓存:利用 HTTP 缓存头控制静态资源(CSS、JS、图片)的缓存策略,减少重复请求。
- 数据缓存:对于 AJAX 请求的响应,可以在前端进行数据缓存,避免短时间内对相同资源的重复请求。
4. 延迟加载(懒加载)
对于非首屏必须的资源(如图片、视频、长列表等),可以采用延迟加载或懒加载的方式,只有当用户滚动到相应位置时才加载这些内容,减少初次加载时的请求数量。
5. 使用服务工作线程(Service Workers)
通过 Service Workers 可以拦截和缓存网络请求,实现离线体验,减少对服务器的请求。此外,Service Workers 还可以用于请求合并、请求失败的重试策略等。
6. 避免重复请求
在某些情况下,为了保证数据的实时性,前端可能会频繁地轮询服务器。可以通过设置合理的轮询间隔或采用基于 WebSocket 的实时数据推送方案,以减少请求次数。
7. 使用 GraphQL
对于 REST API 可能导致的过度取数据(over-fetching)或取少数据(under-fetching)问题,可以考虑使用 GraphQL。GraphQL 允许客户端准确指定所需数据的结构,一次请求准确获取所需信息,减少无效数据的传输。
8. 防抖和节流
在处理连续的事件触发对后端的请求(如输入框实时搜索、窗口大小调整等)时,使用防抖(debouncing)和节流(throttling)技术可以限制触发请求的频率,减少不必要的请求量。
semantic-release
在编写 npm 包时,可以使用自动化工具来生成 changelog 和自动更新 tag。以下是你可以使用的一些流行的工具以及它们的基本用法。
- semantic-release: 这是一个全自动的版本管理和包发布工具。它能根据 commit 信息来自动决定版本号、生成变更日志(changelog)以及发布。
要使用 semantic-release,你需要按照以下步骤操作:
- 安装 semantic-release 工具:
npm install -D semantic-release
- 在项目中添加配置文件 (
semantic-release.config.js
) 或在package.json
中配置。 - 在 CI 工具中(例如 GitHub Actions、Travis CI)配置发布脚本。
- 遵循规范化的 commit 消息风格(如 Angular 规范),因为 semantic-release 会根据 commit 消息来确定版本号和生成 changelog。
- standard-version: 如果你更希望进行半自动化的版本管理,standard-version 是一个很好的替代选择。它可以自动地根据 commit 记录来生成 changelog。
使用 standard-version 的大致步骤如下:
- 安装 standard-version 工具:
npm install --save-dev standard-version
- 在
package.json
中配置脚本:
{
"scripts": {
"release": "standard-version"
}
}
- 当你准备发布新版本时,运行以下命令:
npm run release
- standard-version 会自动根据 commit 消息创建一个新的 tag,并更新 changelog。然后,你可以手动推送这些改动到仓库。
在这两种情况下,都推荐使用遵循某种规范的 commit 消息,如 Conventional Commits 规范,这样可以让工具更准确地解析 commit 消息来进行版本管理。此外,确保你的 CI/CD 系统有足够的权限来推送 tags 到远程仓库。
express middleware(中间件) 工作原理是什么
express middleware 工作原理是什么?
Express middleware 的工作原理是通过拦截 HTTP 请求,对请求进行处理,然后将请求传递给下一个中间件或应用程序的路由处理。在 Express 中,中间件可以是一个或多个函数,每个函数都可以对请求进行操作或响应,从而实现对请求的处理和过滤。
当 Express 应用程序接收到一个 HTTP 请求时,请求将首先被传递给第一个注册的中间件函数。这个中间件函数可以对请求进行操作,例如修改请求的头信息、检查请求是否包含有效的身份验证令牌等等。当这个中间件函数完成操作后,它可以选择将请求传递给下一个中间件函数,或者直接将响应返回给客户端。
如果中间件函数选择将请求传递给下一个中间件函数,它可以调用 next() 函数来将控制权传递给下一个中间件。这个过程可以一直持续到所有中间件函数都被执行完毕,最后将请求传递给应用程序的路由处理函数。
通过使用中间件,开发人员可以将应用程序的功能模块化,从而实现更好的代码组织和可维护性。同时,中间件还可以实现各种功能,例如身份验证、日志记录、错误处理等等,从而为应用程序提供更丰富的功能和更好的用户体验。
的设计模式是啥?写一个简单的示例呢
Express middleware 的设计模式是基于责任链模式。在责任链模式中,每个对象都有机会处理请求,并将其传递给下一个对象,直到请求被完全处理为止。在 Express 中,每个中间件函数都有机会对请求进行处理,并可以选择将请求传递给下一个中间件函数或应用程序的路由处理函数。
以下是一个简单的示例,演示如何使用 Express middleware 实现身份验证:
const express = require('express')
const app = express()
// 定义一个中间件函数,用于验证用户身份
function authenticate (req, res, next) {
const token = req.headers.authorization
if (token === 'secret-token') {
// 如果令牌有效,则将控制权传递给下一个中间件函数
next()
} else {
// 否则,返回 401 错误
res.status(401).send('Unauthorized')
}
}
// 注册中间件函数,用于验证用户身份
app.use(authenticate)
// 定义一个路由处理函数,用于返回受保护的资源
app.get('/protected', (req, res) => {
res.send('Protected resource')
})
// 启动应用程序
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
在上面的示例中,我们定义了一个名为 authenticate 的中间件函数,它用于验证用户的身份。在这个函数中,我们检查请求头中是否包含有效的身份验证令牌。如果令牌有效,则将控制权传递给下一个中间件函数或路由处理函数。否则,返回 401 错误。
然后,我们通过调用 app.use() 方法来注册这个中间件函数,以便在每个请求中都进行身份验证。最后,我们定义一个名为 /protected 的路由处理函数,它表示受保护的资源。只有在身份验证通过后,才能访问这个路由处理函数。
通过这个简单的示例,我们可以看到如何使用 Express middleware 实现基本的身份验证功能。中间件函数充当责任链中的一个环节,通过对请求进行处理和过滤,为应用程序提供更好的安全性和用户体验。