天天看点

2023.05.31 更新前端面试问题总结(9道题)

作者:成都程序员晴小篆

2023.05.29 - 2023.05.31 更新前端面试问题总结(9道题)

获取更多面试相关问题可以访问

github 地址: https://github.com/pro-collection/interview-question/issues

gitee 地址: https://gitee.com/yanleweb/interview-question/issues

目录:

  • 中级开发者相关问题【共计 4 道题】
    • 387.Generator 是如何做到中断和恢复的【热度: 1,558】【JavaScript】【出题公司: 百度】
    • 388.哪些原因会导致js里this指向混乱?【热度: 1,282】【JavaScript】【出题公司: 小米】
    • 389.JS 作用域链链接多少?【热度: 882】【JavaScript】【出题公司: 美团】
    • 393.实现一个JS 函数, 解析 url 参数, 返回一个对象【JavaScript】【出题公司: Shopee】
  • 高级开发者相关问题【共计 5 道题】
    • 385.使用 Promise 实现一个异步流量控制的函数【热度: 517】【JavaScript】【出题公司: 腾讯】
    • 386.不使用任何中间件, koa 如何解析 post 请求参数【热度: 1,359】【Nodejs】【出题公司: 阿里巴巴】
    • 390.webpack5 Module Federation 了解多少【工程化】【出题公司: 京东】
    • 391.小程序为什么会有两个线程【web应用场景】【出题公司: Shopee】
    • 392.[React] react-router 页面跳转时,是如何传递下一个页面参数的?【web框架】【出题公司: 腾讯】

中级开发者相关问题【共计 4 道题】

387.Generator 是如何做到中断和恢复的【热度: 1,558】【JavaScript】【出题公司: 百度】

关键词:Generator 中断、Generator 回复

Generator 是 JavaScript 中一种特殊的函数,它能够通过迭代器协议(Iterator Protocol)实现中断和恢复的功能。

Generator 函数使用 function* 声明,内部可以使用 yield 关键字来定义中断点。当调用 Generator 函数时,它不会立即执行,而是返回一个迭代器对象。通过调用迭代器的 next() 方法,可以逐步执行

Generator 函数,并在每个 yield 关键字处暂停执行并返回一个包含当前值的对象。

当调用 next() 方法时,Generator 函数会从上次暂停的地方继续执行,直到遇到下一个 yield 关键字或函数结束。通过不断调用 next() 方法,可以逐步执行 Generator

函数的代码,并获取每个中断点处的值。

由于 Generator 函数具有中断和恢复的特性,可以用于异步编程,实现一种更直观的方式来处理异步操作。通过 yield 关键字,可以将异步操作分割成多个步骤,每个步骤都可以通过 yield 暂停,等待异步操作完成后再恢复执行。

以下是一个简单的示例,展示了 Generator 函数的中断和恢复特性:

function* generatorFunction() {
  console.log('Step 1');
  yield;
  console.log('Step 2');
  yield;
  console.log('Step 3');
}

const generator = generatorFunction();

generator.next(); // Step 1
generator.next(); // Step 2
generator.next(); // Step 3
           

在上述示例中,我们定义了一个名为 generatorFunction 的 Generator 函数。在函数体内,使用 console.log 打印了三个不同的步骤,并在每个步骤后使用 yield

关键字暂停执行。然后,我们通过调用 generator.next() 方法逐步执行 Generator 函数。每次调用 next() 方法时,函数会从上次暂停的地方恢复执行,打印相应的步骤。

通过使用 Generator 函数,可以实现更灵活、可控的异步编程模式,提供更好的代码可读性和维护性。

388.哪些原因会导致js里this指向混乱?【热度: 1,282】【JavaScript】【出题公司: 小米】

关键词:js 指向

