前端 JS 异常那些事

前言

人无完人,所以代码总会出异常的,异常并不可怕,关键是怎么处理

什么是异常

程序发生了意想不到的情况,影响到了程序的正确运行

从根本上来说,异常就是一个普通的对象,其保存了异常发生的相关信息,比如错误码、错误信息等。以 JS 中的标准内置对象 Error 为例,其标准属性有 message。许多宿主环境额外增加了 filename 和 stack 等属性

错误只有被 throw,才会产生异常,不被抛出的错误不会产生异常。比如直接new Error()甚至打印 Error 但是不 throw,也是不会产生异常

异常的分类

编译时异常

源代码在编译成可执行代码之前产生的异常,无需执行即有异常。编译、语法解析发生错误。编译型语言对于这种很常见的,但是解析型的 js 也是会有编译型异常。通常是非合法的 js 语句、ts 编译报错

console.log(1)let 1 // Uncaught SyntaxError: Unexpected numberfunction test() {console.log(1)await 1}

代码本身不会执行就抛异常,不会处理到打印 1 的阶段。这种情况通常不会有实际影响,因为 babel/ts 等工具处理时就会直接报错。除非不经编译直接写代码,例如有时候我们直接写在 html 中写的一些代码

运行时异常

代码被执行之后产生的异常。这些通常是很难提前发现的,因为代码实际运行中会遇到。比较常见的如TypeError: Cannot read properties of undefined这样的读取了undefined的属性。运行时异常对比编译时异常的特点是代码执行到异常代码前都是会正常执行的

执行到a.b.c前的打印能成功,异常抛出后后面的语句就不能执行了。运行时异常即可是这种引擎层面抛出的也可以是代码手动抛出的

而上面说的编译时异常,即使异常语句前的正常语句也是不会执行

异常传播

异常抛出就像事件冒泡一样具有传递性。如果一个异常没有被 catch,它会沿着函数调用栈一层层传播直到栈空。

异常会不断传播直到遇到第一个 catch。 如果都没有捕获,会抛出类似 unCaughtError,表示发生了一个异常,未被捕获的异常通常会被打印在控制台上

error 对象

Error本身作为函数直接调用和被 new 调用的效果是一样的

const a = Error('a')const b = new Error('b')

javascript 规范中总共有 8 中错误类型构造函数

  • Error – 错误对象
  • SyntaxError --解析过程语法错误(上面提到的编译时异常)
  • TypeError – 不属于有效类型(上面举例的运行时异常)
  • ReferenceError – 无效引用(严格模式下直接访问一个未定义的变量)
  • RangeError – 数值超出有效范围
  • URIError – 解析 URI 编码出错
  • EvalError – 调用 eval 函数错误
  • InternalError – Javascript 引擎内部错误的异常抛出, “递归太多”

Error 是错误的基类,其他类型都继承 Error 这个类

console.log(Object.getPrototypeOf(SyntaxError) === Error);    // trueconsole.log(Object.getPrototypeOf(TypeError) === Error);   // trueconsole.log(Object.getPrototypeOf(ReferenceError) === Error);   // trueconsole.log(Object.getPrototypeOf(RangeError) === Error);   // trueconsole.log(Object.getPrototypeOf(URIError) === Error);   // trueconsole.log(Object.getPrototypeOf(EvalError) === Error);   // true

默认的 error 对象只有一个 message 信息,很多时候对于错误的细分是很不好使,一般可以通过扩展这个错误对象,抛异常时抛出自定义的错误对象,在异常处理或时实现更精细化的处理

class ApiError extends Error {constructor(message, code) {super(message);this.code = code}}const err = new ApiError('xxx', 404)err instanceof  ApiError

一种常见的应用就是在 axios 处理的异常中抛出一个扩展的 ApiError 对象,传递错误信息、错误等,在错误处理时对于这种错误进行特殊处理。如自定义上报、catch 住不作为 js 异常上报。不进行这种处理的话平时比较常见的情况就是会造成 slardar 的中 js 错误部分会有很多 axios 抛出的噪音

