前端JavaScript面试八股常考题(四)

发布于:2025-05-25 ⋅ 阅读:(25) ⋅ 点赞:(0)

文章目录

33. JavaScript 脚本延迟加载(异步加载)的方式

JavaScript 脚本延迟加载可以提高页面加载速度,以下是几种常见的方法:

(1) 使用 async 属性

async 属性让脚本尽可能地异步加载,不会阻塞 HTML 解析。脚本一旦下载完成就会立即执行,但多个 async 脚本的执行顺序不确定。适用于独立性较高的脚本,如第三方统计代码、广告代码等。

<script src="example.js" async></script>
(2) 使用 defer 属性

defer 属性用于延迟脚本加载,保证所有 defer 脚本会按文档中出现的顺序执行,并且在 HTML 解析完成后才执行。适用于依赖 HTML 结构的脚本,如需要操作 DOM 的脚本。

<script src="example.js" defer></script>
(3) 动态创建脚本元素

通过 JavaScript 动态创建 <script> 标签并插入到文档中,可以灵活控制脚本的加载和执行时机,常用于某些条件触发时才加载的场景。

var script = document.createElement('script');
script.src = 'example.js';
document.head.appendChild(script);
(4) 使用模块化加载工具

前端模块化工具(如 Webpack 的动态加载)提供了强大的依赖管理和延迟加载功能,适用于大型项目,解决代码拆分和按需加载的问题。

// 以 Webpack 的动态加载为例
import('example.js').then(module => {
    // 使用加载的模块
});

34. JavaScript 的类数组对象

JavaScript 的类数组对象(Array-like Object)是指具有类似数组特性但不是数组的对象。它们通常具备 length 属性和按索引存储的元素,例如 arguments 对象、DOM 方法返回的集合(如 NodeList)。要将类数组对象转换为真正的数组,可以使用以下方法:

(1) 使用 Array.prototype.slice.call() 方法

这种方法在 ES5 之前非常常见,利用 slice 方法将类数组对象切片成真正的数组,但需要写得较为复杂,效率稍低。

const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.prototype.slice.call(arrayLike);
(2) 使用 Array.from() 方法(ES6 引入)

这种方法简洁直观,Array.from 还可以接受第二个参数用来处理每一个元素。

const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.from(arrayLike);
(3) 使用扩展运算符(ES6 引入)

这是 ES6 引入的语法糖,最为简洁且可读性强,但需要确保类数组对象的结构完整(包括 length 属性)。

const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = [...arrayLike];

注意: 类数组对象和数组的主要区别在于类数组对象没有数组的方法(如 pushpop 等)。转换为数组后,可以更方便地使用这些方法对数据进行操作。

35. JavaScript 数组的原生方法

JavaScript 数组提供了多种原生方法,用于操作和处理数组数据。以下是一些常见的方法:

(1) 数组转成字符串
  • toString():将数组转换为字符串,元素之间用逗号分隔。
  • join():将数组转换为字符串,可以指定分隔符。
const fruits = ["apple", "banana", "orange"];
const fruitString = fruits.toString(); // "apple,banana,orange"

const colors = ["red", "green", "blue"];
const colorString = colors.join(" - "); // "red - green - blue"
(2) 数组尾部操作
  • pop():删除数组最后一个元素。
  • push():向数组末尾添加一个或多个元素。
let arr = [1, 2, 3];
arr.push(4); // arr 现在是 [1, 2, 3, 4]
arr.pop(); // 返回 4,arr 现在是 [1, 2, 3]
(3) 数组开头操作
  • unshift():向数组开头添加一个或多个元素。
  • shift():删除数组第一个元素。
arr.unshift(0); // arr 现在是 [0, 1, 2, 3]
arr.shift(); // 返回 0,arr 现在是 [1, 2, 3]
(4) 数组连接
  • concat():返回拼接好的数组,不影响原数组。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArray = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
(5) 遍历数组
  • forEach():为每个数组元素执行一次回调函数,没有返回值。
  • map():创建一个新数组,其结果是原数组中每个元素调用回调函数后的返回值。
arr.forEach(item => console.log(item));
let doubled = arr.map(item => item * 2);
(6) 查找和测试
  • find():返回数组中满足测试函数的第一个元素。
  • findIndex():返回数组中满足测试函数的第一个元素的索引。
  • some():测试是否至少有一个元素通过测试函数。
  • every():测试是否所有元素都通过测试函数。
  • flat():将多维数组扁平化。
  • flatMap():先对数组中的每个元素执行回调函数,然后将结果扁平化。
const arr = [1, 2, 4];
arr.find(item => item > 2); // 返回 4
arr.some(item => item > 2); // 返回 true
let nested = [1, [2, 3]];
nested.flat(); // 返回 [1, 2, 3]

36 . 为什么 JavaScript 函数的 arguments 参数是类数组而不是数组?