JavaScript 中 this 指向混乱的原因主要有以下几个:

  1. 函数调用方式不同:JavaScript 中函数的调用方式决定了 this 的指向。常见的函数调用方式有函数调用、方法调用、构造函数调用和箭头函数调用。不同的调用方式会导致 this 指向不同的对象,容易引发混乱。
  2. 丢失绑定:当函数作为一个独立的变量传递时,或者作为回调函数传递给其他函数时,函数内部的 this 可能会丢失绑定。这意味着函数中的 this 不再指向原来的对象,而是指向全局对象(在浏览器环境中通常是 window 对象)或

    undefined(在严格模式下)。

  3. 嵌套函数:当函数嵌套在其他函数内部时,嵌套函数中的 this 通常会与外部函数的 this 不同。这可能导致 this 的指向出现混乱,特别是在多层嵌套的情况下。
  4. 使用 apply、call 或 bind 方法:apply、call 和 bind 是 JavaScript 中用于显式指定函数的 this 的方法。如果不正确使用这些方法,比如传递了错误的上下文对象,就会导致 this 指向错误。
  5. 箭头函数:箭头函数具有词法作用域的 this 绑定,它会捕获其所在上下文的 this 值,而不是动态绑定 this。因此,在箭头函数中使用 this 时,它指向的是箭头函数声明时的上下文,而不是调用时的上下文。

为了避免 this 指向混乱的问题,可以采取以下措施:

  • 使用箭头函数,确保 this 始终指向期望的上下文。
  • 在函数调用时,确保正确设置了函数的上下文对象,可以使用 bind、call 或 apply 方法。
  • 使用严格模式,避免函数内部的 this 默认绑定到全局对象。
  • 在嵌套函数中,使用箭头函数或者显式保存外部函数的 this 值,以避免内部函数的 this 指向错误。

理解和正确处理 this 的指向是 JavaScript 开发中重要的一环,它能帮助我们避免许多常见的错误和混乱。

389.JS 作用域链链接多少?【热度: 882】【JavaScript】【出题公司: 美团】

关键词:JS 作用域链链、JS 作用域链链应用

概念

JavaScript 作用域链(Scope Chain)是指变量和函数的可访问性和查找规则。它是由多个执行上下文(Execution Context)的变量对象(Variable Object)按照它们被创建的顺序组成的链式结构。

在 JavaScript 中,每个函数都会创建一个新的执行上下文,并将其添加到作用域链的最前端。当访问一个变量时,JavaScript

引擎会先从当前执行上下文的变量对象开始查找,如果找不到,则沿着作用域链依次向上查找,直到全局执行上下文的变量对象。

作用域链的创建过程如下:

  1. 在函数定义时,会创建一个变量对象(VO)来存储函数的变量和函数声明。这个变量对象包含了当前函数的作用域中的变量和函数。
  2. 在函数执行时,会创建一个执行上下文(Execution Context),并将其添加到作用域链的最前端。执行上下文中的变量对象称为活动对象(Active Object)。
  3. 当访问一个变量时,JavaScript 引擎首先会在活动对象中查找,如果找不到,则沿着作用域链依次向上查找,直到全局执行上下文的变量对象。
  4. 如果在作用域链的任何一个环节找到了变量,则停止查找并返回变量的值;如果未找到,则抛出引用错误(ReferenceError)。

作用域链的特点:

  1. 作用域链是一个静态的概念,它在函数定义时就确定了,不会随着函数的调用而改变。
  2. 作用域链是由多个执行上下文的变量对象按照它们被创建的顺序组成的。
  3. 作用域链的最后一个变量对象是全局执行上下文的变量对象,它是作用域链的终点。
  4. 内部函数可以访问外部函数的变量,因为内部函数的作用域链包含了外部函数的变量对象。

有哪些应用场景