除了扩展错误对象,目前有一个处于 stage 4 的 Error Cause 提案 https://github.com/tc39/proposal-error-cause。这个提案也是由阿里推进的国内的首个es提案

Chrome 96 版本目前还不可用,firefox 可用

通过传递给 Error 构造函数的第二个参数一个 cause 属性为一个 Error 对象,即可看到是哪个错误具体产生当前的错误,对于一些调用链路比较深的可可能存在多个异常抛出情况这个特性还是相当好用的,可以准确追踪。Error Cause 当然用自定义扩展错误也能够实现这个功能

async function doJob() {const rawResource = await fetch('//domain/resource-a').catch(err => {throw new Error('Download raw resource failed', { cause: err });});const jobResult = doComputationalHeavyJob(rawResource);await fetch('//domain/upload', { method: 'POST', body: jobResult }).catch(err => {throw new Error('Upload job result failed', { cause: err });});}try {await doJob();
} catch (e) {console.log(e);console.log('Caused by', e.cause);
}
// Error: Upload job result failed
// Caused by TypeError: Failed to fetch

Error 的相关 api

  • 改变堆栈帧数

默认情况下,V8 引发的几乎所有错误都具有一个 stack 属性,该属性保存最顶层的 10 个堆栈帧,格式为字符串 at xxx

Error.stackTraceLimit

Error.stackTraceLimit 属性指定堆栈跟踪收集的堆栈帧数。默认值为 10,可以设置为任何有效的 JavaScript 数值。 更改将影响值更改后捕获的任何堆栈跟踪。如果设置为非数字值,或设置为负数,则堆栈跟踪将不会捕获任何帧

  • 收集自定义异常
Error.captureStackTrace(error, constructorOpt)

这个 API 可以给自定义对象追加 stack 属性,达到模拟 Error 的效果,追加的 stack 表示调用 Error.captureStackTrace() 的代码中的位置的字符串。

function CustomError(message) {this.message = message;this.name = CustomError.name;Error.captureStackTrace(this); // 给对象追加stack属性}try {throw new CustomError('msg');
} catch (e) {console.log(e)
}

需要注意的是stack属性对于不同浏览器的格式是不一致的,通常而言监控 sdk 会统一做处理

这个方法支持传递一个constructorOpt参数,表示所有 constructorOpt 以上的帧,包括 constructorOpt,都将从生成的堆栈跟踪中省略。具体的差异如下

使用这个参数可以用于调用栈过深时隐藏深层次的一些调用细节

  • sourcemap 还原错误

还原错误也是利用了 error 对象的 stack 属性。可以使用stacktraceysource-map实现根据错误堆栈还原到实际发生错误的代码

线上代码经过压缩后一般只有 1 行,对于定位原始错误是很困难的。并且默认的e.stack属性是个字符串,可以借助stacktracey进行解析并结合source-map进行反解

const sourceMap = require('source-map');
const SourceMapConsumer = sourceMap.SourceMapConsumer;const Stacktracey = require('stacktracey');
const errorStack = '...'; // 错误信息const sourceMapFileContent = '...'; // sourcemap文件内容
const tracey = new Stacktracey(errorStack); // 解析错误信息const sourceMapContent = JSON.parse(sourceMapFileContent);const consumer = await new SourceMapConsumer(sourceMapContent);for (const frame of tracey) { // 这里的frame就是stack中的一行所解析出来的内容// originalPosition不仅仅是行列信息,还有错误发生的文件 originalPosition.sourceconst originalPosition = consumer.originalPositionFor({line: frame.line,column: frame.column,});// 拿到错误所对应的源码以及上面的行列号const sourceContent = consumer.sourceContentFor(originalPosition.source);console.log(sourceContent);}
自动、手动抛出

异常可手动抛出也可自动抛出

自动抛出:代码执行报错由引擎抛出。这种由于逻辑缺失容错造成的自动抛出错误应该是要尽最大程度杜绝并防范的

