北京360前端开发二面

3,410 阅读27分钟

前言

不知不觉已经在360公司开始实习了,入职第一天感觉还不错,公司福利也还不错,三餐免费,中午在公司吃的午饭很美味!感觉很nice!晚上没啥事情想着写写之前的360二面面试题给友友们分享一下。

正文

一、介绍一下你开发的项目的难点与亮点

这个就看各位友友们自己准备的是啥项目了,相信在准备秋招的友友们遇到面试官问这个问题都能框框展示一波了吧,我就不说啦。

二、大文件如何上传

这个问题我之前也被问到过很多次,后面专门写了一篇文章介绍大文件上传相关的问题,大家可以到我的这篇文章里去看看

大厂面试官:如何实现大文件上传前言 小编我这段时间也是在不断的面试,在面试中也是会遇到许多的问题,这不,要是面试官问你如 - 掘金 (juejin.cn)

三、切片传输的过程中前端有很多切片很多请求发送给后端怎么解决?

这个问题其实就是想问你如果前端一次性有很多请求发送给后端怎么办?如果一次性发送,势必造成后端服务器压力过大,因此。我们需要采取一种控制并发的手段限制前端每次允许发送的最大请求量。具体实现过程大家可以到我的这篇文章看看-

前端性能优化——控制并发前言 性能优化这一块在前端是一个比较重要并且常考的知识,小编最近在面试的过程中也是被面试官问到了 - 掘金 (juejin.cn)

四、如何实现的分页请求?

  1. 发送请求获取数据:从前端向后端发送请求,获取数据列表。
  2. 处理数据:解析后端返回的数据,并根据分页逻辑处理显示数据。
  3. 显示数据:将处理好的数据显示在前端界面上。
  4. 分页控件:提供用户交互的分页控件,如页码按钮、上一页/下一页按钮等。

分页的基本原理

分页的基本原理是将大数据集分成较小的、易于管理的部分。在前端,我们通常通过请求特定页码的数据来实现分页,而不是一次性加载所有数据。

实现步骤

1. 发送请求获取数据

前端通常会发送带有页码和每页条目数(通常是 pagepageSize 参数)的请求到后端,后端根据这些参数返回相应页的数据。

2. 处理数据

前端接收到数据后,需要处理数据以便显示在页面上。这可能包括排序、过滤等操作。

3. 显示数据

将处理好的数据显示在前端界面上。通常会有一个容器来展示列表项。

4. 分页控件

提供用户交互的分页控件,如页码按钮、上一页/下一页按钮等。这些控件通常会改变当前页码,并重新发送请求获取新的数据。

HTML 模板
<div id="app">
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>

  <button @click="prevPage">上一页</button>
  <button @click="nextPage">下一页</button>
  <span>当前页: {{ currentPage }}</span>
</div>
JavaScript 代码
new Vue({
  el: '#app',
  data: {
    currentPage: 1,
    pageSize: 10,
    totalItems: 0,
    items: []
  },
  methods: {
    fetchItems(page) {
      // 模拟异步请求
      this.$http.get('/api/items', {
        params: {
          page: page,
          pageSize: this.pageSize
        }
      }).then(response => {
        this.items = response.data.items;
        this.totalItems = response.data.totalItems;
      });
    },
    prevPage() {
      if (this.currentPage > 1) {
        this.currentPage--;
        this.fetchItems(this.currentPage);
      }
    },
    nextPage() {
      if (this.currentPage < Math.ceil(this.totalItems / this.pageSize)) {
        this.currentPage++;
        this.fetchItems(this.currentPage);
      }
    }
  },
  created() {
    this.fetchItems(this.currentPage);
  }
});
使用 Axios 发送请求

如果你使用 Axios 来发送 HTTP 请求,可以这样写:

import axios from 'axios';

new Vue({
  el: '#app',
  data: {
    currentPage: 1,
    pageSize: 10,
    totalItems: 0,
    items: []
  },
  methods: {
    fetchItems(page) {
      axios.get('/api/items', {
        params: {
          page: page,
          pageSize: this.pageSize
        }
      }).then(response => {
        this.items = response.data.items;
        this.totalItems = response.data.totalItems;
      });
    },
    prevPage() {
      if (this.currentPage > 1) {
        this.currentPage--;
        this.fetchItems(this.currentPage);
      }
    },
    nextPage() {
      if (this.currentPage < Math.ceil(this.totalItems / this.pageSize)) {
        this.currentPage++;
        this.fetchItems(this.currentPage);
      }
    }
  },
  created() {
    this.fetchItems(this.currentPage);
  }
});

五、如何模拟的异步请求?

在前端开发中,模拟异步请求是非常有用的,尤其是在没有后端服务或者后端服务尚未准备好的情况下。模拟异步请求可以帮助你测试前端代码的行为,确保前端逻辑正确无误。

下面是一些常用的工具和技术来模拟异步请求:

1. 使用 Fetch API 模拟请求

你可以使用 Fetch API 结合 Promise 来模拟异步请求。这种方式简单易用,适合快速原型开发。