作用域链在 JavaScript 中具有广泛的应用场景。下面列举了一些常见的应用场景:

  1. 变量查找:作用域链决定了变量的访问顺序,当访问一个变量时,会按照作用域链的顺序依次查找变量,直到找到匹配的变量或到达全局作用域。
  2. 闭包:闭包是指函数能够访问和操作它的外部函数中定义的变量。通过作用域链,内部函数可以访问外部函数的变量,实现了闭包的特性。闭包在许多场景中用于创建私有变量和实现函数封装。
  3. 垃圾回收:JavaScript 的垃圾回收机制通过作用域链来判断变量的生命周期。当变量不再被引用时,垃圾回收器可以回收它所占用的内存空间。
  4. 函数作为参数传递:在 JavaScript 中,可以将函数作为参数传递给其他函数。在传递过程中,作用域链决定了内部函数对外部函数变量的访问权限,实现了回调函数和高阶函数的功能。
  5. 面向对象编程:JavaScript 中的对象和原型链是基于作用域链实现的。通过原型链,对象可以访问和继承其原型对象的属性和方法。
  6. 模块化开发:作用域链可以用于实现模块化开发,通过定义私有变量和公共接口,控制模块内部变量的可访问性,避免变量冲突和全局污染。
  7. 作用域链的动态改变:在 JavaScript 中,可以通过闭包和动态作用域的特性来改变作用域链。例如,使用 eval() 函数或 with 语句可以改变当前的作用域链。

总之,作用域链在 JavaScript 中扮演了重要的角色,涵盖了变量的访问、闭包、垃圾回收、模块化开发等多个方面。深入理解作用域链对于编写高质量的 JavaScript 代码和理解其底层工作原理非常重要。

393.实现一个JS 函数, 解析 url 参数, 返回一个对象【JavaScript】【出题公司: Shopee】

基础版本

下面是一个简单的 JavaScript 函数,用于解析 URL 参数并返回一个包含参数键值对的对象:

function parseUrlParams(url) {
  const params = {};
  const queryString = url.split('?')[1];

  if (queryString) {
    const pairs = queryString.split('&');
    pairs.forEach(pair => {
      const [key, value] = pair.split('=');
      params[key] = decodeURIComponent(value);
    });
  }

  return params;
}
           

这个函数接受一个 URL 字符串作为参数,并返回解析后的参数对象。例如:

const url = 'https://example.com?name=John&age=30&city=New%20York';
const params = parseUrlParams(url);

console.log(params);
// Output: { name: "John", age: "30", city: "New York" }
           

这个函数的实现思路是先从 URL 字符串中提取查询字符串部分,然后将查询字符串按照 & 分割成键值对数组。接着遍历键值对数组,将每个键值对按照 = 分割,然后将键和值存储到结果对象 params 中,注意要对值进行 URL

解码以处理特殊字符。最后返回解析后的参数对象。

进阶 - 支持json字符串参数

如果要支持复杂的 JSON 字符串作为查询参数,可以使用 JSON.parse() 方法解析 JSON 字符串,并在解析后的对象中处理参数。

下面是一个修改后的函数,支持解析复杂的 JSON 字符串作为查询参数:

function parseUrlParams(url) {
  const params = {};
  const queryString = url.split('?')[1];

  if (queryString) {
    const pairs = queryString.split('&');
    pairs.forEach(pair => {
      const [key, value] = pair.split('=');
      const decodedValue = decodeURIComponent(value);

      try {
        params[key] = JSON.parse(decodedValue);
      } catch (error) {
        // 如果解析 JSON 失败,则将原始字符串存储到参数对象中
        params[key] = decodedValue;
      }
    });
  }

  return params;
}
           

现在,如果查询参数是一个 JSON 字符串,它将被解析为相应的 JavaScript 对象,并作为参数对象的值。如果解析失败(例如,不是有效的 JSON 字符串),则将保留原始字符串作为值存储在参数对象中。

以下是一个示例:

const url = 'https://example.com?name=John&age=30&address={"city":"New York","zipcode":10001}';
const params = parseUrlParams(url);

console.log(params);
// Output: { name: "John", age: "30", address: { city: "New York", zipcode: 10001 } }
           

再次进阶-支持更复杂的场景, 比如嵌套对象, 数组

下面是修改后的函数,支持解析复杂的查询参数,包括嵌套对象和数组:

