前端面试必备 | JavaScript篇(P91-130)

alt

91. setTimeout和setInterval的区别是什么?

setTimeout和setInterval都是JavaScript中的定时器函数,它们的主要区别在于触发时间和执行次数:

  1. setTimeout函数会在指定的延迟时间后触发一次回调函数。延迟时间可以是一个毫秒数,表示回调函数将在指定的时间间隔后执行。 例如:setTimeout(function() { console.log('Hello!'); }, 1000); // 延迟1秒后执行一次回调函数

  2. setInterval函数会在指定的时间间隔内不断触发回调函数。每次回调函数执行完毕后,会等待指定的时间间隔再次触发。 例如:setInterval(function() { console.log('Hello!'); }, 1000); // 每隔1秒执行一次回调函数

总结来说,setTimeout是在指定的延迟时间后执行一次回调函数,只触发一次;而setInterval会不断地在指定的时间间隔内执行回调函数,一直到停止或页面关闭。

需要注意的是,尽量避免在短时间内频繁地使用setInterval函数,因为它可能会导致性能问题和内存泄漏。对于需要连续执行的任务,推荐使用递归调用的setTimeout函数来模拟setInterval的效果。

92. 如何使用定时器实现延迟执行异步操作?

使用定时器可以实现延迟执行异步操作的方式如下:

  1. 使用setTimeout函数:使用setTimeout函数可以在指定的延迟时间后执行回调函数,并模拟延迟执行异步操作的效果。例如:
setTimeout(function() {
  // 异步操作
}, 1000); // 延迟1秒执行

这段代码会在1秒后执行传入的回调函数。

  1. 使用Promise对象:使用Promise对象可以更方便地实现延迟执行异步操作的效果。可以创建一个包装延迟执行的Promise对象,并在resolve中执行异步操作。例如:
function delayAsyncOperation() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      // 异步操作
      resolve();
    }, 1000); // 延迟1秒执行
  });
}

delayAsyncOperation().then(function() {
  // 延迟1秒后执行的异步操作
});

这段代码中,创建了一个包装延迟执行的Promise对象 delayAsyncOperation,在resolve中执行异步操作。然后,通过调用then方法来处理延迟执行后的异步操作。

以上两种方式都可以实现延迟执行异步操作的效果,具体使用哪种方式取决于个人需求和代码风格。

93. 什么是AJAX?

AJAX(Asynchronous JavaScript and XML)是一种用于创建交互式Web应用程序的技术。它通过使用JavaScript和XML(现在也可用于JSON、HTML和文本等)在不刷新整个页面的情况下与服务器进行异步通信。这使得Web页面可以在后台与服务器进行交互,从而改善了用户体验。AJAX可以用于实现各种功能,如动态加载内容、实时搜索、表单验证等。

94. 如何使用原生JavaScript进行AJAX请求?

要使用原生JavaScript进行AJAX请求,可以通过以下步骤:

  1. 创建XMLHttpRequest对象,即XHR对象。
var xhr = new XMLHttpRequest();
  1. 使用open()方法指定请求的方法(GET、POST等)和URL。
xhr.open('GET', 'http://example.com/api/data', true);
  1. 设置响应类型(可选)。
xhr.responseType = 'json';
  1. 注册事件处理程序来处理请求的各个阶段。
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 请求已完成且响应已成功
    var response = xhr.response;
    // 处理响应数据
  }
};
  1. 发送请求。
xhr.send();

在上述代码中,XHR对象的onreadystatechange事件处理程序会在请求的不同状态发生变化时被触发。当readyState属性等于4(表示请求已完成)且status属性等于200(表示响应已成功)时,可以通过response属性获取响应数据。需要根据具体的需求来处理响应数据。

这只是一个简单的示例,实际上,还可以使用setRequestHeader()方法设置请求头,也可以使用send()方法发送POST请求并传递数据等。

95. fetch API和XMLHttpRequest有什么区别?

FETCH API是一种用于进行网络请求的新的原生JavaScript API,它提供了一种更简单和现代化的方式来发送和接收数据。相比之下,XMLHttpRequest(XHR)是一种较旧的用于进行网络请求的原生JavaScript API

