NXY面试

114 阅读21分钟

你知道webpack基本配置吗?能举一个webpack配置的例子吗(要求包含常用plugin和loader)?

下面是Webpack配置示例:

const path = require('path');

module.exports = {
  entry: {
    // 主应用程序入口文件
    main: './src/index.js',
    // 第三方库入口文件(以React和ReactDOM为例)
    vendor: ['react', 'react-dom']
  },
  output: {
    // 输出文件名使用[name]占位符,对应entry中的键名
    filename: '[name].bundle.js',
    // 输出路径
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      // 指定拆分公共模块的范围,默认为async(异步模块)
      chunks: 'all'
    }
  }
};
  • entry: 配置入口文件,可以有多个入口。main是我们的主应用程序入口,vendor是第三方库的入口。
  • output: 配置输出文件的名称和路径。filename使用[name]占位符,根据entry中的键名生成对应的输出文件名。path指定输出文件的目录。
  • optimization: 配置优化选项。在这里,我们使用splitChunks进行公共模块的拆分。
  • splitChunks: 配置公共模块的拆分选项。chunks指定拆分公共模块的范围,默认为async(异步模块),这里我们配置为all表示拆分所有模块。

通过这个配置,Webpack会根据入口文件的定义,将应用程序代码和第三方库代码分别打包成不同的文件。主应用程序的代码会打包到main.bundle.js文件中,而React和ReactDOM等第三方库的代码会打包到vendor.bundle.js文件中。这样可以实现分包的效果,提高应用程序的加载速度。

你知道webpack的分包配置吗?为什么要分包?

Webpack的splitChunks功能可以帮助我们将公共模块提取出来,单独打包成一个文件。这样做的好处有以下几点:

  1. 代码复用:当多个入口文件共享相同的模块时,通过分包可以避免重复加载和下载相同的代码,提高代码复用性。
  2. 减少文件体积:将公共模块单独打包成一个文件,可以减小每个入口文件的体积,提高页面加载速度。
  3. 并行加载:拆分出的公共模块可以并行加载,提高页面的并发请求能力,加快页面渲染速度。

下面是一个使用splitChunks进行分包的Webpack配置示例,其中我添加了详细的注释来解释每个配置选项的作用:

const path = require('path');

module.exports = {
  entry: {
    main: './src/index.js',
    vendor: ['react', 'react-dom']
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      chunks: 'all', // 指定拆分范围为所有模块
      minSize: 0, // 拆分的模块最小体积为0
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/, // 匹配node_modules目录下的模块
          name: 'vendor', // 拆分出的文件名
          priority: -10, // 优先级较高,先拆分第三方库
          enforce: true
        },
        common: {
          name: 'common', // 拆分出的文件名
          minChunks: 2, // 至少被引用2次才会拆分
          priority: -20, // 优先级较低,后拆分公共模块
          reuseExistingChunk: true
        }
      }
    }
  }
};

通过上述配置,Webpack会将React和ReactDOM等第三方库打包到vendor.bundle.js文件中,并且只有当模块在node_modules目录下,才会被拆分为公共模块。这样,我们可以在不同的入口文件中引入这些公共模块,而无需重复打包。当多个入口文件共享相同的模块时,这些模块会被拆分到common.bundle.js文件中。

在Webpack配置中,加载器(loader)应该使用use属性而不是loader属性。 在上述代码示例中,加载器规则仍然位于module.rules数组中,但是我们使用了use属性来指定加载器。这是因为Webpack 2+版本中已经将loader属性更名为use属性。 所以,正确的配置应该将加载器的配置放在use属性中,如示例中的use: 'babel-loader'use: ['css-loader']use: [{ loader: 'file-loader', options: {...} }]