function parseUrlParams(url) {
  const params = {};
  const queryString = url.split('?')[1];

  if (queryString) {
    const pairs = queryString.split('&');
    pairs.forEach(pair => {
      const [key, value] = pair.split('=');
      const decodedValue = decodeURIComponent(value);

      const keys = key.split('.');
      let current = params;

      for (let i = 0; i < keys.length; i++) {
        const nestedKey = keys[i];
        const isArray = /\[\]$/.test(nestedKey);

        if (isArray) {
          const arrayKey = nestedKey.slice(0, -2);

          if (!current[arrayKey]) {
            current[arrayKey] = [];
          }

          if (i === keys.length - 1) {
            current[arrayKey].push(parseValue(decodedValue));
          } else {
            const newIndex = current[arrayKey].length;
            if (!current[arrayKey][newIndex]) {
              current[arrayKey][newIndex] = {};
            }
            current = current[arrayKey][newIndex];
          }
        } else {
          if (i === keys.length - 1) {
            current[nestedKey] = parseValue(decodedValue);
          } else {
            if (!current[nestedKey]) {
              current[nestedKey] = {};
            }
            current = current[nestedKey];
          }
        }
      }
    });
  }

  return params;
}

function parseValue(value) {
  try {
    return JSON.parse(value);
  } catch (error) {
    // 解析失败,则返回原始值
    return value;
  }
}
           

现在,该函数可以正确解析包含嵌套对象和数组的查询参数。

以下是一个示例:

const url = 'https://example.com?name=John&age=30&address.city=New%20York&address.zipcode=10001&tags[]=tag1&tags[]=tag2';
const params = parseUrlParams(url);

console.log(params);
// Output: { name: "John", age: "30", address: { city: "New York", zipcode: 10001 }, tags: ["tag1", "tag2"] }
           

在这个修改后的函数中,当遇到嵌套对象时,它会递归创建相应的对象属性。当遇到数组时,它会创建一个数组,并将值添加到数组中。

高级开发者相关问题【共计 5 道题】

385.使用 Promise 实现一个异步流量控制的函数【热度: 517】【JavaScript】【出题公司: 腾讯】

关键词:异步流量控制的函数

下面是使用 Promise 实现异步流量控制的函数的示例:

function asyncFlowControl(tasks, limit) {
  let runningCount = 0; // 当前正在运行的任务数
  let index = 0; // 当前执行的任务索引
  const results = []; // 存储任务的结果

  return new Promise((resolve, reject) => {
    function runTask() {
      if (runningCount >= limit || index >= tasks.length) {
        // 达到并发限制或所有任务已执行完毕,返回结果
        if (results.length === tasks.length) {
          resolve(results);
        }
        return;
      }

      const task = tasks[index];
      const currentIndex = index; // 保存当前任务索引

      index++;
      runningCount++;

      task().then((result) => {
        results[currentIndex] = result; // 存储任务结果
        runningCount--;
        runTask(); // 递归执行下一个任务
      }).catch((error) => {
        reject(error);
      });

      runTask(); // 递归执行下一个任务
    }

    runTask(); // 开始执行任务
  });
}

// 示例用法
function asyncTask(value) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(value);
      resolve(value);
    }, Math.random() * 1000);
  });
}

const tasks = [
  () => asyncTask(1),
  () => asyncTask(2),
  () => asyncTask(3),
  () => asyncTask(4),
  () => asyncTask(5),
];

asyncFlowControl(tasks, 2).then((results) => {
  console.log('All tasks completed:', results);
}).catch((error) => {
  console.error('Error occurred:', error);
});
           

以上示例中的 asyncFlowControl 函数接受一个任务数组 tasks 和一个并发限制 limit,它会按照并发限制逐个执行任务,并返回一个 Promise 对象。在示例中,任务数组中的每个任务都是一个返回

Promise 的函数,通过 setTimeout 模拟异步操作。

在执行过程中,asyncFlowControl 函数会维护一个 runningCount 变量来跟踪当前正在运行的任务数,并使用递归的方式执行任务。当达到并发限制或所有任务都已执行完毕时,函数会返回结果。

通过控制并发任务的数量,我们可以限制同时执行的异步操作,实现异步流量控制。在上述示例中,设置并发限制为 2,可以确保最多同时执行 2 个任务,并在任务执行完毕后再执行下一个任务。

386.不使用任何中间件, koa 如何解析 post 请求参数【热度: 1,359】【Nodejs】【出题公司: 阿里巴巴】