// 模拟后端API
const mockApi = {
  getItems: function(page, pageSize) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // 模拟后端返回的数据
        const items = [
          { id: 1, name: 'Item 1' },
          { id: 2, name: 'Item 2' },
          { id: 3, name: 'Item 3' },
          { id: 4, name: 'Item 4' },
          { id: 5, name: 'Item 5' },
          { id: 6, name: 'Item 6' },
          { id: 7, name: 'Item 7' },
          { id: 8, name: 'Item 8' },
          { id: 9, name: 'Item 9' },
          { id: 10, name: 'Item 10' }
        ];

        // 模拟分页逻辑
        const startIndex = (page - 1) * pageSize;
        const endIndex = page * pageSize;
        const data = {
          items: items.slice(startIndex, endIndex),
          totalItems: items.length
        };

        resolve(data);
      }, 1000); // 模拟网络延迟
    });
  }
};

// 使用模拟API
fetch('/api/items', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ page: 1, pageSize: 10 })
})
.then(response => response.json())
.then(data => {
  console.log(data);
})
.catch(error => {
  console.error('Error:', error);
});

// 或者直接使用模拟API
mockApi.getItems(1, 10).then(data => {
  console.log(data);
}).catch(error => {
  console.error('Error:', error);
});

2. 使用 Mocking Libraries

有许多库可以帮助你模拟异步请求,例如 axios-mock-adaptermiragejs 等。

示例:使用 axios-mock-adapter

首先,安装所需的库:

npm install axios axios-mock-adapter
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

// 创建模拟适配器
const mock = new MockAdapter(axios);

// 模拟 GET 请求
mock.onGet('/api/items').reply(config => {
  // 模拟后端返回的数据
  const items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
    { id: 4, name: 'Item 4' },
    { id: 5, name: 'Item 5' },
    { id: 6, name: 'Item 6' },
    { id: 7, name: 'Item 7' },
    { id: 8, name: 'Item 8' },
    { id: 9, name: 'Item 9' },
    { id: 10, name: 'Item 10' }
  ];

  // 模拟分页逻辑
  const { page, pageSize } = config.params;
  const startIndex = (page - 1) * pageSize;
  const endIndex = page * pageSize;
  const data = {
    items: items.slice(startIndex, endIndex),
    totalItems: items.length
  };

  return [200, data];
});

// 使用axios发送请求
axios.get('/api/items', {
  params: {
    page: 1,
    pageSize: 10
  }
})
.then(response => {
  console.log(response.data);
})
.catch(error => {
  console.error('Error:', error);
});

3. 使用 JSON Server

JSON Server 是一个简单的 JSON API 服务器,可以用来模拟后端服务。它基于一个 JSON 文件来提供数据。

示例:使用 JSON Server

首先,安装 JSON Server:

npm install -g json-server

然后,创建一个 db.json 文件:

{
  "items": [
    { "id": 1, "name": "Item 1" },
    { "id": 2, "name": "Item 2" },
    { "id": 3, "name": "Item 3" },
    { "id": 4, "name": "Item 4" },
    { "id": 5, "name": "Item 5" },
    { "id": 6, "name": "Item 6" },
    { "id": 7, "name": "Item 7" },
    { "id": 8, "name": "Item 8" },
    { "id": 9, "name": "Item 9" },
    { "id": 10, "name": "Item 10" }
  ]
}

启动 JSON Server:

json-server --watch db.json

现在你可以通过访问 http://localhost:3000/items 来获取数据。

4. 使用 MirageJS

MirageJS 是一个轻量级的 HTTP 请求模拟库,非常适合用于模拟后端 API。

示例:使用 MirageJS

首先,安装 MirageJS:

npm install miragejs

然后,创建模拟代码:

import { createServer } from 'miragejs';

createServer({
  routes() {
    this.get('/api/items', (schema, request) => {
      // 模拟后端返回的数据
      const items = [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' },
        { id: 4, name: 'Item 4' },
        { id: 5, name: 'Item 5' },
        { id: 6, name: 'Item 6' },
        { id: 7, name: 'Item 7' },
        { id: 8, name: 'Item 8' },
        { id: 9, name: 'Item 9' },
        { id: 10, name: 'Item 10' }
      ];

      // 模拟分页逻辑
      const { page, pageSize } = request.queryParams;
      const startIndex = (page - 1) * pageSize;
      const endIndex = page * pageSize;
      const data = {
        items: items.slice(startIndex, endIndex),
        totalItems: items.length
      };

      return data;
    });
  }
});

启动 MirageJS:

node mirage/server.js

现在你可以通过访问 http://localhost:5000/api/items?page=1&pageSize=10 来获取数据。

六、懒加载是如何实现的

我是自定义v-lazy实现图片的懒加载

在 Vue.js 中,定义一个全局指令 v-lazy 来实现图片懒加载功能。这个指令将使用 Intersection Observer API 来监听图片元素与视口的交叉比例,并在图片进入视口时动态加载图片。

步骤 1: 定义全局指令

  1. 创建一个名为 lazy-load.directive.js 的文件。
  2. 在此文件中定义一个全局指令 v-lazy

