前端面试问题汇总 - JS篇

1. JS的数据类型,如何判断js的数据类型?

数据类型有:Number,String,Boolean,Undefined,Null,Object,Array

其中,Number,String,Boolean,Undefined等基本数据可以直接用 typeof 进行判定。但对于对象类型(如数组和null),typeof通常不能准确判断,它会简单地返回 "object"。为了更准确地判断数据类型,可以使用instanceof运算符来判断对象是否为某个构造函数的实例。对于数组和null,可以使用Array.isArray()方法来判断是否为数组,使用== null来判断是否为null

2. 数组去重方法有哪些?

  • 新建数组,循环遍历去重
  • 借助Set数据类型,[...new Set(array)] 或 Array.from(new Set(arr))
  • 新建对象,遍历数组,在转回新的数组

3. 深拷贝和浅拷贝区别?如何实现深拷贝?

深拷贝和浅拷贝的区别在于是否真正获取了一个对象的复制实体,而不是引用。只针对Object和Array这样的引用数据类型。

浅拷贝仅仅是复制指向的内存地址,如果原地址中对象被改变,那么浅拷贝出来的对象也会相应改变。

深拷贝是在计算机中开辟一块新的内存地址用于存放复制的对象。

4. 说一下防抖和节流,分别如何实现?

  • 防抖的原理是在事件触发后等待一段时间,如果在这段时间内没有再次触发事件,则执行该事件,否则重新等待一段时间。适用于用户输入(如搜索框输入)等频繁触发的事件。
复制代码
// 封装`
`function` `debounce(func, delay)` `{`
  `let timerId;`
  `return` `function(...args)` `{`
    `clearTimeout(timerId);`
`    timerId =` `setTimeout(()` `=>` `{`
      `func.apply(this, args);`
    `}, delay);`
  `};`
`}`
`// 引用示例const debouncedFunction = debounce(function() {`
`  console.log('Debounced function executed');`
`},` `300);`
`// 触发事件`
`debouncedFunction();` `// 不会立即执行`
`debouncedFunction();` `// 不会立即执行`
`// 等待300毫秒后执行`
`// 如果在300毫秒内再次触发,则重新等待300毫秒
  • 节流的原理是规定一个单位时间,在这个单位时间内只能执行一次事件,如果在这个单位时间内触发多次事件,只有一次会生效。适用于用户频繁操作,但是我们希望限制其触发频率的场景,比如滚动加载、拖拽等。
复制代码
// 封装`
`function` `throttle(func, delay)` `{`
  `let canRun =` `true;`
  `return` `function(...args)` `{`
    `if` `(!canRun)` `return;`
`    canRun =` `false;`
    `setTimeout(()` `=>` `{`
      `func.apply(this, args);`
`      canRun =` `true;`
    `}, delay);`
  `};`