arguments 是一个类数组对象,而不是真正的数组,原因如下:

  1. 历史原因arguments 出现得比数组早。如果将其改为真正的数组,需要对现有代码进行大规模修改,因此这种设计被延续下来。
  2. 性能考虑:如果 arguments 是一个真正的数组,每次函数调用时都需要创建一个完整的数组对象,这会增加内存使用和时间开销。

arguments 是一个对象,它的属性是从 0 开始依次递增的数字,还有 length 等属性,与数组相似,但没有数组的常见方法(如 pushpop 等),因此被称为类数组。

37. JavaScript 数组的遍历方法

JavaScript 提供了多种数组遍历方法,适用于不同的场景:

(1) for 循环

最传统的方法,通过索引访问数组的每个元素,可以提前终止循环。

const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
(2) forEach()

更现代、更简洁的方法,为每个数组元素执行一次回调函数,没有返回值。

arr.forEach((item, index) => {
  console.log(item, index);
});
(3) for...of

ES6 引入的新语法,可以直接遍历数组的值,不能用于普通对象。

for (const item of arr) {
  console.log(item);
}
(4) map()

返回一个新数组,其结果是原数组中每个元素调用回调函数后的返回值。

const newArr = arr.map(item => item * 2);
(5) filter()

过滤数组,返回包含符合条件的元素的数组,可链式调用。

const evenNumbers = arr.filter(item => item % 2 === 0);
(6) reduce()reduceRight()

用于对数组元素进行累积操作,reduce 从左到右处理数组,reduceRight 从右到左处理数组。

const sum = arr.reduce((acc, cur) => acc + cur, 0);
const sumRight = arr.reduceRight((acc, cur) => acc + cur, 0);
(7) some()every()
  • some():测试是否至少有一个元素通过测试函数。
  • every():测试是否所有元素都通过测试函数。
const hasEven = arr.some(item => item % 2 === 0);
const allPositive = arr.every(item => item > 0);

38. 如何遍历类数组?

虽然 arguments 是类数组对象,但它具有数组的一些特性,例如可以通过索引访问元素,并且有 length 属性。因此,可以使用以下几种方式来遍历 arguments