关键词:koa 请求、koa 解析、koa body-parser

如果你不想使用任何中间件来解析 POST 请求参数,你可以手动解析请求体数据。在 Koa 中,你可以通过以下步骤来解析 POST 请求的参数:

  1. 使用 ctx.req 获取原始的 Node.js 请求对象。
  2. 将请求对象的数据流通过 ctx.req.on('data', ...) 事件监听进行读取。
  3. 将读取到的数据流进行处理,根据请求头的 Content-Type 判断数据格式,可以是 application/json 或 application/x-www-form-urlencoded。
  4. 将处理后的数据转换为 JavaScript 对象或其他格式进行进一步处理。

以下是一个示例:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
  if (ctx.method === 'POST') {
    // 手动解析 POST 请求的参数
    const requestData = await parseRequestBody(ctx.req);
    // 处理请求参数
    // ...
    ctx.body = 'POST request received';
  } else {
    ctx.body = 'Hello, Koa!';
  }
});

function parseRequestBody(req) {
  return new Promise((resolve, reject) => {
    let data = '';
    req.on('data', (chunk) => {
      data += chunk;
    });
    req.on('end', () => {
      // 根据请求头的 Content-Type 判断数据格式
      if (req.headers['content-type'] === 'application/json') {
        // 解析 JSON 格式数据
        try {
          resolve(JSON.parse(data));
        } catch (error) {
          reject(error);
        }
      } else if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
        // 解析 URL 编码格式数据
        const parsedData = {};
        const keyValuePairs = data.split('&');
        for (const pair of keyValuePairs) {
          const [key, value] = pair.split('=');
          parsedData[key] = decodeURIComponent(value);
        }
        resolve(parsedData);
      } else {
        reject(new Error('Unsupported content type'));
      }
    });
    req.on('error', (error) => {
      reject(error);
    });
  });
}

app.listen(3000, () => {
  console.log('Server started on port 3000');
});
           

在上述示例中,我们在中间件函数中手动解析 POST 请求的参数。parseRequestBody 函数使用 ctx.req 获取原始的 Node.js 请求对象,并通过监听 data

事件将请求体数据流进行读取。然后,根据请求头的 Content-Type 判断数据格式,如果是 application/json,则使用 JSON.parse 解析为 JavaScript

对象;如果是 application/x-www-form-urlencoded,则将数据转换为键值对对象。最后,将解析后的数据传递给处理函数进行进一步处理。

请注意,手动解析请求参数可能更复杂且容易出错,而使用中间件能够更方便地处理和解析请求体数据。因此,在实际开发中,推荐使用合适的中间件来解析请求参数。

390.webpack5 Module Federation 了解多少【工程化】【出题公司: 京东】

概念

Webpack 5 的 Module Federation 是一项功能强大的功能,它允许将 JavaScript 应用程序拆分成独立的模块,并在不同的 Webpack

构建中共享这些模块。它解决了多个独立应用程序之间共享代码的问题,使得实现微前端架构变得更加容易。

Module Federation

可以将一个应用程序拆分成多个独立的子应用,每个子应用都可以被独立开发、部署和运行。每个子应用都可以通过配置指定需要共享的模块,然后将这些共享模块以动态方式加载到其他子应用中使用,而无需将这些模块打包进每个子应用的构建文件中。

Module Federation 的核心概念是 “容器”(Container)和 “远程”(Remote)。容器是一个主应用程序,它可以加载和渲染其他子应用程序,而远程是一个独立的子应用程序,它提供了一些模块供其他子应用程序使用。

Module Federation

提供了一种简单的方式来定义远程模块,并在容器中引用这些远程模块。容器可以从远程加载模块,并通过配置将这些模块暴露给其他子应用程序。这样,子应用程序可以通过远程加载和使用容器中的模块,实现了模块的共享和动态加载。

Module Federation

在实现微前端架构时非常有用,可以将多个独立开发的子应用程序组合成一个整体,并实现共享模块和资源的灵活管理。它提供了一种解决方案,让多个团队可以独立开发和部署自己的子应用程序,同时又能够共享代码和资源,提高开发效率和整体性能。