分页查询的两种模式(用page和pagesize有什么弊端)?

  1. 使用pagepageSize的弊端:
  • 数据不稳定性:使用pagepageSize进行分页查询时,如果数据集发生了变化(例如有新数据插入或旧数据删除),可能会导致查询结果的不稳定性。在两次查询之间,如果有数据的插入、删除或更新,可能会导致页码和页面大小与实际数据不匹配,进而产生错误的结果。
  • 性能问题:如果数据集非常庞大,使用pagepageSize进行分页查询可能会导致性能问题。每次查询都需要扫描整个数据集,并跳过前面的数据,直到达到指定的页码。这对于大型数据集来说是一种昂贵的操作,可能会导致查询速度变慢。
  • 数据重复或丢失:使用pagepageSize进行分页查询时,如果数据集中存在并发写入或删除操作,可能会导致数据的重复或丢失。例如,如果在查询过程中有新数据插入,可能会导致某些数据在连续的两次查询中出现两次,或者某些数据在连续的两次查询中被遗漏。
  1. 其他分页查询模式的选择:
  • 基于游标的分页:使用基于游标的分页可以解决上述问题。它通过记录上一次查询结果的最后一个元素的游标(例如数据的ID或时间戳),作为下一次查询的起点。这种方式可以提供稳定的分页结果,并且不会受到并发操作的影响。常见的实现方式是使用beforeafter参数来控制查询的范围。
  • 限制结果集大小:如果数据集较小,可以考虑在查询时限制结果集的大小,而不是使用分页查询。通过限制结果集大小,可以避免扫描整个数据集的开销,并且可以更快地返回结果。
  • 缓存查询结果:如果查询结果不经常变化,可以考虑将查询结果缓存在缓存中,而不是每次查询都扫描整个数据集。这样可以大大提高查询性能,并降低对数据库的负载。

综上所述,虽然pagepageSize是常见的分页查询方式,但它们可能存在一些弊端。根据具体的需求和数据集大小,可以选择其他分页查询模式来解决这些问题。

看过vue源码吗?

token失效后拿到refresh的token如何处理更新数据接口(如果有很多个接口怎么办)?

当token失效后拿到refresh token,你可以利用axios的拦截器来自动处理更新数据接口。具体来说,你可以在axios的请求拦截器中添加一个检查access token是否过期的逻辑。如果access token已过期,则在刷新成功后再继续发送原本的请求。这样,你就可以确保每个接口都能够及时地使用更新后的access token进行授权。

以下是一个实现的示例:

import axios from 'axios';
import router from './router';

// 创建axios实例
const service = axios.create({
  baseURL: 'http://api.example.com', // 设置接口的基础URL
  timeout: 5000 // 请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 检查access token是否过期
    if (isAccessTokenExpired()) {
      // 如果access token过期了,使用refresh token获取新的access token
      const refreshToken = localStorage.getItem('refresh_token');
      return axios.post('/api/refresh_token', { refresh_token: refreshToken })
        .then(res => {
          // 将新的access token存储起来
          localStorage.setItem('access_token', res.data.access_token);
          // 在请求头中添加新的access token
          config.headers['Authorization'] = 'Bearer ' + res.data.access_token;
          return config;
        })
        .catch(err => {
          // 刷新token失败,跳转到登录页面重新登录
          router.push('/login');
          return Promise.reject(err);
        });
    } else {
      // 如果access token没有过期,直接在请求头中添加access token
      const accessToken = localStorage.getItem('access_token');
      config.headers['Authorization'] = 'Bearer ' + accessToken;
      return config;
    }
  },
  error => {
    // 请求错误处理
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 响应成功处理
    return response;
  },
  error => {
    // 响应错误处理
    if (error.response.status === 401) {
      // 如果返回401未授权状态码,则说明access token已经过期了
      // 此时直接跳转到登录页面重新登录
      router.push('/login');
    }
    return Promise.reject(error);
  }
);

export default service;