const a = {}a.b.c = 1

手动抛出:直接调用throw

那什么时候应该手动抛出异常呢?一个指导原则就是已经预知到程序不能正确进行下去了。

switch (type) {case a:break;case b:break;default:throw new Error('xxxx')}
抛出异常还是返回特定错误信息

对于上面提到可预知的异常需要终止流程,也可以使用抛出异常或者返回特定数据来让调用方感知。

  • 抛出异常

好处,调用方无需判断返回值,抛出异常默认就不会走后面的逻辑代码了。常见于 axios 对于 code 非 0 的异常抛出处理并自定义上报。再结合上面提到的扩展 error 对象,可以在监控上报前判断属于特定错误不作为 js 上报,避免网络异常造成的 js 错误增加噪音

instance.interceptors.response.use(async (res: AxiosResponse<Result>) => {if (res?.data?.statusCode !== 0) {throw new ApiError('xx', res?.data?.statusCode)}return res})
  • 不抛异常而是返回特定信息

如果上述的代码不抛出异常而是直接返回 res 的话,每一处调用就都要手动判断 code。接口 http 返回 http code 200 而响应体 code 不等于 0 也属于不抛异常而是返回特定信息的方式

const res = await api()
if (res.code !==0) {return
}

异常处理

同步、异步

try-catch 作为 JavaScript 中处理异常的一种标准方式,如果 try 块中的任何同步代码发生了错误,就会立即退出代码执行过程,然后执行 catch 块。此时 catch 块会接收到一个包含错误信息的对象。try-catch 使用时也可以搭配 finnally 使用。 finally 一经使用,其代码无论如何都会执行。对于异步调用可封装成 promise 的 catch 方法进行调用或借助 async/await 语法糖使用 try/catch

try {fn()
} catch(e) {}
fn().then(() =>{}, () => {// catch
})fn().catch(() => {})try { await fn() } catch(e) {}

可能到处 try catch 确实不是一种优雅的方式,可以进行适当的封装

  1. 对于异步 promise 调用可以直接使用await-to-js,利用 Promise 的特性,分别在 promise.then 和 promise.catch 中返回不同的数组,其中 fulfilled 的时候返回数组第一项为 null,第二个是结果。rejected 的时候,返回数组第一项为错误信息,第二项为 undefined。使用的时候,判断第一项是否为空,即可知道是否有错误
import to from 'await-to-js';
async function asyncTask() {let err, user, savedTask;[err, user] = await to(UserModel.findById(1));if(!user) throw new CustomerError('No user found');[err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));if(err) throw new CustomError('Error occurred while saving task');if(user.notificationsEnabled) {const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));if (err) console.error('Just log the error and continue flow');}
}
  1. 对于 class 方法调用适当使用装饰器进行 catch