`}`
`// 引用示例`
`const throttledFunction =` `throttle(function()` `{`
`  console.log('Throttled function executed');`
`},` `300);`
`// 触发事件`
`throttledFunction();` `// 立即执行`
`throttledFunction();` `// 在300毫秒内再次触发,不会执行`
`

5. 闭包是什么?如何实现?使用场景有哪些?

闭包(Closure)是指在函数内部创建另一个函数时,内部函数可以访问其外部函数作用域的变量,即使外部函数已经执行结束并返回了,这个内部函数仍然可以访问和修改外部函数的变量。闭包使得函数可以保留对自己定义时所处环境的引用,这样它就可以在其定义的作用域之外执行。

使用场景:封装私有变量、模块化开发、延迟执行

6. 闭包 优缺点 分别有哪些 针对 闭包 缺点 有什么 解决方案

闭包的优点:

  • 访问外部变量: 闭包可以访问函数外部作用域的变量,使得函数能够访问其创建时所处的环境。
  • 保护变量: 闭包可以帮助保护函数内部的变量,防止外部代码对其进行意外修改,提高了代码的安全性。
  • 实现私有变量和方法: 闭包可以模拟私有变量和方法,使得外部无法直接访问,从而实现了封装。
  • 保存状态: 闭包可以保存函数执行时的状态,使得函数在多次调用之间保持状态的连续性。

闭包的缺点:

  • 内存泄漏: 如果闭包中引用了外部函数的变量,而这个闭包又被长期引用(比如被存储在全局变量或定时器中),可能会导致外部函数的变量无法被释放,造成内存泄漏。
  • 性能损耗: 使用闭包会增加内存和 CPU 的开销,因为闭包会捕获外部变量的引用,导致函数的作用域链变长,查找变量时需要遍历更多的作用域。

解决闭包缺点的方法:

  • 手动释放引用: 在不再需要使用闭包时,手动释放对外部变量的引用,以便垃圾回收器能够回收相关的内存。
  • 减少闭包的使用范围: 尽量减少闭包的作用域范围,避免将闭包存储在全局变量中或长期引用,以减少内存占用和性能损耗。
  • 使用模块化: 将功能模块化,通过模块化的方式管理变量和方法,可以降低闭包的使用频率,从而减少潜在的内存泄漏问题。
  • 避免滥用闭包: 在设计和编写代码时,避免过度依赖闭包,只在必要的情况下使用闭包,以免造成性能问题和内存泄漏。

7. 什么是JS原型?原型链是什么?

JS 原型是一个普通的对象,它包含了一组属性和方法,可以被其他对象共享。每个对象都有一个关联的原型对象,可以通过 proto 属性来访问它的原型对象。

原型链是由对象的原型对象构成的链式结构。当 JavaScript 查找对象的属性或方法时,如果当前对象本身没有该属性或方法,则会沿着原型链向上查找,直到找到相应的属性或方法,或者到达原型链的顶端(即 Object.prototype)。

原型链的顶端是 Object.prototype,它是所有 JavaScript 对象的根原型对象。

8. 作用域是什么?

作用域(Scope)是指在程序中定义变量的区域,确定了变量的可访问性和生命周期。

作用域主要有全局作用域(Global Scope)、局部作用域(Local Scope)两种。

9. 数组的常用方法有哪些?

  • push(el_1, ..., el_N): 将一个或多个元素添加到数组的末尾,并返回数组的新长度
  • pop(): 删除数组的最后一个元素,并返回该元素的值
  • shift(): 删除数组的第一个元素,并返回该元素的值,同时将数组的长度减 1
  • unshift(el_1, ..., el_N): 将一个或多个元素添加到数组的开头,并返回数组的新长度
  • concat(arr_1, ..., arr_N): 返回一个新数组,该数组是由当前数组和其他数组或值连接而成的
  • join(''): 将数组中的所有元素连接成一个字符串,使用指定的分隔符来分隔元素
  • slice(start, end): 返回一个新数组,该数组包含从原始数组中指定开始到结束(不包含)的部分
  • splice(start, deleteCount, item1, ..., itemN): 从数组中添加/删除项目,返回被删除的项目
  • indexOf(query, fromIndex): 返回数组中第一个匹配指定值的索引,如果没有找到则返回 -1
  • lastIndexOf(query, fromIndex): 返回数组中最后一个匹配指定值的索引,如果没有找到则返回 -1
  • forEach((el, index, arr) => {}, thisArg): 对数组的每个元素执行提供的函数
  • map((el, index, arr) => {}, thisArg): 创建一个新数组,该数组的每个元素都是原始数组的对应元素上调用回调函数的结果
  • filter((el, index, arr) => {}, thisArg): 创建一个新数组,包含原始数组中所有通过提供的测试函数的元素
  • reduce((accumulator, currentValue, currentIndex, array) => {}, initialValue): 对数组中的每个元素执行一个提供的函数(从左到右),将结果汇总为单个值
  • find((el, index, arr) => {}, thisArg): 返回数组中满足提供的测试函数的第一个元素的值,如果没有找到则返回 undefined
  • findIndex((el, index, arr) => {}, thisArg): 返回数组中满足提供的测试函数的第一个元素的索引,如果没有找到则返回 -1
  • every((el, index, arr) => {}, thisArg): 测试数组的所有元素是否都通过了指定函数的测试
  • some((el, index, arr) => {}, thisArg): 测试数组的某些元素是否通过了指定函数的测试

10. 对象的常用方法有哪些?

  • Object.keys(obj): 返回一个包含对象所有可枚举属性的数组
  • Object.values(obj): 返回一个包含对象所有可枚举属性值的数组
  • Object.entries(obj): 返回一个包含对象所有可枚举属性键值对的数组,每个键值对表示为 [key, value] 的形式
  • Object.assign(target, source1, source2, ...): 将一个或多个源对象的属性复制到目标对象,并返回目标对象
  • Object.create(proto, propertiesObject): 使用指定的原型对象和属性来创建一个新对象
  • Object.defineProperty(obj, prop, descriptor): 定义对象的一个新属性,或者修改现有属性的特性
  • Object.defineProperties(obj, properties): 定义或修改对象的多个属性的特性
  • Object.getOwnPropertyDescriptor(obj, prop): 返回指定对象上一个自有属性对应的属性描述符
  • Object.getOwnPropertyNames(obj): 返回一个包含指定对象所有自身属性名称的数组
  • Object.freeze(obj): 冻结一个对象,防止对对象进行修改
  • Object.is(value1, value2): 判断两个值是否严格相等,类似于 === 运算符
  • Object.seal(obj): 封闭一个对象,防止添加新属性并将所有现有属性标记为不可配置
  • Object.setPrototypeOf(obj, prototype): 设置一个对象的原型(即 proto 属性)为另一个对象或 null
  • Object.getOwnPropertySymbols(obj): 返回一个包含指定对象自有的所有 Symbol 属性的数组
  • Object.fromEntries(entries): 将一个键值对的列表转换为一个对象

11. 0.1+0.2等于0.3吗?为什么?如何解决?

0.30000000000000004。这是因为 JavaScript 使用 IEEE 754 浮点数标准来表示数字,而浮点数在计算机中是以二进制形式存储的,有时无法精确地表示十进制小数。

12. 如何改变一个函数的上下文?

要改变一个函数的上下文,可以使用 bind()、call() 或 apply() 方法来实现。这些方法允许您显式地指定函数在调用时应该使用的上下文(即 this 值)。

  • bind() : 创建一个新的函数,该函数与原始函数具有相同的代码和作用域,但是在调用时,其 this 值被绑定到指定的对象
  • call() : 调用函数,并且可以指定函数内部 this 的值,以及其他参数以逗号分隔的形式传递给函数
  • apply() : 与 call() 方法类似,但是接收一个参数数组,而不是一系列单独的参数

13. 如何做全局错误统一处理?

可以通过window.onerror注册全局错误处理函数并进行处理来实现全局错误的统一处理

14. 如何理解js是单线程的

JavaScript 是单线程的意味着在任何给定的时刻,JavaScript 引擎只能执行一个任务。

在 JavaScript 中,这个单线程被称为主线程(也称为 UI 线程),它负责执行所有的 JavaScript 代码、处理事件、执行 DOM 操作等。这意味着,当 JavaScript 代码正在执行时,其他任务(如用户输入、定时器事件、HTTP 请求等)必须等待。

这种单线程模型的主要原因是为了简化开发,并减少在多线程编程中可能出现的复杂性和竞态条件。JavaScript 最初是作为浏览器中处理用户交互的脚本语言而设计的,因此,使用单线程模型可以避免多个线程同时修改页面状态引发的问题,如数据竞争、死锁等。

虽然 JavaScript 引擎是单线程的,但是浏览器环境提供了一些机制来处理并发任务,比如事件循环(Event Loop)和异步编程模型。通过事件循环,JavaScript 可以在等待异步操作完成时继续执行其他任务,以保持页面的响应性和流畅性。

因此,虽然 JavaScript 是单线程的,但通过异步编程模型和事件循环机制,可以在单线程中实现并发执行任务的效果,从而更好地满足用户交互和应用程序的需求。

15. 事件循环

事件循环的主要工作流程如下:

  1. 执行同步任务(Synchronous Tasks): 当 JavaScript 引擎开始执行代码时,会首先执行当前调用栈中的所有同步任务,直到调用栈为空。
  2. 执行微任务(Microtasks): 在每个宏任务执行完毕之后,会立即执行所有微任务队列中的任务。微任务包括 Promise.then()、MutationObserver 和 process.nextTick(Node.js 中)等。
  3. 执行宏任务(Macrotasks): 在微任务执行完毕之后,会从宏任务队列中选择一个任务来执行。宏任务包括 setTimeout、setInterval、requestAnimationFrame、I/O 操作等。
  4. 等待新的任务: 一旦当前宏任务执行完毕,事件循环会检查是否有新的宏任务需要执行。如果有,事件循环会继续执行宏任务队列中的下一个任务,否则将继续等待新的任务加入。
相关推荐
陈辛chenxin2 分钟前
软件测试大赛Web测试赛道工程化ai提示词大全
前端·可用性测试·测试覆盖率
沿着路走到底3 分钟前
python 判断与循环
java·前端·python
Code知行合壹6 分钟前
AJAX和Promise
前端·ajax
大菠萝学姐16 分钟前
基于springboot的旅游攻略网站设计与实现
前端·javascript·vue.js·spring boot·后端·spring·旅游
心随雨下28 分钟前
TypeScript中extends与implements的区别
前端·javascript·typescript
摇滚侠32 分钟前
Vue 项目实战《尚医通》,底部组件拆分与静态搭建,笔记05
前端·vue.js·笔记·vue
双向3333 分钟前
CANN训练营实战指南:从算子分析到核函数定义的完整开发流程
前端
caleb_52033 分钟前
vue cli的介绍
前端·javascript·vue.js
Baihai_IDP34 分钟前
面向 LLM 的 GPU 系统工程方法论
人工智能·面试·gpu
Swift社区35 分钟前
如何监测 Vue + GeoScene 项目中浏览器内存变化并优化性能
前端·javascript·vue.js