在上述代码中,我们在请求拦截器中添加了一个检查access token是否过期的逻辑。如果access token过期了,我们会使用refresh token获取新的access token,并在刷新成功后继续发送原本的请求。如果刷新失败,则跳转到登录页面重新登录。

使用这种方式,你只需要在每个需要授权的请求中调用axios实例即可,axios会自动处理授权的逻辑。这可以大大简化代码,提高开发效率。

promise.all需要所有都执行才能进行下一步,如果有A,B,C接口需要请求,但是A,C接口拿到值就可以往下走了,这时候用promise.all怎么操作?

Promise.all()方法会等待所有的Promise对象都返回结果后再执行相应的逻辑。如果其中一个Promise对象失败,则整个Promise.all()方法也会失败,无法达到题目要求。

但是,我们可以通过在每个Promise对象上调用.catch()方法,来防止其中的某一个Promise对象失败导致整个Promise.all()方法失败。具体操作如下:

const aPromise = axios.get('/api/a').catch(error => error);
const bPromise = axios.get('/api/b').catch(error => error);
const cPromise = axios.get('/api/c').catch(error => error);

Promise.all([aPromise, bPromise, cPromise])
  .then(results => {
    // 所有Promise对象都返回结果后,会执行这里的逻辑
    console.log('所有请求返回结果', results);
    // 这里可以执行后续的逻辑
  })
  .catch(error => {
    // 如果任意一个Promise对象失败,则会执行这里的逻辑
    console.log('请求失败', error);
  });

在上述代码中,我们在每个Promise对象上调用.catch()方法,将其失败的结果转化为成功的结果(即使是错误对象)。然后,我们使用Promise.all()方法等待所有Promise对象都返回结果,并在所有Promise对象完成后执行相应的逻辑。如果其中某一个Promise对象失败,由于我们使用了.catch()方法将其转化为成功的结果,因此不会影响Promise.all()方法的执行。在这种情况下,Promise.all()方法会将失败的结果作为Promise对象的返回结果,可以在.then()中进行处理。

css的不同浏览器兼容性?

在Webpack中处理CSS的不同浏览器兼容性,可以通过使用PostCSS插件来实现。以下是一个基本的Webpack配置示例:

  1. 首先,安装必要的依赖:
npm install postcss-loader autoprefixer --save-dev
  1. 在Webpack配置文件中添加相应的配置:
module.exports = {
  // ...其它配置项...
  module: {
    rules: [
      // ...其它规则...
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  ['autoprefixer'],
                ],
              },
            },
          },
        ],
      },
    ],
  },
};

上述配置中,我们使用了postcss-loader来处理CSS,并引入了autoprefixer插件。autoprefixer插件可以根据配置的浏览器兼容性要求自动添加相应的前缀。

  1. 创建一个PostCSS配置文件(例如postcss.config.js),并配置浏览器兼容性要求:
module.exports = {
  plugins: [
    require('autoprefixer')({
      browsers: ['> 1%', 'last 2 versions'],
    }),
  ],
};

在上述配置中,我们设置了兼容性要求为支持全球使用率超过1%的浏览器和最近两个版本的浏览器。

通过以上配置,Webpack会在构建过程中自动处理CSS文件,并根据配置的浏览器兼容性要求添加相应的前缀,从而解决不同浏览器之间的兼容性问题。

flex的属性?

在CSS中,flex属性是用于控制Flexbox布局的一个重要属性。它有以下几个子属性:

  1. flex-grow: 定义项目的放大比例,默认为0,即不放大。如果所有项目的flex-grow都为1,则它们将等分剩余空间。如果某个项目的flex-grow值为2,而其他项目都为1,则该项目会占据更多的空间。
  2. flex-shrink: 定义项目的缩小比例,默认为1,即可以缩小。如果某个项目的flex-shrink值为0,而其他项目都为1,则空间不足时,该项目不缩小。
  3. flex-basis: 定义项目的初始大小。默认值为auto,即由项目的内容决定。它可以接受长度值(如px、em等)或百分比。
  4. flex: 是flex-growflex-shrinkflex-basis三个属性的简写形式。例如,flex: 1表示flex-grow: 1; flex-shrink: 1; flex-basis: 0%
  5. align-self: 定义单个项目在交叉轴上的对齐方式,覆盖容器的align-items属性。可取值包括autoflex-startflex-endcenterbaselinestretch

