乐闻世界logo
搜索文章和话题

前端面试题手册

Composition API和Options API 之间的区别是什么

Composition API 和 Options API 是 Vue.js 框架中用于创建和组织组件的两种不同的API。Vue.js 是一个流行的前端JavaScript框架,用于构建用户界面和单页应用程序。下面我将详细说明它们之间的区别:Options APIOptions API 是 Vue.js 最初提供的接口,它是基于一个包含描述组件选项的对象的概念。这些选项包括了data、methods、props、computed、watch、lifecycle hooks等属性。这种API的特点是将组件的不同方面划分到这些选项中,代码按功能组织。例子:export default { data() { return { message: 'Hello Vue!', }; }, props: { user: String, }, computed: { normalizedUser() { return this.user.trim().toLowerCase(); }, }, methods: { sayHello() { alert(this.message); }, },};在这个例子中,data是组件的状态,props是外部传入的属性,computed是计算属性,methods是组件的方法。优点:易于理解和上手,特别是对于初学者。由于选项类型的组织方式,IDEs 和静态类型检查工具通常可以提供更好的支持。缺点:在大型和复杂的组件中,相互关联的逻辑会被拆分到不同的选项中,导致代码维护和理解上的困难。当组件变得庞大时,相同功能的代码可能散布在不同的选项中,难以追踪和组织。Composition APIComposition API 是在 Vue.js 3 中引入的,旨在解决 Options API 在构建大型应用时遇到的问题。它提供了更加灵活的方式来组织和重用代码。使用Composition API,开发者可以更容易地将组件逻辑基于功能划分和抽象成可复用的函数。例子:import { ref, computed } from 'vue';export default { setup(props) { const message = ref('Hello Vue!'); const normalizedUser = computed(() => props.user.trim().toLowerCase()); function sayHello() { alert(message.value); } return { message, normalizedUser, sayHello, }; }, props: { user: String, },};在这个例子中,setup函数是组件中所有Composition API逻辑的起点。通过导入ref和computed,我们可以定义响应式状态和计算属性。setup 函数返回的对象将定义组件的响应式状态和方法。优点:更好的逻辑复用和抽象,便于开发者根据功能组织代码,使得代码更加模块化。更容易控制变量的作用域和生命周期。更好地与TypeScript集成,提升类型推断的能力和开发体验。缺点:学习曲线相对较陡峭,特别是对于那些习惯于 Options API 的开发者。尽管它提供了更大的灵活性,但在小型项目或简单组件中可能会引入不必要的复杂性。结论Options API 和 Composition API 都是 Vue.js 提供的强大工具,它们各有优势。选择哪种API取决于项目的需求、组件的复杂性以及开发团队的偏好。Composition API 在处理大型项目和复杂组件时优势明显,而Options API 在小型项目或对于新手更
阅读 43·2024年8月5日 12:48

什么是 React 的受控组件和非受控组件?

在React中,受控组件(Controlled Components)和非受控组件(Uncontrolled Components)都是处理表单输入的方式,但它们处理数据的方式不同。受控组件(Controlled Components)受控组件是React的一种模式,在这种模式下,表单数据是由React组件的状态管理的。这意味着每次字段的值发生变化时,我们都会通过一个事件处理函数(通常是 onChange)来更新组件的状态。然后,组件的状态被用作输入字段的值,确保组件的状态是数据的唯一来源。示例:假设我们有一个受控的 <input>元素:class ControlledComponent extends React.Component { constructor(props) { super(props); this.state = {value: ''}; } handleChange = (event) => { this.setState({value: event.target.value}); } render() { return ( <form> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> </form> ); }}在上面的例子中,<input>的值始终由 this.state.value决定,而且每当用户输入时,handleChange函数都会被调用,更新状态,因此界面显示的内容总是和状态同步。非受控组件(Uncontrolled Components)非受控组件是另一种模式,在这种模式下,表单数据是由DOM本身处理的,而不是由React状态管理。这就像传统的HTML表单工作方式。在非受控组件中,我们通常使用 ref来从DOM节点获取表单数据,而不是为每个状态变化编写事件处理函数。示例:下面是一个非受控组件的例子:class UncontrolledComponent extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } handleSubmit = (event) => { alert('A name was submitted: ' + this.inputRef.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={this.inputRef} /> </label> <button type="submit">Submit</button> </form> ); }}在上面的例子中,<input>不是通过状态来控制其值,而是通过 ref来访问DOM节点获取其值。 总结受控组件允许你更好地控制表单的行为,因为组件的状态充当了数据的真实来源。非受控组件舍弃了对表单状态的即时控制,使得组件的代码更简洁,但可能会更难管理表单的状态,尤其是在复杂的表单交互时。在实际的开发实践中,受控组件通常是首选方法,因为它们更加符合React的数据流概念,使得状态的管理更加清晰和可预测。然而,对于一些简单的表单或者集成第三方DOM库时,非受控组件也可能是一个不错的选择。
阅读 57·2024年8月5日 12:48