lazy-load.directive.js 文件中,我们定义了一个 Vue 指令,该指令在 mounted 生命周期钩子中初始化。我们创建了一个 IntersectionObserver 实例来观察图片元素,并在图片进入视口时替换 src 属性。

// lazy-load.directive.js

import { onMounted, onUnmounted, ref } from 'vue';

export default {
  mounted(el, binding) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          el.src = entry.target.getAttribute('data-src');
          observer.disconnect();
        }
      });
    });

    observer.observe(el);
  },
};

步骤 2: 注册全局指令

  1. 在你的 main.jsmain.ts 文件中导入并注册全局指令。
// main.js

import lazyLoadDirective from './directives/lazy-load.directive.js';

Vue.directive('lazy', lazyLoadDirective);

new Vue({
  render: h => h(App),
}).$mount('#app');

main.js 文件中,我们导入了定义好的指令,并使用 Vue.directive() 方法将其注册为全局指令。

步骤 3: 使用全局指令

  1. 在你的 Vue 组件模板中使用 v-lazy 指令。
<!-- Your component template -->
<template>
  <div>
    <img v-lazy="imageSrc" data-src="your-image-url.jpg" alt="Your Image" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      imageSrc: '',
    };
  },
};
</script>

在你的 Vue 组件模板中,你可以使用 v-lazy 指令,并通过 data-src 属性指定图片的实际 URL。v-lazy 指令绑定的值在这里可以为空,因为实际的图片 URL 存储在 data-src 属性中。

代码详解

  1. 导入 Vue.js 的实用函数:
    • onMounted: 一个生命周期钩子,在组件挂载完成后调用。
    • onUnmounted: 一个生命周期钩子,在组件卸载前调用。
    • ref: 用于创建响应式的引用。
  2. 定义指令:
    • export default: 导出自定义指令。
    • { mounted }: 定义指令的 mounted 生命周期钩子。
  3. 初始化 IntersectionObserver:
    • 创建一个新的 IntersectionObserver 实例,传入一个回调函数。
    • 回调函数接收一个 entries 数组,其中包含所有被观察的节点及其与视口的交集信息。
  4. 处理交集变化:
    • 遍历 entries 数组,检查每个条目的 isIntersecting 属性。
    • 如果某个条目的 isIntersectingtrue,表示该元素已经进入了视口。
    • 从该元素的 data-src 属性中获取实际的图片 URL,并赋值给 el.src
    • 调用 observer.disconnect() 来停止对该元素的观察,防止多次加载图片。
  5. 开始观察:
    • 使用 observer.observe(el) 开始观察当前指令绑定的元素。

七、前端发送重复请求如何解决?

前端发送重复请求是一个常见的问题,特别是在用户快速点击按钮或其他交互元素时容易发生。重复请求可能导致不必要的服务器负载,甚至可能导致数据一致性问题。以下是一些解决前端重复请求的方法:

1. 防抖(Debounce)

防抖技术可以用来避免短时间内多次触发同一事件。防抖函数会在最后一次触发后的一段时间内没有新的触发时才执行。

function debounce(func, wait) {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      func.apply(context, args);
    }, wait);
  };
}

// 使用示例
document.getElementById('submit-button').addEventListener('click', debounce(function() {
  // 发送请求的逻辑
  sendRequest();
}, 500));

2. 节流(Throttle)

节流技术可以限制一个函数在一定时间内只执行一次。这可以用来限制请求发送的频率。

function throttle(func, limit) {
  let inThrottle;
  return function() {
    const args = arguments;
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 使用示例
document.getElementById('submit-button').addEventListener('click', throttle(function() {
  // 发送请求的逻辑
  sendRequest();
}, 1000));

3. 禁用按钮

在用户发起请求时禁用按钮,直到请求完成后再启用按钮。这种方法简单有效,但用户体验可能稍差一些。

<button id="submit-button" disabled>提交</button>
document.getElementById('submit-button').addEventListener('click', function() {
  this.disabled = true; // 禁用按钮
  sendRequest().finally(() => {
    this.disabled = false; // 启用按钮
  });
});

4. 使用 Loading 状态

在发起请求时显示加载状态,请求完成后隐藏加载状态。这种方法可以让用户知道请求正在进行中。

<button id="submit-button">提交</button>
<div id="loading" style="display:none;">加载中...</div>
document.getElementById('submit-button').addEventListener('click', function() {
  document.getElementById('loading').style.display = 'block'; // 显示加载状态
  sendRequest().finally(() => {
    document.getElementById('loading').style.display = 'none'; // 隐藏加载状态
  });
});

5. 请求取消(AbortController)

使用 AbortController 可以取消未完成的请求。这种方法适用于需要长时间监听用户交互的情况,例如长轮询(long polling)。

const controller = new AbortController();
const signal = controller.signal;

document.getElementById('submit-button').addEventListener('click', function() {
  controller.abort(); // 取消之前的请求
  controller = new AbortController(); // 创建新的控制器
  const signal = controller.signal;
  sendRequest(signal).finally(() => {
    // 请求完成
  });
});

6. 唯一标识符

为每次请求生成一个唯一的标识符,如果用户在请求未完成前再次点击按钮,则忽略新的请求。

let requestId = null;

document.getElementById('submit-button').addEventListener('click', function() {
  const newRequestId = Date.now();
  if (requestId !== null && requestId >= newRequestId) {
    return; // 忽略重复请求
  }
  requestId = newRequestId;
  sendRequest().finally(() => {
    requestId = null; // 清除标识符
  });
});

八、了解promise吗

JavaScript 中的 Promise 是一种处理异步操作的模式,它提供了比传统的回调函数更优雅的方式来处理异步流程。Promise 设计的目的是解决回调地狱(Callback Hell)的问题,并提供更强大的错误处理能力。

Promise 的基本概念

一个 Promise 对象代表了一个最终可能会完成或失败的操作,并且其结果(成功值或失败原因)是未知的。Promise 对象有三种状态:

  1. Pending(待定):初始状态,既不是成功也不是失败。
  2. Fulfilled(已成功):表示操作已完成,并且有了一个结果。
  3. Rejected(已失败):表示操作已失败,并且有了一个失败的原因。

一旦一个 Promise 的状态从 Pending 变为 Fulfilled 或 Rejected,状态就不会再改变。这被称为 Promise 的不可变性。

创建 Promise

创建一个 Promise 对象通常需要提供一个构造函数,该构造函数接收一个执行器(executor)函数作为参数。执行器函数会在 Promise 创建时立即执行,并且有两个参数:resolvereject

const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    try {
      // 成功时调用 resolve
      resolve('Operation succeeded');
    } catch (error) {
      // 失败时调用 reject
      reject('Operation failed');
    }
  }, 1000);
});