Webpack 5 的 Module Federation 是一项用于实现微前端架构的功能,它可以将 JavaScript 应用程序拆分成独立的子应用程序,并通过动态加载和共享模块的方式实现子应用程序之间的交互和共享。

使用示范

下面是一个简单的示例,演示如何在 Webpack 5 中使用 Module Federation。

假设我们有两个独立的应用程序:App1 和 App2。我们将使用 Module Federation 将 App2 的模块共享给 App1。

首先,我们需要在 App2 的 Webpack 配置中启用 Module Federation:

// webpack.config.js (App2)

const { ModuleFederationPlugin } = require('webpack');

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button', // 暴露 App2 的 Button 模块
      },
    }),
  ],
};
           

接下来,我们需要在 App1 的 Webpack 配置中配置远程加载 App2 的模块:

// webpack.config.js (App1)

const { ModuleFederationPlugin } = require('webpack');

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      remotes: {
        app2: 'app2@http://localhost:3002/remoteEntry.js', // 远程加载 App2 的模块
      },
    }),
  ],
};
           

在 App1 中,我们可以像使用本地模块一样使用 App2 的模块:

// App1

import React from 'react';
import ReactDOM from 'react-dom';
import App2Button from 'app2/Button'; // 远程加载 App2 的 Button 模块

ReactDOM.render(<App2Button/>, document.getElementById('root'));
           

在上面的示例中,我们通过 Module Federation 将 App2 的 Button 模块暴露给了 App1,然后在 App1 中可以直接通过 import 语句引入并使用。

需要注意的是,App1 需要在 remotes 配置中指定远程加载的模块,其中 app2 是一个远程模块的名称,而 http://localhost:3002/remoteEntry.js 是 App2 构建输出的远程入口文件。

这只是一个简单的示例,实际使用中可能涉及更复杂的配置和场景。但通过以上配置,我们可以实现在不同应用程序之间共享模块,并通过动态加载的方式使用远程模块。

391.小程序为什么会有两个线程【web应用场景】【出题公司: Shopee】

小程序之所以有两个线程,是为了实现小程序的高效运行和良好的用户体验。

  1. 渲染线程(UI 线程): 渲染线程负责小程序界面的渲染和响应用户的交互。它使用 WebView 进行页面渲染,包括解析和绘制 DOM、布局、样式计算和渲染等操作。渲染线程是单线程的,所有的界面操作都在这个线程中进行。
  2. 逻辑线程(JS 线程): 逻辑线程负责小程序的逻辑运算和数据处理。它是基于 JavaScript

    运行的,负责处理用户交互、业务逻辑、数据请求、事件处理等操作。逻辑线程是独立于渲染线程的,可以并行处理多个任务,避免阻塞界面的渲染和响应。

将界面渲染和逻辑运算分离成两个线程的设计有以下好处:

  • 响应速度:逻辑线程和渲染线程分开,可以并行执行,提高了小程序的响应速度和用户体验。
  • 防止阻塞:逻辑线程的运行不会阻塞渲染线程,避免了长时间的计算或数据处理导致界面卡顿或无响应的情况。
  • 资源隔离:渲染线程和逻辑线程是独立的,它们有各自的资源和运行环境,可以避免相互干扰和影响。

需要注意的是,小程序的渲染线程和逻辑线程之间通过微信客户端进行通信和交互。逻辑线程可以发送请求给微信客户端,然后客户端将渲染指令发送给渲染线程进行界面渲染,同时渲染线程可以将用户的交互事件发送给逻辑线程进行处理。这种通信方式保证了渲染和逻辑的协同工作,实现了小程序的正常运行。

小程序之所以有两个线程,是为了提高渲染速度、避免阻塞和资源隔离。渲染线程负责界面渲染,逻辑线程负责业务逻辑和数据处理,两者通过微信客户端进行通信和交互,共同实现小程序的功能和性能。

392.[React] react-router 页面跳转时,是如何传递下一个页面参数的?【web框架】【出题公司: 腾讯】

路由数据