(1) 使用传统的 for 循环
function myFunction() {
  for (let i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(2) 使用 for...of 循环(需要先将类数组转换为数组)
function myFunction() {
  for (const arg of Array.from(arguments)) {
    console.log(arg);
  }
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(3) 使用 forEach 方法(需要先将类数组转换为数组)
function myFunction() {
  Array.from(arguments).forEach(arg => {
    console.log(arg);
  });
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(4) 使用扩展运算符(...)将类数组转换为数组
function myFunction() {
  const args = [...arguments];
  for (const arg of args) {
    console.log(arg);
  }
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(5) 使用 map 方法(需要先将类数组转换为数组)
function myFunction() {
  const args = Array.from(arguments);
  args.map(arg => console.log(arg));
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(6) 使用 Array.prototype.forEach.callapply 方法
function foo() {
  Array.prototype.forEach.call(arguments, arg => console.log(arg));
}
拓展:使用剩余参数(Rest Parameters)

在现代 JavaScript 中,推荐使用剩余参数来替代 arguments,因为剩余参数提供了一个真正的数组,更易于使用:

function example(...args) {
  args.forEach(arg => console.log(arg));
}
example(1, 2, 3); // 输出:1, 2, 3

39. JavaScript 数组的 mapforEach 函数中能否通过 break 等语法结束循环?

在 JavaScript 中,mapforEach 方法中不能直接使用 breakcontinue 语句来结束循环,因为它们是高阶函数,设计初衷是遍历整个数组。

不推荐的实现方式:

使用异常来终止循环:

try {
  [1, 2, 3, 4, 5].forEach(item => {
    if (item > 3) throw new Error("Break");
    console.log(item);
  });
} catch (e) {
  if (e.message !== "Break") throw e;
}
推荐的实现方式:
  1. 使用 Array.prototype.some()Array.prototype.every()

    • 这两个方法允许在满足某个条件时提前结束循环,更加优雅且符合函数式编程思想。
    [1, 2, 3, 4, 5].some(item => {
      if (item > 3) return true;
      console.log(item);
      return false;
    });
    
  2. 使用普通的 for 循环

    • 虽然不够简洁,但在需要更细粒度控制循环的情况下很有用。
    for (let item of [1, 2, 3, 4, 5]) {
      if (item > 3) break;
      console.log(item);
    }
    
  3. 使用 Array.prototype.reduce()

    • 比较灵活,可以根据需要存储和传递更多信息。
    [1, 2, 3, 4, 5].reduce((acc, item) => {
      if (acc.break) return acc;
      if (item > 3) return { break: true };
      console.log(item);
      return acc;
    }, {});
    

40. 什么是 DOM 和 BOM?

DOM(Document Object Model)
  • 定义:文档对象模型,将网页内容转换为 JavaScript 可以操作的对象,允许程序和脚本动态地访问和更新文档的内容、结构和样式。
  • 常见操作
    • 获取元素:document.getElementById()document.querySelector() 等。
    • 修改元素内容:element.innerHTMLelement.textContent 等。
    • 修改元素样式:element.style.property
    • 添加或删除元素:document.createElement()element.appendChild() 等。
BOM(Browser Object Model)
  • 定义:浏览器对象模型,是浏览器提供的用于操作浏览器的接口。
  • 主要组成部分
    • window 对象:表示浏览器窗口。
    • navigator 对象:包含有关浏览器的信息。
    • location 对象:包含当前 URL 的信息。
    • history 对象:包含浏览器的历史记录。
    • screen 对象:包含有关用户屏幕的信息。
常见操作:
  • 打开新窗口:window.open()
  • 移动、调整窗口大小:window.moveTo()window.resizeTo()
  • 导航到其他 URL:window.location.href = "http://example.com"
  • 获取浏览器信息:navigator.userAgent
  • 操作浏览历史:history.back()history.forward()
DOM 和 BOM 的主要区别:
  1. DOM 主要处理网页内容,而 BOM 处理浏览器窗口和功能。
  2. DOM 是 W3C 标准,而 BOM 没有相关标准。
  3. DOM 可以在任何支持 XML 的环境中使用,而 BOM 只能在浏览器环境中使用。

41. encodeURIencodeURIComponent 的区别是什么?

这两个函数都用于对 URI(统一资源标识符)进行编码,但它们的编码范围和适用场景有所不同。

encodeURI()
  • 用途:用于编码完整的 URI。

  • 特点:会编码所有对 URI 有特殊含义的字符,但不会编码以下字符:/, ? : @ & = + $ #

  • 示例

    console.log(encodeURI("https://example.com/path?name=张三&age=18"));
    // 输出: https://example.com/path?name=%E5%BC%A0%E4%B8%89&age=18
    
encodeURIComponent()
  • 用途:用于编码 URI 的组成部分。

  • 特点:会编码所有对 URI 有特殊含义的字符,包括 /, ? : @ & = + $ #

  • 示例

    console.log(encodeURIComponent("https://example.com/path?name=张三&age=18"));
    // 输出: https%3A%2F%2Fexample.com%2Fpath%3Fname%3D%E5%BC%A0%E4%B8%89%26age%3D18
    
主要区别:
  1. encodeURI 不会编码 URI 中的功能字符(如 &, =),而 encodeURIComponent 会编码这些字符。
  2. encodeURIComponent 编码的字符范围更广,通常用于编码 URL 的参数。
拓展:

在实际开发中:

  • 当需要编码整个 URI 时,使用 encodeURI
  • 当需要编码 URI 参数时,使用 encodeURIComponent

42. 什么是 AJAX?如何实现一个 AJAX 请求?

定义

AJAX(Asynchronous JavaScript and XML)是一种通过 JavaScript 进行异步通信的技术,允许从客户端向服务器发送请求,获取数据并更新页面的特定部分,而无需刷新整个页面。

创建 AJAX 请求的步骤
  1. 创建 XMLHttpRequest 对象

    const xhr = new XMLHttpRequest();
    
  2. 通过 open 方法设置请求方式并建立连接

    xhr.open(method, url, true);
    
  3. 构建请求数据并发送

    if (method.toUpperCase() === 'POST') {
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.send(JSON.stringify(data));
    } else {
      xhr.send();
    }
    
  4. 监听通信状态并处理响应

    xhr.onreadystatechange = function() {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
          callback(null, xhr.responseText);
        } else {
          callback(new Error('请求失败: ' + xhr.status));
        }
      }
    };
    
示例代码
function makeAjaxRequest(url, method, data, callback) {
  const xhr = new XMLHttpRequest();
  
  xhr.onreadystatechange = function() {
    if (xhr.readyState === XMLHttpRequest.DONE) {
      if (xhr.status === 200) {
        callback(null, xhr.responseText);
      } else {
        callback(new Error('请求失败: ' + xhr.status));
      }
    }
  };
  
  xhr.open(method, url, true);
  
  if (method.toUpperCase() === 'POST') {
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
  } else {
    xhr.send();
  }
}

// 使用示例
makeAjaxRequest('https://api.example.com/data', 'GET', null, function(error, response) {
  if (error) {
    console.error('出错了:', error);
  } else {
    console.log('收到响应:', response);
  }
});
使用 Fetch 实现 AJAX 请求(更现代的方法)
function makeAjaxRequest(url, method, data) {
  const options = {
    method: method,
    headers: {
      'Content-Type': 'application/json'
    }
  };

  if (method.toUpperCase() !== 'GET' && data) {
    options.body = JSON.stringify(data);
  }

  return fetch(url, options)
    .then(response => {
      if (!response.ok) {
        throw new Error('请求失败: ' + response.status);
      }
      return response.json();
    });
}

// 使用示例
makeAjaxRequest('https://api.example.com/data', 'GET')
  .then(data => console.log('收到数据:', data))
  .catch(error => console.error('出错了:', error));

网站公告

今日签到

点亮在社区的每一天
去签到