2024最新前端面试真题-超详细答题思路解析(持续更新ing,记得收藏)

220 阅读32分钟

为了更加高效的准备面试,所以面试题都来源于牛客网真题,让面试题更符合实际。

1 cookie,sessionStorage,localStorage的区别

(1)考察点分析

  • 存储机制:了解每种存储方式的实现原理和数据存储方式。
  • 生命周期:掌握每种存储方式的数据存储时长和生命周期管理。
  • 作用域:理解每种存储方式的数据访问范围和作用域限制。

(2)最终答案

  1. Cookie

    • 存储机制:以文本形式存储在客户端浏览器中,通过 HTTP 头部在客户端和服务器之间传递。
    • 生命周期:可以设置过期时间,在有效期内持久保存,过期后会自动删除。
    • 作用域:可以设置路径和域名限制访问,同源策略下会自动发送到服务器端。
  2. sessionStorage

    • 存储机制:HTML5 提供的会话存储机制,仅在当前会话(浏览器标签页)下有效。
    • 生命周期:数据在页面关闭后自动清除,不同页面或标签页间数据不共享。
    • 作用域:每个页面拥有独立的 sessionStorage,不同页面之间无法共享数据。
  3. localStorage

    • 存储机制:HTML5 提供的持久化本地存储机制,数据永久保存在客户端浏览器上。
    • 生命周期:除非被显式清除,否则数据永久保存,即使浏览器关闭也不会被清除。
    • 作用域:每个域名拥有独立的 localStorage,不同页面和标签页间可以共享数据。

2 position 有哪些属性值?

(1)考察点分析

  • 基础知识:考察候选人是否理解CSS中的position属性及其各种可能的值。
  • 应用能力:考察候选人能否正确选择和使用不同的position属性值来实现所需的布局效果。
  • 问题解决能力:考察候选人是否能通过对position属性的理解,解决实际开发中的布局问题。

(2)最终答案

CSS 的 position 属性有以下几种属性值:

  • static:默认值。元素按照正常的文档流进行定位,不受 top、right、bottom、left 等属性影响。 示例:
<div style="position: static;">This is static</div>
  • relative:相对定位。元素相对于其正常位置进行偏移,但仍保留在文档流中。
<div style="position: relative; top: 10px; left: 20px;">This is relative</div>
  • absolute:绝对定位。元素相对于最近的定位祖先(非 static)进行定位,如果没有定位祖先,则相对于初始包含块(通常是浏览器窗口)。
<div style="position: relative;">
  <div style="position: absolute; top: 10px; left: 20px;">This is absolute</div>
</div>
  • fixed:固定定位。元素相对于浏览器窗口进行定位,即使页面滚动也不会移动。 示例:
<div style="position: fixed; top: 10px; left: 20px;">This is fixed</div>
  • sticky:粘性定位。元素在相对定位和固定定位之间切换,依赖于用户的滚动位置。在元素的滚动范围内表现为相对定位,超出范围后表现为固定定位。
<div style="position: sticky; top: 0;">This is sticky</div>

3 叙述一下不同情境下的 This 指向

(1)考察点分析:

  • 基础知识掌握:考察候选人是否理解 JavaScript 中 this 的工作原理和不同的指向情况。
  • 语言和执行上下文理解:考察候选人对 JavaScript 执行上下文的理解,包括全局执行上下文、函数执行上下文和对象方法执行上下文。
  • 调试和问题解决能力:考察候选人是否能够理解和解释代码中 this 的行为,以及解决 this 指向不明确导致的问题。

(2)最终答案

// 1. 全局上下文中的 this
console.log(this); // 输出全局对象,如 window(浏览器环境)

// 2. 函数调用中的默认绑定
function foo() {
  console.log(this);
}
foo(); // 输出全局对象,如 window(非严格模式下)

// 3. 对象方法调用中的 this
const obj = {
  name: 'John',
  greet: function() {
    console.log(this.name);
  }
};
obj.greet(); // 输出 'John'

// 4. 构造函数中的 this
function Person(name) {
  this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.name); // 输出 'Alice'

// 5. 使用 call、apply、bind 改变 this 的指向
const obj2 = {
  name: 'Emily'
};
function introduce() {
  console.log(`My name is ${this.name}`);
}
introduce.call(obj2); // 输出 'My name is Emily'

// 6. 箭头函数中的 this
const obj3 = {
  name: 'Tom',
  greet: () => {
    console.log(this.name); // this 指向全局对象,因为箭头函数没有自己的 this 绑定
  }
};
obj3.greet(); // 输出全局对象的 name(如果定义了)

4 TS 泛型函数是什么?

(1)考察点分析

  • 泛型概念理解:考察候选人是否理解泛型在编程语言中的作用和基本概念。
  • 泛型函数的使用场景:考察候选人能否识别并利用适合使用泛型的场景,如处理集合类型、返回值类型不确定等。
  • 类型安全性和代码复用性:考察候选人能否通过泛型函数提高代码的可维护性、扩展性和类型安全性。

(2)最终答案

1.定义泛型函数: 泛型函数使用 或其他字母来表示类型变量,这些类型变量可以在函数体内用作参数类型或返回类型的占位符。

function identity<T>(arg: T): T {
    return arg;
}

在上面的示例中, 表示这是一个泛型函数,T 是类型变量,它表示任意类型。函数 identity 接受一个参数 arg,类型为 T,并返回一个 T 类型的值。 2. 使用泛型函数:

// 调用泛型函数,并传入具体类型为 number
let result1 = identity<number>(5); // result1 的类型为 number
console.log(result1); // 输出 5

// 调用泛型函数,并传入具体类型为 string
let result2 = identity<string>("Hello"); // result2 的类型为 string
console.log(result2); // 输出 "Hello"

在这个示例中,identity(5) 和 identity("Hello") 分别传入了 number 类型和 string 类型的参数,TypeScript 根据传入的参数类型推导出 result1 和 result2 的具体类型。 3. 泛型函数处理数组示例:

function reverse<T>(items: T[]): T[] {
    return items.reverse();
}

let numbers = [1, 2, 3, 4, 5];
let reversedNumbers = reverse(numbers); // reversedNumbers 的类型为 number[]
console.log(reversedNumbers); // 输出 [5, 4, 3, 2, 1]

let names = ["Alice", "Bob", "Charlie"];
let reversedNames = reverse(names); // reversedNames 的类型为 string[]
console.log(reversedNames); // 输出 ["Charlie", "Bob", "Alice"]

在这个示例中,reverse 函数是一个泛型函数,接受一个数组 items,其中的元素类型为 T。根据传入的 numbers 和 names 数组,分别推导出 reversedNumbers 和 reversedNames 的具体类型。

5 比较 Vue 2 和 Vue 3 的响应式系统有什么区别?

(1)考察点分析

  • 深入了解 Vue 框架:考察候选人对 Vue.js 框架的理解程度,特别是其核心的响应式系统。
  • 版本差异的掌握:考察候选人是否能够区分和理解 Vue 2 和 Vue 3 在响应式系统实现上的主要区别。
  • 性能优化和技术升级:考察候选人对新技术(如 Proxy)的接受和掌握程度,以及这些技术在框架升级中的应用。

(2)最终答案

  • 性能优化:Vue 3 的 Proxy 相较于 Vue 2 的 Object.defineProperty 有更好的性能,因为 Proxy 的代理操作更高效。
  • 功能增强:Vue 3 的 Proxy 支持更多的操作捕获,使得框架能够更精确地追踪响应式依赖。
  • 适应新特性:Vue 3 的响应式系统更适应现代 JavaScript 和浏览器的发展趋势,如 ES6+ 的语法特性和新的浏览器API。

6 请解释一下 CSS 盒模型及其组成部分

(1)考察点分析

  • 基本概念理解:考察候选人对 CSS 盒模型的基本理解和应用能力。
  • 属性应用能力:考察候选人是否能清晰地解释盒模型的各个组成部分及其对页面布局的影响。
  • 问题解决能力:考察候选人在实际开发中如何应用盒模型进行页面设计和调试。

(2)最终答案

  1. 盒模型概述

    CSS 盒模型是用来描述每个 HTML 元素在页面布局中所占空间的模型。它包括了元素的内容区域、内边距、边框和外边距四个部分。

  2. 组成部分详解

    • 内容区域(Content):显示元素实际内容的区域,可以通过设置 widthheight 属性控制大小。
    • 内边距(Padding):内容区域与边框之间的空白区域,可以使用 padding 属性设置,如 padding: 10px;
    • 边框(Border):内边距外的边框,可以使用 border 属性定义边框的样式、宽度和颜色,如 border: 1px solid #000;
    • 外边距(Margin):元素与相邻元素之间的空白区域,可以使用 margin 属性设置,如 margin: 10px;