使用 Promise

使用 Promise 主要有两种方式:.then().catch() 方法。

.then()

.then() 方法接受两个回调函数作为参数,第一个是成功回调(resolve),第二个是失败回调(reject)。如果只提供一个回调函数,则默认为成功回调。

promise
  .then(result => {
    console.log('Success:', result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
.catch()

.catch() 方法用于处理失败情况,它接收一个回调函数作为参数,该函数会在 Promise 被拒绝(rejected)时被调用。

promise
  .then(result => {
    console.log('Success:', result);
  })
  .catch(error => {
    console.error('Error:', error);
  });

链式调用

Promise 支持链式调用,即在一个 .then().catch() 方法之后可以继续调用另一个 .then().catch() 方法。

promise
  .then(result => {
    console.log('First success:', result);
    return result.toUpperCase();
  })
  .then(modifiedResult => {
    console.log('Second success:', modifiedResult);
  })
  .catch(error => {
    console.error('Error:', error);
  });

静态方法

Promise 还提供了一些静态方法,如 Promise.all()Promise.race()Promise.resolve() 等。

Promise.all()

Promise.all() 接收一个 Promise 数组作为参数,当所有的 Promise 都成功(resolved)时返回一个包含所有成功结果的数组。如果有任何一个 Promise 失败(rejected),则立即返回一个失败的 Promise。

const promises = [
  new Promise(resolve => setTimeout(() => resolve(1), 100)),
  new Promise(resolve => setTimeout(() => resolve(2), 200)),
  new Promise(resolve => setTimeout(() => resolve(3), 300))
];

Promise.all(promises)
  .then(results => {
    console.log('All results:', results);
  })
  .catch(error => {
    console.error('Error:', error);
  });
Promise.race()

Promise.race() 接收一个 Promise 数组作为参数,当第一个 Promise 完成(无论成功还是失败)时返回该 Promise 的结果。

const promises = [
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 100)),
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 200)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failed')), 300))
];

Promise.race(promises)
  .then(result => {
    console.log('First result:', result);
  })
  .catch(error => {
    console.error('First error:', error);
  });
Promise.resolve()

Promise.resolve() 可以将一个值直接转换为一个已成功的 Promise。

const immediateSuccess = Promise.resolve('Immediate success');

immediateSuccess.then(result => {
  console.log('Result:', result);
});
Promise.reject()

Promise.reject() 可以将一个值直接转换为一个已失败的 Promise。

const immediateFailure = Promise.reject(new Error('Immediate failure'));

immediateFailure.catch(error => {
  console.error('Error:', error);
});

总结

Promise 是 JavaScript 中处理异步操作的强大工具,它提供了比传统回调函数更优雅的解决方案。通过使用 Promise,你可以更好地管理异步流程,避免回调地狱,并提供统一的错误处理机制。

九、http发送请求的方式

http请求的八大方式

HTTP 的 八大请求方式:GET、 POST、 HEAD、OPTIONS、 PUT、 DELETE、 TRACE、 CONNECT。

一、GET

GET 动作:用于获取资源,当采用 GET 方式请求指定资源时, 被访问的资源经服务器解析后立即返回响应内容。

通常以 GET 方式请求特定资源时, 请求中不应该包含请求体,所有需要向被请求资源传递的数据都应该通过 URL 向服务器传递。

二、POST

POST 动作:用于提交数据, 当采用 POST 方式向指定位置提交数据时,数据被包含在请求体中,服务器接收到这些数据后可能会建立新的资源、也可能会更新已有的资源。

