你的前端技术是否能通过这些高频面试题?

文章目录

  • 1.储存了某个数据到 localStorage,立即进行 localStorage.getItem,能否取到值?
  • 2.实现异步的方式有几种
  • 3.异步不阻塞
  • 4.选择 div 的第二个子元素
  • 5.display: none 和 visibility: hidden 的区别
  • 6.如果想要让一个元素的宽高包含元素的 boder 和 padding
  • 7.const, let 和 var 有什么区别
  • 8.处理 any 类型,如何避免 any
  • 9.举几个常用的 Hook,并且解释一下作用
  • 10.List, Map, Set 有什么区别?

1.储存了某个数据到 localStorage,立即进行 localStorage.getItem,能否取到值?

如果您已经成功将数据存储到 localStorage 中,调用 localStorage.getItem 方法可以立即取出对应的值。举个例子,如果您使用以下代码将一个键值对存储到 localStorage 中:

localStorage.setItem('key', 'value');

那么您可以立即使用以下代码从 localStorage 中获取该键对应的值:

const value = localStorage.getItem('key');
console.log(value);  // 输出 "value"

当然,在某些情况下(例如浏览器隐私模式下或者被禁用了 localStorage 功能等),调用 localStorage.getItem 方法可能无法取到值,请确保您的代码在适当的环境下运行。

2.实现异步的方式有几种

在 JavaScript 中,实现异步的方式有以下几种:

1. 回调函数:在函数内部执行异步操作,然后将异步操作的结果作为参数传递到回调函数中。

function doAsyncTask(data, callback) {setTimeout(function() {const result = data.toUpperCase();callback(result);}, 1000);
}doAsyncTask('hello', function(result) {console.log(result); // 'HELLO'
});

回调函数的方式非常常见,但也很容易导致回调地狱和可读性问题。

2. Promise:Promise 为一种更加优雅的异步模式,可以更好地解决回调地狱和可读性问题。