7 请解释一下动画在前端开发中的应用及其实现方式

(1)考察点分析

  • 基本概念理解:考察候选人对动画的基本概念和在前端开发中的重要性理解。
  • 技术实现能力:考察候选人是否了解和熟悉常见的动画实现方式,如 CSS 动画和 JavaScript 动画。
  • 性能优化和交互设计:考察候选人在动画设计中如何考虑性能优化和用户交互体验。

(2)最终答案

  1. 动画概述

    动画在前端开发中广泛应用,用于增强用户体验和提升网页交互性。它能够吸引用户的注意力,改善页面流畅性,并提供反馈和状态转换的视觉效果。

  2. 实现方式

    • CSS 动画:使用 @keyframes 定义动画的关键帧,结合 transition 属性实现简单的过渡效果和动画效果。例如:

      .box {
        width: 100px;
        height: 100px;
        background-color: red;
        transition: width 0.3s ease-in-out;
      }
      
      .box:hover {
        width: 150px;
      }
      
    • JavaScript 动画:通过 JavaScript 可以实现更复杂的动画效果和交互。常用的库如 GSAP(GreenSock Animation Platform)提供了丰富的动画 API,可以实现复杂的序列动画和交互效果。

      // 使用 GSAP 库实现动画
      gsap.to(".box", { duration: 1, x: 100, rotation: 360, ease: "power2.inOut" });
      
  3. 性能优化和交互设计

    • 动画性能优化包括减少动画数量和复杂度、使用硬件加速、避免频繁的布局重绘和重排。
    • 在设计动画时,应考虑用户交互体验和无障碍访问,确保动画不会干扰用户的操作和浏览体验。

8 如何使用 Flex实现水平和垂直居中

(1)考察点分析

  • Flexbox 基础理解:考察候选人对 Flexbox 布局模型的基本理解和应用能力。
  • 居中实现能力:考察候选人是否能清晰地解释如何使用 Flexbox 实现元素的水平和垂直居中。
  • 应对不同情况的能力:考察候选人在面对各种布局需求时,如何灵活应用 Flexbox 进行布局和居中。

(2)最终答案

  1. Flexbox 概述

    Flexbox 是一种用于页面布局的弹性盒子模型,通过设置父容器的 display: flex; 属性,可以控制子项目的布局和排列方式。

  2. 水平居中实现

    .container {
      display: flex;
      justify-content: center; /* 水平居中 */
    }
    

    上述代码中,通过设置 .container 容器为 flex 布局,并使用 justify-content: center; 属性将子项目水平居中。

9 请解释一下 JavaScript 中的闭包(Closure)及其应用场景

(1)考察点分析

  • 基本概念理解:考察候选人对闭包的基本定义和理解能力。
  • 闭包的特性和作用:考察候选人是否能清晰地说明闭包的特性以及在实际开发中的作用。
  • 应用场景:考察候选人能否举例说明闭包在实际开发中的常见应用场景。

(2)最终答案

  1. 闭包概述

    闭包是指在函数内部创建另一个函数,并且内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕。闭包通过保存了外部函数的执行环境,使得内部函数能够继续访问和操作外部函数的变量。

  2. 特性和作用

    • 保护变量:内部函数可以访问外部函数的局部变量,但外部函数无法直接访问内部函数的变量,从而实现了数据的私有化和保护。
    • 延长变量的生命周期:外部函数执行完毕后,其作用域中的变量在闭包的作用下仍然可以被内部函数访问和操作,延长了变量的生命周期。
    • 模块化开发:利用闭包可以实现模块化开发,封装私有变量和方法,并提供接口供外部调用,实现了模块的封装和重用。
  3. 应用场景示例

    • 事件处理函数:在事件监听中使用闭包可以保存事件触发时的状态或数据。

      function addClickHandler() {
        var count = 0;
        document.getElementById('btn').addEventListener('click', function() {
          count++;
          console.log('Button clicked ' + count + ' times');
        });
      }
      
    • 定时器:利用闭包可以保存定时器中的变量状态,实现循环或延时操作。

      function delayedMessage() {
        var count = 0;
        var interval = setInterval(function() {
          count++;
          console.log('Delayed message ' + count);
          if (count >= 5) {
            clearInterval(interval);
          }
        }, 1000);
      }
      

10 请解释一下 JavaScript 中的深拷贝与浅拷贝及其区别

(1)考察点分析

  • 基本概念理解:考察候选人对深拷贝和浅拷贝的基本概念和区别的理解。
  • 实际操作能力:考察候选人是否能清晰地解释和实现深拷贝和浅拷贝的方法。
  • 问题解决能力:考察候选人在面对不同的对象复制需求时,如何选择和应用合适的方法进行拷贝。

(2)最终答案

  1. 深拷贝与浅拷贝概述

    • 浅拷贝:浅拷贝是指创建一个新对象,这个新对象的属性是原对象属性的引用。浅拷贝只复制对象的第一层属性,如果属性是对象,则只复制其引用,原对象和新对象共享同一块内存空间。
    • 深拷贝:深拷贝是指创建一个新对象,并递归地复制所有嵌套对象的属性,使得新对象和原对象完全独立,互不影响。
  2. 浅拷贝的实现方法

    • 使用 Object.assign() 实现浅拷贝:

      let obj1 = { a: 1, b: { c: 2 } };
      let shallowCopy = Object.assign({}, obj1);
      
    • 使用展开运算符 ... 实现浅拷贝:

      let shallowCopy = { ...obj1 };
      
  3. 深拷贝的实现方法

    • 使用递归函数手动实现深拷贝:

      function deepClone(obj) {
        if (obj === null || typeof obj !== 'object') {
          return obj;
        }
        let copy = Array.isArray(obj) ? [] : {};
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
            copy[key] = deepClone(obj[key]);
          }
        }
        return copy;
      }
      
    • 使用 JSON.parse(JSON.stringify()) 实现深拷贝(注意局限性):

      let deepCopy = JSON.parse(JSON.stringify(obj1));
      
    • 使用 Lodash 的 _.cloneDeep 方法实现深拷贝:

      let _ = require('lodash');
      let deepCopy = _.cloneDeep(obj1);
      
  4. 实际应用和注意事项

    • 在实际开发中,选择深拷贝还是浅拷贝取决于具体需求。如果只需要复制对象的第一层属性,浅拷贝通常更高效;如果需要完全独立的副本,深拷贝是必需的。
    • 使用 JSON.parse(JSON.stringify()) 方法时需要注意它的局限性,如不能复制函数、不可枚举属性、以及处理循环引用的能力。
    • 处理复杂对象结构或需要高性能时,可以考虑使用第三方库如 Lodash 来实现深拷贝。

11 请解释一下 ES6(ECMAScript 2015)有哪些新特性,并举例说明其应用。

(1)考察点分析

  • 基础知识掌握:考察候选人对 ES6 的基本新特性是否熟悉。
  • 理解和应用能力:考察候选人是否能够理解每个新特性及其应用场景。
  • 实际编码能力:考察候选人是否能够通过代码示例展示对新特性的理解和应用。