一些FETCH API与XMLHttpRequest之间的主要区别包括:

  1. 语法的不同:FETCH API使用基于Promise的新语法,使其更易读和使用。而XMLHttpRequest使用传统的回调函数方式。

  2. 处理响应类型:FETCH API提供了更简单的方法来处理各种不同类型的响应,如JSON、文本、blob等。而在XMLHttpRequest中,需要手动设置responseType属性来处理不同的响应类型。

  3. 跨域请求:使用FETCH API进行跨域请求更简单,因为默认情况下它会处理跨域资源共享(CORS)。而XMLHttpRequest需要通过设置适当的请求头和服务器响应来处理跨域请求。

  4. 取消请求:FETCH API提供了一个AbortController来取消请求的能力,而在XMLHttpRequest中取消请求相对复杂。

  5. 上传和下载进度:FETCH API提供了更简便的方式来获取请求的上传和下载进度,而在XMLHttpRequest中需要手动设置事件处理程序来追踪进度。

总体而言,FETCH API提供了一种更加简单和现代化的方式来进行网络请求,并具有更多的灵活性和易用性。不过,XMLHttpRequest仍然是广泛使用的技术,特别是在对较旧的浏览器提供支持时。

96. 如何处理AJAX请求中的错误?

在处理AJAX请求中的错误时,你可以使用以下策略:

  1. 在XMLHttpRequest的onreadystatechange事件处理程序中检查status属性来捕获HTTP状态码,以确定请求是否成功。例如,status等于200表示成功,而status等于404表示未找到资源。
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // 请求成功
    } else {
      // 请求失败
    }
  }
};
  1. 可以使用XMLHttpRequest对象的onerror事件处理程序捕获网络错误。例如,当请求无法发送或服务器无法访问时,会触发onerror事件。
xhr.onerror = function() {
  // 网络错误处理
};
  1. 在发送请求之前,可以使用try-catch块来捕获可能引发的JavaScript错误。例如,在使用JSON.parse()解析响应数据时,如果数据格式不正确,可能会引发错误。
try {
  var response = JSON.parse(xhr.responseText);
} catch (e) {
  // 错误处理
}
  1. 使用Promise API可以更方便地处理错误。XHR对象可以将其封装为Promise对象来实现更灵活的错误处理。例如,可以使用fetch()方法来发送请求,并使用.then()方法和.catch()方法链式调用来处理成功和失败的情况。
fetch('http://example.com/api/data')
  .then(function(response) {
    if (!response.ok) {
      throw new Error('请求失败');
    }
    return response.json();
  })
  .then(function(data) {
    // 处理响应数据
  })
  .catch(function(error) {
    // 错误处理
  });

以上是一些常用的错误处理策略,根据具体的情况和需求,你还可以继续定制和优化错误处理逻辑。

97. 什么是跨域请求?如何解决跨域问题?

跨域请求指的是在浏览器中跨不同域名、端口或协议进行网络请求的情况。由于浏览器的同源策略限制,JavaScript在跨域情况下无法直接访问其他域名下的数据。

要解决跨域问题,可以采用以下几种方法:

  1. JSONP(JSON with Padding):JSONP是一种利用script标签可以跨域请求资源的技术。服务器返回的数据需要放在一个函数调用中,浏览器通过动态创建script标签来请求资源,并执行返回的函数。

  2. CORS(Cross-Origin Resource Sharing):CORS是一种现代浏览器支持的解决跨域请求问题的机制。服务器在响应中设置一些头部信息,告诉浏览器该资源是否允许跨域访问。

  3. 代理服务器:可以设置一个代理服务器,将前端的请求发送到同源的代理服务器上,由代理服务器再发送跨域请求,并将返回结果返回给前端。

  4. WebSocket:使用WebSocket可以在浏览器与服务器之间建立一个长连接通信管道,绕过浏览器的同源策略限制。

需要根据具体的场景选择合适的解决方案。

98. 什么是WebSocket?它如何实现实时通信?