同时 POST 方式的请求体可以包含非常多的数据,而且格式不限。因此 POST 方式用途较为广泛,几乎所有的提交操作都可以使用 POST 方式来完成。

虽然用 GET 方式也可以提交数据,但一般不用 GET 方式而是用 POST 方式。在 HTTP协议中,建议 GET 方式只用来获取数据,而 POST 方式则用来提交数据(而不是获取数据)。

★ get方式和post方式有何区别

简单来说,本质上区别:

- GET产生 一个 TCP数据包
- POST产生 两个 TCP数据包

也就是说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

详细区别: 数据携带上:

GET方式:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。 POST方式:可以在请求的实体内容中向服务器发送数据,传送的数据量无限制。 请求参数的位置上:

GET方式:请求参数放在URL地址后面,以 ? 的方式来进行拼接 POST方式:请求参数放在HTTP请求包中 用途上:

GET方式一般用来获取数据

POST方式一般用来提交数据

首先是因为GET方式携带的数据量比较小,无法带过去很大的数量

POST方式提交的参数后台更加容易解析(使用POST方式提交的中文数据,后台也更加容易解决)

GET方式比POST方式要快 GET方式比POST方式要快

更快的原因:

1.post请求包含更多的请求头 因为post需要在请求的body部分包含数据,所以会多了几个数据描述部分的首部字段(如content-type),这其实是微乎其微的。

2.post在真正接受数据之前会先将请求头发送给服务器进行确认,然后才真正发送数据

post请求的过程:

[1].浏览器请求tcp连接(第一次握手)

[2].服务器答应进行tcp连接(第二次握手)

[3].浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)

[4].服务器返回100 continue响应

[5].浏览器开始发送数据

[6].服务器返回200 ok响应

get请求的过程:

[1] .浏览器请求tcp连接(第一次握手)

[2].服务器答应进行tcp连接(第二次握手)

[3].浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)

[4].服务器返回200 ok响应

也就是说,目测get的总耗是post的2/3左右

3、get会将数据缓存起来,而post不会

4、post不能进行管道化传输

所以,在可以使用get请求通信的时候,不要使用post请求,这样用户体验会更好,当然,如果有安全性要求的话,post会更好。

三、PUT

PUT 动作:用于向指定位置提交数据, 当采用 PUT 方式向指定位置提交数据时, 数据被包含在请求体中, 服务器接收到这些数据后直接在当前位置(即提交数据时指定的位置) 创建新的资源。

PUT 方式和 POST 方式极为相似,都可以向服务器提交数据,

PUT 方式通常指定了资源的存放位置(即提交数据时指定的位置)

POST 方式所提交的数据由服务器决定存放位置(可能是新增数据,也可能是更新数据)。

在 HTTP 规范中,建议 PUT 方式只用来创建新的资源。

四、DELETE

DELETE 动作:用于删除特定位置的资源。

采用 DELETE 方式访问特定位置的资源时, 服务器接受到请求后会删除当前位置对应的资源。

五、HEAD

HEAD 动作:用于获取响应头,采用 HEAD 方式请求指定资源时,被访问的资源经服务器解析后立即返回响应,但返回的响应中仅包含状态行和响应头,不包含响应体。

HEAD 动作通常 用于完成测试 URI 的有效性、 获取资源更新时间等操作。

六、TRACE

TRACE 动作:用于回显服务器收到的请求,主要用于测试或诊断。

七、OPTIONS

OPTIONS 动作:用于查询服务器针对特定资源所支持的 HTTP 请求方式,即询问客户端可以以那些方式来请求相应的资源, 同时使用 OPTIONS 方式也可以用来测试服务器的性能。

八、CONNECT

CONNECT 动作:要求在与代理服务器通信时建立隧道, 实现用隧道协议进行 TCP 通信。主要使用 SSL(安全套接层)和 TLS(传输层安全) 协议把通信内容加密后经网络隧道传输。

十、css的布局有哪些?

CSS(层叠样式表)提供了多种布局模型,用于控制网页元素的位置和外观。不同的布局模型适用于不同的场景,可以根据实际需求选择合适的布局方式。以下是常用的几种 CSS 布局模型:

1. 流式布局(Flow Layout)

流式布局是最基本的布局方式,元素按照文档流(Document Flow)自然排列。块级元素垂直堆叠,行内元素水平排列。

p {
  margin-bottom: 1em;
}
<p>段落 1</p>
<p>段落 2</p>

2. 盒模型(Box Model)

盒模型定义了元素的尺寸和位置,包括宽度、高度、内边距(padding)、边框(border)和外边距(margin)。

.box {
  width: 100px;
  height: 100px;
  padding: 10px;
  border: 1px solid black;
  margin: 20px;
}
<div class="box"></div>

3. 浮动布局(Floating Layout)

浮动布局允许元素脱离文档流,浮动到父容器的左侧或右侧。

示例
.box {
  float: left;
  width: 50%;
  background-color: lightblue;
}
<div class="box">浮动元素 1</div>
<div class="box">浮动元素 2</div>