手写 javascript 中 new 的实现过程

当我们在JavaScript中使用new操作符创建一个新对象时,实际上会发生以下几个步骤:创建一个新对象。 使用new操作符时,JavaScript会自动为我们创建一个新的空对象。设置原型链。 新对象内部的[[Prototype]](或者__proto__)属性会被赋值为构造函数的prototype属性,这样新对象就可以访问到构造函数原型上的属性和方法。绑定this并执行构造函数。 构造函数内部的this将会被绑定到新创建的对象上,然后执行构造函数中的代码,这样新对象就可以具有构造函数中定义的属性和方法。返回新对象。 如果构造函数返回的是一个对象,则返回该对象;如果没有返回对象或者返回的不是一个对象,那么将返回步骤1创建的新对象。如果我们要手写一个new的实现,可以定义一个函数来模拟这个过程。以下是一个例子:function myNew(constructor, ...args) { // 步骤1:创建一个空对象,并设置原型链 const obj = Object.create(constructor.prototype); // 步骤2:将构造函数的this绑定到新对象上,并执行构造函数 const result = constructor.apply(obj, args); // 步骤3:根据返回值判断 return result instanceof Object ? result : obj;}// 测试用例function Person(name, age) { this.name = name; this.age = age; this.sayHello = function() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); };}// 使用自定义的myNew来替代new操作符const person = myNew(Person, 'Alice', 30);person.sayHello(); // 输出: Hello, my name is Alice and I am 30 years old.以上代码中,myNew函数模拟了new操作符的所有关键步骤,能够模拟出通过new操作符创建对象的效果。
阅读 36·2024年8月5日 12:48

如何自定义实现 Redux 中间件

在Redux中,中间件是一种强大的机制,允许开发者在action被发送到reducer之前插入自己的逻辑。创建自定义的Redux中间件涉及到编写一个函数,该函数按照Redux中间件API的规格返回一个满足特定签名的函数。我将向您展示如何自定义实现一个简单的日志中间件,该中间件的作用是在action被派发时在控制台输出日志信息。以下是自定义Redux中间件的基本步骤:编写一个函数,该函数接收store的dispatch和getState方法。该函数返回一个接收下一个中间件的next函数的函数。返回的函数再返回一个接收action的函数。在最内层的函数体内,可以执行自定义的逻辑,然后调用next(action)将action传递给链中的下一个中间件或reducer。下面是一个自定义日志中间件的例子:// 自定义日志中间件const loggerMiddleware = store => next => action => { // 自定义的逻辑:在当前action被处理之前输出日志 console.log('dispatching', action); // 调用链中的下一个中间件或reducer let result = next(action); // 自定义的逻辑:在action被处理后输出新的状态 console.log('next state', store.getState()); // 返回result,因为middleware的链需要从next(action)获取返回值 return result;};export default loggerMiddleware;在上述的中间件代码中:store: Redux store实例,它包含了dispatch和getState方法。next: 是一个将action传递给链中下一个处理者(中间件或reducer)的函数。action: 是当前正在处理的action对象。使用这个中间件的典型方式是在创建Redux store时应用它:import { createStore, applyMiddleware } from 'redux';import rootReducer from './reducers';import loggerMiddleware from './middleware/loggerMiddleware';// 使用applyMiddleware来增强store,添加自定义的loggerMiddlewareconst store = createStore( rootReducer, applyMiddleware(loggerMiddleware));export default store;在这个例子中,任何派发到store的action都会先经过loggerMiddleware这个中间件,在控制台输出action信息,然后继续沿中间件链传递,直到最终被reducer处理。这只是自定义中间件的一个简单例子,但您可以根据需要在中间件中实现更复杂的逻辑,例如异步操作、路由导航或其他您想要的任何自定义行为。
阅读 32·2024年8月5日 12:48

css 清除浮动的几种方式以及各自的优缺点

CSS中清除浮动(Float)的几种常见方法如下:1. 使用clear属性在浮动元素之后添加一个空的元素,并为其设置clear属性。<div class="float-element"></div><div class="clear"></div>.float-element { float: left;}.clear { clear: both;}优点:简单易懂。兼容性好,适用于所有浏览器。缺点:需要额外的标记(markup),可能导致HTML结构变得臃肿。与内容分离度不高,不符合现代Web开发的最佳实践。2. 使用overflow属性为父元素设置overflow: auto或overflow: hidden可以清除子元素的浮动。.parent { overflow: auto;}优点:不需要添加额外的HTML元素。代码简洁。缺点:在某些情况下可能会导致不期望的滚动条出现。如果子元素需要超出父容器边界显示,此方法可能会剪切子元素的部分内容。3. 使用伪元素清除浮动(clearfix hack)通过在父元素上添加一个伪元素来清除浮动。.clearfix::after { content: ""; display: block; clear: both;}优点:不需要在HTML中添加额外的元素。代码整洁且符合无障碍标准。被广泛采纳,成为一种标准做法。缺点:在旧版IE浏览器中可能需要额外的兼容性处理。4. 使用Flexbox将父元素设为Flex容器。.parent { display: flex;}优点:为现代网站提供了更强大的布局选项。自动处理了元素的浮动问题,不需要显式清除。缺点:在不支持Flexbox的老旧浏览器中不可用。涉及到布局方式的更改,可能需要重新考虑整个布局结构。5. 使用Grid布局将父元素设为Grid容器。.parent { display: grid;}优点:更先进的布局系统,提供了更多布局选项。同样自动处理元素的浮动问题。缺点:兼容性不如Flexbox,特别是在老旧浏览器上。总的来说,选择哪种方法取决于具体项目的要求、浏览器兼容性,以及开发者对于CSS规范的熟悉程度。清除浮动是一个常见的问题,现代前端开发倾向于使用clearfix技术或更现代的布局方法(如Flexbox或Grid)来避免这个问题。
阅读 31·2024年8月5日 12:48

React 如何使用异步组件以及异步组件的使用场景

React 的异步组件(通常被称为懒加载组件)主要是通过动态 import() 语法和 React 的 React.lazy 函数来实现的。它们用于在需要时才加载组件,可以显著提高应用程序的性能,尤其是当应用程序很大并且有许多不同的页面和组件时。接下来,我会详细介绍如何使用异步组件以及它们的使用场景。 如何使用异步组件使用 React 异步组件的基本步骤如下:使用 React.lazy 函数分别导入组件。这个函数允许你定义一个动态导入的组件。该函数接受一个函数,这个函数必须调用一个 import(),它返回一个 Promise,该 Promise 解析为一个有 default 导出的模块。 const AsyncComponent = React.lazy(() => import('./AsyncComponent'));将 React.lazy 返回的组件与 React.Suspense 组件结合使用。Suspense 组件允许你指定加载指示器(例如:加载中的旋转器),在等待异步组件加载时显示给用户。 import React, { Suspense } from 'react'; // 异步导入组件 const AsyncComponent = React.lazy(() => import('./AsyncComponent')); function App() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <AsyncComponent /> </Suspense> </div> ); }使用场景性能优化: 对于大型应用程序,将不同的页面或功能分割成独立的代码块,然后只在用户需要时才加载,可以减少应用程序的初始负载时间。条件渲染组件: 当一个组件只在某些条件下才需要时,例如特定的用户角色或权限,可以使用异步组件按需加载,从而节省资源。路由懒加载: 在使用如 React Router 这样的库进行路由管理时,可以结合 React.lazy 和 Suspense 来实现路由级别的懒加载。 import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense } from 'react'; const Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> </Switch> </Suspense> </Router> );组件库懒加载: 如果你的应用程序使用了庞大的第三方组件库,而只有少数组件被频繁使用,可以选择仅懒加载那些较少使用的组件,以减少初始包的大小。使用异步组件的主要目标是提升用户体验,减少页面加载时间,并且按需加载资源,避免浪费客户端的计算和带宽资源。React 的懒加载功能是实现上述目标的重要手段之一。
阅读 93·2024年8月5日 12:48

React Router 是如何配置组件的懒加载?

React Router 可以通过配合 React 的 React.lazy() 和 Suspense 组件来配置组件的懒加载。以下是使用 React Router 实现懒加载的基本步骤:使用 React.lazy 实现动态导入: React.lazy() 是一个允许你动态加载组件的函数。它可以让你定义一个动态导入的组件,并且这个组件会在首次渲染时自动加载。 const LazyComponent = React.lazy(() => import('./LazyComponent'));使用 Suspense 组件包裹路由: 在你的应用中,你需要使用 Suspense 组件来包裹懒加载的路由。Suspense 可以指定一个加载指示器(比如一个 spinner),它会在懒加载组件加载完成之前显示。 import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense } from 'react'; const LazyComponent = React.lazy(() => import('./LazyComponent')); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route path="/lazy" component={LazyComponent} /> {/* 其他路由 */} </Switch> </Suspense> </Router> ); }为懒加载组件创建独立的 chunk: 当你使用 create-react-app 或其他构建工具时,它会为每个用 React.lazy() 引入的组件自动创建一个独立的 JavaScript chunk 文件。这意味着这些代码只会在用户需要时才会被加载。举个例子,假设你有一个很大的组件 BigComponent,你不希望它在应用首次加载时就加载进来,而是希望当用户真正访问到该组件对应的路由时再加载,你可以这样设置:import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';import React, { Suspense } from 'react';const BigComponent = React.lazy(() => import('./BigComponent'));function App() { return ( <Router> <Suspense fallback={<div>Loading Big Component...</div>}> <Switch> <Route path="/big-component" component={BigComponent} /> {/* 其他路由 */} </Switch> </Suspense> </Router> );}在上述例子中,当用户访问 /big-component 路径时,BigComponent 会被动态加载。用户会看到 "Loading Big Component…" 的文本,直到 BigComponent 加载完成并准备好渲染。这样可以减少应用的初始加载时间,并且按需加载资源,提高性能。
阅读 111·2024年8月5日 12:48