(2)最终答案

  1. 简要概述 ES6

    ES6 是 ECMAScript 的第六版,也是 JavaScript 的重大更新,带来了许多新特性和改进,提升了开发效率和代码可读性。

  2. 主要新特性及应用

    • letconst 声明:块级作用域变量声明。

      let x = 10;
      const y = 20;
      
    • 箭头函数:更简洁的函数语法和词法作用域的 this 绑定。

      const add = (a, b) => a + b;
      console.log(add(5, 3)); // 8
      
    • 模板字符串:嵌入变量的字符串模板,更加简洁易读。

      const name = 'Alice';
      const greeting = `Hello, ${name}!`;
      console.log(greeting); // Hello, Alice!
      
    • 解构赋值:从数组或对象中提取值到变量中。

      const [a, b] = [1, 2];
      const {x, y} = {x: 3, y: 4};
      console.log(a, b); // 1, 2
      console.log(x, y); // 3, 4
      
    • 默认参数:为函数参数设置默认值。

      function greet(name = 'Guest') {
        return `Hello, ${name}!`;
      }
      console.log(greet()); // Hello, Guest!
      console.log(greet('Bob')); // Hello, Bob!
      
    • 展开运算符(...:用于数组和对象的展开。

      const arr1 = [1, 2, 3];
      const arr2 = [...arr1, 4, 5];
      console.log(arr2); // [1, 2, 3, 4, 5]
      
    • for...of 循环:遍历可迭代对象的值。

      const numbers = [10, 20, 30];
      for (const num of numbers) {
        console.log(num);
      }
      
    • MapSet 数据结构:提供更灵活的数据存储方式。

      const map = new Map();
      map.set('key1', 'value1');
      console.log(map.get('key1')); // value1
      
      const set = new Set([1, 2, 3]);
      set.add(4);
      console.log(set.has(2)); // true
      
    • 模块化:使用 importexport 进行模块化开发。

      // module.js
      export const greet = (name) => `Hello, ${name}!`;
      
      // main.js
      import { greet } from './module.js';
      console.log(greet('Alice')); // Hello, Alice!
      
    • 类(Class):更接近面向对象编程的语法糖。

      class Person {
        constructor(name) {
          this.name = name;
        }
        greet() {
          return `Hello, ${this.name}!`;
        }
      }
      const alice = new Person('Alice');
      console.log(alice.greet()); // Hello, Alice!
      
    • Promise:异步编程的新方式,取代回调函数。

      const fetchData = () => {
        return new Promise((resolve, reject) => {
          setTimeout(() => resolve('Data fetched'), 1000);
        });
      };
      fetchData().then((data) => console.log(data)); // Data fetched
      

12 请解释一下 JavaScript 中的箭头函数及其应用场景

(1)考察点分析

  • 基本概念理解:考察候选人对箭头函数基本语法和特点的理解。
  • 特性和使用场景:考察候选人是否能够清晰地说明箭头函数的特性及其在实际开发中的应用场景。
  • 比较和选择能力:考察候选人能否理解箭头函数与传统函数的区别,并在适当的场景中做出选择。

(2)最终答案

  1. 箭头函数概述

    箭头函数是一种更简洁的函数书写方式,引入了新的语法,减少了冗余代码。箭头函数不会创建自己的 this,而是从外层作用域继承 this

  2. 箭头函数的语法

    • 基本语法:使用箭头 => 定义函数。
    • 单个参数时,可以省略参数括号:
      const add = x => x + 10;
      console.log(add(5)); // 15
      
    • 多个参数时,需要使用括号:
      const add = (x, y) => x + y;
      console.log(add(5, 3)); // 8
      
    • 单行语句隐式返回结果:
      const square = x => x * x;
      console.log(square(4)); // 16
      
    • 多行语句需使用花括号和 return 语句:
      const sum = (x, y) => {
        const result = x + y;
        return result;
      };
      console.log(sum(5, 3)); // 8
      
  3. 箭头函数的特性

    • 没有 this 绑定:箭头函数不会创建自己的 this,而是继承自外层作用域:
      function Timer() {
        this.seconds = 0;
        setInterval(() => {
          this.seconds++;
          console.log(this.seconds);
        }, 1000);
      }
      const timer = new Timer(); // 每秒输出递增的数字
      
    • 没有 arguments 对象:箭头函数没有自己的 arguments 对象,可使用展开运算符 ...args 代替:
      const sum = (...args) => args.reduce((acc, val) => acc + val, 0);
      console.log(sum(1, 2, 3, 4)); // 10
      
    • 不能用作构造函数:箭头函数不能使用 new 关键字实例化对象。
    • 没有 prototype 属性:箭头函数没有 prototype 属性。
  4. 应用场景

    • 简化回调函数:箭头函数在回调函数中使用简洁明了:
      const numbers = [1, 2, 3, 4];
      const doubled = numbers.map(n => n * 2);
      console.log(doubled); // [2, 4, 6, 8]
      
    • 保持 this 上下文:在需要保持当前 this 上下文的场景中使用,如事件处理器、定时器等:
      class MyComponent {
        constructor() {
          this.name = 'MyComponent';
          document.getElementById('myButton').addEventListener('click', () => {
            console.log(this.name); // 输出 'MyComponent'
          });
        }
      }
      const component = new MyComponent();
      

13 请解释一下 JavaScript 中 Map 和 Object 的区别

(1)考察点分析

  • 基础知识理解:考察候选人对 Map 和 Object 基本概念的理解。
  • 特性和使用场景:考察候选人是否能清晰地说明 Map 和 Object 各自的特性及其在实际开发中的适用场景。
  • 实际操作能力:考察候选人是否能够通过代码示例展示对 Map 和 Object 的理解和应用。

(2)最终答案

  1. 基本概念

    • Object:用于存储键值对,键名只能是字符串或符号(Symbol)。
    • Map:一种新的键值对存储结构,键名可以是任意类型的值(包括对象)。
  2. 主要区别

    • 键的类型

      • Object 的键名只能是字符串或符号。
      • Map 的键名可以是任意类型。
    • 键值对的迭代顺序

      • Object 的键值对没有固定的迭代顺序。
      • Map 的键值对按照插入顺序进行迭代。
    • 性能

      • Map 在频繁增删键值对时性能较优。
      • Object 在简单的、静态键值对存储时较为高效。
    • 常用操作

      • Map 提供了更丰富的操作方法,如 setgethasdeleteclear
      • Object 需要使用更多的原生方法来实现类似功能。
    • 大小

      • Mapsize 属性,可以直接获取键值对的数量。
      • Object 没有直接获取大小的属性,需要手动计算。
    • 原型链

      • Object 有原型链,会继承一些默认属性和方法。
      • Map 不会继承任何默认属性和方法。
  3. 代码示例

    • 创建和操作 Map

      const map = new Map();
      map.set('key1', 'value1');
      map.set(2, 'value2');
      console.log(map.get('key1')); // 'value1'
      console.log(map.get(2)); // 'value2'
      console.log(map.size); // 2
      map.delete('key1');
      console.log(map.has('key1')); // false
      
    • 创建和操作 Object

      const obj = {};
      obj['key1'] = 'value1';
      obj[2] = 'value2';
      console.log(obj['key1']); // 'value1'
      console.log(obj[2]); // 'value2'
      console.log(Object.keys(obj).length); // 2
      delete obj['key1'];
      console.log('key1' in obj); // false
      

14 请解释一下 MVVM 和 MVC 的区别

(1)考察点分析

  • 基础概念理解:考察候选人对 MVVM 和 MVC 两种架构模式的基本理解。
  • 特性和结构:考察候选人是否能清晰地说明两者的结构及其特性。
  • 应用场景和选择能力:考察候选人能否理解 MVVM 和 MVC 的适用场景,并在实际开发中做出选择。

(2)最终答案

  1. MVVM 和 MVC 概述

    • MVC(Model-View-Controller):一种经典的软件架构模式,将应用分为三个部分:模型(Model)、视图(View)和控制器(Controller)。
    • MVVM(Model-View-ViewModel):一种现代的软件架构模式,将应用分为三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。
  2. 结构和特性

    • MVC

      • Model:负责数据和业务逻辑。
      • View:负责用户界面展示。
      • Controller:处理用户输入,更新模型和视图。
    • MVVM

      • Model:负责数据和业务逻辑。
      • View:负责用户界面展示。
      • ViewModel:处理视图的行为和状态,通过双向数据绑定将视图和模型连接起来。
  3. 主要区别

    • 数据绑定

      • MVC:数据绑定通常是手动的,通过控制器来更新视图。
      • MVVM:采用双向数据绑定,视图和模型之间的同步由框架自动处理。
    • 关注点分离

      • MVC:控制器直接处理用户输入,关注点相对集中。
      • MVVM:视图模型解耦了视图和模型,使得视图和业务逻辑的关注点更加清晰。
    • 复杂度和灵活性

      • MVC:结构较为简单,适用于中小型应用。
      • MVVM:适用于需要复杂状态管理和动态更新的大型应用。
    • 框架支持

      • MVC:常见于服务器端框架,如 Ruby on Rails、ASP.NET MVC。
      • MVVM:常见于前端框架,如 Angular、Vue.js。
  4. 代码示例

    • MVC

      // Model
      class Model {
        constructor() {
          this.data = '';
        }
        setData(newData) {
          this.data = newData;
        }
        getData() {
          return this.data;
        }
      }
      
      // View
      class View {
        constructor(controller) {
          this.controller = controller;
          this.init();
        }
        init() {
          document.getElementById('button').addEventListener('click', () => {
            this.controller.updateModel();
          });
        }
        render(data) {
          document.getElementById('output').innerText = data;
        }
      }
      
      // Controller
      class Controller {
        constructor(model, view) {
          this.model = model;
          this.view = view;
        }
        updateModel() {
          this.model.setData('Hello MVC');
          this.view.render(this.model.getData());
        }
      }
      
      const model = new Model();
      const controller = new Controller(model);
      const view = new View(controller);
      
    • MVVM(以 Vue.js 为例):

      <!-- View -->
      <div id="app">
        <input v-model="message" />
        <p>{{ message }}</p>
      </div>
      
      <script>
      // ViewModel
      new Vue({
        el: '#app',
        data: {
          message: 'Hello MVVM'
        }
      });
      </script>
      

15 请解释一下 Vue 的生命周期

(1)考察点分析

  • 基础概念理解:考察候选人对 Vue 生命周期的基本理解。
  • 各个生命周期钩子函数的作用和用法:考察候选人是否能清晰地说明各个生命周期钩子的作用和具体使用场景。
  • 实际操作能力:考察候选人是否能够通过代码示例展示对生命周期钩子函数的理解和应用。

(2)最终答案

  1. Vue 生命周期概述

    Vue 实例在创建过程中会经历一系列的初始化过程,从创建、挂载、更新到销毁,这一过程称为生命周期。

  2. 各个生命周期钩子函数的介绍

    • beforeCreate:实例初始化之后,数据观测和事件配置之前调用。
    • created:实例创建完成,数据观测和事件配置已完成,但还未挂载到 DOM。
    • beforeMount:在挂载开始之前调用,相关的 render 函数首次被调用。
    • mounted:实例挂载到 DOM 后调用,DOM 操作可以在此进行。
    • beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
    • updated:由于数据更改导致虚拟 DOM 重新渲染和打补丁后调用。
    • beforeDestroy:实例销毁之前调用,此时实例仍然完全可用。
    • destroyed:实例销毁后调用,组件的所有事件监听器被移除,所有子实例也被销毁。
  3. 代码示例

    展示一个简单的 Vue 组件,使用所有生命周期钩子函数:

    <template>
      <div>
        <p>{{ message }}</p>
        <button @click="updateMessage">Update Message</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          message: 'Hello Vue!'
        };
      },
      beforeCreate() {
        console.log('beforeCreate: 实例初始化之后,数据观测和事件配置之前调用');
      },
      created() {
        console.log('created: 实例创建完成,数据观测和事件配置已完成,但还未挂载到 DOM');
      },
      beforeMount() {
        console.log('beforeMount: 在挂载开始之前调用,相关的 render 函数首次被调用');
      },
      mounted() {
        console.log('mounted: 实例挂载到 DOM 后调用,DOM 操作可以在此进行');
      },
      beforeUpdate() {
        console.log('beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前');
      },
      updated() {
        console.log('updated: 由于数据更改导致虚拟 DOM 重新渲染和打补丁后调用');
      },
      beforeDestroy() {
        console.log('beforeDestroy: 实例销毁之前调用,此时实例仍然完全可用');
      },
      destroyed() {
        console.log('destroyed: 实例销毁后调用,组件的所有事件监听器被移除,所有子实例也被销毁');
      },
      methods: {
        updateMessage() {
          this.message = 'Message Updated!';
        }
      }
    };
    </script>
    