4. 绝对定位(Absolute Positioning)

绝对定位允许元素相对于最近的已定位祖先元素(position: relative 或 position: absolute)进行定位。

示例
.container {
  position: relative;
}

.box {
  position: absolute;
  top: 10px;
  left: 20px;
  width: 100px;
  height: 100px;
  background-color: lightblue;
}
<div class="container">
  <div class="box"></div>
</div>

5. 相对定位(Relative Positioning)

相对定位允许元素相对于其正常位置进行偏移。

示例
.box {
  position: relative;
  top: 10px;
  left: 20px;
  width: 100px;
  height: 100px;
  background-color: lightblue;
}
<div class="box"></div>

6. 网格布局(Grid Layout)

网格布局(CSS Grid)是一种二维布局模型,可以灵活地控制元素的位置和大小。网格布局非常适合构建复杂的页面布局。

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.box {
  background-color: lightblue;
  padding: 20px;
}
<div class="container">
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>
  <div class="box">6</div>
</div>

7. 弹性盒子布局(Flexbox Layout)

弹性盒子布局(Flexbox)是一种一维布局模型,可以轻松实现居中对齐、换行和方向控制等功能。弹性盒子非常适合构建响应式布局。

示例
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
}

.box {
  background-color: lightblue;
  padding: 20px;
}
<div class="container">
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
</div>

8. 多列布局(Multi-column Layout)

多列布局允许元素在多列中进行排版,适用于新闻网站等多列文本布局。

示例
.container {
  column-count: 3;
  column-gap: 20px;
}

.box {
  background-color: lightblue;
  padding: 20px;
}
<div class="container">
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>
  <div class="box">6</div>
</div>

十一、flex布局中给元素设置宽度有效吗

在弹性布局(Flexbox)中,给元素设置宽度通常是有效的,但是其具体表现会受到多个因素的影响。这些因素包括元素的 flex 属性、容器的 justify-content 属性以及其他相关的布局属性。下面详细解释这些因素如何影响元素宽度的表现:

1. flex 属性的影响

flex 属性决定了元素如何扩展或收缩以适应容器的空间。它由三个子属性组成:

  • flex-grow:元素扩展的比例。
  • flex-shrink:元素收缩的比例。
  • flex-basis:元素的基础大小(宽度或高度)。

默认情况下,flex 的值为 0 1 auto,这意味着元素不会扩展,但会根据内容自动调整大小。

示例:默认情况下设置宽度
.container {
  display: flex;
}

.item {
  width: 100px; /* 设置固定宽度 */
  background-color: lightblue;
}
<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
</div>

在这个例子中,.item 元素将保持 100px 的宽度。

示例:设置 flex-growflex-shrink
.container {
  display: flex;
}

.item {
  flex-grow: 1;
  flex-shrink: 1;
  width: 100px; /* 设置固定宽度 */
  background-color: lightblue;
}
<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
</div>

在这个例子中,.item 元素会根据容器的可用空间进行扩展或收缩,而不仅仅是保持 100px 的宽度。

2. justify-content 属性的影响

justify-content 属性决定了容器内的元素如何沿主轴(main axis)对齐。常见的值包括:

  • flex-start:默认值,元素靠左对齐。
  • center:元素居中对齐。
  • space-between:元素之间平均分布空间。
  • space-around:元素周围平均分布空间。
  • space-evenly:元素之间和周围平均分布空间。
.container {
  display: flex;
  justify-content: space-between;
}

.item {
  width: 100px; /* 设置固定宽度 */
  background-color: lightblue;
}
<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
</div>

3. 其他相关属性

除了 flexjustify-content 属性之外,还有一些其他的属性会影响元素的宽度表现,例如:

  • align-items:控制元素沿交叉轴(cross axis)的对齐方式。
  • align-self:允许单个元素覆盖容器的 align-items 值。
  • order:控制元素的顺序。

在弹性布局中,给元素设置宽度通常是有效的,但其具体表现会受到 flex 属性和其他相关布局属性的影响。如果你需要精确控制元素的宽度,可以结合使用 flex-basis 和其他属性来达到预期效果。

十二、如何实现一个两栏布局,左边宽度固定,右边自适应

使用 Flexbox 实现两栏布局

Flexbox 是一个非常强大且灵活的布局模型,非常适合用来实现这种类型的布局。以下是使用 Flexbox 实现这种布局的具体步骤:

  1. 创建容器:使用 display: flex 将父容器设置为 Flex 容器。
  2. 设置左边栏:设置固定的宽度。
  3. 设置右边栏:使用 flex-grow 属性让右边栏占据剩余空间。
<div class="container">
  <div class="left">左边栏</div>
  <div class="right">右边栏</div>
</div>
.container {
  display: flex; /* 设置为 Flex 容器 */
}

.left {
  width: 200px; /* 固定宽度 */
  background-color: #f0f0f0; /* 可选背景颜色 */
}

.right {
  flex-grow: 1; /* 占据剩余空间 */
  background-color: #e0e0e0; /* 可选背景颜色 */
}

十三、vue3的响应式原理