这些属性可以应用于Flex容器(display: flex)的子元素,用于控制子元素在主轴和交叉轴上的布局和分配空间。通过调整这些属性的值,可以实现强大的灵活布局效果。

vue中父组件A和子组件B挂载和卸载的生命周期执行顺序?

Vue中,父组件和子组件的挂载的生命周期执行顺序如下:

  1. 父组件beforeCreate
  2. 父组件created
  3. 父组件beforeMount
  4. 子组件beforeCreate
  5. 子组件created
  6. 子组件beforeMount
  7. 子组件mounted
  8. 父组件mounted

此时,父组件和子组件都已经完成挂载。当需要卸载组件时,执行顺序如下:

  1. 父组件beforeDestroy
  2. 子组件beforeDestroy
  3. 子组件destroyed
  4. 父组件destroyed

在卸载过程中,先执行父组件的beforeDestroy函数,然后执行子组件的beforeDestroy函数和destroyed函数,最后再执行父组件的destroyed函数。

需要注意的是,在父子组件嵌套的情况下,如果父组件被销毁,那么它的所有子组件也会被销毁。而子组件的销毁不会影响到父组件和其他兄弟组件。

箭头函数能用bind绑定改变this指向吗?怎么能在箭头函数中使用不同的this指向?

箭头函数是具有词法作用域的函数,它会自动继承父级作用域的this值,因此不能使用bind()方法来改变箭头函数中的this指向。

箭头函数的this指向是固定的,取决于它定义时所处的上下文。一般来说,箭头函数的this指向是定义时所在作用域的this,而不是调用时的this

如果你希望在箭头函数中使用不同的this指向,可以使用以下方法之一:

  1. 使用闭包:在箭头函数外部创建一个函数,并在该函数内部使用bind()方法来改变this指向,然后将该函数作为箭头函数的参数传入。

    function myFunction() {
      const self = this;
      const arrowFunc = () => {
        // 在箭头函数中使用self代替this
        console.log(self);
      };
      arrowFunc.bind(otherObject)();
    }
    
  2. 使用函数表达式:使用普通的函数表达式来定义函数,并在函数内部使用bind()方法来改变this指向。

    const myFunction = function() {
      const arrowFunc = () => {
        // 在箭头函数中使用this指向其他对象
        console.log(this);
      }.bind(otherObject);
      arrowFunc();
    };
    

请注意,以上方法都是通过创建一个包装函数来改变this指向,而不是直接改变箭头函数本身的this指向。因为箭头函数的this指向是无法改变的,它始终指向定义时的上下文。

前端代码如何让测试测,测试在git上拉取代码测是怎么配置的服务器访问(我回答的是前端打包,把打包的文件放在nginx上)?

