2024前端面试训练计划-高频题-JavaScript基础篇
具体内容结构(可作为回答思路)为:简略回答,详细回答
1、JavaScript有几种数据类型?
-
简略回答
JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
-
详细回答
具体来说,分为两种类型:原始数据类型和引用数据类型:
- 原始数据类型:
- string
- number
- boolean
- null
- undefined
- symbol
- 表示创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
- bigInt
- 一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
- 引用数据类型:
- object
- array
- function
原始数据类型和引用数据类型的区别:
- 区别在于存储位置的不同
- 原始数据类型 直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型 存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
- 堆和栈的概念存在于数据结构和操作系统内存中
- 在数据结构中
- 栈中数据的存取方式为先进后出。
- 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
- 在操作系统中,
- 内存被分为栈区和堆区
- 栈区内存由编译器自动分配释放,
- 存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
- 堆区内存一般由开发者分配释放,
- 若开发者不释放,程序结束时可能由垃圾回收机制回收。
- 在数据结构中
2、JavaScript中如何判断数据类型
- 简略回答
- 使用typeof操作符,可以返回一个值的数据类型,它适用于除了 null 以外的所有值。
- 使用 instanceof 操作符,可以判断一个对象是否是某个构造函数的实例。
- 使用 Object.prototype.toString 方法,可以返回一个值的内部类型,它适用于所有值,包括 null 和 undefined。
- 还有一些其他的判断类型的方式,如 Array.isArray 判断数组类型、isNaN 判断是否为 NaN、Number.isInteger 判断是否为整数等。
- 详细回答
3、typeof null 为什么是object
- 简略回答
typeof null 的结果为 “object”,这是 JavaScript 语言的一个历史遗留问题。
- 详细回答
在 JavaScript 最初的版本中,使用 32 位的值表示一个变量,其中前 3 位用于表示值的类型。
在这种表示法下,null 被解释为一个全零的指针,000 表示对象,也就是说它被认为是一个空对象引用,因此 typeof null 的结果就是 “object”。
4、深拷贝和浅拷贝的原理
- 简略回答
浅拷贝:
- 浅拷贝指的是将一个对象的属性值复制到另一个对象,
- 如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。
- 浅拷贝可以使用 Object.assign 和展开运算符来实现。
深拷贝:
- 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,
- 因此对象获得的一个新的引用类型而不是一个原有类型的引用。
- 深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败
- 详细回答
5、变量提升可能带来什么问题
- 简略回答
1. 未经意的变量访问:
由于变量提升,变量的声明会被提升到当前作用域的顶部。这意味着在变量声明之前,变量已经存在,但其值为undefined。如果在变量声明之前使用变量,它将被视为存在但未定义,可能导致意外的行为或错误。
2. 变量覆盖:
由于变量提升,同一个作用域内的多个变量声明可能会相互覆盖。这可能导致变量值的意外改变或冲突。
3. 难以追踪变量声明:
由于变量提升,变量的声明可能分散在作用域的不同位置。这使得代码阅读和理解变得困难,因为变量的声明点与其实际使用点不一致。这可能导致代码的可读性和维护性下降。
- 详细回答
6、暂时性死区的问题
- 简略回答
let,const 定义变量时存在暂时性死区。在变量声明之前的区域内,变量是不可访问的。
- 详细回答
临时性死区的意义在于引入了更严格的变量声明和使用规则, 规避变量提升带来的结构不稳定性,提高代码的可靠性和可维护性
1. 错误捕获
-
- TDZ 的存在可以帮助开发者在开发过程中捕获潜在的错误。
- 在变量声明之前 访问变量会在静态检查阶段引发 ReferenceError,从而提醒开发者注意变量的声明和初始化顺序。
- 这有助于避免使用未声明或未初始化的变量,减少代码中的潜在错误。
2. 可读性和维护性
-
- TDZ 要求在使用变量之前显式声明和初始化它们。这使得代码更易于阅读和理解,因为变量的使用点和定义点更加清晰明确。开发者可以更轻松地追踪变量的生命周期,并更好地组织和维护代码。
3. 避免变量提升问题
-
- 在ES6之前,使用var声明的变量存在变量提升的问题,可能导致变量在使用之前被意外地访问。TDZ的引入消除了变量提升,强制开发者在使用变量之前进行显式声明和初始化,从而减少了因变量提升而产生的潜在问题。
4. 规范一致性:
-
- TDZ的规则和行为在不同的JavaScript环境中是一致的,如浏览器和Node.js。这提供了跨平台和跨环境的一致性,使开发者可以编写可移植的代码,并减少由于环境差异而引起的错误。
7、用const声明的变量是否可以被改变
- 简略回答
- substring() 方法接收两个参数,起始位置和结束位置。它截取从起始位置到结束位置之间的字符
- substr() 方法接收两个参数,起始位置和截取的字符数。它从起始位置开始截取指定数量的字符
- 详细回答
8、JavaScript有几种数据类型?
- 简略回答
-
splice() 方法可以在数组中添加、删除或替换元素,并返回被删除的元素,它会改变原数组。
-
slice() 方法是从原数组中返回指定开始和结束位置的元素组成的新数组,它不会改变原数组。
- 详细回答
9、promise.all 和 promise.allsettled区别
- 简略回答
promise.all 和 promise.allsettled都是promise的静态方法,用于处理多个promise对象的并发执行。
- 详细回答
区别
1. promise.all 方法在需要所有 Promise 对象 都成功的情况下使用。
Promise.all() 方法返回的 Promise 对象在所有的 Promise 对象都 resolve 之后,才会 resolve 并返回一个由所有 Promise 返回值组成的数组。如果其中有一个 Promise 被 reject,则会立即 reject 并返回相应的错误信息。简单来说,只有所有 Promise 都成功了才算成功,有一个失败了就算失败。
2. Promise.allSettled() 方法则适用于需要知道每个 Promise 的执行结果的情况下使用。
Promise.allSettled() 方法返回的 Promise 对象在所有的 Promise 对象都 resolve 或 reject 之后,才会 resolve 并返回一个由所有 Promise 状态对象组成的数组,每个状态对象包含一个 status 字段表示 Promise 状态,和一个 value 或 reason 字段表示 Promise 的返回值或错误信息。简单来说,它会等到所有 Promise 都执行完毕,无论成功还是失败,都会把每个 Promise 的状态信息收集到一个数组里面返回。
10、new 的理解
- 简略回答
new运算符用于创建新的对象并调用构造函数来初始化该对象
- 详细回答
实现步骤
- 创建一个空对象
- 通过Object.create()方法将新对象的原型设置为构造函数的原型
- 调用构造函数,使用apply方法将this指向新对象,并传入参数
- 如果构造函数返回一个对象,则返回这个对象,否则返回新创建的对象
11、let全局声明变量,window能取到吗
- 简略回答
使用 let 声明的变量不会挂在全局对象 window 上,因此无法通过 window.variableName 的方式访问。
- 详细回答
使用 var 声明的变量不同,var 声明的变量会被挂载在全局对象上,因此可以通过 window.variableName 的方式访问。
12、javascript连续多个bind,最后this指向是什么
- 简略回答
指向第一个绑定的对象
- 详细回答
- 原因是每次bind都会返回一个新的函数,这个新函数的this值被固定为bind调用时传入的第一个参数。
- 但是,后续的bind调用并不会改变已经被第一个bind固定的this值。
- 所以,连续多个bind时,最终的this指向第一个bind方法所绑定的对象。
13、javascript 异步加载如何实现
- 简略回答
- 使用
async
和defer
属性
当给<script>
标签添加async
属性时,浏览器会在下载脚本的同时继续解析 HTML 文档。脚本下载完成后,会立即停止 HTML 解析,执行脚本,脚本执行完毕后再继续解析 HTML。
- 动态创建 script 标签,并设置其 src 属性为需要加载的脚本 URL。
这种方式可以通过设置 onload 或 onreadystatechange 事件来检测脚本是否加载完成。
- 使用 XMLHttpRequest 对象或 Fetch API 发送异步请求,并在请求成功后将响应文本解析为 JavaScript 代码,然后使用 eval() 函数或 Function() 构造函数来执行脚本。
- 详细回答
异步加载和同步加载的区别
- 异步加载可以提高页面的加载速度和响应性能,避免因 JavaScript 阻塞而造成页面卡顿的情况。
- 异步加载可以避免因加载脚本而造成的阻塞情况,使页面的其他资源可以更快地加载和呈现。
- 异步加载可以更灵活地控制脚本的加载顺序和执行时间,可以根据页面需要动态加载和卸载脚本,提高页面的可维护性和可扩展性。
14、JavaScript有几种数据类型?
- 简略回答
作用域 是指变量和函数的可访问范围。它可以控制变量的可见性和生命周期,避免命名冲突和意外修改变量值等问题。
- 详细回答
作用域类型
- 全局作用域
在全局作用域中声明的变量可以在任何地方访问;
- 函数作用域
函数作用域中声明的变量只能在函数内部访问;
- 块级作用域
ES6引入了块级作用域,块级作用域指在代码块中定义的变量,使用let和const关键字声明的变量只能在块级作用域中访问。
作用域链 当程序执行到一个作用域时,它会先搜索该作用域中的变量,如果找到了就直接使用,否则就向上一级作用域继续搜索,直到找到全局作用域为止。这个 搜索过程 形成了作用域链,它保证了变量在正确的作用域中被访问和使用。
15、事件循环
- 简略回答
浏览器的事件循环(Event Loop)可以使单线程的JavaScript代码在 等待某些操作完成时 继续执行其他任务,从而处理多个任务,实现异步编程。
- 详细回答
事件循环是一种机制,它会不断地轮询任务队列(Task Queue),并将队列中的任务依次执行。
- 任务可以分为两类:宏任务(Macro Task)和 微任务(Micro Task)。
- 宏任务通常包括一些需要花费较长时间的操作,例如定时器、事件回调等等。
当一个宏任务执行完毕后,JavaScript 引擎会检查是否存在未执行的微任务,如果存在,则立即执行这些微任务。在所有微任务执行完毕后,JavaScript 引擎会再次开始执行宏任务。
- 微任务通常包括一些需要尽快执行的操作,例如 Promise 的回调函数、MutationObserver 的回调函数等等。
微任务可以使用 Promise 对象的 所有方法 或者 MutationObserver 的 observe() 方法注册。
执行步骤
- 执行当前执行当前宏任务中的同步代码,直到遇到第一个宏任务或微任务。
- 如果遇到微任务,则将它添加到微任务队列中,继续执行下一个同步代码。
- 如果遇到宏任务,则将它添加到宏任务队列中,继续执行下一个同步代码。
- 当前微任务或者宏任务执行完毕后,JavaScript 引擎会检查微任务或宏任务队列是否为空。如果不为空,则执行队列中的第一个任务,重复执行该步骤直到任务队列为空。
- 当前事件轮询结束,等待下一次事件的触发。
16、事件流
- 简略回答
javascript 事件流 是浏览器处理事件的方式。事件流描述了从页面中接收事件的顺序。顺序是从父节点到子节点,再从子节点到父节点。
- 详细回答
事件流分为三个阶段:捕获阶段、目标阶段和冒泡阶段。
- 捕获阶段:是指事件从最外层的节点开始,逐层向下传递到目标节点。
- 目标阶段:是指事件到达目标节点后,触发目标节点的事件处理程序。
- 冒泡阶段:是指事件从目标节点开始,逐层向上传递到最外层节点。
事件传播过程停止和取消方法
- 事件的传播过程被停止的方法是调用事件对象的stopPropagation()方法。
- 取消事件的默认行为的方法是调用事件对象的preventDefault()方法。
17、instanceof 的理解
- 简略回答
- instanceof 运算符可以用于判断一个对象是否是某个构造函数的实例
- 原理是基于原型链的检查,如果对象的原型链比较深,那么检查的效率会比较低,影响性能。
- 详细回答
实现步骤
- 检查是否为基本数据类型,如果是,直接返回false
- 通过Object.getPrototypeOf()方法获取对象的原型
- 循环检查原型链,直到找到constructor.prototype构造函数上的原型 或者 原型链为null为止
18、for-in 和 for-of 的区别
- 简略回答
for…in 循环用于遍历对象的可枚举属性
- 它会将对象的每个属性名称(或键名)作为迭代变量来遍历,包括自有属性和继承属性。
- 因此,它并不适用于遍历数组和类数组对象。
for…of 循环用于遍历可迭代对象的元素
-
它会将对象的每个元素作为迭代变量来遍历,
-
只能遍历实现了迭代器接口(Iterator)的对象
-
详细回答
19、箭头函数和普通函数的区别
- 简略回答
- 箭头函数没有自己的 this 上下文,它的 this 上下文继承自外部作用域,因此不能使用 call()、apply() 或 bind() 方法改变 this 上下文。
- 箭头函数没有自己的 arguments 对象,如果需要获取函数参数,可以使用 rest 参数或者展开运算符。
- 箭头函数不能作为构造函数使用,不能使用 new 关键字创建对象。
- 详细回答
20、怎么删除数组最后一个元素
- 简略回答
- 使用pop() 方法,删除并返回数组的最后一个元素,即原数组值的最后一个元素被删除;
- 使用 splice() 方法,删除并返回数组,数组中为被删除的最后一个元素,即原数组值的最后一个元素被删除;
- 使用 slice() 方法截取指定索引元素,创建一个新的数组,包含除了最后一个元素以外的所有元素;
- 详细回答
21、原型和原型链
- 简略回答
原型(Prototype)
定义:在 JavaScript 中,原型是一个对象,它是函数所特有的属性。当使用函数创建对象(通过new关键字)时,新创建的对象会从这个函数的原型对象继承属性和方法。
原型链(Prototype Chain)
定义:原型链是 JavaScript 中对象之间的一种链式查找机制。当访问一个对象的属性或方法时,如果在该对象本身不存在这个属性或方法,JavaScript 会沿着这个对象的原型(__proto__属性,它指向对象的原型对象)向上查找,这个查找路径形成的链条就是原型链。
- 详细回答
原型链作用
- 实现javascript的继承。
- 使用场景使用
a. instanceof操作符在原型链上查找并判断某个对象是否是某个构造函数的实例。
特点 是查询原型链上的属性方法时性能差,试图访问不存在的属性时会遍历整个原型链。
原型对象获取方式
- 通过__proto__属性获取
- 使用Object.getPrototypeOf()方法获取
22、数组foreach能否提前结束循环
- 简略回答
1、数组的 forEach 方法默认不支持提前结束循环,即无法使用类似于 break 或 return 的语法来跳出循环。
2、但是可以使用抛出异常的方式来达到提前结束循环的效果。
- 详细回答
23、事件池
- 简略回答
事件池 是指浏览器在事件处理中,为了节省内存和提高性能,会对事件对象进行重复利用。
- 详细回答
实现方式
事件池机制会让浏览器,先检查是否存在空闲的事件对象,如果有,则直接从池中取出事件对象进行使用,避免了重复创建对象的开销,提高了性能。
特点
- 事件池机制只针对同一类型的事件。
a. 例如,当有多个鼠标点击事件同时发生时,浏览器会在内部为每个鼠标点击事件维护一个事件池,而不是把不同类型的事件混合在同一个事件池中进行管理。 - 由于事件池机制,在事件处理函数中不能异步获取事件对象,因为事件对象会在事件处理函数执行完成后被返回池中,如果此时异步代码仍然在使用事件对象,就会出现不可预料的后果。
24、如何判断一个对象是不是空对象
- 简略回答
- 使用Object.keys()方法,看返回的数组长度是否为0。
- 使用JSON.stringfy()方法,看返回的值是否为“{}”字符串大挎号。
- 使用for-in循环方法,看是否执行循环遍历,没有执行则为空对象
- 详细回答
25、如何合并对象
- 简略回答
- 使用Object.assign()方法
- 使用… 扩展运算符
- 详细回答
26、requestAnimationFrame和requestIdleCallback
-
简略回答
-
requestAnimationFrame 是浏览器提供的一种动画帧请求机制,作用是会在浏览器下一次绘制之前执行指定的回调函数。
-
requestIdleCallback 作用是在浏览器空闲时执行指定的回调函数。这个 API 的目的是让开发者能够在浏览器空闲时,进行一些比较耗时的任务,例如计算和渲染。
-
详细回答
27、Symbol 基础数据类型
- 简略回答
- Symbol 是在 ES6 中新增的基础数据类型。
- 它的主要作用是创建一个唯一的标识符,用于对象属性名的命名、常量的定义等场景。
- 详细回答
不会出现在 for…in、for…of、Object.keys()、Object.getOwnPropertyNames() 等遍历对象属性的方法中,因此可以用来定义一些不希望被遍历到的属性,例如一些内部实现细节或隐藏属性
28、闭包
- 简略回答
闭包是指有权访问另一个函数作用域中变量的函数
- 优点是私有化数据,在私有化数据的基础上保持数据,
- 缺点使用不恰当会导致内存泄漏,在不需要用到的时候及时把变量置为null
- 详细回答
- 闭包的作用 可以将变量和函数私有化,从而避免命名冲突和变量污染。
- 闭包的原理 是在内存中创建一个包含函数和变量的环境,当函数返回后,该环境仍然存在于内存中,因此可以被其他函数访问和使用。闭包中的变量和函数可以被多次调用和修改,从而在私有化数据上保持数据。
- 常见的使用场景
- 保存变量状态和私有化变量和函数。
- 用于事件处理和回调函数。
- 用于解决循环中异步问题。
哪些方式可以防止闭包引起的内存泄露
- 避免创建不必要的闭包:
如果闭包中包含的变量在函数执行完后不再需要使用,可以避免创建闭包,从而避免内存泄漏的问题
- 及时释放闭包:
在使用闭包时,需要在不需要时及时释放闭包,可以使用变量赋值为 null 或者手动解除对闭包的引用等方式来释放闭包。
- 使用模块模式:
在模块模式中,可以使用立即执行函数(IIFE)来创建一个私有作用域,从而避免闭包中的变量被外部访问,避免了内存泄漏的问题
- 避免循环引用:
如果闭包中引用了 DOM 元素或其他对象,需要确保在不需要时及时释放这些对象,避免循环引用造成内存泄漏的问题。
29、0.1加0.2为什么不等于0.3
- 简略回答
-
原因是 在js中,浮点数采用二进制形式表示实数。而二进制无法精确地表示某些十进制小数,比如0.1和0.2,在二进制下是无限循环小数。
-
所以js中执行0.1加0.2的计算时,由于无法精确表示这两个数字,所以他们会被转换成最接近的可表示二进制数,再进行计算。
-
这会导致微小的舍入误差,使结果不等于0.3。
解决方法
- 使用toFixed() 方法将结果四舍五入到指定小数位数。再进行类型转换。
- 行整数运算。
- 使用第三方高精度库。
- 详细回答
30、== 和 === 的区别
- 简略回答
- ==非严格运算符进行比较时,会进行类型转换,然后在比较两个值是否相等;
- ===严格比较运算符进行比较时,不会进行类型转换,直接比较两个值,值和类型相等时才会返回true;
- 详细回答
类型转换规则
- 如果两个值类型相同,则直接比较他们的值。
- 如果一个值是null,另一个值是undefined,则它们相等。
- 如果一个值是数字,另一个值是字符串,则将字符串转换为数字后再比较。
- 如果一个值是布尔值,另一个值是非布尔值,则将布尔值转换为数字或再比较。
- 如果一个值是对象,另一个值是数字、字符串或布尔值,则将对象转换为原始值后再比较。
31、ES6新特性
- 简略回答
- 块级作用域
a. 通过 let 和 const 声明的变量只在当前块级作用域中有效。 - 箭头函数
a. 使用 => 符号定义的函数,具有简化的语法和自动绑定 this 上下文的特点。 - 模板字符串
a. 使用反引号 `` 和 ${} 操作符,可以方便地拼接字符串和变量。 - 解构赋值
a. 可以将数组或对象的值解构赋给变量。 - 类和继承
a. 引入了 class 和 extends 关键字,使得 JavaScript 支持面向对象编程。 - Promise 和 async、await,用于处理异步函数。
- 详细回答
PS.未完待续,文中答案有误也欢迎评论指出!
另外作者也在找工作,欢迎公司有HC的同学内推,base地:上海、北京或杭州。