function doAsyncTask(data) {return new Promise(function(resolve, reject) {setTimeout(function() {if (data) {const result = data.toUpperCase();resolve(result);} else {reject(new Error('Data is null'));}}, 1000);});
}doAsyncTask('hello').then(function(result) {console.log(result); // 'HELLO'}).catch(function(error) {console.error(error);});

3. Async/Await:Async/Await 是 ES2017 的新增语法,它是基于 Promise 的封装,可以更加方便地编写异步代码,解决了 Promise 的then方法链式调用的可读性问题。

async function main() {try {const result = await doAsyncTask('hello');console.log(result); // 'HELLO'} catch (error) {console.error(error);}
}main();

上述三种方式都可以实现异步,具有不同的特点。回调函数是传统方式, Promise 可以更好的处理 Promise 的嵌套和 then 方法的链式调用问题,Async/Await 则提供了更加优雅的方式,在可读性和维护性方面更加友好。

3.异步不阻塞

异步不阻塞是指当一个异步操作被触发时,它会在后台执行(非主线程中执行),在等待操作完成的同时,主线程可以继续执行其他代码,不会被该异步操作所阻塞。

在 JavaScript 中,异步操作通常使用回调函数、Promise 对象或 async/await 语法来实现。例如,以下代码使用 Promise 对象实现了一个异步操作:

function asyncOperation() {return new Promise((resolve, reject) => {// 模拟一个异步操作setTimeout(() => {resolve('异步操作完成');}, 1000);});
}console.log('异步操作执行前');asyncOperation().then((result) => {console.log(result);
});console.log('异步操作执行后');

在上面的示例中,当程序执行到 console.log('异步操作执行前') 时会首先输出该语句,然后立即执行下一行代码 asyncOperation(),该函数返回一个 Promise 对象,这个 Promise 对象被 resolve 后继续执行后面的 then 方法中的回调函数,并输出 '异步操作完成',最后输出 '异步操作执行后'

因此,异步操作的执行不会阻塞主线程的代码,而是后台执行,等待异步操作完成后再去执行相应的回调函数或 Promise 对象的 then 方法中的回调函数。

4.选择 div 的第二个子元素

您可以使用以下 CSS 选择器来选择 div 的第二个子元素:

div:nth-child(2)

在这个选择器中,div 表示选择所有的 div 元素,nth-child(2) 表示选择 div 的第二个子元素。注意,这里的计数不是从 0 开始的,而是从 1 开始的。

以下是一个示例,展示如何使用此选择器选择 div 的第二个子元素,并设置其背景颜色为红色:

<div><p>第一个子元素</p><p>第二个子元素</p><p>第三个子元素</p>
</div>
div:nth-child(2) {background-color: red;
}

在上面的示例中,第二个 <p> 元素的背景颜色将变为红色。

5.display: none 和 visibility: hidden 的区别

display: nonevisibility: hidden 二者都可以用来隐藏元素,但它们的实现方式和效果略有不同:

  1. display: none:该样式会将元素完全隐藏,不占据页面布局,也不会响应用户事件(例如点击、鼠标移入等)。设置元素的 display 属性为 none 会将该元素从渲染树中移除,因此该元素及其所有后代元素在页面上不会被显示,且不占用绘制资源和空间。
  2. visibility: hidden:该样式会使元素隐藏,但在页面布局中仍占据空间,并继续响应用户事件。而这种方式只是让元素不可见,但是元素仍会被渲染出来,只不过不可见罢了。以至于它仍然对渲染树和布局模型可能产生影响。

下面是一个示例,展示 display: nonevisibility: hidden 的效果差异:

<div><p>文本内容</p><button>按钮</button>
</div>
div {width: 200px;height: 200px;background-color: #ccc;display: flex;justify-content: center;
}p {font-size: 24px;display: none;
}button {font-size: 18px;visibility: hidden;
}

在上面的示例中,pbutton 元素分别被设置为 display: nonevisibility: hidden。当浏览器渲染页面时,p 元素被完全移除了,因此页面上不会看到任何文本内容,而 button 元素虽然占据了页面的空间,但是在页面上不可见,以至于它不会响应用户事件,即使用户点击了该区域。

因此,一般情况下,如果需要完全删除某个元素,不想再让它占据空间或影响其他元素,应该使用 display: none,如果只需要将元素隐藏起来,但仍占据空间并继续响应事件,可以使用 visibility: hidden

6.如果想要让一个元素的宽高包含元素的 boder 和 padding

| < - a - > | b: 50px | < - c - > |,a 和 c 等比填充,用 flex 怎么实现;如果我想让它变成垂直方向,怎么做?

可以使用 box-sizing 属性来解决这个问题。将 box-sizing 属性设置为 border-box 可以使元素的宽度和高度包括了 padding 和 border,而不用再考虑它们的影响。

对于您的要求,假设父元素宽度已经确定,如果要让其中的子元素宽度和高度等比例填充,可以将父元素的 display 属性设置为 flex,并在子元素上使用一个占比为 1 的 Flexbox 布局框架。然后将子元素的 box-sizing 属性设置为 border-box 即可。示例代码如下:

<div class="parent"><div class="child"></div>
</div>
.parent {display: flex;justify-content: center;align-items: center;width: 200px;height: 200px;border: 10px solid #ccc;padding: 20px;
}.child {flex: 1;box-sizing: border-box;background: #f00;
}

在上面的示例中,父元素 .parent 的宽度为 200px,高度为 200px,同时设置了边框和内边距。子元素 .child 使用了 Flexbox 布局的占比为 1 的框架,在 box-sizing 属性设置为 border-box 之后,它的实际宽度和高度就会考虑到父元素的边框和内边距。

如果希望这个布局框架垂直方向上往下延伸,只需将 .parent.child 元素都添加上 flex-direction: column 属性,示例代码如下:

.parent {display: flex;justify-content: center;align-items: center;width: 200px;height: 200px;border: 10px solid #ccc;padding: 20px;flex-direction: column;
}.child {flex: 1;box-sizing: border-box;background: #f00;
}

7.const, let 和 var 有什么区别

constletvar 是用于声明变量的关键字,它们有以下区别:

  1. const:用于声明一个只读的常量,一旦被声明,它的值不能被修改。但是,如果是引用类型(例如对象、数组)时,引用本身是不能被修改的,但对象或数组中的属性或元素是可以修改的。
  2. let:用于声明一个块级作用域的变量,它的作用域仅限于当前的代码块,不会被提升到函数作用域或全局作用域。
  3. var:用于声明一个函数作用域或全局作用域的变量,它的作用域不受代码块的限制,而是在整个函数或全局范围内都可以访问。

下面是一个示例,展示 constletvar 的差异:

function test() {var a = 10;let b = 20;const c = 30;if (true) {var a = 50;let b = 60;const c = 70;console.log(a, b, c);}console.log(a, b, c);
}test(); // 输出:50 60 70,10 20 30

在上面的示例中,使用 var 声明的变量 a 在代码块内部被重新赋值,同时在代码块外部也被重新赋值。这是因为 var 声明的变量的作用域不仅局限于代码块内部,而是在整个函数作用域范围内有效。而使用 letconst 声明的变量 bc 只在代码块内部有效,并且代码块内的重新赋值不会影响到代码块外部的变量。

因此,对于变量的声明,应该优先使用 constlet,避免使用 varconst 用于声明不会被重新赋值的常量(例如数学常数、常量字符串等),使用 let 声明变量并且在需要重新赋值的情况下使用它。如果您需要使用一个变量,但不希望它在函数或全局范围内被访问,请使用 let

8.处理 any 类型,如何避免 any

any 是 TypeScript 中的一种通用类型,用于表示任何类型的值。由于它是一种非常宽泛的类型,无法提供静态类型检查的保障,使用 any 类型会增加代码出错的风险,并且对于代码可读性和可维护性也不利。

为了更好地利用 TypeScript 的静态类型检查,应该尽可能避免使用 any 类型。下面是一些方法可以帮助您减少在代码中使用 any

  1. 尽可能地使用具体的类型:在代码中使用具体的类型可以帮助您充分利用 TypeScript 的类型检查功能,这有助于在开发过程中尽早发现潜在的类型转换错误等问题。例如,尽量使用字符串、数字、布尔等具体类型,而不是将它们视为 any 类型。
  2. 使用泛型:在需要处理多种类型的函数或类时,可以使用泛型来代替 any。泛型可以帮助您在编译时捕获类型错误,并提高代码的可读性和可维护性。
  3. 定义接口或类型别名:在需要包含多个属性或方法的复杂结构中,可以使用接口或类型别名来定义类型,而不是使用 any。使用接口或类型别名可以帮助您充分利用 TypeScript 的类型检查功能,并避免类型转换错误。
  4. 使用非空断言操作符:在某些情况下,可以使用非空断言操作符 ! 来告诉 TypeScript 某个变量不为空,从而避免将其视为 any 类型。但是,使用非空断言操作符需要慎重,可能会掩盖真正的类型错误。

总之,尽可能避免使用 any 类型可以帮助您充分利用 TypeScript 的静态类型检查功能,提高代码的可读性和可维护性。在编写代码时,应该尽可能使用具体的类型、泛型、接口等替代方案来代替 any 类型。

9.举几个常用的 Hook,并且解释一下作用

React Hooks 是 React 16.8 版本引入的新功能,当我们需要在函数式组件中使用状态和生命周期方法时,可以使用 Hooks,以下是几个常用的 Hooks。

  1. useState()

useState Hook 允许函数组件使用状态,这是一个能够重复使用的类似 this.state 和 this.setState 方法的 Hook。useState 为函数组件提供了在不转换为类的情况下存储和更新组件状态和其它变量的能力。

import React, { useState } from 'react';function Example() {// 声明一个新的状态变量,名为 "count"const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}

在上述示例中,useState 的初始值为 0,然后修改 count 的值需要使用 setCount 方法。每次修改了 count 的值,整个组件都会重新渲染。

  1. useEffect()

useEffect 可以用来在函数组件中执行副作用操作,例如获取远程数据、添加或删除 DOM 元素等。useEffect 在每次渲染后执行,但是可以根据需要进行激活和清除。

import React, { useState, useEffect } from 'react';function Example() {const [count, setCount] = useState(0);// 相当于 componentDidMount 和 componentDidUpdate:useEffect(() => {// 更新文档标题document.title = `You clicked ${count} times`;});return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}

上述示例中,每次 count 改变,useEffect 执行后会修改文本标题。如果需要进行清除操作,则需要返回一个清除函数:

useEffect(() => {// 执行副作用操作return () => {// 清除操作};
});
  1. useContext()

useContext 用于在组件之间共享数据,可以避免使用 props 进行层层传递。通过使用 useContext,您可以将公共数据从父组件提取并将其传递到子组件中。

import React, { useContext } from 'react';// 创建一个上下文对象
const MyContext = React.createContext(defaultValue);function ChildComponent() {// 读取 MyContext 的值const contextValue = useContext(MyContext);return (<div>{contextValue}</div>);
}function ParentComponent() {// 将值传递给 MyContextreturn (<MyContext.Provider value="Hello World!"><ChildComponent /></MyContext.Provider>);
}

在上述示例中,将值“Hello World!”传递给 MyContext,并在 ChildComponent 中使用 const contextValue = useContext(MyContext) 访问该上下文的值。

React Hooks 还有很多其它 Hook,比如 useReduceruseRefuseCallbackuseMemo 等,不同的 Hook 对应不同的用途。在实践中,您可以根据具体需要选择适当的 Hook,避免重复代码和 class 声明。

10.List, Map, Set 有什么区别?

ListMapSet 都是 JavaScript 中常用的数据结构,它们有以下区别:

  1. List:一组有序的值的集合,类似于数组。你可以通过索引或值来访问列表中的一项。List 具有可变性,可以添加、删除或修改其元素。在 JavaScript 中,可以使用数组(Array)实现类似 List 的数据结构。
// 使用数组实现类似于 List 的数据结构
const list = ['a', 'b', 'c'];
list.push('d');
console.log(list); // ['a', 'b', 'c', 'd']
  1. Map:一组键值对的集合,可以像字典一样使用。每个键都必须是唯一的,而且可以通过键快速查找值。Map 具有可变性,可以添加、删除或修改其键值对。在 JavaScript 中,可以使用对象(Object)实现类似 Map 的数据结构。
// 使用对象实现类似于 Map 的数据结构
const map = { a: 1, b: 2, c: 3 };
map.d = 4;
console.log(map); // { a: 1, b: 2, c: 3, d: 4 }
  1. Set:一组唯一值的集合,不考虑元素的顺序。Set 具有可变性,可以添加或删除其元素。在 JavaScript 中,可以使用数组(Array)和indexOf()或者includes()方法或者对象字面量({})实现类似 Set 的数据结构。
// 使用数组和 indexOf() 方法实现类似于 Set 的数据结构
const set1 = ['a', 'b', 'c'];
if (!set1.includes('d')) {set1.push('d');
}
console.log(set1); // ['a', 'b', 'c', 'd']// 使用对象字面量实现类似于 Set 的数据结构
const set2 = { a: true, b: true, c: true };
set2.d = true;
console.log(Object.keys(set2)); // ['a', 'b', 'c', 'd']

综上所述,ListMapSet 的主要区别在于其元素的组织方式和基本操作。List 以有序方式组织元素,随着元素个数增加,查找速度变慢;Map 以键值对的形式组织元素,查找速度较快,但是需要占用更多内存;Set 则是以无序方式组织元素,并保证元素唯一。在实际开发中,应根据要求和场景的不同,选择适合的数据结构。

本文链接:https://my.lmcjl.com/post/5399.html

展开阅读全文

4 评论

留下您的评论.