在前端开发中,为了进行测试,你可以通过以下步骤配置一个服务器来访问你的代码:

  1. 准备一个 Web 服务器:你可以选择使用 Node.js 的一些框架(如 Express、Koa)或其他 Web 服务器(如 Nginx)来搭建一个本地测试服务器。确保服务器能够监听指定的端口,并能够提供静态文件服务。
  2. 在本地运行测试服务器:启动你搭建的测试服务器,确保它正常运行。你可以通过命令行控制台运行服务器脚本,或使用相应的集成开发环境(IDE)提供的工具来启动服务器。
  3. 配置服务器端口和访问路径:在服务器配置中,设置服务器监听的端口号以及用于访问你的前端代码的路径。例如,你可以将服务器监听的端口设置为 3000,并将前端代码放在服务器的 public 文件夹下。
  4. 将代码部署到服务器:将你的前端代码部署到服务器上。可以直接将代码复制到服务器指定的目录下,或使用一些自动化部署工具(如 Jenkins、Git Hooks)来实现自动部署。
  5. 确认服务器可访问:打开浏览器,输入 http://localhost:3000(假设服务器监听的端口为 3000),确认服务器已经正确配置并能够访问到你的前端代码。
  6. 在 Git 上拉取代码进行测试:在 Git 上拉取你的代码到本地,然后切换到测试分支,并将本地代码与测试服务器进行同步。可以使用 Git 的一些命令(如 git clonegit pull)来操作。
  7. 进行测试:在本地进行测试之前,请确保已经安装并配置好了所需的开发环境和依赖。然后,打开浏览器,输入测试服务器的地址,例如 http://localhost:3000,查看前端代码的运行效果并进行测试。

以上是一个基本的流程,具体的配置和步骤可能会根据你使用的开发工具、服务器和版本控制系统而有所不同。

let a=10,b=20可以,那const a=10,b=20可以吗?

可以使用const关键字来声明常量,并在一行中声明多个常量。

const a = 10, b = 20;

上述代码将声明两个常量ab,并分别赋值为1020。这样的语法是有效的,并且符合 JavaScript 的语法规范。

请注意,使用const关键字声明的常量是不可变的,即它们的值在声明后不能被修改。如果你尝试修改一个用const声明的常量的值,会导致语法错误。

const a = 10;
a = 20; // 会导致语法错误,因为常量a的值不能被修改

因此,在使用const声明常量时,请确保你在声明后不会对其进行重新赋值。如果需要可变的变量,可以使用let关键字。

vue中用函数组件有什么好处?

  1. 简洁:函数组件只需定义一个函数,不需要额外的选项对象,代码结构更加简洁明了。
  2. 性能优化:函数组件没有实例化的过程,渲染性能相对较高。
  3. 更好的重用性:函数组件可以被多个父组件复用,增加了组件的可维护性和可扩展性。

下面是一个使用函数组件的示例代码:

<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="increaseCount">点击增加计数</button>
  </div>
</template>

<script>
// 使用函数组件定义一个计数器组件
const Counter = {
  props: ['initialCount'],
  data() {
    return {
      count: this.initialCount,
    };
  },
  methods: {
    increaseCount() {
      this.count++;
    },
  },
  computed: {
    message() {
      return `当前计数:${this.count}`;
    },
  },
};

export default {
  components: {
    Counter,
  },
  data() {
    return {
      initialCount: 0,
    };
  },
};
</script>

在上面的代码中,我们定义了一个函数组件Counter,它接收一个初始计数值initialCount作为props,并维护一个内部的计数器count。点击按钮时,调用increaseCount方法增加计数器的值,并通过computed属性message动态显示当前计数值。这样,我们可以在父组件中多次使用Counter组件,并且每个实例之间都是独立的,可以方便地进行复用和管理。

分页你是用page pagesize还是offset的方式?你知道page,pagesize这种方式有什么弊端吗?

在分页中,我可以使用两种方式,即使用 "page" 和 "pageSize" 进行分页,或者使用 "offset" 和 "limit" 进行分页。

  1. 使用 "page" 和 "pageSize" 的方式:

    • "page" 表示要请求的页数,通常从 1 开始计数。
    • "pageSize" 表示每页的数据条数。
  2. 使用 "offset" 和 "limit" 的方式:

    • "offset" 表示要请求的数据偏移量,即从第几条数据开始获取。
    • "limit" 表示每页的数据条数。

这两种方式都可以实现分页效果,并且在不同的项目中可能会根据需求选择不同的方式。