16 请解释一下 Vue 父子组件的生命周期顺序

(1)考察点分析

  • 生命周期钩子的理解:考察候选人对 Vue 组件生命周期钩子的理解。
  • 父子组件的生命周期顺序:考察候选人是否清楚父子组件在创建和销毁时生命周期钩子的触发顺序。
  • 实际操作能力:考察候选人是否能够通过代码示例展示对父子组件生命周期顺序的理解和应用。

(2)最终答案

  1. 父子组件生命周期钩子的基本概念

    了解 Vue 组件的生命周期钩子:beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed

  2. 父子组件生命周期的触发顺序

    • 创建阶段

      1. 父组件的 beforeCreate
      2. 父组件的 created
      3. 父组件的 beforeMount
      4. 子组件的 beforeCreate
      5. 子组件的 created
      6. 子组件的 beforeMount
      7. 子组件的 mounted
      8. 父组件的 mounted
    • 更新阶段

      1. 父组件的 beforeUpdate
      2. 子组件的 beforeUpdate
      3. 子组件的 updated
      4. 父组件的 updated
    • 销毁阶段

      1. 父组件的 beforeDestroy
      2. 子组件的 beforeDestroy
      3. 子组件的 destroyed
      4. 父组件的 destroyed
  3. 代码示例

    展示一个简单的父子组件,打印出生命周期钩子的触发顺序:

    父组件

   <template>
     <div>
       <p>Parent Component</p>
       <child-component />
     </div>
   </template>
   <script>
   import ChildComponent from './ChildComponent.vue';

   export default {
     components: {
       ChildComponent
     },
     beforeCreate() {
       console.log('Parent beforeCreate');
     },
     created() {
       console.log('Parent created');
     },
     beforeMount() {
       console.log('Parent beforeMount');
     },
     mounted() {
       console.log('Parent mounted');
     },
     beforeUpdate() {
       console.log('Parent beforeUpdate');
     },
     updated() {
       console.log('Parent updated');
     },
     beforeDestroy() {
       console.log('Parent beforeDestroy');
     },
     destroyed() {
       console.log('Parent destroyed');
     }
   };
   </script>

子组件:

<template>
  <div>
    <p>Child Component</p>
  </div>
</template>

<script>
export default {
  beforeCreate() {
    console.log('Child beforeCreate');
  },
  created() {
    console.log('Child created');
  },
  beforeMount() {
    console.log('Child beforeMount');
  },
  mounted() {
    console.log('Child mounted');
  },
  beforeUpdate() {
    console.log('Child beforeUpdate');
  },
  updated() {
    console.log('Child updated');
  },
  beforeDestroy() {
    console.log('Child beforeDestroy');
  },
  destroyed() {
    console.log('Child destroyed');
  }
};
</script>

17 Vue双向数据绑定的原理

(1)考察点分析

  • 理解数据绑定的机制:考察候选人对 Vue 双向数据绑定的底层实现原理的理解。
  • 数据劫持与发布订阅模式:考察候选人是否了解 Vue 是如何利用数据劫持和发布订阅模式来实现双向数据绑定的。
  • Vue 的响应式系统:考察候选人对 Vue 响应式系统的基本了解,包括依赖追踪和更新策略。

(2)最终答案

  1. 双向数据绑定的基本概念

    Vue 的双向数据绑定是指数据模型层和视图层之间的自动同步,数据的变化会实时更新到视图,而视图中的变化也会反映到数据模型中。

  2. 数据劫持(Object.defineProperty)

    Vue 使用 Object.defineProperty 方法来劫持对象属性的访问和修改。通过这种方式,Vue 能够监听到每个属性的变化,从而实现数据的响应式更新。

  3. 发布订阅模式

    在 Vue 中,每个数据都有对应的依赖收集器(Dep),而视图中的指令(比如 v-model)会创建一个监听器(Watcher)。当数据变化时,会触发对应属性的依赖收集器,通知所有相关的监听器更新视图。

  4. 响应式系统的运作机制

    • 初始化阶段
      • Vue 在组件实例化时,对数据对象进行递归遍历,对每个属性使用 Object.defineProperty 进行数据劫持。
      • 同时,为每个属性初始化一个依赖收集器(Dep),用来存储依赖该属性的监听器(Watcher)。
    • 依赖追踪
      • 当模板中使用了某个属性时,会创建一个对应的监听器(Watcher),并将其加入到属性的依赖收集器中。
      • 当属性被修改时,会触发该属性的依赖收集器,通知所有相关的监听器更新视图。
    • 更新策略
      • Vue 使用虚拟 DOM 和差异化比较算法,找出需要更新的部分,并高效地更新到视图上,从而实现了快速响应和高效的渲染性能。

18 vue组件的通信方式

(1)考察点分析

  • 组件通信方式:考察候选人是否了解 Vue 中组件之间通信的多种方式,如 props、事件、Vuex 等。
  • 适用场景和优缺点:考察候选人是否能根据不同的场景选择合适的通信方式,并理解各种方式的优缺点。
  • 实际应用能力:考察候选人是否能够通过示例或场景来展示组件通信的实际应用能力。