WebSocket是一种在单个TCP连接上全双工通信的协议,它能够实现客户端和服务器之间的实时通信,能够提供更快、更稳定的通信效果。

与传统的HTTP协议不同,WebSocket在握手阶段使用HTTP协议进行协商,然后在建立连接后,双方可以通过发送特定的数据帧进行实时通信。这种方式避免了每次请求都要建立和关闭连接的开销,提高了通信的效率。

WebSocket的实时通信原理如下:

  1. 客户端和服务器首先通过HTTP协议进行握手,建立WebSocket连接。
  2. 握手成功后,客户端和服务器可以通过发送数据帧进行实时通信。
  3. 客户端可以使用JavaScript提供的WebSocket API发送和接收消息,服务器也可以通过WebSocket API与客户端进行通信。
  4. 在连接保持的时间内,客户端和服务器可以随时发送或接收消息,实现实时的双向通信。
  5. 当通信结束时,可以通过关闭连接来终止WebSocket通信。

alt

WebSocket在实时通信方面具有较低的延迟以及更好的性能表现,因此广泛应用于实时消息推送、聊天应用、在线游戏等场景。

99. 如何使用WebSocket进行双向通信?

要使用WebSocket进行双向通信,你可以按照以下步骤进行操作:

  1. 在客户端使用JavaScript创建WebSocket对象,并指定服务器的WebSocket地址。

    const socket = new WebSocket("ws://localhost:8080");
    
  2. 监听WebSocket对象的事件:

    • 打开连接事件:当WebSocket连接成功建立时触发。
    socket.onopen = function(event) {
      console.log("WebSocket连接已打开");
    };
    
    • 接收消息事件:当客户端接收到服务器发送的消息时触发。
    socket.onmessage = function(event) {
      const message = event.data;
      console.log("接收到消息:" + message);
    };
    
    • 发送消息事件:当客户端发送消息时触发。
    socket.onopen = function(event) {
      const message = "Hello, server!";
      socket.send(message);
      console.log("发送消息:" + message);
    };
    
    • 关闭连接事件:当WebSocket连接关闭时触发。
    socket.onclose = function(event) {
      console.log("WebSocket连接已关闭");
    };
    
  3. 服务器端需要处理与客户端的WebSocket连接,接收和发送消息。

    • 在Node.js中,你可以使用ws模块创建WebSocket服务器。
    const WebSocket = require("ws");
    
    const wss = new WebSocket.Server({ port: 8080 });
    
    wss.on("connection", function(ws) {
      console.log("已建立WebSocket连接");
      
      ws.on("message", function(message) {
        console.log("接收到消息:" + message);
        // 处理接收到的消息
      });
    
      ws.send("Hello, client!");
    });
    
    • 在其他服务器端语言中,你可以查找相应的WebSocket库。

通过以上步骤,你可以实现双向通信,服务器与客户端可以互相发送消息并进行实时通信。记得在开发时处理错误和关闭连接的情况,以确保通信的稳定性和可靠性。

100. 什么是事件驱动编程(Event-driven programming)?

事件驱动编程(Event-driven programming)是一种编程范式,其中程序的执行流程主要由事件的产生和处理驱动。在事件驱动编程中,程序通过监听和响应事件的方式来执行任务,而不是按照传统的顺序执行。

在事件驱动编程中,事件是指程序中发生的特定操作或状态变化,如用户操作、网络请求完成、定时器到期等。程序通过监听这些事件并分配对应的事件处理程序来响应事件。

事件驱动编程的主要特点包括:

  • 异步性:事件驱动编程充分利用了异步处理的特点,能够在事件发生时立即响应,而无需等待。
  • 松耦合性:事件驱动编程允许多个模块独立运行,并通过事件来进行交互,使得各模块之间的耦合度降低。
  • 可扩展性:通过监听和响应不同类型的事件,可以轻松地添加或修改功能,从而实现程序的可扩展性。
  • 反应式:事件驱动编程强调对事件的即时响应,能够使程序以较快的速度处理事件并提供及时的反馈。 alt

事件驱动编程在图形用户界面(GUI)开发、服务器编程、异步编程等领域得到了广泛应用。常见的事件驱动编程框架包括Node.js的事件驱动模型、浏览器的DOM事件模型等。