关于使用 "page" 和 "pageSize" 方式的弊端:

  • 数据不稳定性:如果在请求期间有新数据插入或旧数据删除,可能会导致分页结果不稳定。例如,当获取第一页数据时,某些数据被删除后,下一页的数据可能会有重复或缺失。
  • 性能问题:当请求大量数据时,使用 "page" 和 "pageSize" 方式可能会导致性能问题。因为每次请求都需要返回整个页的数据,无论实际需要多少条数据。
  • 不适用于排序:如果需要对数据进行排序,使用 "page" 和 "pageSize" 方式可能会比较困难,因为在不同页之间排序可能会产生不一致的结果。

总的来说,使用 "offset" 和 "limit" 方式可能更适合大型数据集以及需要排序和稳定分页结果的情况。但在一些小型应用或简单场景下,使用 "page" 和 "pageSize" 方式也是常见的选择。

你是怎么做响应式布局的,如果PC端,IPad端,移动端css都有差别?怎么做最合理?

实现响应式布局的关键是使用媒体查询(Media Queries)和流动布局(Fluid Layout)。下面是一些常用的方法,可以帮助你在不同设备上实现最合理的响应式布局。

  1. 使用媒体查询(Media Queries):媒体查询是一种CSS3的功能,它允许你根据不同的设备特性(如屏幕宽度、高度、设备类型等)应用不同的样式。通过在 CSS 文件中使用媒体查询,你可以为不同的设备定义不同的样式规则。
/* PC端样式 */
@media screen and (min-width: 1024px) {
  /* PC端样式规则 */
}

/* iPad端样式 */
@media screen and (min-width: 768px) and (max-width: 1023px) {
  /* iPad端样式规则 */
}

/* 移动端样式 */
@media screen and (max-width: 767px) {
  /* 移动端样式规则 */
}

上述代码展示了针对不同设备的媒体查询示例。通过设置不同的屏幕宽度范围,可以应用特定的 CSS 样式规则。

  1. 使用相对单位:在响应式布局中,应该尽量避免使用固定像素单位(如px),而是使用相对单位(如百分比、em、rem)来定义宽度、高度和间距等。这样可以使元素根据其容器的大小自动调整。
  2. 使用流动布局:流动布局是一种基于相对宽度的布局方式,可以帮助页面中的元素自适应屏幕大小。使用百分比来设置元素的宽度,让它们根据父容器的宽度进行调整。
  3. 图片适应性:在响应式设计中,确保图片也能根据设备的大小进行适应。你可以使用CSS的max-width: 100%属性或background-size: cover属性来实现。
  4. 渐进增强与优雅降级:在设计响应式布局时,建议从移动端优先开始,然后渐进增加样式和功能,以适应大屏幕设备。这样可以确保较小的设备上的基本功能和内容可用,同时逐渐增加更复杂的样式和功能。

综上所述,实现最合理的响应式布局需要使用媒体查询、相对单位、流动布局等技术,并遵循渐进增强和优雅降级的原则。通过灵活运用这些技术,你可以根据不同设备的特点为用户提供更好的用户体验。

有哪几种做响应式的方式,分别有什么优势和弊端?

实现响应式布局的方式有很多种,下面列举了几种常用的方式,以及它们的优势和弊端:

  1. 媒体查询(Media Queries)

    • 优势:使用CSS的媒体查询功能可以根据设备的特性(如屏幕尺寸、分辨率等)应用不同的样式规则,适应不同设备的布局需求。
    • 弊端:需要手动编写和维护多套样式表或大量的媒体查询规则,增加了开发工作量和维护成本。
  2. CSS框架(如Bootstrap)

    • 优势:使用CSS框架可以快速地构建响应式布局,并提供了一系列的样式和组件,简化了开发流程。
    • 弊端:框架可能会包含一些不必要的样式和组件,导致页面加载时间增加。同时,对于特定的设计需求,可能需要覆盖或自定义框架的样式。
  3. Flexbox(弹性盒子布局)

    • 优势:Flexbox是一种强大的布局模型,可以轻松实现水平和垂直居中,灵活调整布局结构和顺序。
    • 弊端:在处理复杂布局时,有时候需要使用其他布局方式的辅助。
  4. CSS Grid(网格布局)

    • 优势:CSS Grid是一种二维网格布局,可以实现复杂的布局结构,支持自动调整和对齐。
    • 弊端:对于一些老版本的浏览器,可能不完全支持CSS Grid。
  5. JavaScript 框架(如React、Vue等)

    • 优势:使用JavaScript框架可以根据设备特性动态地加载和渲染组件,实现更灵活的响应式布局。
    • 弊端:需要学习和掌握框架的相关知识,对于一些简单项目来说可能会显得过于复杂。

