15. 基础数据类型与复杂数据类型的区别
基础数据类型(Primitive Data Types)和复杂数据类型(Complex Data Types)在JavaScript中有着明显的区别,主要体现在以下几个方面:
-
存储方式:
- 基础数据类型: 存储在栈内存中,它们的值直接存储在变量访问的位置上。
- 复杂数据类型: 存储在堆内存中,变量存储的是对对象的引用(内存地址),而非对象本身。
-
传递方式:
- 基础数据类型: 以值的形式传递,赋值时复制数据本身。
- 复杂数据类型: 以引用的形式传递,赋值时传递对象的引用,多个变量可以指向同一个对象。
-
拷贝行为:
- 基础数据类型: 在赋值或传递参数时,会创建数据的副本,互不影响。
- 复杂数据类型: 在赋值或传递参数时,传递的是对象的引用,对对象的修改会影响所有引用该对象的变量。
-
内存管理:
- 基础数据类型: 存储在栈内存中,当变量超出作用域时会被自动销毁。
- 复杂数据类型: 存储在堆内存中,需要手动管理内存,通过垃圾回收机制来释放不再使用的内存。
-
数据类型:
- 基础数据类型: 包括数字(Number)、字符串(String)、布尔值(Boolean)、空(null)、未定义(undefined)、Symbol。
- 复杂数据类型: 包括对象(Object)、数组(Array)、函数(Function)、日期(Date)、正则表达式(RegExp)等。
综上所述,基础数据类型和复杂数据类型在存储、传递、拷贝和内存管理等方面有着明显的区别。理解这些区别有助于更好地理解JavaScript中的数据类型,以及在开发中避免一些常见的问题。
17. 场景:我现在有两变量一个是number 类型的a,另一个是数组b,把他们作为参数传到一个函数里面,然后再函数内部进行变量值的修改,他外部也会收到影响吗。那如果我对这个数组又重新赋了一个数组呢,他会修改吗
在JavaScript中,基础数据类型(如数字)和复杂数据类型(如数组)在传递给函数时表现不同,这涉及到值的传递方式。
-
基础数据类型(数字): 当将一个数字类型的变量作为参数传递给函数时,实际上是将该数字的值传递给了函数,而不是引用。因此,在函数内部对该参数的修改不会影响到原始变量。
-
复杂数据类型(数组): 当将一个数组作为参数传递给函数时,传递的是数组的引用,而不是数组的副本。因此,在函数内部对数组的修改会影响到原始数组,因为它们引用的是同一个数组对象。
下面是一个示例代码来说明这一点:
function modify(a, b) {
a = 10; // 修改数字变量的值
b.push(4); // 修改数组,向数组末尾添加元素
b = [1, 2, 3]; // 重新赋值一个新数组给参数b
}
let a = 5;
let b = [1, 2, 3];
modify(a, b);
console.log(a); // 输出 5,数字变量a没有被修改
console.log(b); // 输出 [1, 2, 3, 4],原始数组b被修改了,因为是引用传递
在这个示例中,函数内部修改了数组b,并且也重新赋了一个新的数组给参数b。但是外部的数组b只有在被修改时才会受到影响,重新赋值并不会影响外部的变量。 19. 函数的防抖节流他们是基于什么实现的呢 应用场景。
函数的防抖(Debounce)和节流(Throttle)都是用来限制函数的执行频率,以优化性能和提升用户体验的。
防抖(Debounce):
防抖的基本思想是,在函数需要频繁触发时,只有当事件停止触发一段时间后才执行函数,如果在这段时间内事件又被触发,则重新计时。防抖常用来处理用户输入、搜索框输入联想等场景,以减少不必要的请求或函数执行次数。
实现原理:
- 在事件被触发后设定一个定时器。
- 如果定时器存在,则清除之前的定时器。
- 重新设定定时器,在指定的延迟时间后执行函数。
应用场景:
- 搜索框输入联想,减少请求次数。
- 窗口大小调整事件,避免频繁触发。
- 按钮点击事件,避免多次连续点击。
节流(Throttle):
节流的基本思想是,每隔一段时间只执行一次函数,无论事件触发频率有多高,保证函数在规定时间内至多执行一次。节流常用来控制事件触发的频率,以减少函数的执行次数,提升性能。
实现原理:
- 记录上次函数执行的时间。
- 每次触发事件时,判断距离上次执行的时间是否超过指定的时间间隔。
- 如果超过时间间隔,则执行函数,并更新执行时间。
应用场景:
- 滚动事件,限制页面滚动时函数执行的频率。
- 拖拽事件,控制拖拽时函数的执行频率。
- 网络请求,限制接口请求的频率,减轻服务器压力。
综上所述,防抖和节流都是用来优化函数的执行频率,提升性能和用户体验。选择合适的方法取决于具体的应用场景和需求。
21. 闭包
闭包(Closure)是指函数和函数内部引用的变量(环境)的组合。换句话说,闭包是一个函数和其相关的引用环境的组合体,它允许函数访问定义时的词法作用域,即使函数在定义之后被调用或在其定义的词法作用域之外执行。
闭包产生的原因是由于 JavaScript 的词法作用域规则,内部函数会保持对外部函数作用域的引用,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的变量。
闭包的特点包括:
-
函数嵌套: 闭包通常是由一个函数定义在另一个函数内部的情况下产生的。
-
内部函数引用外部变量: 闭包函数可以访问外部函数中声明的变量,即使外部函数已经执行完毕。
-
外部函数返回内部函数: 通常情况下,闭包函数会作为外部函数的返回值,被外部函数调用时形成闭包。
-
延长变量的生命周期: 闭包使得外部函数中的变量在函数执行完毕后仍然可以被内部函数访问,从而延长了变量的生命周期。
闭包在 JavaScript 中有许多应用,包括但不限于:
- 封装私有变量和方法。
- 实现模块化的代码结构。
- 在异步编程中处理回调函数。
- 创建特定作用域的函数。
- 用于柯里化函数的实现。
需要注意的是,由于闭包会引用外部函数的变量,可能会导致内存泄漏的问题,因此在使用闭包时需要注意避免循环引用或不必要的变量持久化。
23. TS 为什么要用,好处 ;接口、泛型;interface与type的区别,使用场景,结合你在实际中的开发
TypeScript(TS)是JavaScript的一个超集,它添加了可选的静态类型以及一些其他的语法扩展。使用TypeScript的好处包括类型检查、代码提示、更好的文档生成、更容易的重构、更好的开发工具支持等。
TypeScript的好处:
- 类型检查: TypeScript可以在编译时进行静态类型检查,避免了许多常见的运行时错误,提高了代码的可靠性和稳定性。
- 代码提示: TypeScript提供了更好的代码补全和提示,可以帮助开发者更快速地编写代码,并降低出错的可能性。
- 更好的文档生成: TypeScript的类型注解可以被自动生成文档,提供了更加清晰的API文档。
- 更容易的重构: 由于有类型检查的支持,重构代码时更加安全和可控。
- 更好的开发工具支持: 许多主流的开发工具(如VSCode、WebStorm等)都对TypeScript提供了良好的支持,包括语法高亮、错误提示、代码导航等功能。
接口(Interface)与泛型(Generics):
- 接口(Interface): 用于定义对象的形状,包括属性的类型和方法的签名,但不提供实现。接口可以被类实现(implements),也可以用于类型检查。接口提供了一种定义复杂数据类型的方法,使得代码更易读、易维护。
- 泛型(Generics): 用于在定义函数、类、接口时使用类型参数,以在多种类型上重复使用相同的代码。泛型可以在编译时确定具体类型,提高了代码的灵活性和复用性。
interface与type的区别:
- 接口(Interface): 主要用于定义对象的形状和结构,可以被类实现(implements),也可以用于类型检查。接口支持多重继承和扩展。
- 类型别名(Type): 主要用于定义复杂的类型别名,包括基本类型、联合类型、交叉类型等,但不能被类实现或继承,也不能被扩展。类型别名提供了更灵活的定义方式,但不支持多重继承和扩展。
在实际开发中,我通常使用TypeScript来开发大型项目,以提高代码的可维护性和可靠性。在项目中,接口主要用于定义对象的形状和结构,以保证数据的一致性和可读性;而类型别名则主要用于定义复杂的数据类型,提高了代码的灵活性和可读性。泛型在函数和类中的使用,使得代码更加通用和可复用,减少了重复编写相似代码的工作量。
25. 正则表单式学过吗(我说学过,立马后悔了捏)g 和 i 表示什么;怎么表示a开头b结尾;怎么匹配大写字母呢
26. 没问题,我可以帮您解答。
g 和 i 表示什么:
- g: 表示全局匹配(global),即不止匹配一次,而是匹配全部。如果不使用 g,那么只会匹配到第一个匹配项就停止。
- i: 表示不区分大小写(case-insensitive),即在匹配时忽略大小写的差异。
表示以 a 开头 b 结尾:
要匹配以 a 开头、b 结尾的字符串,可以使用正则表达式 /^a.*b$/。
^表示匹配字符串的开头。a表示匹配以 a 开头的字符。.*表示匹配任意数量的任意字符(除换行符外)。b表示匹配以 b 结尾的字符。$表示匹配字符串的结尾。
匹配大写字母:
要匹配大写字母,可以使用正则表达式 /[A-Z]/。
[A-Z]表示匹配任意大写字母。- 如果想匹配多个大写字母,可以在正则表达式中添加
+,如/[A-Z]+/,表示匹配至少一个大写字母。
希望这些解释对您有帮助。如果您有任何其他问题,都可以随时问我。
27. 本地存储的方案有那些,应用场景,你在实际中是再怎么用的,会考虑那些问题
本地存储是指将数据存储在客户端的浏览器中,而不是在服务器上。常见的本地存储方案包括:
-
Cookies(Cookie): 是存储在客户端浏览器中的小型文本文件,用于存储特定网站的用户信息。Cookies的大小有限制(通常为4KB),并且会随着每次HTTP请求自动发送到服务器端,因此不适合存储大量数据。
-
Web Storage:
- LocalStorage: 用于在客户端浏览器中存储键值对数据,不会随着HTTP请求发送到服务器端,且容量通常较大(通常为5MB)。
- SessionStorage: 与LocalStorage类似,但是数据在会话结束时自动删除,通常用于临时存储会话相关的数据。
-
IndexedDB: 是一种浏览器内置的数据库,提供了更强大的存储功能,支持存储大量结构化数据,并提供事务支持和索引查询等功能。
-
Web SQL Database: 是一种使用SQL语法的客户端数据库,已经被标准废弃,不再推荐使用。
在实际开发中,选择合适的本地存储方案取决于具体的应用场景和需求:
- Cookies 适用于存储少量的用户信息或会话信息,例如用户登录状态、语言偏好等。
- LocalStorage 适用于存储较大的非敏感数据,例如用户配置信息、本地缓存数据等。
- SessionStorage 适用于存储会话相关的临时数据,例如表单数据、页面状态等。
- IndexedDB 适用于需要存储大量结构化数据,并且需要进行复杂的查询和事务处理的场景,例如离线应用、大型数据集的存储等。
在使用本地存储方案时,需要考虑以下问题:
- 数据安全性: 敏感数据应加密存储,避免被恶意访问。
- 存储容量: 不同的存储方案有不同的容量限制,需要根据实际需求选择合适的方案。
- 跨浏览器兼容性: 不同的浏览器对于本地存储的支持程度有所差异,需要考虑跨浏览器兼容性。
- 数据同步: 对于需要在多个设备之间同步的数据,需要考虑使用云存储或同步算法来实现数据同步功能。
- 数据失效策略: 针对不再需要的数据,需要考虑采取合适的失效策略,及时清理过期数据,以减少存储空间的占用。
29. React的通信方案;尽可能多的再想想React自身跨组件的通信方式
在React中,组件之间的通信是非常常见的需求,可以通过多种方式实现:
-
Props传递:
- 父组件可以通过props将数据传递给子组件。
- 子组件可以通过props接收父组件传递的数据。
- 适用于父子组件之间的简单通信。
-
Context API:
- 可以在React中使用Context API来实现跨层级组件之间的数据传递。
- Context提供了一种在组件树中传递数据的方法,而不必一级一级手动传递props。
- 适用于多层级嵌套的组件之间需要共享数据的情况。
-
状态提升(Lifting State Up):
- 当多个组件共享相同的数据,或者多个组件需要更新同一部分数据时,可以将共享的状态提升到它们最近的共同祖先组件中管理。
- 通过在共同祖先组件中管理状态,可以实现跨组件的状态同步。
-
事件总线(Event Bus):
- 可以通过创建一个全局的事件总线来实现组件之间的通信。
- 组件通过订阅事件和发布事件的方式来进行通信,从而实现解耦和独立性。
- 适用于非直接父子关系的组件之间的通信。
-
Redux等状态管理库:
- 使用Redux等状态管理库可以将组件之间共享的状态集中管理,从而实现跨组件的状态共享和通信。
- Redux使用单一的全局状态树来管理应用的所有状态,通过派发action和触发reducer来更新状态。
- 适用于大型应用中需要复杂状态管理和组件之间频繁通信的情况。
-
React Hooks:
- 使用React Hooks中的useState、useReducer等钩子可以在函数组件中管理局部状态。
- 通过useContext钩子可以使用Context API来实现跨组件的状态共享。
- 适用于函数组件中的状态管理和通信。
以上是React中常见的通信方案,选择合适的方案取决于具体的应用场景和需求。在实际开发中,根据项目的复杂程度和团队的技术栈选择合适的通信方式是很重要的。
31. React中的异常是怎么处理的
在React中,异常处理通常通过错误边界(Error Boundaries)来实现。错误边界是一种React组件,用于捕获并处理其子组件树中发生的JavaScript错误,从而防止整个组件树崩溃。
要创建一个错误边界,需要使用componentDidCatch生命周期方法来捕获错误,并在错误发生时进行相应的处理。通常情况下,错误边界会渲染备用UI,以展示用户友好的错误信息,同时也可以将错误信息记录到日志中,或者向开发人员报告错误。
下面是一个简单的错误边界的示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// 将错误信息记录到日志中
console.error('Error occurred:', error, info);
// 更新状态以渲染备用UI
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
// 渲染备用UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 在需要使用错误边界的组件中进行包裹
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
在上面的示例中,ErrorBoundary是一个错误边界组件,它通过componentDidCatch方法捕获了子组件中发生的错误,并在错误发生时渲染了备用UI。然后,将需要进行错误处理的组件包裹在ErrorBoundary组件中,这样当子组件中发生错误时,错误边界会捕获并处理错误。
需要注意的是,错误边界只能捕获其子组件树中的错误,并不能捕获自身组件的错误、事件处理、异步代码(例如setTimeout或fetch请求)以及服务端渲染期间的错误。因此,在编写React应用时,应该将错误边界放置在合适的位置,以尽可能覆盖整个应用的错误处理需求。
33. 看你也写过vue,vue中有keep-alive,你能想想react中有那些方法可以去实现vue的这一个功能吗,尽可能的回答:就是如何实现一个组件的缓存,快速的回到上一个组件,不需要再走一遍数据的请求或组件的生命周期啊
在React中,要实现类似Vue中<keep-alive>的功能,即组件的缓存和快速回到上一个组件,可以考虑以下几种方法:
-
自行实现组件缓存逻辑:
- 创建一个缓存组件(例如
CacheComponent),在其中动态地渲染需要缓存的组件。 - 使用状态或上下文来管理需要缓存的组件的状态,并根据需要进行缓存和清除。
- 在切换到下一个组件时,检查是否有缓存的组件,如果有则直接显示缓存的组件,而不重新渲染。
- 创建一个缓存组件(例如
-
使用React Router中的
<Route>组件:- 使用React Router管理路由,使用
<Route>组件渲染需要缓存的组件。 - 使用React Router中的
<Switch>组件来切换路由,通过设置<Route>的exact属性,可以在路由切换时保持之前的组件状态。
- 使用React Router管理路由,使用
-
使用第三方库:
- 可以使用像
react-router-cache-route等第三方库来实现组件的缓存功能。 - 这些库提供了类似Vue中
<keep-alive>的功能,通过在路由配置中添加相应的配置,可以轻松地实现组件的缓存和复用。
- 可以使用像
-
使用上下文(Context):
- 可以使用React的上下文(Context)来共享组件状态,在需要缓存的组件中将状态保存在上下文中。
- 在切换到下一个组件时,检查上下文中是否存在缓存的状态,如果存在则直接使用缓存的状态渲染组件。
以上是一些在React中实现组件缓存和快速回到上一个组件的方法。具体选择哪种方法取决于项目的需求和复杂程度,以及开发团队的偏好和经验。
35. 算法:两个了解的排序算法
36. http缓存的命中过程,越详细越好
HTTP缓存是Web开发中常用的技术,它可以有效地减少网络传输量,提高网站的加载速度和性能。HTTP缓存的命中过程如下:
-
客户端发起请求:
- 客户端(通常是浏览器)向服务器发送HTTP请求,请求获取特定资源(例如网页、图片、CSS文件等)。
-
服务器处理请求:
- 服务器接收到客户端的请求后,首先会进行相应的处理,包括验证缓存是否命中、生成响应头等操作。
-
缓存验证:
- 服务器根据请求头中的信息判断是否需要使用缓存。
- 如果请求中包含了缓存相关的头部信息(如
If-Modified-Since、If-None-Match等),服务器会根据这些信息来验证缓存的有效性。
-
缓存命中:
- 如果缓存有效,服务器返回HTTP 304状态码(Not Modified),表示资源未被修改,客户端可以使用缓存。
- 客户端收到304响应后,将直接从缓存中获取资源,不再向服务器请求资源内容,从而加快页面加载速度。
-
缓存未命中:
- 如果缓存失效或未命中,服务器将返回HTTP 200状态码(OK),并返回最新的资源内容。
- 客户端收到200响应后,将使用服务器返回的最新资源内容进行页面渲染。
-
缓存更新:
- 如果服务器返回的资源和客户端缓存的资源不同(即缓存失效),客户端会将新的资源保存到缓存中,以便下次使用。
通过以上过程,可以看出HTTP缓存的命中过程是在客户端发起请求后,服务器根据请求的缓存验证信息来判断是否使用缓存。如果缓存有效,服务器返回304状态码,客户端直接从缓存中获取资源;如果缓存失效,服务器返回200状态码,并返回最新的资源内容给客户端。这样就能有效地减少网络传输量,提高网站的加载速度和性能。
38. 说一下你遇到的最大挑战,怎么解决的呢
39. 反问
面试官说我回答的有点快,所以半个小时问了我很多问题(八股选手,就只会背),当天晚上十点多,主管电话,二面,二面之前的。(面了阿里的菜鸟与饿了么,四个面试官体验整体都很好)
作者:在学习的Gemini
链接:www.nowcoder.com/feed/main/d…
来源:牛客网