实现原理: 通过Proxy(代理): 拦截对象中任意属性的变化,包括:属性值的读写,属性的增加,属性的删除等。

通过Reffect(反射): 对源对象的属性进行操作

【Vue3中的响应式原理】_vue3响应式原理-CSDN博客

十四、有哪些方式可以实现移动端适配?

响应式布局的几种方式 一、百分比布局 二、Rem和em布局 三、@media screen布局 (css3) 四、vw和vh布局 五、Flex布局 1、什么是flex布局 2、基本概念 3、容器的属性 (1)flex- direction属性 (2)flex-wrap属性 (3)flex-flow (4)justify-content (5)align-items属性 (6)align-content属性 4、项目的属性 (1)order属性 (2)flex-grow属性 (3)flex-shrink属性 (4)flex-basis属性 (5)flex属性 (6)align-self属性

十五、css的单位有哪些

1、px:相对长度单位。像素px是相对于显示器屏幕分辨率而言的。

2、em:相对长度单位。基准点为父节点字体的大小,即相对于当前对象内文本的字体尺寸(继承父节点字体大小)。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸( 16px )。

3、rem:相对长度单位。r’是“root”的缩写,相对于根元素的字体大小。

4、vh and vw:相对于视口的高度和宽度,而不是父元素的(CSS百分比是相对于包含它的最近的父元素的高度和宽度)。

5、vmin and vmax:关于视口高度和宽度两者的最小值或者最大值。

6、pt 设备像素(物理像素)

7、dpr = 设备像素 / 设备独立像素

8、ppi 每英寸像素,值越大,图像越清晰

十六、es6了解吗

ES6 (ES2015)和ES5是JavaScript的两个不同版本。

主要区别如下:

  1. 新的语法特性:ES6添加了let和const关键字、箭头函数、模板字面量、解构赋值、默认参数、rest参数、扩展操作符和类等。

  2. 新的数据类型:ES6添加了Symbol类型,用于创建独一无二的值。

  3. 模块化:ES6引入了模块化,使得JavaScript代码更加易于维护和重用。

  4. Promise:ES6添加了Promise API,更好地处理异步操作。

  5. 变量作用域:ES6中引入了块级作用域,let和const关键字只在其声明的块级作用域内有效。

  6. 箭头函数:ES6中的箭头函数可以更简洁地定义函数,同时不会改变this的指向。

  7. 类:ES6中引入了类,让面向对象编程更加易于理解和实现。

总之,ES6是ES5的一个重要升级版本,添加了许多新的特性和功能,使得JavaScript编程更加容易,代码更加模块化、清晰和可读性强。

十七、强缓存与协商缓存

一、缓存介绍 1、什么是缓存? 缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。

2、为什么需要缓存? 如果没有缓存的话,每次网络请求都要加载大量的图片和资源,这会使页面的加载变慢许多。 缓存的目的就是为了尽量减少网络请求的体积和数量,让页面加载的更快,节省带宽,提高访问速度,降低服务器压力。

3、哪些资源可以被缓存?——静态资源(css、js、img) 网站的 html 是不能被缓存的。因为网站在使用过程中 html 随时有可能被更新,随时有可能被替换模板。 网页的业务数据也是不能被缓存的。比如留言板和评论区,用户随时都可以在底下评论,那数据库的内容就会被频繁被更新。

二、强制缓存 1、定义: 强缓存:浏览器不会向服务器发送任何请求,直接从本地缓存中读取文件并返回。

三、协商缓存

1、定义

协商缓存是一种服务端缓存策略,即通过服务端来判断某件事情是不是可以被缓存。 服务端判断客户端的资源,是否和服务端资源一样,如果一致则返回304,反之返回200和最新的资源。

十八、算法题

一、如何将一个包含对象的数组转化为树状结构?

将一个数组转换成一棵树结构(通常是对象树)是一种常见的数据结构转换操作,常用于构建目录结构、组织数据等场景。下面是一个示例,展示如何将一个包含对象的数组转换成一棵树形结构。

假设我们有一个数组,其中每个对象都有一个 id 和一个 parentIdparentId 表示该对象的父节点的 id。如果 parentIdnullundefined,则表示该对象是树的根节点。

[
  { "id": 1, "parentId": null, "name": "Root" },
  { "id": 2, "parentId": 1, "name": "Child 1" },
  { "id": 3, "parentId": 2, "name": "Grandchild 1" },
  { "id": 4, "parentId": 1, "name": "Child 2" },
  { "id": 5, "parentId": 4, "name": "Grandchild 2" },
  { "id": 6, "parentId": 5, "name": "Great-grandchild 1" }
]
转换逻辑

我们将使用以下步骤来构建树形结构:

  1. 创建一个哈希表(Map),用于快速查找每个节点。
  2. 遍历数组,为每个节点创建一个子节点数组。
  3. 根据 parentId 关系,将每个节点添加到其父节点的子节点数组中。
  4. 查找根节点(parentIdnullundefined 的节点)。