(2)最终答案

  1. 组件通信的基本方式

    • props / $emit:父子组件通信的基础方式。父组件通过 props 向子组件传递数据,子组件通过 $emit 触发事件向父组件传递数据。

    • 事件总线:利用 Vue 实例作为中央事件总线,可以在任意关系的组件之间传递事件或数据,适合于兄弟组件通信。

    • Vuex:用于大型应用中集中管理共享状态。通过 store 存储和管理状态,各组件通过 getters 和 mutations 修改和获取状态。

    • $attrs / $listeners:透传父组件的属性和监听器到子组件,简化了属性的传递和事件的监听,适合于需要传递大量属性的情况。

    • provide / inject:祖先组件通过 provide 提供数据,后代组件通过 inject 注入数据,适合于跨层级组件通信的场景。

  2. 场景和优缺点分析

    • props / $emit:简单直观,适合于父子组件通信,但对于复杂嵌套或多层级组件通信可能会显得繁琐。

    • 事件总线:适合于非直接关系的组件通信,但组件之间的关系不明确,不易于追踪和调试。

    • Vuex:适合于状态复杂或需要多个组件共享状态的场景,但对于小型应用可能会显得过于复杂。

    • $attrs / $listeners:适合于需要将父组件的属性和事件传递给子组件的情况,但不适合于多层级嵌套的场景。

    • provide / inject:适合于祖先组件向所有后代组件传递依赖,但当跨级关系不清晰时会导致组件之间的耦合性增加。

  3. 示例代码

    父子组件通信示例(props / $emit)

    ParentComponent.vue

   <template>
     <div>
       <p>Parent Component</p>
       <ChildComponent :message="parentMessage" @update="handleUpdate" />
     </div>
   </template>

   <script>
   import ChildComponent from './ChildComponent.vue';

   export default {
     components: {
       ChildComponent
     },
     data() {
       return {
         parentMessage: 'Message from parent'
       };
     },
     methods: {
       handleUpdate(newMessage) {
         this.parentMessage = newMessage;
       }
     }
   };
   </script>

ChildComponent.vue:

<template>
 <div>
   <p>Child Component</p>
   <input type="text" v-model="message">
   <button @click="updateParent">Update Parent</button>
 </div>
</template>

<script>
export default {
 props: ['message'],
 methods: {
   updateParent() {
     this.$emit('update', this.message);
   }
 }
};
</script>

这个示例展示了父组件通过 props 向子组件传递数据,并通过 $emit 触发事件向父组件传递数据的过程。

19 如何解决跨域问题?

(1)考察点分析

  • 理解跨域原理:考察候选人对跨域问题的基本理解,包括同源策略及其限制。
  • 解决跨域的方法:考察候选人是否了解常见的跨域解决方案,如 CORS、JSONP、代理服务器等。
  • 安全性考虑:考察候选人在解决跨域问题时是否考虑到安全性和最佳实践。

(2)最终答案

  1. 同源策略

    浏览器的同源策略限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。

  2. 解决跨域的常见方法

    • CORS(跨域资源共享):通过服务端设置响应头部,如 Access-Control-Allow-Origin,允许指定的源访问资源。

    • JSONP(JSON with Padding):利用 <script> 标签的跨域特性,动态创建 <script> 标签请求跨域数据,并通过回调函数处理返回数据。

    • 代理服务器:前端通过向同域名下的代理服务器发起请求,再由代理服务器转发到目标服务器,绕过浏览器的同源策略限制。

    • WebSocket:使用 WebSocket 协议进行通信,支持跨域,但需要服务器端支持 WebSocket。

    • 跨域资源嵌入:在响应中设置合适的跨域资源嵌入标记,如 <link rel="stylesheet" crossorigin="anonymous"><img crossorigin="anonymous">

  3. 安全性考虑

    • 使用 CORS 时,确保服务器设置合适的响应头,避免开放过多权限,防止恶意请求。

    • JSONP 存在安全隐患,必须信任响应的内容,可能容易受到 XSS 攻击的影响。

    • 使用代理服务器时,需要进行严格的安全审查和过滤,防止恶意请求通过代理访问目标服务器。

20 请你谈谈你对堆和栈的理解

(1)考察点分析

  • 存储结构:了解堆和栈在内存中的具体存储结构和组织方式。
  • 数据存取:理解在堆和栈中数据的存储和访问方式。
  • 使用场景:掌握在不同情况下应该选择使用堆还是栈的原则。

(2)最终答案

  1. 栈(Stack)

    • 栈是一种后进先出(LIFO)的数据结构,数据的存取速度快,存储在连续的内存空间中。
    • 栈的大小有限,由编译器或运行时系统进行管理,存储函数调用、局部变量以及函数执行上下文。
    • 函数调用时,会创建栈帧,函数执行完毕后,栈帧被销毁,释放栈空间。
  2. 堆(Heap)

    • 堆是一种不连续的内存区域,用于动态分配内存空间。
    • 堆的存取速度相对较慢,因为数据存储在不同的内存块中,需要通过指针进行间接访问。
    • 堆的大小通常比栈大,用于存储动态分配的对象、全局变量等,需要手动管理内存的分配和释放,否则可能导致内存泄漏或碎片化问题。
  3. 使用场景

    • 适合存储函数调用、局部变量等生命周期短暂且大小确定的数据。
    • 适合存储动态分配的数据、全局变量等生命周期较长或大小不确定的数据。

21 TCP 和 UDP 的区别

(1)考察点分析

  • 协议类型:理解 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)的基本特点和用途。
  • 特点对比:对比它们在可靠性、连接性、数据传输方式等方面的不同。
  • 适用场景:了解在不同的应用场景下,选择 TCP 还是 UDP 的考量。

(2)最终答案

  1. TCP(传输控制协议)

    • 连接性:面向连接,通信前需要建立连接,形成可靠的数据传输通道。
    • 可靠性:提供数据传输的可靠性保证,通过确认和重传机制确保数据的正确性和顺序性。
    • 流量控制:使用滑动窗口协议进行流量控制,防止数据丢失和网络拥塞。
    • 应用场景:适用于需要可靠数据传输、数据顺序性和不允许丢包的应用,如文件传输、网页访问等。
  2. UDP(用户数据报协议)

    • 连接性:无连接,发送数据前不需要建立连接。
    • 可靠性:不提供数据传输的可靠性保证,数据可能丢失或者乱序到达。
    • 传输方式:面向无连接的简单数据传输方式,快速传输数据,不保证数据顺序和到达。
    • 应用场景:适用于实时性要求高、允许丢包的应用,如音频/视频流传输、在线游戏数据传输等。

22 直播使用TCP还是UDP?

(1)考察点分析

  • 协议特性:理解TCP和UDP协议的基本特性和差异。
  • 直播需求:了解直播场景对网络传输的具体需求。
  • 技术选择:根据直播的特点,评估和选择适合的传输协议。

(2)最终答案

直播场景下TCP和UDP的比较:

  1. TCP(Transmission Control Protocol)

    • 可靠性:TCP提供数据包的确认和重传机制,确保数据可靠传输。
    • 有序性:TCP保证数据包按顺序到达。
    • 拥塞控制:TCP具有流量控制和拥塞控制机制。
    • 适用性:适用于对数据完整性要求高的应用,如文件传输、网页浏览等。
  2. UDP(User Datagram Protocol)

    • 低延迟:UDP没有建立连接的过程,数据传输速度快。
    • 无状态:UDP不维护连接状态,不保证数据包的顺序、可靠性或完整性。
    • 简单高效:UDP协议简单,头部开销小,适合快速传输。
    • 适用性:适用于对实时性要求高的应用,如在线游戏、实时视频会议等。

直播场景分析:

  • 实时性:直播对实时性要求极高,观众希望看到尽可能无延迟的画面。
  • 流畅性:直播过程中,轻微的数据丢失可能不会显著影响观看体验,但长时间的卡顿或重连会极大影响用户体验。
  • 并发性:直播可能面临大量用户同时观看,需要高效处理高并发的数据传输。

结论:

对于直播应用,UDP 通常是更好的选择,因为它提供了更低的延迟和更高的传输效率,适合实时传输。然而,完全使用UDP可能会遇到数据丢失的问题,特别是在不稳定的网络环境下。因此,实际应用中可能会采用以下策略:

  • UDP为主:主要使用UDP来保证直播的实时性和流畅性。
  • FEC(Forward Error Correction):使用前向纠错技术来减少数据丢失的影响,提高传输的可靠性。
  • HLS/DASH:采用HTTP Live Streaming或Dynamic Adaptive Streaming over HTTP等自适应比特率流技术,允许客户端根据网络状况选择不同质量的视频流。