每种方式都有其独特的优势和弊端,具体选择哪种方式取决于项目需求和开发团队的技术栈。通常,结合媒体查询和流动布局是最常见和灵活的方法,同时也可以考虑使用CSS框架来快速搭建基础布局。在选择方案时,要权衡利弊并根据项目实际情况做出决策。

前端做登录有几种方式?各有什么优缺点?

  1. 基于Cookie的登录方式:

    • 优点:简单易用,适用于简单的应用场景。
    • 缺点:不够安全,容易被攻击者进行Cookie劫持,存在安全风险。
  2. 基于Session的登录方式:

    • 优点:相对较安全,服务端保存会话信息,可有效防止恶意篡改。
    • 缺点:需要服务器维护会话信息,对服务器造成一定压力,不适合高并发场景。
  3. 基于Token的登录方式:

    • 优点:相对安全,无需在服务端保存会话信息,可支持分布式系统和多平台登录。
    • 缺点:需要前端额外存储Token,如果不采取额外保护措施,可能存在信息泄露风险(如XSS攻击)。
  4. 社交账号登录方式:

    • 优点:提供了用户使用第三方账号快速登录的便利性,减少了账号注册流程。
    • 缺点:需要与第三方服务商集成开发,增加了开发复杂度,同时也要考虑数据隐私和安全问题。

综上所述,不同的登录方式具有不同的优缺点,选择适合的方式应根据项目需求和安全性来决定。基于Token的登录方式相对灵活,适用于分布式系统和多平台登录;基于Session的方式相对稳定,适用于中小规模应用;而基于Cookie的登录方式则相对简单,适用于简单的应用场景。在实现登录功能时,还需要注意对用户输入进行安全验证和防止各种安全风险,以保护用户信息的安全。

基于Cookie的登录方式前端应该怎么做?

基于Cookie的登录方式需要在后端服务器设置一个cookie,前端在用户登录成功后,将cookie值写入到浏览器的cookie中,在之后的每个请求中都携带该cookie,从而实现登录状态的保持。以下是基于Cookie的登录方式前端的实现步骤:

  1. 用户登录成功后,后端返回一个包含用户信息的cookie,前端在保存cookie时需要考虑安全性,可以设置HttpOnly和Secure属性来防止XSS等攻击。
res.cookie('userInfo', JSON.stringify(userInfo), {
  maxAge: 2 * 60 * 60 * 1000, // cookie有效期为2小时
  httpOnly: true, // 防止XSS攻击
  secure: true // 只在HTTPS下传输
});
  1. 在后续的请求中,前端需要通过document.cookie读取保存的cookie值,并在请求头中携带cookie信息发送给后端。
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/userInfo', true);
xhr.setRequestHeader('Cookie', document.cookie);
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    const userInfo = JSON.parse(xhr.responseText);
    // 处理用户信息
  }
};
xhr.send();
  1. 在用户退出登录时,需要清除保存的cookie信息。
document.cookie = 'userInfo=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';

需要注意的是,基于Cookie的登录方式存在一定的安全风险,容易受到CSRF攻击和Cookie劫持等攻击,需要在后端设置防护机制来增强安全性。

基于Session的登录方式前端应该怎么做?

