记录_面试题_JS篇

103 阅读9分钟

1. JavaScript 有几种类型的值?

js 可以分为两种类型的值,一种是基本数据类型,一种是复杂数据类型

  • 基本数据类型分别是 Undefined、Null、Boolean、Number、String。
  • 复杂数据类型指的是 Object 类型,所有其他的如 Array、Date 等数据类型都可以理解为 Object 类型的子类。

主要区别

  • 它们的存储位置不同,基本数据类型的值占据空间小、大小固定,属于被频繁使用数据,所以直接保存在栈中;而复杂数据类型的值占据空间大、大小不固定,所有保存在堆中,通过使用在栈中保存对应的指针来获取堆中的值。

2. null 和 undefined 的区别?

(1)首先 UndefinedNull 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

(2)undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined;null主要用于赋值给一些可能会返回对象的变量,作为初始化。

(3)当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

3. js 原型,原型链? 有什么特点?

原型:

  • 原型是一个对象,通过构造函数创建的对象实例可以通过内部的指针找到这个原型对象,它的主要作用就是实现属性和方法的共享

原型链:

  • 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会沿着它的原型对象依次向上查找,直到找到该属性或方法,或者到达原型链的顶端。

特点

  • JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

4. js 作用域,作用域链?

作用域: 主要分为全局、局部、块级作用域,定义了当前变量,函数和对象可访问性。它存在的意义就是实现变量隔离不同作用域下同名变量不会有冲突

作用域链: 访问变量时,自己的作用域中没有,一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃,这种一层一层的关系,就是作用域链。

5. 什么是闭包?

当内部函数引用了外部函数的变量,并且外部函数执行后返回内部函数时就会产生闭包。闭包使得内部函数可以访问外部函数的变量,并且延长了这些变量的生命周期,从本质上来说,它将内部函数和外部函数的变量环境连接起来了。但是闭包会造成内存泄漏,要及时释放内存

常见的闭包有:

  1. 将函数作为另一个函数的返回值
  2. 将函数作为实参传递给另一个函数调用

闭包的应用:防抖节流

防抖:一定时间内多次触发同一个事件,只执行最后一次操作,如输入框的input事件,全部输入完在执行。

节流:一定时间内多次触发同一个事件,只执行第一次操作,如网页滚动时,只计算一次滚动距离。

6. 浅拷贝和深拷贝