最终,选择哪种协议或技术,需要根据具体的应用需求和网络环境进行权衡。

23 为什么TCP需要三次握手?

(1)考察点分析

  • TCP连接建立:理解TCP连接建立的基本原理。
  • 三次握手过程:了解三次握手的每个步骤及其目的。
  • 安全性和可靠性:认识到三次握手在确保连接双方准备就绪和防止重复连接中的作用。

(2)最终答案

TCP三次握手的步骤和原因:

  1. 第一次握手 - SYN

    • 客户端选择一个初始序列号 x,向服务器发送一个SYN报文段(SYN=1),请求建立连接。
    • 客户端进入 SYN_SENT 状态,等待服务器确认。
  2. 第二次握手 - SYN-ACK

    • 服务器收到客户端的SYN报文后,如果同意建立连接,会分配TCP资源,并发送一个SYN-ACK报文段(SYN=1, ACK=1)。
    • 服务器的ACK确认号为 x+1,表示期望收到从 x+1 开始的数据。
    • 服务器进入 SYN_RCVD 状态。
  3. 第三次握手 - ACK

    • 客户端收到服务器的SYN-ACK报文后,会发送一个ACK报文段(ACK=1),确认收到服务器的SYN。
    • 客户端的ACK确认号为服务器的初始序列号加1,即 y+1
    • 客户端和服务器此时都进入了 ESTABLISHED 状态,连接建立成功。

为什么需要三次握手:

  • 防止失效的连接请求突然传送到了服务端:三次握手可以防止服务器端打开一个不存在的连接。
  • 确保双方的接收和发送能力都是正常的:三次握手可以确认客户端和服务器都能够接收和发送数据。
  • 同步初始序列号:三次握手过程中,双方交换各自的初始序列号,为后续的数据传输建立基准。

结论:

三次握手是TCP连接建立过程中的一个关键步骤,它确保了连接的可靠性和数据传输的稳定性。通过这个机制,TCP协议能够在不可靠的IP网络层之上提供可靠的数据传输服务。

24 为什么TCP需要四次挥手?

(1)考察点分析

  • TCP连接终止:理解TCP连接终止的基本原理。
  • 四次挥手过程:了解四次挥手的每个步骤及其目的。
  • 数据传输完成:认识到TCP需要确保双方数据传输都完成后才能安全关闭连接。

(2)最终答案

TCP四次挥手的步骤和原因:

  1. 第一次挥手 - FIN

    • 当一方(假设为客户端)完成数据发送任务后,发送一个FIN报文段(FIN=1),用来关闭主动方到被动方的数据传输。
    • 客户端进入 FIN-WAIT-1 状态。
  2. 第二次挥手 - ACK

    • 接收方(服务器)收到FIN报文后,发送一个ACK报文段确认收到FIN,并告知对方可以关闭其到发送方的数据传输。
    • 客户端收到ACK后,进入 FIN-WAIT-2 状态。
  3. 第三次挥手 - FIN

    • 接收方(服务器)完成数据发送任务后,发送一个FIN报文段关闭其到发送方的数据传输。
    • 服务器进入 LAST-ACK 状态。
  4. 第四次挥手 - ACK

    • 发送方(客户端)收到这个FIN报文后,发送一个ACK报文段作为回应,并进入 TIME-WAIT 状态。
    • 经过一段时间(称为2MSL,即最大报文段生存时间的两倍)后,客户端关闭连接。

为什么需要四次挥手:

  • 全双工通信:TCP连接是全双工的,意味着双方都可以独立关闭各自的发送和接收通道。
  • 确保数据传输完成:四次挥手确保了即使在数据传输完成后,双方也能够独立地关闭发送通道,而不是同时关闭。
  • 防止数据丢失:确保所有数据都被接收和确认,防止在连接关闭过程中数据丢失。
  • 有序关闭连接:四次挥手允许双方分别确认对方的关闭请求,确保连接能够有序地关闭。

结论:

四次挥手是TCP连接终止过程中的一个关键步骤,它确保了连接的有序和安全关闭。通过这个机制,TCP协议能够在保证数据传输完整性的同时,安全地结束通信会话。

25 标准盒模型(Standard Box Model)和怪异盒模型(IE Box Model)的区别

(1)考察点分析

  • 盒模型理解:理解CSS盒模型的基本概念,包括元素的宽度、高度、内边距、边框和外边距。
  • 盒模型差异:了解标准盒模型和怪异盒模型在计算元素宽度和高度时的区别。
  • 兼容性考虑:认识到不同盒模型对网页布局的影响,以及在不同浏览器中的表现差异。

(2)最终答案

标准盒模型和怪异盒模型的区别:

  1. 标准盒模型(W3C标准)

    • 在标准盒模型中,元素的 widthheight 指的是内容区域的宽度和高度。
    • 元素的总宽度是 width 加上左右两侧的 paddingborder
    • 元素的总高度同理。
  2. 怪异盒模型(IE盒模型)

    • 在怪异盒模型中,元素的 widthheight 包括了内容区域、内边距、边框,但不包括外边距。
    • 这意味着如果你设置了一个元素的 width,这个值实际上已经包含了 paddingborder
  3. 示例比较

    • 假设有一个元素,内容区域宽 100px,内边距 10px,边框 5px
    • 标准盒模型中,如果你设置 width: 100px;,元素的总宽将是 100px + 10px*2 + 5px*2 = 130px
    • 怪异盒模型中,如果同样设置 width: 100px;,元素的总宽仍然是 100px,因为这里的 100px 已经包括了 paddingborder

为什么存在两种盒模型:

  • 历史原因:怪异盒模型是早期IE浏览器的实现方式,而标准盒模型是后来W3C制定的标准。
  • 兼容性:开发者需要根据目标用户的浏览器情况选择合适的盒模型,或者使用CSS的 box-sizing 属性来统一盒模型的行为。

结论:

了解两种盒模型的差异对于跨浏览器开发非常重要。开发者可以通过设置 box-sizing: border-box; 来让元素的 widthheight 按照标准盒模型来计算,这样可以简化布局并提高代码的可维护性。

26 上下两个元素的margin都是20,那他们的间距是多少?

(1)考察点分析

  • 外边距折叠:理解CSS中外边距折叠的基本概念。
  • 间距计算:掌握如何计算两个元素相遇时的实际间距。

(2)最终答案

上下两个元素的外边距间距问题:

假设我们有两个元素,每个元素的 margin-bottommargin-top 都是20px。

外边距折叠现象: 在CSS中,当两个垂直方向相邻的元素相遇时,它们的外边距不会累加。相反,它们会折叠成一个单一的外边距,这个外边距的值等于两个相遇外边距中较大的那个。

间距计算规则

  • 如果两个元素的外边距值相同(在这个例子中都是20px),那么它们之间的间距将是这个值,即20px。
  • 如果外边距值不同,比如一个元素的 margin-bottom 是10px,另一个元素的 margin-top 是20px,那么间距将是20px,因为20px是两者中较大的值。

结论: 在这种情况下,两个元素的外边距都是20px,所以它们之间的间距是20px,而不是40px。这是因为发生了外边距折叠,只保留了较大的外边距值。

27 css垂直水平居中方法

(1)考察点分析

  • 布局技术:理解不同的CSS布局技术,如Flexbox、Grid等。
  • 居中策略:掌握使用CSS实现元素垂直和水平居中的方法。
  • 兼容性:了解不同居中方法的浏览器兼容性。

(2)最终答案