function arrayToTree(array) {
  const nodeMap = new Map(); // 创建一个哈希表来存储每个节点
  const tree = [];

  // 遍历数组,创建节点的哈希表
  array.forEach(node => {
    node.children = []; // 初始化子节点数组
    nodeMap.set(node.id, node); // 存储节点
    if (!node.parentId) {
      tree.push(node); // 如果没有父节点,将其作为根节点添加到树中
    }
  });

  // 遍历数组,根据 parentId 将节点添加到其父节点的 children 数组中
  array.forEach(node => {
    if (node.parentId) {
      const parentNode = nodeMap.get(node.parentId);
      if (parentNode) {
        parentNode.children.push(node);
      }
    }
  });

  return tree; // 返回根节点组成的数组
}

// 示例数据
const data = [
  { id: 1, parentId: null, name: "Root" },
  { id: 2, parentId: 1, name: "Child 1" },
  { id: 3, parentId: 2, name: "Grandchild 1" },
  { id: 4, parentId: 1, name: "Child 2" },
  { id: 5, parentId: 4, name: "Grandchild 2" },
  { id: 6, parentId: 5, name: "Great-grandchild 1" }
];

// 转换数组为树
const tree = arrayToTree(data);

// 输出树
console.log(tree);

转换后的树形结构如下:

[
  {
    "id": 1,
    "parentId": null,
    "name": "Root",
    "children": [
      {
        "id": 2,
        "parentId": 1,
        "name": "Child 1",
        "children": [
          {
            "id": 3,
            "parentId": 2,
            "name": "Grandchild 1",
            "children": []
          }
        ]
      },
      {
        "id": 4,
        "parentId": 1,
        "name": "Child 2",
        "children": [
          {
            "id": 5,
            "parentId": 4,
            "name": "Grandchild 2",
            "children": [
              {
                "id": 6,
                "parentId": 5,
                "name": "Great-grandchild 1",
                "children": []
              }
            ]
          }
        ]
      }
    ]
  }
]

通过这种方式,我们可以将一个扁平化的数组转换成一个树形结构。这种方法适用于具有明确层次关系的数据集,并且可以根据具体需求进行扩展或修改。

二、括号匹配问题

function isValid(s) {
  // 定义一个栈来存储左括号
  const stack = [];
  // 定义一个对象,用于映射右括号和对应的左括号
  const bracketsMap = {
      '}': '{',
      ']': '[',
      ')': '('
  };

  for (let char of s) {
      // 如果是左括号,将其入栈
      if (char === '{' || char === '[' || char === '(') {
          stack.push(char);
      } else {
          // 如果是右括号
          // 检查栈是否为空,如果为空则说明没有对应的左括号,返回 false
          if (stack.length === 0) {
              return false;
          }
          // 取出栈顶元素
          const top = stack.pop();
          // 如果栈顶元素不是当前右括号对应的左括号,返回 false
          if (top!== bracketsMap[char]) {
              return false;
          }
      }
  }
  // 遍历完字符串后,如果栈为空,说明所有括号都匹配,返回 true,否则返回 false
  return stack.length === 0;
}
console.log(isValid("()")); // 应该输出 true
console.log(isValid("()[]{}")); // 应该输出 true
console.log(isValid("(]")); // 应该输出 false
console.log(isValid("([)]")); // 应该输出 false
console.log(isValid("{[]}")); // 应该输出 true

十九、了解AI吗,你觉得像chatgpt这种生成式AI内容逐字返回的效果是怎么实现的?为什么不是一整段文字直接返回?

要实现 AI 内容逐字返回的效果,可以通过以下几种方式:

一、前端设计

在前端界面开发中,可以使用特定的脚本语言(如 JavaScript)来控制显示效果。

  1. 设置定时器:通过设置定时器,每隔一段时间(比如几百毫秒)从服务器获取一个字或者一小部分内容,并将其添加到显示区域,从而营造出逐字出现的效果。
  2. 动画效果:利用 CSS 动画或者 JavaScript 动画库,为逐字显示添加一些视觉效果,如淡入、滑动等,增强用户体验。

二、后端处理

在服务器端进行一定的控制也可以实现这种效果。

  1. 分批次返回:服务器可以将生成的内容分成多个小部分,依次返回给前端。可以根据时间间隔或者内容长度进行划分。例如,先返回前几个字,然后等待一段时间再返回接下来的几个字。
  2. 流式传输:采用流式处理技术,将生成的内容以流的形式发送给前端,前端不断接收并显示这些流中的数据,就像视频的流式播放一样。

三、技术架构考虑

  1. 缓存机制:为了提高响应速度,可以使用缓存来存储已经生成的部分内容。这样,当用户再次请求相同或类似的问题时,可以更快地返回已经缓存的内容,减少逐字显示的延迟。
  2. 负载均衡:如果有大量用户同时请求,需要使用负载均衡技术将请求分配到多个服务器上,以确保系统的稳定性和响应速度。避免因为负载过高而导致内容返回缓慢,影响逐字显示效果。

总结

二面问到的问题全部在这了,我觉的考查的还是很细致的,考查了很多问题包括基础和项目和八股和算法,以及现在的很火的AI都问到了。所以小伙伴们还是需要好好准备的呢!