React 函数组件和 class 组件之间的区别

React 函数组件和类组件是 React 中创建组件的两种不同方式。它们有几个主要区别:语法:函数组件:使用 JavaScript 函数(或箭头函数)定义,它接收一个 props 参数并返回 JSX。函数组件通常更简洁。 jsx function Welcome(props) { return <h1>Hello, {props.name}</h1>; }类组件:使用 ES6 类来定义,它扩展自 React.Component,必须包含一个 render() 方法,该方法返回 JSX。 jsx class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }状态管理:函数组件:在 React 16.8 之前,函数组件不具备状态(state)和生命周期方法。但随着 React Hooks 的引入,函数组件可以使用 useState 和其他 Hooks 来管理状态和生命周期。类组件:具有内置的状态和生命周期方法。它们使用 this.state 和 this.setState 来管理组件的状态,以及一系列的生命周期函数(如 componentDidMount,componentShouldUpdate 等)来执行副作用操作。生命周期方法:函数组件:通过使用 React Hooks(如 useEffect),函数组件可以执行与类组件生命周期方法相似的操作,但它们不直接拥有生命周期方法。类组件:具有完整的生命周期方法,可以在组件的不同阶段执行代码。this 关键字:函数组件:不使用 this 关键字。所有的数据(包括 props 和 state)都通过函数参数或 Hooks 访问。类组件:需要使用 this 关键字来访问 props、state 和类方法。优化性能:函数组件:因为它们没有类实例,理论上可以更轻量。并且可以通过使用 React.memo 进行性能优化。类组件:可以使用 shouldComponentUpdate 或 PureComponent 来优化性能,但这些通常比函数组件中的优化方法更复杂。钩子(Hooks):函数组件:可以使用 Hooks,如 useState、useEffect 等,使得在不使用类的情况下也能拥有类似的功能。类组件:无法使用 Hooks,必须依靠类本身的特性和生命周期。部署和维护:函数组件:通常来说,由于它们更加简洁,函数组件更容易编写和维护。它们也更容易分割成更小的函数。类组件:可能会更加冗长,特别是当涉及到多个生命周期方法和状态管理时,这可能使得维护和重构变得更加困难。代码复用:函数组件:可以通过自定义 Hooks 实现逻辑的复用。类组件:通常通过高阶组件(HOCs)或渲染道具(Render Props)来实现逻辑的复用。
阅读 50·2024年8月5日 12:43