浅拷贝: 浅拷贝是对原始对象属性值的精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。所以如果其中一个对象改变了这个地址,就会影响到另一个对象。(扩展运算符,for循环

深拷贝: 深拷贝是将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。(用Json.stringify()把对象转换成字符串,再用Json.parse把字符串转换成新的对象;或者使用函数库lodash的_.cloneDeep()

总而言之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。因此深拷贝的内存开销会比较大。

7. let 和 const 的注意点?

  • let声明的变量是可变的,可以在声明之后对其进行重新赋值
  • const声明的变量是常量,一旦被声明并初始化,就不能再对其值进行重新赋值
  • 声明的变量只在声明时的代码块内有效
  • 不存在声明提升
  • 不允许重复声明,重复声明会报错

8. 什么是 Promise?

Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变就无法再被改变。状态的改变是通过 resolve() 和 reject() 函数来实现的,我们可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

9. async/await

async用于申明一个函数是异步的,await等的是函数返回结果await关键词只能在async函数中用。如果是同步的情况,就直接返回;如果是异步的情况下,await会阻塞整个流程,直到结果返回之后,才会继续下面的代码。作用和promise类似,跟promise相比它的代码更简洁

10. 本地存储

cookie: 存储大小有4k,自身有过期属性,一旦过期就会自动删除, 在前端请求后端时会自动携带cookie,一般用于存储登录信息,在所有同源窗口中都是共享的

localStorage: 存储大小有5m,只要用户不删除就会一直存在,一般用于存储不易改变的数据,在所有同源窗口中都是共享的

sessionStorage: 存储大小有5m,只存在于一个页面的生命周期,页面关闭就会清除,在不同的浏览器窗口中不共享

11. 强缓存,协商缓存

(1)强缓存:强缓存是浏览器直接从本地缓存中获取资源的一种机制,不需要与服务器进行任何交互。当资源在缓存中且未过期时,浏览器会直接使用这些资源。

主要作用

  • 提高访问速度:由于资源已经存储在本地,用户访问时可以快速加载,无需从服务器下载。
  • 减少服务器负载:通过减少对服务器的请求,可以减轻服务器的压力。

(2)协商缓存:协商缓存是当浏览器发现缓存中的资源已过期时,会向服务器发送一个请求,询问是否可以使用缓存中的资源。如果服务器确认可以使用,则返回304状态码,浏览器使用缓存资源;如果资源已更新,则返回新的资源内容。

主要作用

  • 确保资源更新:即使资源已缓存,也能确保用户获取到最新的资源。

12. js数组常用方法

1. 数组的合并

  • forEach遍历其中一个数组,将该数组的每一项push到另一个数组中
  • concat合并多个数组
  • ...展开运算符

2. 数组的去重

  • 利用对象属性的唯一性使用foreach遍历去重:
// 初始化一个数组,包含一些具有id和name属性的对象
let arr = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Charlie' },
  { id: 3, name: 'David' }
]

// 创建一个Map对象,用于存储已经出现过的id
let uniqueMap = new Map()

// 创建一个数组,用于存储id唯一的对象
let uniqueArr = []

// 遍历原始数组
arr.forEach(item => {
  // 如果Map对象中不存在当前对象的id
  if (!uniqueMap.has(item.id)) {
    // 将当前对象的id作为键,true作为值存入Map对象,表示这个id已经出现过
    uniqueMap.set(item.id, true)
    // 将当前对象添加到uniqueArr数组中
    uniqueArr.push(item)
  }
})

console.log(uniqueArr)

/**  输出结果:
[
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'David' }
]
 */
  • new set()去重:
let arr = [1, 2, 2, 3, 4, 4, 5]

let uniqueArr = [...new Set(arr)]

console.log(uniqueArr)

/**  输出结果:
  [1, 2, 3, 4, 5]
 */
  • 借助第三方库lodash去重:

(1)uniq(适用于简单数据类型数组)

(2)uniqBy(适用于根据指定属性去重的复杂数据类型数组)

3. 数组的累加

Arr.reduce((返回值,当前元素)=>{条件},初始值)

每次运行都会 将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。reduce也可以实现数组去重

function unique(arr) {
  return arr.reduce((prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]), [])
}

let arr = [1, '1', 2, '1', 5, 5, 6, '6']

console.log(unique(arr))

/* 输出结果:
[ 1, '1', 2, 5, 6, '6' ]
* */

13. 什么是伪数组,如何将其转化为真数组?

伪数组:是指具有 length 属性并且元素按索引排列的对象,但不具备数组的方法

伪数组-->真数组

  • Array.from()
  • 使用扩展运算符

14. foreach 和 map 的区别?

  • forEach没有返回值,而map会返回一个新的数组
  • 当数组为 基础数据类型 时,forEachmap不会改变原数组
  • 当数组为 复杂数据类型 时,forEachmap会改变原数组
  • foreach不支持链式调用,而map支持链式调用,可以继续对返回的新数组进行操作。

15. js执行机制(事件循环Event Loop)

在js代码执行过程中,遇到同步任务直接执行遇到异步任务将其挂起,等到所有的同步任务执行完成后再将任务队列中的异步任务执行

异步任务分为宏任务和微任务每个宏任务都包含一个微任务队,微任务的优先级高于宏任务。意味着每次事件循环时,微任务队列中的任务会优先被执行。

常见的宏任务有:script中的代码,定时器,ajax。

常见的微任务有:Promise.thenasync/await