CSS垂直水平居中的方法:

  1. 使用margin属性(传统方法)

    • 对于一个已知宽高的元素,可以通过设置margin属性为auto来实现水平居中。
    • 垂直居中则较难实现,通常需要额外的HTML元素或使用line-height属性。
  2. 使用Flexbox

    • Flexbox提供了一个简单的方式来居中子元素。
    • 容器设置为display: flex;,然后使用justify-content: center;align-items: center;实现水平和垂直居中。
    .container {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
  3. 使用Grid

    • CSS Grid布局同样提供了居中的方法。
    • 容器设置为display: grid;,并使用place-items: center;实现居中。
.container {
  display: grid;
  place-items: center;
}
  1. 使用绝对定位和transform属性

    • 将元素绝对定位到一个父容器的中心,然后使用transform: translate(-50%, -50%);来调整位置。
.container {
  position: relative;
}
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
  1. 使用text-align和line-height(仅限于内联或内联块元素)

    • 对于内联或内联块元素,可以使用text-align: center;实现水平居中。
    • 垂直居中可以通过设置line-height等于元素的高度来实现。

28 前端路由及其在单页应用中的作用

(1)考察点分析:

  • 前端路由概念:理解前端路由的基本原理和实现方式。
  • SPA架构:了解单页应用的特点和为何需要前端路由。
  • 路由库使用:掌握至少一种前端路由库的使用。

(2)最终结果:

  • 前端路由定义:前端路由是一种在不重新加载整个页面的情况下,通过改变URL来实现页面内容更新的技术。
  • SPA与前端路由
    • 单页应用(SPA)通过动态加载资源在用户与应用程序交互时不需要重新加载整个页面。
    • 前端路由在SPA中用于管理URL和视图的映射,使得用户可以通过浏览器的前进和后退按钮导航历史。
  • 前端路由实现
    • 使用前端路由库,如React Router或Vue Router,可以轻松定义路由规则和组件映射。
    • 路由库监听URL的变化,并根据配置的路由规则动态加载相应的组件或视图。

假设我们使用React Router来实现前端路由:

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';

function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={HomePage} />
        <Route path="/about" component={AboutPage} />
      </Switch>
    </Router>
  );
}

29 如何实现一个Promise?

(1)考察点分析

  • 理解Promise概念:掌握Promise对象的基本概念,包括其作用和工作流程。
  • 状态管理:理解Promise的三种状态:Pending(等待)、Fulfilled(已成功)、Rejected(已失败)。
  • 执行器函数:理解在Promise构造函数中执行的异步操作,以及如何通过resolve和reject函数改变Promise的状态。
  • 链式调用:掌握thencatch方法的使用,以及Promise链的构建。
  • 错误处理:理解如何在Promise中进行错误捕获和处理。

(2)最终答案

实现一个基本的Promise可以按照以下步骤进行:

  1. 定义Promise类:创建一个类,其构造函数接收一个执行器函数作为参数。
  2. 初始化状态:在构造函数中初始化状态为Pending。
  3. 定义resolve和reject方法:这两个方法用于改变Promise的状态,并允许链式调用。
  4. 实现then和catch方法:允许用户添加回调函数,以处理Promise的成功或失败状态。

以下是使用JavaScript实现的简单Promise示例:

class MyPromise {
  constructor(executor) {
    // 初始化状态为Pending
    this.state = 'Pending';
    // 成功或失败的结果值
    this.value = undefined;
    // 存储then方法的回调队列
    this.onFulfilled = [];
    this.onRejected = [];

    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }

  resolve(value) {
    // 状态改变为Fulfilled,并存储值
    if (this.state === 'Pending') {
      this.state = 'Fulfilled';
      this.value = value;
      this.onFulfilled.forEach(fn => fn(value));
    }
  }

  reject(error) {
    // 状态改变为Rejected,并存储错误
    if (this.state === 'Pending') {
      this.state = 'Rejected';
      this.value = error;
      this.onRejected.forEach(fn => fn(error));
    }
  }