101. 如何使用事件驱动编程模式处理异步操作?

使用事件驱动编程(Event-driven programming)模式处理异步操作的基本思想是基于事件和回调函数机制。下面是使用事件驱动编程模式处理异步操作的一般步骤:

  1. 定义事件:首先,您需要确定您希望在程序中触发的事件。事件可以是用户操作、系统事件等。例如,使用WebSocket进行双向通信时,可以定义一些事件,例如接收消息、连接断开等。

  2. 注册回调函数:一旦定义了事件,您需要为每个事件注册相应的回调函数。回调函数是处理特定事件的函数。当事件发生时,系统会调用相应的回调函数来执行操作。

  3. 触发事件:当某个条件满足,触发特定的事件。例如,在WebSocket通信中,当接收到新消息时,可以触发"on message"事件。

  4. 执行回调函数:一旦事件被触发,系统会自动调用相应的回调函数来执行操作。您可以在回调函数中处理异步操作。例如,在WebSocket的"on message"回调函数中,您可以处理接收到的消息。

这样,您就可以使用事件驱动编程模式来处理异步操作。

注意,具体的实现方式会根据您所使用的编程语言和框架而有所不同。下面是一个使用JavaScript实现的简单示例:

// 定义事件
const event = new EventEmitter();

// 注册回调函数
event.on('someEvent', function(data) {
  console.log('Event occurred:', data);
});

// 触发事件
event.emit('someEvent', 'Some data');

// 执行回调函数
// Output: Event occurred: Some data

在上面的示例中,我们使用Node.js的EventEmitter模块实现了事件驱动编程模式。我们首先定义了一个事件,然后为事件注册了一个回调函数。当事件被触发时,回调函数会被执行并打印一条消息。

希望这可以帮助您理解如何使用事件驱动编程模式处理异步操作。

102. 什么是并发和并行?JavaScript中如何实现并发操作?

在编程领域中,并发(Concurrency)和并行(Parallelism)是两个相关但不同的概念。

并发指的是同时处理多个任务的能力,这些任务可以在重叠的时间段内执行,交替地进行。在并发中,任务可以通过时分复用机制,轮流获取CPU时间片执行。并发通常用于提高系统的吞吐量和资源利用率。

并行指的是同时执行多个任务的能力,这些任务在多个处理单元(如多核CPU)上同时进行。在并行中,任务可以同时执行,彼此独立。并行通常用于加速计算密集型任务的速度。

JavaScript是一种单线程语言,即一次只能执行一个任务。由于JavaScript中没有多线程的概念,因此无法实现真正的并行操作。然而,JavaScript中可以通过异步编程实现并发操作。

异步编程是一种基于回调函数、Promise、async/await等机制的编程方式,可以在等待I/O、网络请求和其他异步操作的同时继续执行其他任务。通过这种方式,JavaScript可以在等待结果时执行其他代码,达到并发操作的效果。

下面是一个使用Promise实现并发操作的示例:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    // 发起异步请求
    makeAsyncHttpRequest(url, (data) => {
      resolve(data); // 请求成功时调用resolve
    }, (error) => {
      reject(error); // 请求失败时调用reject
    });
  });
}

const promise1 = fetchData('url1');
const promise2 = fetchData('url2');

Promise.all([promise1, promise2])
  .then(([data1, data2]) => {
    // 并发操作完成后的处理逻辑
    console.log('Data 1:', data1);
    console.log('Data 2:', data2);
  })
  .catch((error) => {
    // 处理错误
    console.error('Error:', error);
  });

在上面的示例中,我们定义了一个fetchData函数来发起异步请求。我们同时调用fetchData函数两次,分别传递不同的URL参数,以实现并发操作。然后,我们使用Promise.all方法来等待两个异步操作都完成。当完成时,我们可以在回调函数中处理并发操作的结果。

总结一下,尽管JavaScript本身是单线程的,但可以通过异步编程实现并发操作。这种并发操作可以改善性能,并提高代码的可读性和可维护性。

