一、自我介绍
二、JS问题
1.检测数据类型的方法
1. `typeof` 运算符:`typeof` 运算符可以返回一个字符串,表示操作数的数据类型。
它可以用来检测基本数据类型(如 `number`、`string`、`boolean`、`undefined`、`symbol`)和函数
但对于 `null` 和对象(包括数组和日期等),它都会返回 `"object"`。
2.`instanceof` 运算符:`instanceof` 运算符用于检测构造函数的原型是否存在于指定对象的原型链中。
它可以用来检测对象是否属于某个特定的类或类型。
3.多态`Object.prototype.toString.call()` 方法:这个方法可以返回一个字符串,表示对象的类型。
它可以用来检测所有数据类型,包括基本数据类型和对象。
返回结果 [Object 数据类型]
4.`Array.isArray()` 方法:这个方法专门用来检测一个值是否是数组。
5.`isNaN()` 函数:这个函数用来检测一个值是否是非数字。
6.`Array.prototype`:判断是否继承自数组的原型
Array.prototype.isPrototype()
2.模块化
在 JavaScript 中,模块化是一种将代码分割成独立的、可重用的部分的方法,每个部分都可以被单独导入和使用。模块化的主要目的是提高代码的可维护性、可重用性和可读性。
在 React 项目中,模块化通常通过以下几种方式实现:
-
使用 ES 模块(ES Modules) : ES 模块是 JavaScript 标准的一部分,它允许你将代码分割成多个文件,每个文件都是一个模块。你可以使用
import和export关键字来导入和导出模块中的函数、类、变量等。 -
使用 CommonJS 模块: CommonJS 模块是另一种流行的模块化方案,它主要用于服务器端 JavaScript,但也可以在 React 项目中使用。CommonJS 使用
require和module.exports来导入和导出模块。 -
使用第三方库: 有许多第三方库可以帮助你管理 React 项目中的模块,例如
webpack、Rollup和Browserify。这些库可以将你的模块打包成单个文件,以便在浏览器中使用。
3.闭包
什么是:希望保护一个可以反复使用的局部变量的一种词法结构
作用:保护一个可以反复使用的局部变量
使用方法:
1.两个函数互相嵌套
2.外层函数创建出受保护的变量
3.外层函数return返回内层函数
4.内层函数要操作受保护的变量
使用场景:
1.防抖节流
2.鼠标移动事件 onmousemove
3.输入内容有变化就触发 input.oninput
4.js版本的媒体查询(windows窗口发生变化就会触发) windows.onresize
4.防抖节流的原理
在 JavaScript 中,防抖(Debounce)和节流(Throttle)是两种用于控制函数执行频率的技术。
它们通常用于处理频繁触发的事件,例如窗口调整大小、滚动事件或输入框的输入事件,以避免函数被过于频繁地调用,从而提高性能和用户体验。
防抖(Debounce)
防抖的原理是:在事件触发后,等待一段时间再执行函数。如果在这段时间内事件再次被触发,则重新计时。这样可以确保函数只在事件触发停止后执行一次。
节流(Throttle)
节流的原理是:在事件触发后,每隔一段时间执行一次函数。即使事件在这段时间内被多次触发,函数也只会执行一次。这样可以确保函数在一定时间内只执行一次,从而减少函数的调用次数。
5.new方法创建一个对象经历了什么
在 JavaScript 中,new 关键字用于创建一个类的实例。当你使用 new 关键字时,会发生以下几件事情:
- 创建一个新的对象。
- 将这个新对象的
__proto__属性设置为类的prototype。 - 执行类的构造函数,并将
this绑定到新创建的对象上。 - 如果构造函数返回一个对象,则返回这个对象;否则,返回新创建的对象。
6.修改this的指向
在 JavaScript 中,this 关键字的指向取决于函数的调用方式。在类的方法中,this 通常指向类的实例。然而,在某些情况下,你可能需要改变 this 的指向,例如在事件处理函数中,this 可能指向触发事件的 DOM 元素,而不是类的实例。
在你的代码中,如果你想在 App 组件的某个方法中改变 this 的指向,你可以使用 Function.prototype.bind() 方法。这个方法会创建一个新的函数,当这个新函数被调用时,它的 this 关键字会被设置为你指定的值。
7.call的原理
call 方法是 JavaScript 中函数对象的一个内置方法,它允许你调用一个函数,并指定函数执行时的 this 值。call 方法的基本语法如下:
function.call(thisArg,arr1,arr2,....)
thisArg:指定函数执行时的this值。如果thisArg是null或undefined,则this指向全局对象(在浏览器中是window对象,在 Node.js 中是global对象)。arg1, arg2,...:传递给函数的参数。
call 方法的原理是:
- 将函数作为
thisArg的一个方法来调用。 - 将
thisArg作为函数执行时的this值。 - 将
arg1, arg2,...作为函数的参数传递。
例如,假设有一个函数 sayHello,它需要一个 name 参数,并打印一条问候语:
function sayHello(name) {
console.log(`Hello, ${name}!`);
}
如果你想以 thisArg 为 this 值来调用 sayHello 函数,你可以使用 call 方法:
const person = {
name: 'Alice'
};
sayHello.call(person, 'Bob'); // 输出: Hello, Bob!
在这个例子中,sayHello 函数被调用时,this 指向 person 对象,所以 name 参数的值是 Bob。
call 方法的主要用途是在需要改变函数执行时的 this 值时,特别是在类的方法中,或者在回调函数中。它可以让你更灵活地控制函数的执行环境。
8.深拷贝
在 JavaScript 中,深拷贝是指创建一个新的对象,并且将原对象的所有属性和属性值都复制到新对象中,包括嵌套的对象和数组。这样,修改新对象不会影响原对象。
实现深拷贝的方法有多种,以下是其中的几种:
1.JSON.parse(JSON.stringify(object)) : 这是一种简单的深拷贝方法,它将对象转换为 JSON 字符串,然后再将其解析回对象。这种方法的缺点是它不能处理函数、正则表达式、日期对象等特殊类型。
2.递归拷贝: 这种方法通过递归遍历对象的所有属性,对每个属性值进行拷贝。如果属性值是对象或数组,则继续递归拷贝。
3.使用第三方库: 有许多第三方库可以帮助你实现深拷贝,例如 lodash 的 cloneDeep 方法。
9.数组的扁平化
在 JavaScript 中,数组的扁平化是指将一个多维数组(即数组中包含数组)转换为一个一维数组。这在处理嵌套数组时非常有用,尤其是在需要对数组进行遍历或操作时。
以下是几种实现数组扁平化的方法:
1.使用 concat 和 apply: 这种方法通过递归地将数组展开并使用 concat 方法将它们合并。
2.使用 flat 方法: ECMAScript 6 (ES6) 引入了 flat 方法,它可以将多维数组扁平化。你可以指定要展开的嵌套层级,或者使用 Infinity 来展开所有层级。
3.使用 reduce 和 concat: 这种方法与第一种方法类似,但使用了 reduce 和 concat 来实现扁平化。
4.使用 forEach 和 push: 这种方法通过遍历数组并将每个元素添加到一个新数组中来实现扁平化。
9.伪递归
在 JavaScript 中,伪递归(Pseudo-Recursion)通常指的是使用循环来模拟递归的行为。这种方法可以避免递归调用带来的栈空间消耗,特别是在处理深度嵌套的数据结构时。
以下是一个使用伪递归实现数组扁平化的示例:
function flattenArray(arr, result = []) {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flattenArray(arr[i], result);
} else {
result.push(arr[i]);
}
}
return result;
}
const originalArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(originalArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
在这个示例中,flattenArray 函数使用一个外部变量 result 来存储扁平化后的数组。它通过一个循环遍历输入数组 arr,如果遇到数组元素,则递归调用 flattenArray 函数,否则将元素添加到 result 数组中。
这种伪递归的方法可以有效地处理多维数组的扁平化,同时避免了递归调用带来的潜在问题,如栈溢出。在实际开发中,特别是在处理大型数据结构时,伪递归可以提供更好的性能和稳定性。
10.forEach的基础使用及原理
forEach 是 JavaScript 数组的一个内置方法,它允许你遍历数组的每个元素,并对每个元素执行一个回调函数。这个回调函数可以对数组元素进行操作,或者执行其他任何你需要的逻辑。
forEach 方法的基本语法如下:
array.forEach(function(currentValue, index, array) {
// 在这里执行你的逻辑
});
currentValue:当前正在处理的数组元素的值。index:当前元素在数组中的索引。array:正在遍历的数组。
forEach 方法会为数组中的每个元素调用一次回调函数,并且不会改变原始数组。
forEach 方法的原理是:
- 遍历数组的每个元素。
- 对每个元素调用回调函数,并将当前元素的值、索引和数组作为参数传递给回调函数。
- 重复步骤 1 和步骤 2,直到遍历完数组中的所有元素。
forEach 方法的优点是它提供了一种简单、直观的方式来遍历数组,并且可以在回调函数中执行任意逻辑。它的缺点是它不能像 map、filter 和 reduce 那样返回一个新的数组,或者对数组进行聚合操作。
11.forEach可以跳出循环吗
在 JavaScript 中,forEach 方法本身并不提供直接跳出循环的机制。一旦开始遍历数组,forEach 会执行到数组的最后一个元素,除非在回调函数中抛出异常。
如果你需要在某个条件满足时提前结束循环,你可以使用其他循环方法,比如 for 循环或者 while 循环,并结合 break 语句来实现。
如果你坚持要使用 forEach 方法,并且需要在特定条件下跳出循环,你可以在回调函数中抛出一个异常,然后在 forEach 方法外部捕获这个异常来实现跳出循环的效果。但是,这种方法并不推荐,因为它会影响代码的可读性和健壮性。
总的来说,虽然可以通过异常来跳出 forEach 循环,但通常不建议这样做,因为这会使代码变得复杂且难以维护。在大多数情况下,使用 for 循环或 while 循环并结合 break 语句是更合适的选择。
三、react问题
1.react hooks有哪些
React Hooks 是 React 16.8 版本引入的新特性,它允许你在不编写 class 的情况下使用 state 和其他 React 特性。以下是一些常用的 React Hooks:
useState: 用于在函数组件中添加状态。它返回一个状态值和一个更新状态的函数。useEffect: 用于处理组件的副作用,比如数据获取、订阅事件等。它类似于类组件中的componentDidMount、componentDidUpdate和componentWillUnmount生命周期方法。useContext: 用于在组件树中共享状态。它允许你订阅 React 上下文,从而避免通过 props 层层传递数据。useReducer: 用于管理复杂的状态逻辑。它类似于useState,但更适合于状态更新逻辑较为复杂的场景。useRef: 用于获取 DOM 元素的引用或保存可变值。它类似于类组件中的ref。useCallback: 用于缓存函数,避免不必要的重新渲染。它返回一个记忆化的回调函数,只有在依赖项改变时才会更新。useMemo: 用于缓存计算结果,避免不必要的重新计算。它返回一个记忆化的值,只有在依赖项改变时才会重新计算。useLayoutEffect: 与useEffect类似,但它会在所有 DOM 更新完成后同步调用。它适合用于处理需要同步执行的副作用,比如测量布局。
2.useEffect的使用
useEffect 是 React 中的一个钩子(Hook),它用于处理组件的副作用,比如数据获取、订阅事件等。它类似于类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期方法。
useEffect 的基本语法如下:
import React, { useState, useEffect } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// 副作用逻辑,比如数据获取、订阅事件等
document.title = `You clicked ${count} times`;
// 清理函数,用于清理副作用,比如取消订阅事件等
return () => {
document.title = 'React App';
};
}, [count]); // 依赖项数组,只有当依赖项变化时,副作用才会重新执行
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
在这个示例中,useEffect 用于更新文档标题,当 count 状态改变时。useEffect 的第一个参数是一个回调函数,它包含了副作用逻辑。第二个参数是一个依赖项数组,只有当数组中的依赖项发生变化时,副作用才会重新执行。
useEffect 还可以返回一个清理函数,用于清理副作用,比如取消订阅事件等。这个清理函数会在组件卸载时执行,或者在依赖项变化时,先执行清理函数,再执行副作用逻辑。
在使用 useEffect 时,需要注意以下几点:
- 确保在组件的顶层调用
useEffect,不要在条件语句或循环中调用。 - 如果副作用不需要依赖任何状态,可以传递一个空数组
[]作为依赖项,这样副作用只会在组件挂载和卸载时执行一次。 - 如果副作用需要依赖某个状态,可以将该状态添加到依赖项数组中,这样副作用会在状态变化时重新执行。
- 当组件卸载时,通常需要进行一些清理工作,例如取消订阅事件、清除定时器、关闭网络请求等,以避免内存泄漏和不必要的副作用。为了在组件卸载时执行这些清理操作,可以使用
useEffect钩子,并在其返回的函数中进行清理。
useEffect 是一个非常强大的钩子,它可以让你在函数组件中处理各种副作用,从而使代码更加简洁和易于维护。
3.react hooks为什么不能打乱顺序
Hooks 的调用顺序之所以重要,是因为 React 需要维护每个 Hook 的状态,并确保它们在组件的多次渲染之间保持一致。如果你打乱了 Hooks 的顺序,React 可能无法正确地跟踪每个 Hook 的状态,这可能导致错误或不可预测的行为。
例如,如果你在一个组件中先调用了 useState 来定义一个状态,然后在另一个地方又调用了 useState,React 会认为这是两个不同的状态,而不是同一个状态的更新。这可能会导致你的组件无法正确地更新状态,或者在某些情况下,可能会导致组件的重新渲染出现问题。
此外,React 还依赖于 Hooks 的调用顺序来确定哪些副作用应该在何时清除。例如,如果你在 useEffect 中订阅了一个事件,你需要在组件卸载时取消订阅,以避免内存泄漏。如果你打乱了 useEffect 的调用顺序,React 可能无法正确地清除副作用,这可能会导致性能问题。
因此,为了确保你的 React 组件能够正确地工作,你应该遵循以下规则:
- 不要在循环、条件语句或嵌套函数中调用 Hooks。
- 确保 Hooks 在组件的所有层级中都以相同的顺序调用。
- 如果你需要在多个地方使用相同的状态或副作用逻辑,考虑将它们提取到自定义 Hook 中。
4.react hooks调用顺序注意点
在使用 Hooks 时,需要注意以下几点:
- 确保在组件的顶层调用 Hooks,不要在条件语句或循环中调用。
- 如果副作用不需要依赖任何状态,可以传递一个空数组
[]作为依赖项,这样副作用只会在组件挂载和卸载时执行一次。 - 如果副作用需要依赖某个状态,可以将该状态添加到依赖项数组中,这样副作用会在状态变化时重新执行。
- 如果副作用需要在组件卸载时清理,可以在
useEffect中返回一个清理函数。
总之,Hooks 的调用顺序是 React 中非常重要的概念,它确保了组件的状态和副作用逻辑能够正确地工作。
5.虚拟DOM
虚拟DOM(Virtual DOM)是一种编程概念,用于在Web应用程序中高效地更新和渲染用户界面。它是React框架的核心特性之一,通过引入虚拟DOM,React能够优化DOM操作,减少不必要的重新渲染,从而提高应用程序的性能。
在传统的Web开发中,当需要更新页面的一部分时,通常需要直接操作DOM,这可能导致性能问题,尤其是在处理大量数据或频繁更新时。虚拟DOM的引入解决了这个问题,它提供了一个中间层,允许开发者以更高效的方式管理和更新UI。
虚拟DOM的工作原理如下:
- 状态变化:当组件的状态发生变化时,React会创建一个新的虚拟DOM树来表示新的状态。
- 差异比较:React会将新的虚拟DOM树与旧的虚拟DOM树进行比较,找出两者之间的差异。
- 更新DOM:只更新实际发生变化的部分,而不是整个DOM树。这是通过最小化DOM操作来实现的,从而提高性能。
- 渲染:更新后的虚拟DOM树会被渲染到实际的DOM中,用户看到的是更新后的界面。
虚拟DOM的主要优点包括:
- 性能优化:通过减少不必要的DOM操作,提高应用程序的性能。
- 跨平台:虚拟DOM可以在不同的平台上运行,如Web、移动应用等。
- 易于调试:虚拟DOM提供了一个清晰的结构,使得调试和错误追踪更加容易。
在React中,虚拟DOM是通过JSX(JavaScript XML)语法来创建的,JSX允许开发者以类似HTML的方式编写组件的结构,然后通过Babel等工具将其转换为JavaScript代码。
总之,虚拟DOM是React框架中一个重要的概念,它通过优化DOM操作来提高应用程序的性能,使得开发者能够更高效地构建和维护复杂的用户界面。
6.diff算法
1.虚拟DOM中key的作用:
1).简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的决定。
2).详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a.旧虚拟DOM找到了与新虚拟DOM相同的key:
1).若虚拟DOM中内容没变,直接使用之前的真实DOM
2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.旧虚拟DOM未找到与新虚拟DOM相同的key:
根据数据创建新的真实DOM,随后渲染到页面
2.用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低
2.如果结构中还包含输入类DOM:产生错误DOM更新 ==> 界面有问题
3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用idnex作为key是没有问题的。
7.父组件调用子组件的方法
在 React 中,父组件可以通过引用子组件的实例来调用子组件的方法。这通常涉及到使用 ref 属性来获取子组件的引用,然后通过这个引用来调用子组件的方法
// 子组件
class ChildComponent extends React.Component {
myMethod() {
console.log('子组件的方法被调用');
}
render() {
return <div>子组件</div>;
}
}
// 父组件
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.childRef = React.createRef();
}
handleClick() {
// 通过 ref 调用子组件的方法
this.childRef.current.myMethod();
}
render() {
return (
<div>
<ChildComponent ref={this.childRef} />
<button onClick={this.handleClick}>调用子组件的方法</button>
</div>
);
}
}
ReactDOM.render(<ParentComponent />, document.getElementById('root'));
在这个示例中,我们首先在父组件的构造函数中创建了一个 ref,并将其赋值给 this.childRef。然后,在父组件的 render 方法中,我们将这个 ref 传递给子组件,通过 ref 属性绑定到子组件上。
在父组件的 handleClick 方法中,我们通过 this.childRef.current 获取到子组件的实例,然后调用子组件的 myMethod 方法。
请注意,这种方法只适用于类组件。如果你使用的是函数组件,你可以使用 React 的 useImperativeHandle 钩子来暴露子组件的方法给父组件调用。
此外,使用 ref 来调用子组件的方法可能会导致组件之间的耦合度增加,因此在使用时需要谨慎考虑。在某些情况下,使用 props 来传递回调函数可能是一个更好的选择。
useImperativeHandle 是一个 React Hook,它允许你在使用 ref 时自定义暴露给父组件的实例值。通常情况下,当你在子组件上使用 ref 时,父组件可以访问到子组件的整个实例。但是,有时候你可能只希望父组件能够访问子组件的某些特定方法或属性。这时,你就可以使用 useImperativeHandle 来实现这一点。
四、简单的JS数学题(我也不知道为啥最后给我出个这个)
写一个3-8的随机数
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
let randomNumber = getRandomNumber(3, 8);
console.log(randomNumber);