React Router 是一个用于管理前端路由的库,它与 React 应用程序集成在一起,提供了一种在单页面应用中处理路由的方式。React Router 并没有直接提供数据存储的功能,它主要负责路由的匹配和导航。

在 React Router 中,路由相关的数据主要存储在组件的 props 和组件的状态中。以下是一些常见的数据存储方式:

  1. 路由参数(Route Parameters): React Router 允许通过路径参数(如 /users/:id)传递参数给路由组件。这些参数可以通过 props.match.params

    对象在路由组件中获取。路由参数通常用于标识唯一资源的ID或其他需要动态变化的数据。

  2. 查询参数(Query Parameters): 查询参数是通过 URL 查询字符串传递的键值对数据,如 /users?id=123&name=John。React Router

    可以通过 props.location.search 属性获取查询字符串,并通过解析库(如 query-string)将其转换为 JavaScript 对象。查询参数通常用于筛选、分页或其他需要传递额外数据的场景。

  3. 路由状态(Route State): 在某些情况下,可能需要将一些状态信息传递给路由组件,例如从一个页面跳转到另一个页面时需要携带一些额外的状态。React Router 提供了 props.location.state

    属性,可以用于存储和传递路由状态。

  4. 上下文(Context): React Router 提供了一个 Router 组件,可以使用 React 的上下文功能共享路由相关的数据。通过在 Router 组件的上下文中提供数据,可以在路由组件中访问该数据,而无需通过

    props 层层传递。这在需要在多个嵌套层级中访问路由数据时非常方便。

总的来说,React Router 并没有专门的数据存储机制,它主要利用 React 组件的 props

和状态来传递和存储路由相关的数据。这些数据可以通过路由参数、查询参数、路由状态以及上下文等方式来传递和获取。根据具体的需求和场景,可以选择适合的方式来存储和管理路由相关的数据。

路由状态是如何存储的

在 React Router 中,路由状态可以通过 props.location.state 属性来存储和获取。

当使用 React Router 进行页面导航时,可以通过 history.push 或 history.replace 方法传递一个包含状态数据的对象作为第二个参数。例如:

history.push('/dashboard', { isLoggedIn: true, username: 'John' });
           

这个对象会被存储在新页面的 props.location.state 中,可以在目标页面的组件中通过 props.location.state 来访问它。例如:

import { useLocation } from 'react-router-dom';

function Dashboard() {
  const location = useLocation();
  const { isLoggedIn, username } = location.state;

  // 使用路由状态数据
  // ...
}
           

需要注意的是,路由状态仅在通过 history.push 或 history.replace 导航到新页面时才可用。如果用户通过浏览器的前进/后退按钮进行导航,或者直接输入 URL 地址访问页面,路由状态将不会被保留。

另外,路由状态也可以在类组件中通过 this.props.location.state 进行访问,或者在函数组件中使用 props.location.state。

props.location.state 数据是如何存储的

在 React Router 中,路由状态数据实际上是存储在客户端的内存中。

当使用 history.push 或 history.replace 方法导航到一个新页面时,React Router

将路由状态数据作为对象附加到浏览器历史记录中的对应路由条目。这个对象会存储在浏览器的会话历史中,并在新页面加载时被 React Router 读取并提供给组件。

具体地说,React Router 使用 HTML5 的 History API(pushState 或 replaceState

方法)来实现路由导航,并将路由状态数据作为一个特殊的字段存储在历史记录中。这个字段通常被称为 state 字段,用于存储路由状态数据。

在浏览器中,历史记录和相应的状态数据会被保存在内存中。当用户进行前进、后退或直接访问某个 URL 时,浏览器会根据历史记录加载对应的页面,并将相关的状态数据提供给 React

Router。这样,组件就能够通过 props.location.state 来访问之前存储的路由状态数据。

需要注意的是,路由状态数据仅在客户端内存中存在,每个用户的路由状态是独立的。如果用户刷新页面或关闭浏览器,路由状态数据将丢失,并需要重新通过导航操作来设置。因此,路由状态适合存储短期或临时的数据,而对于长期或持久化的数据,应该考虑其他的数据存储方式,如服务器端存储或状态管理库。

继续阅读