103. 箭头函数的特点及用途。

箭头函数是一种函数的简化语法,可以更简洁地定义函数。它具有以下特点和用途:

  1. 简化语法:箭头函数可以用更短的语法定义函数,不需要使用function关键字和return语句。例如,(x, y) => x + y可以代替function(x, y) { return x + y; }

  2. 简化this绑定:在箭头函数中,this的值由外部最接近的非箭头函数的作用域决定,而不是动态绑定。这意味着在箭头函数中,this指向的是定义时的对象,而不是调用时的对象。这种特性可以避免在回调函数中出现this指向错误的问题。

  3. 简化语法绑定:在箭头函数中,没有arguments对象。这意味着箭头函数不能通过arguments对象访问传递给函数的参数。如果需要访问传递的参数,可以使用剩余参数(rest parameters)或者使用展开语法(spread syntax)。

  4. 需要注意的是,箭头函数不能用作构造函数,不能使用new关键字实例化,并且没有prototype属性。箭头函数也没有自己的this、arguments、super或new.target绑定。

alt

箭头函数适用于简单的函数定义,尤其是回调函数或者需要使用this保持为定义时对象的情况。然而,在某些情况下,例如需要使用arguments对象或动态绑定this的情况下,仍然需要使用传统的函数定义方式。

104. 解构赋值的概念,并用代码示例展示如何使用解构赋值。

解构赋值是一种从数组或对象中提取值,并将它们赋给对应变量的方法。通过解构赋值,可以方便地将数组或对象的值解构为单个变量,简化代码并提高可读性。

下面是几个使用解构赋值的示例:

  1. 解构数组:
const numbers = [1, 2, 3, 4, 5];
const [a, b, ...rest] = numbers;
console.log(a);  // Output: 1
console.log(b);  // Output: 2
console.log(rest);  // Output: [3, 4, 5]

在这个示例中,我们将数组numbers的第一个元素赋给变量a,第二个元素赋给变量b,剩余的元素赋给变量rest

  1. 解构对象:
const user = { name: 'Alice', age: 30 };
const { name, age } = user;
console.log(name);  // Output: Alice
console.log(age);  // Output: 30

这个示例中,我们将对象user的属性name的值赋给变量name,属性age的值赋给变量age

  1. 在函数参数中使用解构赋值:
function printName({ firstName, lastName }) {
  console.log(`Hello, ${firstName} ${lastName}`)
}

const person = { firstName: 'Alice', lastName: 'Smith' };
printName(person);  // Output: Hello, Alice Smith

在这个示例中,我们定义了一个接受一个对象参数的函数printName,并通过解构赋值来获取对象的属性值,并打印出相应的输出。

解构赋值能够简化我们在处理复杂数据结构时的代码,并提高代码的可读性和易维护性。

105. let和const关键字的区别,以及它们在作用域和变量声明方面的差异。

let和const都是在ES6中引入的新的变量声明方式。它们与传统的var关键字相比,在作用域和变量声明方面都有一些差异。

  1. 作用域:let和const都具有块级作用域,而var关键字只有函数作用域和全局作用域。块级作用域意味着变量只在声明它们的块级语句中可见,而在其他语句中是不可见的。

  2. 变量声明:使用var声明的变量可以重复声明,并且不会报错,而let和const不能重复声明。这意味着在同一个作用域内,你不能声明两个相同名称的let或const变量。

  3. 变量提升:使用var声明的变量会有变量提升的特性,即变量可以在声明之前使用。而let和const声明的变量不具有变量提升,必须在声明之后才能使用。

  4. 初始化:使用var声明的变量在声明时不会自动初始化,默认值为undefined。而let和const声明的变量在声明时未赋值会报错,必须在声明时或之后将其初始化。

  5. 常量声明:const用于声明常量,一旦被赋值后,不能被重新赋值。let和var声明的变量可以重新赋值。

总的来说,let和const相对于var提供了更好的作用域控制和变量声明约束。推荐在使用变量时优先考虑使用let和const,尤其是在块级作用域内。如果变量不需要被重新赋值,可以使用const声明为常量。