  then(onFulfilled, onRejected) {
    // 处理Fulfilled状态
    if (this.state === 'Fulfilled') {
      onFulfilled(this.value);
    } else if (this.state === 'Rejected' && typeof onRejected === 'function') {
      onRejected(this.value);
    }

    // 将回调加入队列,等待状态改变时调用
    return new MyPromise((resolve, reject) => {
      if (this.state === 'Pending') {
        this.onFulfilled.push(value => {
          // 提供对 then 的链式调用支持
          resolve(onFulfilled ? onFulfilled(value) : value);
        });
        this.onRejected.push(error => {
          reject(onRejected ? onRejected(error) : error);
        });
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

30 JS 类型有哪些?

(1)考察点分析

  • 基本数据类型:理解JavaScript的五种基本数据类型及其用途。
  • 引用数据类型:掌握对象(Object)、数组(Array)、函数(Function)等引用类型的结构和行为。
  • 特殊值:识别并区分NaNundefined这两种特殊值。
  • 类型转换:了解JavaScript中类型转换的规则,包括强制类型转换和隐式类型转换。
  • 类型检测:掌握如何使用typeofinstanceof等操作符进行类型检测。

(2)最终答案

JavaScript的类型可以分为两大类:基本数据类型和引用数据类型。

  • 基本数据类型
// Number
let age = 25;

// String
let name = 'John Doe';

// Boolean
let isApproved = false;

// Undefined
let x;

// Null
let y = null;

// Symbol (ES6 新增)
let mySymbol = Symbol('mySymbol');
  • 引用数据类型
// Object
let person = {
  name: 'Alice',
  age: 30
};

// Array
let numbers = [1, 2, 3, 4, 5];

// Function
function sayHello() {
  console.log('Hello, World!');
}
  • 特殊值 NaN:不是一个数字(Not a Number),是一个代表错误数值的特殊值。 undefined:表示变量已声明但未初始化。
  • 类型转换 JavaScript在运算或操作中可能会进行类型转换,例如:
'5' + 3; // "53",字符串和数字相加时,数字会被转换为字符串
  • 类型检测 typeof:返回一个变量类型的字符串值。 instanceof:检测一个实例是否是特定构造函数的实例。 Object.prototype.toString.call():返回对象的类型字符串,是一种更准确的方法。

31 js的几种作用域,有什么区别?

(1)考察点分析

  • 理解作用域的概念:考察候选人是否理解作用域及其在 JavaScript 中的重要性。
  • 识别不同类型的作用域:考察候选人是否能够识别并区分不同类型的作用域,包括全局作用域、函数作用域和块级作用域。
  • 应用场景与区别:考察候选人是否能够理解并解释不同作用域在实际应用中的区别和使用场景。

(2)最终答案

  1. 全局作用域

    • 在全局作用域中声明的变量可以在任何地方访问。
    • 例如:
      var globalVar = "I am a global variable";
      
      function showGlobalVar() {
          console.log(globalVar); // 可以访问到 globalVar
      }
      
      showGlobalVar(); // 输出:I am a global variable
      
    • 未使用 varletconst 声明的变量会自动成为全局变量:
      function createGlobalVar() {
          undeclaredVar = "I am also a global variable";
      }
      
      createGlobalVar();
      console.log(undeclaredVar); // 输出:I am also a global variable
      
  2. 函数作用域

    • 在函数作用域中声明的变量只能在函数内部访问。
    • 例如:
      function localScope() {
          var localVar = "I am a local variable";
          console.log(localVar); // 可以访问到 localVar
      }
      
      localScope();
      console.log(localVar); // 报错:localVar 未定义
      
  3. 块级作用域

    • 在块级作用域中声明的变量只能在块内部访问。

    • 例如:

      if (true) {
          let blockVar = "I am a block variable";
          console.log(blockVar); // 可以访问到 blockVar
      }
      
      console.log(blockVar); // 报错:blockVar 未定义
      
    • const 也具有块级作用域:

      for (const i = 0; i < 3; i++) {
          console.log(i); // 输出 0, 1, 2
      }
      
      console.log(i); // 报错:i 未定义
      

32 let,var,const是哪种作用域下定义的?

(1)考察点分析

  • 理解关键字的作用域:考察候选人是否理解 let、var 和 const 的作用域规则。
  • 变量声明和作用域控制:考察候选人是否能够正确使用这些关键字来控制变量的作用域。
  • 区别和最佳实践:考察候选人是否能够解释它们之间的区别,并在实际开发中选择适当的关键字。

(2)最终答案

  1. var 的作用域

    • var 声明的变量具有函数作用域。
    • 在函数内部声明的变量只能在函数内部访问。
    • 变量声明会被提升到函数或全局作用域的顶部,但初始化仍在原来的位置。

    例如:

    function varExample() {
        console.log(varVar); // 输出 undefined(变量提升)
        var varVar = "I am a var variable";
        console.log(varVar); // 输出 "I am a var variable"
    }
    
    varExample();
    console.log(varVar); // 报错:varVar 未定义
    
  2. let 的作用域

    • let 声明的变量具有块级作用域。
    • 变量只能在块 {} 内部访问,不会被提升到块的顶部。 例如:
if (true) {
    let letVar = "I am a let variable";
    console.log(letVar); // 输出 "I am a let variable"
}
console.log(letVar); // 报错:letVar 未定义
  1. const 的作用域
    • const 声明的变量也具有块级作用域。
    • 与 let 类似,const 声明的变量只能在块 {} 内部访问。
    • 声明时必须初始化,且声明后不能重新赋值。 例如:
if (true) {
    const constVar = "I am a const variable";
    console.log(constVar); // 输出 "I am a const variable"
}

console.log(constVar); // 报错:constVar 未定义

// 声明后不能重新赋值
const anotherConstVar = "Initial value";
anotherConstVar = "New value"; // 报错:Assignment to constant variable.

33 定位有哪几种?

(1)考察点分析

  • 理解不同定位方式的概念:考察候选人是否理解 CSS 中不同定位方式的基本概念。
  • 识别定位方式的区别和应用场景:考察候选人是否能够识别并解释不同定位方式的区别和适用场景。
  • 实际应用:考察候选人是否能够将定位方式应用于实际布局和样式设计中。

(2)最终答案

  1. 静态定位(static

    • 默认定位方式。
    • 元素按照文档流的正常顺序进行布局,不受 toprightbottomleftz-index 属性的影响。
  2. 相对定位(relative

    • 相对于元素自身的正常位置进行偏移。
    • 元素仍然占据原来的空间,但可以通过 toprightbottomleft 属性进行偏移。
  3. 绝对定位(absolute

    • 相对于最近的定位祖先元素(非 static)进行定位。
    • 如果没有定位祖先,则相对于初始包含块(通常是视口)进行定位。
    • 元素脱离文档流,不占据空间。
  4. 固定定位(fixed

    • 相对于视口进行定位。
    • 元素脱离文档流,不占据空间。
    • 在滚动页面时,元素保持相对于视口的位置不变。
  5. 粘性定位(sticky

    • 元素根据用户的滚动位置进行定位。
    • 在跨越特定阈值前表现为相对定位,之后表现为固定定位。
    • 常用于制作粘性导航栏等效果。

34 HTTP和HTTPS的区别

(1)考察点分析

  • 基础知识:了解HTTP和HTTPS协议的基本原理和区别。
  • 安全性:理解HTTPS的安全性特性以及如何实现。
  • 性能:知道HTTPS对性能的影响及其优化方法。
  • 应用场景:能够解释何时应使用HTTP,何时应使用HTTPS。

(2)最终答案

  1. 协议概述

    • HTTP是超文本传输协议,数据以明文方式传输。
    • HTTPS是在HTTP的基础上加入SSL/TLS层,用于加密数据传输,保证数据的安全性。
  2. 安全性

    • HTTP的数据传输是明文的,容易被中间人攻击、窃听和篡改。
    • HTTPS通过SSL/TLS加密数据,确保数据在传输过程中的保密性、完整性和真实性。
  3. 端口

    • HTTP默认使用端口80。
    • HTTPS默认使用端口443。
  4. 性能

    • HTTP无加密开销,速度相对较快。
    • HTTPS由于加解密过程,初次握手时会有一定的性能开销,但可以通过硬件加速、HTTP/2等技术优化。
  5. 证书

    • HTTP不需要证书。
    • HTTPS需要SSL/TLS证书,证书可以从证书颁发机构(CA)获取。
  6. SEO和用户信任

    • HTTPS对SEO友好,搜索引擎更倾向于排名HTTPS站点。同时,浏览器会标记不安全的HTTP站点,影响用户信任。

35 Vue 3 中 ref 和 reactive 底层实现的区别

(1)考察点分析

  • Vue 3 的响应式系统:了解 Vue 3 的响应式系统的基本原理。
  • ref 和 reactive 的使用场景:理解 ref 和 reactive 的使用场景及其区别。
  • 底层实现细节:掌握 ref 和 reactive 在底层实现上的不同点。
  • 优化和性能:理解两者的性能影响和优化方法。

(2)最终答案

在 Vue 3 中,refreactive 都是用于创建响应式数据的工具,但它们在底层实现上有所不同。

  1. 基本概念

    • ref 用于创建包含单个值的响应式引用,适用于基本类型和需要单独引用的场景。
    • reactive 用于创建包含多个属性的响应式对象,适用于复杂对象。
  2. 使用场景

    • ref 适合处理基本类型(如字符串、数字)或需要对整个对象进行引用变更的场景。例如:
      const count = ref(0);
      count.value++;  // 通过 .value 访问和修改值
      
    • reactive 适合处理复杂对象和嵌套结构。例如:
      const state = reactive({ count: 0, nested: { foo: 'bar' } });
      state.count++;  // 直接访问和修改属性
      state.nested.foo = 'baz';  // 深层次属性的响应式更新
      
  3. 底层实现

    • ref
      • ref 使用 RefImpl 类来实现。其核心是一个对象,用于存储内部值,并通过 gettersetter 来拦截读取和修改操作。
      • ref 包装的值变化时,dep 依赖收集机制会通知所有依赖此 ref 的副作用进行更新。
    • reactive
      • reactive 使用 Proxy 来代理对象的所有属性访问和变更。
      • 代理对象的 gettersetter 触发依赖收集和派发更新,确保对象内部的任何属性变化都能引起响应式更新。
  4. 性能和优化

    • 对于单一值或简单对象,ref 的性能较优,因为其包装开销较小。
    • 对于复杂对象或嵌套对象,reactive 提供了更全面的响应式支持,但在频繁的深层次属性访问和变更时可能有较高的性能开销。

36 v-for 加 key 的作用

(1)考察点分析

  • Vue.js 渲染机制:了解 Vue.js 的虚拟 DOM 渲染机制。
  • 性能优化:理解使用 key 对性能优化的影响。
  • 组件重用:知道 key 如何影响组件重用和状态管理。

(2)最终答案

在 Vue.js 中,v-for 指令用于渲染一个列表,当列表项发生变化时,通过 key 来帮助 Vue.js 更高效地更新视图。具体作用如下:

  1. 唯一标识key 为每个 v-for 渲染的元素提供一个唯一的标识符。例如:
   <div v-for="item in items" :key="item.id">
     {{ item.name }}
   </div>

这里,item.id 作为每个元素的唯一 key。

问题:v-for 加 key 的作用 考察点 Vue.js 渲染机制:了解 Vue.js 的虚拟 DOM 渲染机制。 性能优化:理解使用 key 对性能优化的影响。 组件重用:知道 key 如何影响组件重用和状态管理。 答案思路 基本概念:

v-for:用于在 Vue.js 模板中渲染一个列表。 key:在 v-for 中提供一个唯一标识符,用于优化列表渲染。 Vue.js 渲染机制:

Vue.js 使用虚拟 DOM 来高效地更新视图。 当数据发生变化时,Vue.js 通过 Diff 算法比较新旧虚拟 DOM 树,并进行最小化的 DOM 更新。 key 的作用:

唯一标识:每个 v-for 渲染的元素需要一个唯一的 key 以标识自己。 优化渲染性能:key 帮助 Vue.js 更高效地识别元素变化,减少不必要的 DOM 更新和重绘。 保持组件状态:当列表项顺序变化时,key 能确保组件实例和其内部状态正确对应。 不使用 key 的问题:

错误的更新:Vue.js 可能错误地重用元素,导致显示错误或状态不一致。 性能下降:缺少 key 会使 Vue.js 在更新列表时无法有效识别变化,导致更多的 DOM 操作。 答题示例 markdown 复制代码 在 Vue.js 中,v-for 指令用于渲染一个列表,当列表项发生变化时,通过 key 来帮助 Vue.js 更高效地更新视图。具体作用如下:

  1. 唯一标识key 为每个 v-for 渲染的元素提供一个唯一的标识符。例如:
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
    

这里,item.id 作为每个元素的唯一 key。

  1. 优化渲染性能: Vue.js 使用虚拟 DOM 和 Diff 算法来更新视图。当列表数据发生变化时,key 帮助 Vue.js 准确识别哪些元素发生了变化,避免不必要的 DOM 更新和重绘,从而提升渲染性能。

  2. 保持组件状态: 当列表项顺序变化时,key 能确保组件实例和其内部状态正确对应。例如,如果列表顺序改变或有新项插入,key 能帮助 Vue.js 确保每个组件实例的状态保持一致。

  3. 不使用 key 的问题: 如果不使用 key,Vue.js 在更新列表时可能会错误地重用元素,导致显示错误或状态不一致。比如:

<div v-for="item in items">
  {{ item.name }}
</div>

这种情况下,Vue.js 可能会错误地重用 DOM 元素,导致数据展示错误。此外,缺少 key 也会使 Vue.js 无法高效地识别变化,导致性能下降。

综上所述,v-for 中使用 key 是为了确保每个列表元素有一个唯一的标识符,从而帮助 Vue.js 更高效地更新视图并保持组件状态一致。