export function CatchAsync(errorLabel = '') {return function (target: unknown, key: string, descriptor: PropertyDescriptor) {errorLabel = errorLabel || keyconst originFn = descriptor.valuedescriptor.value = async function (...rest: unknown[]) {try {return await originFn.call(this, ...rest)} catch (e) {// do somethingthrow e}}}
}export function CatchSync(errorLabel = '') {return function (target: unknown, key: string, descriptor: PropertyDescriptor) {errorLabel = errorLabel || keyconst originFn = descriptor.valuedescriptor.value = function (...rest: unknown[]) {try {return originFn.call(this, ...rest)} catch (e) {// do somethingthrow e}}}
}class A {@CatchAsync('1')async request() {}
}

Promise catch 小细节

以下两种写法的区别

then(f1,f2) vs then(f1).catch(f2)

绝大多数情况下是相同的。

区别在于第一种写法 f2 无法捕获 f1 中的异常。第二种写法 f2 能捕获 f1 中的异常

全局兜底

对于无需手动捕获或者没有捕获的异常最终会抛到全局。通过全局errorunhandledrejection进行监听并处理。监听全局异常和未捕获的 Promise 异常并进行相关处理

function onReject(e) {// ...report(e)}window.addEventListener('unhandledrejection', onReject, true)
window.addEventListener('error', this.onError, true)

window.onerrorwindow.addEventListener error的区别

  • window.onerror 函数返回 true 可以阻止执行默认事件处理函数(即控制台没有 error 打印出来)
  • window.addEventListener error若为捕获阶段,则可额外捕获静态资源的加载错误。window.onerror则无法捕获静态资源的加载错误

React 中的异常

白屏异常

React 处理阶段的同步代码报错,整个组件树挂了导致卸载掉,页面展示白屏

  • 生命周期函数报错
  • render 方法报错
  • 构造函数报错

上述提到的是同步代码报错,异步代码的报错是不会产生页面白屏,只是会产生一些 console 中的 error。同理,因为事件回调函数的处理不是在 React 处理阶段(初始化或者事件处理setState驱动 react 进行下次渲染的),所以事件处理函数中的报错同样不会触发白屏

Error Boundary

既然白屏问题如此严重,必须要有一种方式帮助开发者来感知 React 中的白屏问题。 于是 React16 就有了Error Boundary来用来捕获渲染时错误的概念,在 React 新增了两个生命周期componentDidCatchstatic getDerivedStateFromError用于捕获渲染时的错误,也仅能捕获上面提到的白屏异常(如异步错误等也是没有办法被捕获到),也就是说如果我们在Error Boundary中捕获到错误并上报,这个错误通常是非常严重的。Error Boundary只可用于捕获子组件中发生的异常(自身出现渲染错误也是无法捕获的)

无法捕获的异常

  • 事件处理

  • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)

  • 服务端渲染

  • 它自身抛出来的错误(并非它的子组件)

  • componentDidCatch

用于出错时去执行的副作用代码,比如错误上报、错误兜底等

  • static getDerivedStateFromError

在出错后触发,改函数返回的值能进行 setState 更新,触发一次重新 render 来渲染错误时的 fallback 组件。如果这次渲染仍然出现渲染错误,页面仍然会白屏,而不是执行类似 render error -> getDerivedStateFromError -> render error 这样的死循环

class Demo extends Component {state = {error: false}static getDerivedStateFromError(){return {error: true}}componentDidCatch(error, info){this.setState({error: true})}render() {if (this.state.error) {return <div>error</div>}// Child中发生渲染错误return <div><Child/></div>}}

`

  • static getDerivedStateFromError渲染阶段调用的,所以不允许出现副作用
  • componentDidCatch【commit】阶段被调用,所以允许出现副作用

目前 React 的 Error Boundary 提供的两个生命周期只存在于 class 组件;并没有相应的 hooks 能实现类似的功能

There are no Hook equivalents to the uncommon getSnapshotBeforeUpdate, getDerivedStateFromError and componentDidCatch lifecycles yet, but we plan to add them soon.

但是有一个比较有趣的是,Preact 提供了相应的 hook useErrorBoundary去实现 Error Boundary。preact 中的useErrorBoundary的功能和getDerivedStateFromErrorcomponentDidCatch是一模一样的

// error = The error that was caught or `undefined` if nothing errored.// resetError = Call this function to mark an error as resolved. It's//   up to your app to decide what that means and if it is possible//   to recover from errors.const [error, resetError] = useErrorBoundary();

用法也是非常简单,子组件触发异常会触发函数组件的 render 并且 error 是对应的错误信息,并且还提供了对应的 resetError 去重置错误。至于为何 Preact 能先于 React 支持功能,原因在于对于 Preact 的实现来说,它的函数组件和 class 组件都是实例化成一样的实例,函数组件的 hook 中直接定义componentDidCatch进行处理,componentDidCatch 捕获到错误后通过setState设置错误对象驱动下一次的 render 来拯救白屏

export function useErrorBoundary(cb) {const state = getHookState(currentIndex++, 10);const errState = useState();state._value = cb;if (!currentComponent.componentDidCatch) {currentComponent.componentDidCatch = err => {if (state._value) state._value(err);errState[1](err);};}return [errState[0],() => {errState[1](undefined);}];}

虽然这是一个 react 的 Error Boundary 只存在于 class 组件,但是对于子组件是函数组件的情况下,相关 hooks 的异常(useEffectuseLayoutEffect)一样是能捕获到的

实践

这么基础常用的 error-boundary 通常来说不需要我们手动去搞。开源社区已经有了成熟的封装解决方案react-error-boundary。它基于 React 提供的 error boundary 能力提供了开箱即用的功能,使用的时候只需要将我们的组件作为ErrorBoundary的子组件传入即可,并且 ErrorBoundary 还提供 FallbackComponent 属性供出错时渲染 fallback 内容、错误恢复等许多更进阶的功能。并且也提供了 HOC 的方式供使用

Error Boundary 包子组件
import {ErrorBoundary} from 'react-error-boundary'function ErrorFallback({error, resetErrorBoundary}) {return (<div role="alert"><p>Something went wrong:</p><pre>{error.message}</pre><button onClick={resetErrorBoundary}>Try again</button></div>)}const ui = (<ErrorBoundaryFallbackComponent={ErrorFallback}><ComponentThatMayError /></ErrorBoundary>)
高阶组件
import {withErrorBoundary} from 'react-error-boundary'const ComponentWithErrorBoundary = withErrorBoundary(ComponentThatMayError, {FallbackComponent: ErrorBoundaryFallbackComponent,onError(error, info) {// Do something with the error// E.g. log to an error logging client here},})// or@withErrorBoundary({FallbackComponent: ErrorBoundaryFallbackComponent,onError(error, info) {// Do something with the error// E.g. log to an error logging client here},})class ComponentThatMayError extends Component {}const ui = <ComponentWithErrorBoundary />

在需要使用的地方对我们的组件进行一层包装即可。这时候可能会一种需求,手动包一层太麻烦了,为啥 react 不提供一个配置字段每个组件自带 error boundary 呢?

万能的开源社区也有人通过 babel 插件实现了这个能力babel-plugin-transform-react-error-boundary

{test: /.jsx?$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {plugins: [[ 'babel-plugin-transform-react-error-boundary', {ErrorBoundary: 'common/components/ErrorBoundary/index.js'} ]]}}}

通过配置一个自定义的 Error Boundary 路径,即可实现所有的组件包一个 ErrorBoundary 了,再结合react-error-boundary一顿操作,页面再也不会白屏了。具体实现就是通过 babel 实现以下这样的转换

class TestComponent extends React.Component {render() {return <div />;}}// 转换const ErrorBoundary = require('./path/to/my/ErrorBoundary.js');class TestComponent extends React.Component {render() {return <ErrorBoundary>{this.__r__()}</ErrorBoundary>;}__r__) {return <div />;}}

上面提到 Error boundaries是不支持 ssr 场景的,所以又有人做了一个针对 ssr 的 babel 插件

babel-plugin-transform-react-ssr-try-catch。 通过对 render 函数进行 trycatch 实现类似的功能

{"plugins": [["react-ssr-try-catch", {// global errorHandler"errorHandler": "./path/to/my/SSRErrorHandler.js",// component error render method"errorRenderMethod": "renderErrorState",}]]}
class MyCompoenent extends React.PureComponent {render() {return <div/>;}}// 转换const ReactSSRErrorHandler = require('./path/to/my/SSRErrorHandler.js');class MyCompoenent extends React.PureComponent {render() {try {return this.__originalRenderMethod__();} catch (e) {return ReactSSRErrorHandler(e, this.constructor.name, this);}}__originalRenderMethod__() {return <div />;}}
实现更多的功能

Error Boundary 除了用于捕获错误,这个特性也可以用来实现 React Suspense 相关的功能

  1. Suspense + Lazy
// 用法const ProfilePage = lazy(() => import('./ProfilePage'));<Suspense fallback={<Spinner />}><ProfilePage /></Suspense>
// 最简化实现Lazy + Suspensefunction lazy(loader) {let prom;let component;let error;function Lazy(props) {if (!prom) {prom = loader();prom.then(exports => {component = exports.default || exports;},e => {error = e;});}if (error) {throw error;}// Lazy组件的render中,若组件未加载完成,抛出一个promise异常供Suspense的componentDidCatch捕获if (!component) {throw prom;}return createElement(component, props);}return Lazy;}export class Suspense extends React.Component {state = {isLoading: false};componentDidCatch(error) {if (this._mounted) {if (typeof error.then === 'function') {this.setState({ isLoading: true });error.then(() => {if (this._mounted) {this.setState({ isLoading: false })}});}}}componentDidMount() {this._mounted = true;}componentWillUnmount() {this._mounted = false;}render() {const { children, fallback } = this.props;const { isLoading } = this.state;return isLoading ? fallback : children;}}
  1. Suspense + render 中的【同步】数据获取
const fetchApi = () => {// 异步apireturn new Promise((resolve, reject) => {setTimeout(() => {resolve("数据");}, 3000);});}const useData = () => {const [data,setData] = useState()useEffect(() => {fetchApi().then((x) => setData(x))},[])return data}const ProfilePage = () => {const data = useData()if (!data) {return <Loading/>}return <div>{data}</div>}
const fetchApi = () => {// 异步apireturn new Promise((resolve, reject) => {setTimeout(() => {resolve("数据");}, 3000);});
}const getData = createFetcher(fetchApi);const ProfilePage = () => {const data = getData()return <div>{data}</div>}const Demo = () => {return <Suspense fallback={<Loading />}><ProfilePage /></Suspense>}
var cached = {};export const createFetcher = (promiseTask) => {let ref = cached;return () => {if (ref !== cached) {return ref}const task = promiseTask();task.then(res => {ref = res});if (ref === cached) {throw task}}}

Vue 中的异常

vue 提供了 4 个异常处理的 API,分别是 errorHandler,errorCaptured,renderError,warnHandler

errorHandler

我们最常用的是全局配置中注册的 errorHandler,例如异常上报场景,可用如下代码:

Vue.config.errorHandler = function (error, vm, info) {window.Slardar && window.Slardar('Sentry', (Sentry) => {Sentry.captureMessage(error);});}

errorHandler 可以捕获 render(vue 模板)、生命周期钩子、watch 回调、methods 方法等函数内的同步代码异常,info 参数会接收到报错函数类型(render/mounted/…);如果这些函数返回 promise ,则 promise 异常也会被捕获;

errorCaptured

errorCaptured 入参和 errorHandler 一样,它是 vue 组件的钩子函数,作用是捕获来自后代组件(注意不包含本组件)的错误。 vue 中的错误传播规则可以总结为,从子到父传播,依次触发各组件的 errorCaptured 钩子,若某 errorCaptured 返回 false,则停止传播,否则最终会传播到全局的 errorHandler

使用场景:我们可以在组件库等场景使用 errorCaptured,捕获内部异常并上报,从而避免和业务代码报错混淆;

renderError

renderError 只在开发者环境下工作,当 render 函数报错时,其错误将会作为第二个参数传递到 renderErrorrenderError 返回的 vnode 将会被渲染。

new Vue({render (h) {throw new Error('oops')},renderError (h, err) {return h('pre', { style: { color: 'red' }}, err.stack)}}).$mount('#app')

使用场景:renderError可用于开发环节实时把组件错误渲染到页面;

warnHandler

warnHandlererrorHandler一样是全局配置项,但 warnHandler 只在开发者环境下生效,用于捕获 vue 告警。

Vue.config.warnHandler = function (msg, vm, trace) {// `trace` 是组件的继承关系追踪}

使用场景:一般情况下开发者直接在控制台查看 warn,所以 warnHandler 使用场景非常有限。

参考

React,优雅的捕获异常 - 掘金

精读《React Error Boundaries》

React:Suspense 的实现与探讨

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1410680.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

《罪与罚》读后感

陀思妥耶夫斯基和列夫托尔斯泰是公认的俄国文学黄金时代的两座高峰&#xff0c;分别代表着俄国文学的“深度”和“广度”。列夫托尔斯泰的鸿篇巨著《复活》《安娜卡列尼娜》等等都已经拜读过&#xff0c;但陀思妥耶夫斯基的作品却一本也没有看过&#xff0c;实在是有点遗憾。这…

JUC并发-共享模型-工具-线程池

1、自定义线程池 每创建一个线程资源&#xff0c;就要占用一定的内存&#xff1b; ①如果是高并发场景&#xff0c;一下子来了很多任务&#xff0c;如果为每个任务都创建一个线程&#xff0c;对内存占用较大&#xff0c;甚至可能出现OOM。 ②大量任务来了&#xff0c;创建了很…

中国人自己编的百科全书,百科知识全书10册(外国卷)

一、电子书描述 中国人自己编的与国际接轨的百科全书&#xff0c;也是真正意义上的现代百科全书&#xff0c;在坚持全面反映了人类知识的同时&#xff0c;也突出了中国特色&#xff0c;充分显示世界科学文化的新成就和新发展。本套电子书&#xff0c;大小367.39M&#xff0c;共…

每日一题:两地调度

公司计划面试 2n 人。给你一个数组 costs &#xff0c;其中 costs[i] [, ] 。第 i 人飞往 a 市的费用为 &#xff0c;飞往 b 市的费用为 。 返回将每个人都飞到 a 、b 中某座城市的最低费用&#xff0c;要求每个城市都有 n 人抵达。 示例 1&#xff1a; 输入&#xff1a;c…

【深耕 Python】Data Science with Python 数据科学(19)书402页练习题:模型准确率对比研究、KMeans算法的一点探讨

写在前面 关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 往期数据科学博文一览&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;2&…

Redis 实战1

SDS Redis 只会使用 C 字符串作为字面量&#xff0c; 在大多数情况下&#xff0c; Redis 使用 SDS &#xff08;Simple Dynamic String&#xff0c;简单动态字符串&#xff09;作为字符串表示。 比起 C 字符串&#xff0c; SDS 具有以下优点&#xff1a; 常数复杂度获取字符串…

【高质量精品】2024五一数学建模C题成品论文22页matlab和13页python完整建模代码、可视图表+分解结果等(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下卡片&#xff0c;那是获取资料的入口&#xff01; 【高质量精品】2024五一数学建模C题成品论文22页matlab和13页python完整建模代码、可视图表分解结果等「首先来看看目前已有的资料&#xff0c;还会不断更新哦…

『MySQL 实战 45 讲』19 - 为什么我只查一行的语句,也执行这么慢?

为什么我只查一行的语句&#xff0c;也执行这么慢&#xff1f; 需求&#xff1a;创建一个表&#xff0c;有两个字段 id 和 c&#xff0c;并且在里面插入了 10 万行记录 CREATE TABLE t (id int(11) NOT NULL,c int(11) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB;delimit…

硬件知识积累 DP 接口简单介绍以及 DP信号飞线到显示屏的问题

1. DP 接口的介绍 定义与起源&#xff1a; DP接口是由PC及芯片制造商联盟开发&#xff0c;并由视频电子标准协会&#xff08;VESA&#xff09;标准化的数字式视频接口标准。它的设计初衷是为了取代传统的VGA、DVI和FPD-Link&#xff08;LVDS&#xff09;接口&#xff0c;以满足…

Qt QImageReader类介绍

1.简介 QImageReader 是用于读取图像文件的类。它提供了读取不同图像格式的功能&#xff0c;包括但不限于 PNG、JPEG、BMP 等。QImageReader 可以用于文件&#xff0c;也可以用于任何 QIODevice&#xff0c;如 QByteArray &#xff0c;这使得它非常灵活。 QImageReader 是一个…

323_C++_QT_QProcess执行cmd解压、压缩、删除tar.gz等等其他压缩包文件到指定目录,不需要外部库,QT自带API的就行

// decompressPath : 解压到此目录 // fileName : 解压的tar.gz文件名executeCommand(decompressPath , QString::fromStdString(fileName));// 开始解压 void executeCommand

uni-app scroll-view隐藏滚动条的小细节 兼容主流浏览器

开端 想写个横向滚动的列表适配浏览器&#xff0c;主要就是隐藏一下滚动条在手机上美观一点。 但是使用uni-app官方文档建议的::-webkit-scrollbar在目标标签时发现没生效。 .scroll-view_H::-webkit-scrollbar{display: none; }解决 F12看了一下&#xff0c;原来编译到浏览…

漏洞扫描神器:Nessus 保姆级教程(附破解步骤)

一、介绍 Nessus是一款广泛使用的网络漏洞扫描工具&#xff0c;用于发现和评估计算机系统和网络中的安全漏洞。它是一款功能强大的商业工具&#xff0c;由Tenable Network Security开发和维护。 以下是Nessus的一些主要特点和功能&#xff1a; 1. 漏洞扫描&#xff1a;Nessu…

转义字符解释

也许在一些代码中你看到 \n, \0 很纳闷是啥。其实在字符中有一组特殊的字符是转义字符&#xff0c;转义字符顾名思义&#xff1a;转变原来的意思的字符。 比如&#xff1a;我们有字符n&#xff0c;在字符串中打印的时候自然能打印出这个字符&#xff0c;如下&#xff1a; #in…

通过 API 接口,实现增值税发票智能识别

增值税发票智能识别是一项应用于财务管理和数据分析的技术&#xff0c;通过使用API接口&#xff0c;我们可以轻松地将增值税发票的各项信息进行结构化识别。本文将详细介绍如何通过API接口实现增值税发票的智能识别&#xff0c;并给出相应的代码说明。 首先&#xff0c;我们需…

自动安装环境shell脚本使用和运维基础使用讲解

title: 自动安装环境shell脚本使用和运维基础使用讲解 tags: [shell,linux,运维] categories: [开发记录,系统运维] date: 2024-3-27 14:10:15 description: 准备和说明 确认有网。 依赖程序集&#xff0c;官网只提供32位压缩包&#xff0c;手动编译安装后&#xff0c;在64位机…

Java 新手上路常见的5个经典问题,你遇到过吗?

当我们开始学习一门新的编程语言或者开发平台时&#xff0c;经常会遇到一些常见的问题。这些问题不仅是学习过程中的一部分&#xff0c;也是成长和提高的机会。 1. 空指针异常&#xff08;NullPointerException&#xff09; 空指针异常是 Java 开发中最常见的问题之一。它的产…

docker学习笔记3:VmWare CentOS7安装与静态ip配置

文章目录 一、安装CentOS71、下载centos镜像2、安装二、设置静态ip三、xshell连接centos本专栏的docker环境是在centos7里安装,因此首先需要会安装centos虚拟机。 本篇博客介绍如何在vm虚拟机里安装centos7。 一、安装CentOS7 1、下载centos镜像 推荐清华源,下载如下版本 …

OpenCV4.9去运动模糊滤镜(68)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV4.9失焦去模糊滤镜(67) 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目标 在本教程中&#xff0c;您将学习&#xff1a; 运动模糊图像的 PSF 是多少如何恢复运动模…

2024-5-3学习笔记 继承关系拓展

一.继承与友元 友元类不能继承&#xff0c;也就是说基类友元不能访问子类私有和保护成员。简单的理解就是&#xff0c;爸爸的朋友不是儿子的朋友。 二.继承与静态成员 基类定义了static静态成员&#xff0c;则整个继承体系里面只有一个这样的成员。无论派生出多少个子类&…