106. 什么是模板字符串,并提供一个使用模板字符串的示例。

模板字符串是一种特殊的字符串,使用反引号(``)包裹起来,内部可以包含占位符(${expression})用来插入变量或表达式的值。模板字符串可以在多行上进行书写,并且可以直接嵌入变量或表达式的值,使得代码更加简洁和易读。

下面是一个使用模板字符串的示例:

const name = 'Alice';
const age = 25;
const greeting = `Hello, my name is ${name} and 
                  I'm ${age} years old.`;

console.log(greeting);

输出结果为:

Hello, my name is Alice and 
I'm 25 years old.

在这个示例中,我们使用了模板字符串来定义一个greeting变量。通过使用${name}${age}对应的占位符,我们可以在模板字符串中直接插入变量的值。这样就可以动态地创建字符串,而不需要通过字符串拼接或解析语法来构建复杂的字符串。

107. Promise的概念,并说明它与回调函数的区别。

Promise是一种用于异步编程的对象,用于处理异步操作和回调函数过多导致的代码可读性差、嵌套深等问题。它可以将异步操作以一种更直观、更可控的方式进行管理。

Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当Promise处于pending状态时,可以转为fulfilled或rejected状态。一旦状态确定,就不可再改变。

Promise使用一个then方法来处理异步操作的结果。then方法接收两个参数,分别是成功的回调函数和失败的回调函数。当异步操作成功完成时,成功的回调函数会被执行,可以获取到异步操作的结果。当异步操作失败时,失败的回调函数会被执行,可以获取到失败的原因。

与回调函数相比,Promise的优势在于:

  1. 可链式调用:Promise可以通过then方法链式调用,可以避免回调函数嵌套过深问题,使代码更加清晰易读。

  2. 错误处理:Promise可以使用catch方法捕获错误,而回调函数需要通过错误回调来处理错误。

  3. 可以通过Promise.all、Promise.race等方法对多个Promise进行并行执行、竞争等操作,更加方便地处理多个异步操作的结果。

综上所述,Promise相对于回调函数更加灵活、可读性更好,能够更好地处理和控制异步操作。

108. 什么是模块化,以及ES6引入的模块化语法是什么样的。

模块化是一种软件设计的方法,它将程序拆分成一个个独立的模块,每个模块只关注自己的功能,并与其他模块进行交互。这样做有助于提高代码的可维护性和可重用性。

在ES6之前,JavaScript并没有原生支持模块化。开发者们通常使用一些第三方工具或者自己的约定来实现模块化。但在ES6中,JavaScript引入了一种新的模块化语法。

ES6的模块化语法使用import和export关键字来导入和导出模块。通过使用export关键字,我们可以将一个或多个变量、函数或类导出为一个模块。然后,使用import关键字来在另一个模块中引入这些导出的内容。

下面是一个简单的示例,展示了如何使用ES6的模块化语法:

// math.js模块

export const add = (a, b) => {
  return a + b;
};

export const subtract = (a, b) => {
  return a - b;
};

// app.js模块

import { add, subtract } from './math.js';

console.log(add(5, 3)); // 输出 8
console.log(subtract(5, 3)); // 输出 2

在上面的示例中,math.js模块导出了add和subtract函数,并通过export关键字暴露给其他模块使用。app.js模块通过import关键字引入了math.js模块导出的

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

前端面试必备 文章被收录于专栏

前端面试必备知识点:HTML和CSS、JS(变量/数据类型/操作符/条件语句/循环;面向对象编程/函数/闭包/异步编程/ES6)、DOM操作、HTTP和网络请求、前端框架、前端工具和构建流程、浏览器和性能优化、跨浏览器兼容性、前端安全、数据结构和算法、移动端开发技术、响应式设计、测试和调试技巧、性能监测等。准备面试时,建议阅读相关的技术书籍、参与项目实践、刷题和练习,以深化和巩固你的知识。

全部评论
曾经被面试官问过深拷贝里面如果有循环引用怎么解决
点赞 回复 分享
发布于 2023-08-29 16:28 广东

相关推荐

12-19 16:52
门头沟学院
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
6
9
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务