以下是对前端JS面试题的详细回答:
1. 对 fetch 的理解及其优缺点
fetch 是一个基于 Promise 的,用于浏览器和服务器之间进行数据交换的 API。它提供了一个简洁的接口来请求资源。
优点:
- 语法简洁:fetch 的语法更加简洁,易于理解和使用。
- 基于 Promise:fetch 返回的是一个 Promise 对象,这使得它很容易与 async/await 一起使用,实现异步操作的简洁性。
- 更加语义化:fetch 的 API 设计更加语义化,符合现代 JavaScript 的编程习惯。
不足:
- 低层次 API:fetch 是一个低层次的 API,使用起来可能不如一些封装好的库(如 axios)方便。
- 错误处理不足:fetch 只在网络错误时拒绝 Promise,对于 HTTP 状态码 4xx 和 5xx 的错误,并不会拒绝 Promise,而是正常返回。这可能需要开发者手动检查响应状态码来处理错误。
- 默认不携带 Cookie:fetch 在默认情况下不会携带 Cookie,需要显式设置 credentials 选项为 'include'。
- 不支持超时控制和取消请求:fetch 原生不支持超时控制和取消请求,这可能需要开发者使用额外的代码来实现。
2. JavaScript 中 Object.keys 的返回值是无序的吗?
是的,JavaScript 中 Object.keys()
方法返回的对象的属性数组是无序的。虽然 ECMAScript 5.1 规范之前曾规定属性遍历的顺序应基于属性在对象定义时的顺序,但 ECMAScript 2015(ES6)及之后的规范并未对属性的遍历顺序做出明确规定。因此,Object.keys()
返回的属性数组的顺序可能会因 JavaScript 引擎的实现或对象属性的添加方式而有所不同。
3. JavaScript 的 BigInt 和 Number 类型有什么区别?
BigInt 和 Number 是 JavaScript 中表示数字的两种不同的数据类型。
- 表示范围:Number 类型可以表示有限范围内的整数和浮点数(通常是 -253),而 BigInt 类型可以表示任意大的整数。
- 精度:Number 类型是浮点数,可以表示小数和指数,而 BigInt 类型只能表示整数,具有更高的整数精度。
- 运算:BigInt 类型支持所有整数运算(加、减、乘、除、模),而 Number 类型还支持浮点数运算。
4. 什么是 JavaScript 的尾调用?使用尾调用有什么好处?
尾调用是指在函数的执行过程中,如果最后一个动作是一个函数的调用,并且这个调用的返回值被当前函数直接返回,则称该调用为尾调用。
好处:
- 优化性能:在支持尾调用优化的 JavaScript 引擎中,尾调用可以被优化为只使用一个调用栈帧,从而避免栈溢出的问题。这在进行大量递归操作时尤为重要。
- 提高代码可读性:尾调用通常使递归函数的实现更加简洁和直观。
5. JavaScript 为什么要进行变量提升?它导致了什么问题?
JavaScript 进行变量提升的原因主要是为了提高性能和容错性。
- 提高性能:在 JavaScript 代码执行之前,会进行语法检查和预编译。变量提升允许在预编译阶段就确定变量的声明和函数的定义,从而避免在每次执行代码时都重新解析一遍。这可以提高代码的执行效率。
- 容错性:变量提升可以在一定程度上提高 JavaScript 的容错性。即使代码中存在先使用后声明的变量,由于变量提升的存在,这些代码也不会报错。然而,这也可能导致一些难以察觉的错误,因为开发者可能误以为变量在使用前已经被声明和初始化了。
导致的问题:
- 变量覆盖:由于变量提升的存在,后声明的变量可能会覆盖先声明的同名变量,导致不可预测的行为。
- 全局变量污染:使用
var
声明的全局变量会成为全局对象的属性(在浏览器中是window
对象),这可能导致全局变量污染和命名冲突。
6. 能通过 window 对象取到全局变量吗?
是的,在浏览器环境中,可以通过 window
对象访问全局变量。全局变量实际上是 window
对象的属性。例如,如果声明了一个全局变量 var myVar = 10;
,则可以通过 window.myVar
来访问这个变量。
7. let、const 和 var 的区别是什么?
- 作用域:
var
声明的是函数作用域或全局作用域的变量,而let
和const
声明的是块级作用域的变量。 - 变量提升:
var
声明的变量会发生变量提升,可以在声明之前访问(但值为undefined
),而let
和const
声明的变量不会发生变量提升,在声明之前访问会导致 ReferenceError。 - 重新赋值:
var
和let
声明的变量可以被重新赋值,而const
声明的变量一旦被赋值后就不能再被重新赋值(但如果是对象或数组,可以修改其内部属性或元素)。
8. 对 JS 作用域的理解
JavaScript 的作用域决定了变量、函数和对象的可访问性。JavaScript 有三种主要的作用域:全局作用域、函数作用域和块级作用域。
- 全局作用域:在全局作用域中声明的变量和函数可以在整个 JavaScript 代码中访问。
- 函数作用域:在函数内部声明的变量和函数只能在该函数内部访问,这就是函数作用域。使用
var
关键字声明的变量具有函数作用域。 - 块级作用域:块级作用域是由
{}
包围的代码块创建的。在块级作用域中声明的变量和函数只能在该块内部访问。使用let
和const
关键字声明的变量具有块级作用域。
9. 什么是 JavaScript 的临时性死区?
临时性死区(Temporal Dead Zone,TDZ)是指在变量声明之前,该变量所处的作用域(对于 let
和 const
来说,是块级作用域;对于 var
来说,是函数作用域或全局作用域)中的一个“死区”。在这个死区中,变量是不可访问的,如果尝试访问会导致 ReferenceError。这是因为在变量声明之前,该变量在作用域中并不存在。
10. JavaScript 事件冒泡和捕获的区别是什么?默认是冒泡还是捕获?
区别:
- 事件冒泡:事件从目标元素开始,逐层向上传播到祖先元素,直到文档的根元素(在浏览器中通常是
document
对象)。 - 事件捕获:事件从文档的根元素开始,逐层向下传播到目标元素。
默认行为:
在 JavaScript 中,事件默认是按照冒泡阶段进行传播的。但是,可以通过在事件监听器中使用 capture
选项或 addEventListener
方法的第三个参数来指定事件在捕获阶段进行传播。
11. 什么是 JavaScript 的事件代理?
事件代理(Event Delegation)是一种常用的技术,它利用事件冒泡的原理,将事件监听器添加到父元素上,而不是直接添加到目标元素上。当目标元素触发事件时,事件会冒泡到父元素,父元素上的事件监听器可以捕获并处理该事件。这种方法可以减少事件监听器的数量,提高性能,并且便于管理事件监听器。
综上所述,这些问题是前端 JS 面试中常见的问题,涵盖了 fetch API、数据类型、作用域、变量提升、事件处理等多个方面。希望这些回答能够帮助你更好地准备面试。
JavaScript 的事件流
事件流是 JavaScript 处理事件的机制,它描述了事件从发生到被处理的过程。事件流主要包括两个阶段:捕获阶段和冒泡阶段。在捕获阶段,事件从文档的根元素开始,逐层向下传播到目标元素,这个阶段可以阻止事件到达目标元素。在冒泡阶段,事件从目标元素开始,逐层向上传播到文档的根元素,这个阶段可以对事件进行响应。事件流的顺序是:捕获处理程序 → 目标处理程序 → 冒泡处理程序。
JavaScript 的事件轮询机制
JavaScript 事件轮询(event loop)是 JavaScript 运行时环境中的一个机制,它允许 JavaScript 能够执行非阻塞的异步操作。由于 JavaScript 在大多数环境下(比如 Web 浏览器、Node.js)是单线程运行的,这意味着在任何给定的时刻它只能执行一个任务。事件轮询使得 JavaScript 能够表现出异步行为,即使它在内部是单线程的。事件轮询将任务分为同步任务和异步任务,异步任务又分为异步微任务和异步宏任务。同步任务首先执行,然后是异步微任务,最后是异步宏任务。
JavaScript 的原型链
在 JavaScript 中,原型链是一种继承和委托机制,它允许对象访问和继承其原型的属性和方法。原型链的运作方式如下:当访问一个对象的属性或方法时,如果该对象没有该属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的终点 null。原型链是 JavaScript 实现继承的主要方式之一。
原型重写
原型重写通常指的是修改一个对象的原型对象,或者修改一个类的原型链上的方法或属性。这可以通过多种方式实现,比如直接修改原型对象上的属性或方法,或者通过继承来重写父类的方法。
JavaScript 的原型修改
JavaScript 的原型修改指的是改变一个对象的原型对象,或者改变一个类的原型链。这可以通过多种方式实现,比如直接修改 __proto__
属性(尽管这种做法不推荐,因为 __proto__
不是标准属性,并且在严格模式下可能会引发错误),或者使用 Object.setPrototypeOf()
方法(这是一个更标准的方法,但也可能在某些环境中不被支持)。另外,也可以通过修改构造函数的 prototype
属性来影响由该构造函数创建的所有对象的原型链。
JavaScript 的原型链指向什么?
JavaScript 的原型链指向的是对象的原型对象。每个对象都有一个内部属性 [[Prototype]]
(在 ES5 中可以通过 __proto__
访问,但 __proto__
不是标准属性,并且在严格模式下可能会引发错误;ES6 引入了 Object.getPrototypeOf()
方法来获取对象的原型),它指向该对象的原型对象。原型对象本身也可以有自己的原型,这样就形成了一个链式结构,即原型链。
JavaScript 原型链的终点是什么?如何打印出原型链的终点?
JavaScript 原型链的终点是 null
。当访问一个对象的属性或方法时,如果沿着原型链一直向上查找都没有找到该属性或方法,那么最终会到达原型链的终点 null
。要打印出原型链的终点,可以通过遍历对象的原型链,直到找到 null
为止。例如,可以使用以下代码来打印出一个对象的原型链,并找到原型链的终点:
function printPrototypeChain(obj) {
let current = obj;
while (current !== null) {
console.log(current);
current = Object.getPrototypeOf(current);
}
console.log('Prototype chain end: null');
}// 示例对象
let obj = {};
printPrototypeChain(obj);
JavaScript 如何获得对象非原型链上的属性?
要获得对象非原型链上的属性,可以直接访问对象的属性。如果属性存在于对象本身(而不是在原型链上),那么就可以通过点操作符(.
)或方括号操作符([]
)来访问它。例如:
let obj = {a: 1, b: 2};
console.log(obj.a); // 输出: 1
console.log(obj['b']); // 输出: 2
在上面的例子中,a
和 b
都是对象 obj
本身的属性,而不是在原型链上的属性。
JavaScript 的闭包
闭包是指能够访问另一个函数作用域中变量的函数。即使外部函数已经执行完毕,闭包仍然可以访问其内部的变量和函数。闭包的作用是实现数据的封装和私有性,以及创建工厂函数等。闭包的使用场景包括数据缓存、函数柯里化、防抖和节流等。
JavaScript 闭包的实现原理
闭包的实现原理是基于 JavaScript 的作用域链和函数对象。当一个函数被定义时,它会创建一个新的作用域,并包含所有局部变量和函数声明。当这个函数被调用时,它的作用域链会被创建,并包含它自己的作用域、它的父作用域以及更上层的作用域(直到全局作用域)。如果一个函数在另一个函数内部被定义,并且引用了外部函数的变量,那么这个内部函数就形成了一个闭包。即使外部函数已经执行完毕,内部函数仍然可以通过作用域链访问外部函数的变量。
JavaScript 作用域、作用域链的理解
作用域是指变量、函数和对象在代码中的可访问范围。JavaScript 有三种主要的作用域:全局作用域、函数作用域和块级作用域。作用域链是在嵌套的作用域中解析变量时沿着的作用域链的列表。当访问一个变量时,JavaScript 引擎会沿着作用域链向上查找,直到找到该变量或者到达全局作用域为止。作用域链是闭包实现的基础。
JavaScript 的执行上下文
执行上下文是 JavaScript 代码在执行时的一个抽象环境。它包括变量对象(VO/AO)、作用域链和 this
值。每当 JavaScript 代码执行时,都会创建一个新的执行上下文。执行上下文分为全局执行上下文和函数执行上下文两种。全局执行上下文在 JavaScript 代码开始执行时创建,而函数执行上下文在每次调用函数时创建。
JavaScript 中 this 的理解、指向什么?
在 JavaScript 中,this
是一个特殊的关键字,它代表当前执行上下文中的一个对象。this
的值取决于函数的调用方式,而不是函数被定义的位置。具体来说,this
的指向有以下几种情况:
- 全局上下文中:在全局执行上下文中,
this
指向全局对象(在浏览器中通常是window
对象,在 Node.js 中是global
对象)。 - 函数上下文中:在函数执行上下文中,
this
的值取决于函数的调用方式。如果是作为对象的方法被调用,则this
指向调用该方法的对象;如果是作为普通函数被调用,则this
指向全局对象(在严格模式下为undefined
);如果是通过call
、apply
或bind
方法调用,则this
的值被显式指定为调用时传入的第一个参数。 - 构造函数中:在构造函数中,
this
指向新创建的对象实例。 - 箭头函数中:箭头函数没有自己的
this
值,它会捕获其所在上下文的this
值作为自己的this
值。
总之,this
的指向是 JavaScript 中的一个重要概念,它决定了函数执行时内部变量的访问方式。理解 this
的指向是掌握 JavaScript 面向对象编程的关键之一。