基于Session的登录方式在前端的实现相对简单,主要是通过与后端服务器进行交互来管理用户的会话状态。以下是基于Session的登录方式前端的实现步骤:

  1. 用户登录成功后,后端会在服务器端创建一个唯一的会话标识,通常会将该会话标识保存在Session对象中,并返回给前端。
// 后端代码示例(Node.js)
req.session.userId = userInfo.id;
  1. 在后续的请求中,前端需要在请求头中携带会话标识,以告知服务器当前请求属于哪个会话。
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/userInfo', true);
xhr.setRequestHeader('Authorization', req.session.userId);
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    const userInfo = JSON.parse(xhr.responseText);
    // 处理用户信息
  }
};
xhr.send();
  1. 在用户退出登录时,前端需要向服务器发送请求来清除会话状态。
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/logout', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    // 清除本地会话状态
  }
};
xhr.send();

需要注意的是,基于Session的登录方式需要后端服务器的支持,因为会话状态是由服务器来管理的。在实际应用中,还需要考虑会话超时时间、会话保持机制以及安全性等问题,例如使用HTTPS传输数据,设置会话过期时间等。

基于Token的登录方式?

基于Token的登录方式是目前比较常用的一种前后端分离的认证方式,前端通过发送请求获取Token并保存在本地,之后每次请求都在请求头中携带Token来进行身份认证。以下是基于Token的登录方式前端的实现步骤:

  1. 用户登录成功后,后端返回一个包含Token信息的JSON对象,前端将该Token信息保存在本地。
// 假设后端返回的JSON格式为{"token": "xxx"}
localStorage.setItem('token', JSON.stringify(tokenObj));

需要注意的是,Token应该采用随机生成的字符串,并设置过期时间和签名等安全措施,以防止被恶意获取或篡改。

  1. 在后续的请求中,前端需要在请求头中携带Token信息,以便后端进行身份认证。
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/userInfo', true);
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    const userInfo = JSON.parse(xhr.responseText);
    // 处理用户信息
  }
};
xhr.send();

需要注意的是,请求头中的Authorization字段一般采用Bearer加空格的形式,如Bearer xxx。

  1. 在用户退出登录时,前端需要清除保存的Token信息。
localStorage.removeItem('token');

需要注意的是,基于Token的登录方式需要后端服务器的支持,因为Token的生成和校验都是在后端完成的。同时,还需要考虑Token的安全性,例如使用HTTPS传输数据,设置Token过期时间等。

你了解前后端不分离的时候,后端怎么开发前端页面吗?

当前后端不分离的情况下,通常是由后端程序员负责开发前端页面。这种方式通常被称为“服务端渲染”,即后端通过模板引擎等技术将前端页面直接渲染到服务器上,并将渲染结果发送给浏览器进行展示。

具体实现步骤如下:

  1. 后端开发人员编写HTML、CSS和JavaScript等前端页面文件,并使用模板引擎等技术将动态数据注入其中。
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{{title}}</title>
  <link href="style.css" rel="stylesheet">
  <script src="script.js"></script>
</head>
<body>
  <h1>{{title}}</h1>
  <p>{{content}}</p>
</body>
</html>
  1. 当用户请求该页面时,后端程序读取数据库或其他数据源,将数据填充到页面模板中,并返回完整的HTML页面。
const http = require('http');
const fs = require('fs');
const template = require('art-template');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    const data = { title: 'Hello', content: 'World!' };
    const html = template(__dirname + '/index.html', data);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(html);
  }
});

server.listen(3000, () => {
  console.log('Server is running at http://localhost:3000/');
});

需要注意的是,在服务端渲染的情况下,前后端耦合度较高,开发效率可能会受到影响。而且由于每次请求都需要从服务器获取页面内容,因此渲染速度可能较慢,不利于用户体验。因此现在越来越多的应用采用前后端分离的方式进行开发。