React 的合成事件的原理是什么?

React 的合成事件(SyntheticEvent)是 React 为了跨浏览器兼容性而实现的一个事件封装。合成事件的原理可以总结为以下几个关键点:1. 事件封装React 为了解决原生事件在不同浏览器中的兼容问题,实现了一套自己的事件系统。这个系统模拟原生事件系统,但是提供了一致的接口和行为。当事件发生时(如用户点击一个按钮),React 会创建一个合成事件的实例,这个实例包含了所有事件的信息,无论在哪个浏览器上。2. 事件冒泡在 React 中,所有的事件都会自动应用事件冒泡(bubbling),即事件会从触发它的最深的节点开始,逐层向上传播到最外层的节点。合成事件同样遵循这个机制,这意味着您只需要在一个高层节点上监听事件,就能处理下层节点的相应事件。3. 事件委托React 并不会将事件处理器直接绑定到真实的 DOM 元素上,而是使用了一种叫做事件委托的技术。React 在最顶层的文档节点上(通常是document)添加了一个单一的事件监听器,用来监听所有支持的事件类型。当一个事件发生时,React 会根据这个事件的目标和冒泡路径,来决定哪些注册的事件处理器需要被调用。4. 合成事件对象池出于性能考虑,React 实现了一个合成事件的对象池。每当一个事件发生并且事件处理器被调用时,React 从池中分配一个合成事件对象,并填充事件的相关信息。一旦事件处理器被调用,这个对象会被清空,并回收到池中以供后续的事件重复使用。这个过程减少了垃圾回收的压力和合成事件对象的创建成本。5. 与原生事件的关系尽管 React 使用了合成事件,但它仍然是基于原生事件的。当原生事件被触发时,React 的事件委托机制会处理这个事件,并创建一个合成事件传递给相应的事件处理器。开发者在编写事件处理函数时,操作的是由 React 提供的合成事件,而不是直接操作原生 DOM 事件。实例假设我们有一个按钮的点击事件,我们希望在点击时打印出事件对象:class MyComponent extends React.Component { handleClick = (event) => { console.log(event); // 这里的 event 是一个 SyntheticEvent 的实例 console.log(event.nativeEvent); // 这里可以访问原生的 DOM 事件对象 }; render() { return <button onClick={this.handleClick}>Click me</button>; }}这段代码中,handleClick 方法接收一个合成事件的实例。我们可以访问此对象的所有属性和方法,如同处理一个原生事件一样。但由于它是合成的,它在所有浏览器中的行为都是一致的。综上所述,React 的合成事件系统提供了一种高效且一致的方式来处理浏览器间的事件差异,并优化了性能,同时简化了事件处理的复杂性。
阅读 33·2024年8月5日 12:43

javascript 如何实现高效的字符串前缀匹配

在JavaScript中实现高效的字符串前缀匹配通常可以通过以下几种方式:1. 原生字符串方法使用字符串的startsWith()方法,这是最简单直接的方法,性能也相当好。function isPrefix(str, prefix) { return str.startsWith(prefix);}// 使用示例console.log(isPrefix('javascript', 'java')); // 输出: trueconsole.log(isPrefix('javascript', 'script')); // 输出: false2. 正则表达式利用正则表达式的^锚点来匹配字符串的开头。function isPrefix(str, prefix) { let regex = new RegExp('^' + escapeRegExp(prefix)); return regex.test(str);}// 为了安全性,对特殊字符进行转义,防止注入攻击function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');}// 使用示例console.log(isPrefix('javascript', 'java')); // 输出: trueconsole.log(isPrefix('javascript', 'script')); // 输出: false3. 字符串切片比较通过截取原字符串前N个字符,然后与前缀进行比较。function isPrefix(str, prefix) { return str.slice(0, prefix.length) === prefix;}// 使用示例console.log(isPrefix('javascript', 'java')); // 输出: trueconsole.log(isPrefix('javascript', 'script')); // 输出: false4. 循环比较逐个字符比较,这通常不是最高效的方法,但在某些特定情况下可能是必要的。function isPrefix(str, prefix) { if (str.length < prefix.length) return false; for (let i = 0; i < prefix.length; i++) { if (str[i] !== prefix[i]) { return false; } } return true;}// 使用示例console.log(isPrefix('javascript', 'java')); // 输出: trueconsole.log(isPrefix('javascript', 'script')); // 输出: false5. 使用内置方法 indexOf检查前缀是否在字符串的开头位置。function isPrefix(str, prefix) { return str.indexOf(prefix) === 0;}// 使用示例console.log(isPrefix('javascript', 'java')); // 输出: trueconsole.log(isPrefix('javascript', 'script')); // 输出: false每种方法都有其适用场景,一般而言,如果只需要简单的前缀匹配,推荐使用startsWith()方法,因为它简单且意图明确。如果需要对匹配进行更复杂的控制,可能会选择正则表达式。在处理大量数据或性能至关重要的情况下,可以进行基准测试以确定哪种方法最有效。
阅读 50·2024年8月5日 12:43