前端面试题

CSS 常见问题解答

1. CSS盒模型

CSS盒模型由内容区域(content)、内边距(padding)、边框(border)和外边距(margin)组成,分为两种类型:

  • 标准盒模型 :元素宽度 = content宽度
  • 替代盒模型 (通过 box-sizing: border-box 设置):元素宽度 = content + padding + border
2. CSS选择器的优先级

优先级从高到低:

  1. !important(覆盖所有规则)
  2. 内联样式(如 <div style="color:red">
  3. ID 选择器(如 #header
  4. 类/伪类/属性选择器(如 .btn, :hover, [type="text"]
  5. 元素/伪元素选择器(如 div, ::before
  6. 通配符/继承样式(如 *
    计算规则(a, b, c) 形式,其中:
  • a = ID 选择器数量
  • b = 类/伪类选择器数量
  • c = 元素选择器数量
    例如:#nav .item 优先级为 (1,1,0)
3. 隐藏元素的方法
方法 特点
display: none 元素不占空间,无法交互,DOM 中移除渲染
visibility: hidden 元素占空间,不可见但保留布局,无法交互
opacity: 0 元素占空间,完全透明,可交互(如点击事件)
position: absolute; left: -9999px 移出视口,可访问性差,但保留交互性
4. px 和 rem 的区别
  • px:绝对单位,1px 对应屏幕物理像素点,固定大小不受父元素影响。
  • rem :相对单位,基于根元素(<html>)的字体大小(默认 1rem = 16px)。
    优势:响应式设计中,通过修改根字体大小可全局调整布局(如 html { font-size: 62.5%; } 使 1rem ≈ 10px)。
5. 重绘和重排的区别
  • 重排(Reflow)
    布局变化(如修改宽度、位置、字体大小),浏览器重新计算元素几何属性,触发整个渲染树更新。
    常见触发操作width, height, margin, display, offsetTop 等。
  • 重绘(Repaint)
    外观变化但不影响布局(如修改颜色、背景),浏览器仅重绘受影响区域。
    性能影响 :重排一定触发重绘,重绘开销较小。优化建议:减少 DOM 操作,使用 transform 代替位置修改。
6. 元素水平垂直居中

Flexbox 方案(推荐)

复制代码
.parent {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center;     /* 垂直居中 */
}

Grid 方案

复制代码
.parent {
  display: grid;
  place-items: center; /* 同时居中 */
}

绝对定位方案

复制代码
.parent { position: relative; }
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 兼容不同尺寸元素 */
}
7. CSS 属性继承性
  • 可继承属性
    文本相关(如 font-family, color, line-height, text-align)和部分列表属性(如 list-style)。
  • 不可继承属性
    布局相关(如 width, height, margin, padding, border)和显示属性(如 display, background)。
    强制继承 :对不可继承属性使用 inherit 值(如 border: inherit)。
8. CSS 预处理器使用

预处理器(如 Sass, Less)通过变量、嵌套、混合宏(mixin)等功能提升开发效率:

复制代码
// Sass 示例
$primary-color: #3498db;
@mixin center-flex {
  display: flex;
  justify-content: center;
  align-items: center;
}
.container {
  @include center-flex;
  background: lighten($primary-color, 20%);
}

优势:代码复用、模块化、函数运算(如颜色处理),最终编译为标准 CSS。

js常见问题

1. JavaScript 有哪三部分组成?

JavaScript 主要由以下三部分组成:

  • ECMAScript :定义了语言的核心语法、数据类型、语句和基本对象(如 Array, Object)。
  • DOM (Document Object Model):用于操作HTML和XML文档的接口,允许JavaScript访问和修改页面内容。
  • BOM (Browser Object Model) :提供与浏览器交互的接口,包括 windowlocationnavigator 等对象,用于控制浏览器窗口、历史记录等。

2. JavaScript 有哪些内置对象?

JavaScript 的内置对象包括:

  • 基本对象:ObjectFunctionArrayStringNumberBooleanSymbol(ES6 新增)。
  • 其他对象:Date(日期处理)、Math(数学运算)、RegExp(正则表达式)、JSON(JSON 解析)、Error(错误处理),以及全局对象如 window(浏览器环境)或 global(Node.js 环境)。

3. 操作数据的方法有哪些?

操作数据的方法主要包括:

  • 数组方法:如 push()pop()shift()unshift()slice()splice()map()filter()reduce()

  • 字符串方法:如 charAt()substring()replace()split()toUpperCase()

  • 对象方法:如 Object.keys()Object.values()Object.assign()(ES6)。

  • 其他:使用循环(如 forforEach)或高阶函数处理数据。 示例代码:

    const arr = [1, 2, 3];
    arr.push(4); // 添加元素
    console.log(arr); // 输出 [1, 2, 3, 4]

4. JavaScript 对数据类型的检测方式有哪些?

检测数据类型的方式包括:

  • typeof 操作符:返回基本类型的字符串表示,如 typeof "hello" 返回 "string",但对 null 返回 "object"(历史遗留问题)。
  • instanceof 操作符:检测对象是否属于某个构造函数,如 [] instanceof Array 返回 true
  • Object.prototype.toString.call():最准确的方式,返回 [object Type] 格式,如 Object.prototype.toString.call([]) 返回 "[object Array]"
  • Array.isArray():专门检测数组类型,ES6 新增。

5. 闭包是什么,有什么特点?

闭包(Closure)是指一个函数能访问并记住其外部作用域变量的能力。特点包括:

  • 封装性:可以创建私有变量,避免全局污染。

  • 内存驻留:外部变量不会被垃圾回收,可能导致内存泄漏。

  • 延迟执行:常用于回调函数、模块化设计。 示例:

    function outer() {
    let count = 0;
    return function inner() {
    count++;
    return count;
    };
    }
    const counter = outer();
    console.log(counter()); // 输出 1

6. 前端内存泄漏怎么理解?

前端内存泄漏指浏览器中不再使用的内存没有被释放,导致应用变慢或崩溃。常见原因:

  • 未清理的全局变量或事件监听器。
  • 闭包引用外部变量。
  • DOM 元素引用未被删除(如移除元素后仍有变量引用它)。 解决方法:使用工具(如 Chrome DevTools Memory 面板)检测,手动解除引用、移除事件监听器。

7. 事件委托是什么?

事件委托是一种优化事件处理的技术,利用事件冒泡将事件监听器绑定到父元素,而非每个子元素。优点:

  • 减少内存占用(只需一个监听器)。

  • 动态元素无需重新绑定。 示例:点击列表项时输出内容。

    document.getElementById('parent').addEventListener('click', (e) => {
    if (e.target.tagName === 'LI') {
    console.log(e.target.textContent);
    }
    });

8. 基础数据类型和引用数据的区别?

  • 基础数据类型 :包括 StringNumberBooleannullundefinedSymbol(ES6)。值直接存储在栈内存中,赋值时复制值。

  • 引用数据类型 :包括 ObjectArrayFunction。值存储在堆内存中,变量存储引用地址;赋值时复制地址,多个变量可能共享同一数据。 示例:

    let a = 1; // 基础类型,a 存储值
    let b = a; // b 复制值,a 和 b 独立
    a = 2;
    console.log(b); // 输出 1

    let obj1 = { key: 'value' }; // 引用类型
    let obj2 = obj1; // obj2 复制引用地址
    obj1.key = 'new';
    console.log(obj2.key); // 输出 'new',共享数据

9. 原型链是什么?

原型链是 JavaScript 实现继承的机制。每个对象都有一个原型对象(通过 __proto__ 访问),原型对象也有原型,形成链式结构。当访问对象属性时,如果自身不存在,会沿原型链向上查找,直到 Object.prototype(原型链顶端)。构造函数通过 prototype 属性设置原型。 示例:

复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log('Hello, ' + this.name);
};
const person = new Person('Alice');
person.sayHello(); // 输出 "Hello, Alice",方法来自原型

10. new 操作符具体做了什么?

new 操作符创建对象实例时执行以下步骤:

  1. 创建一个新空对象。

  2. 将新对象的原型链接到构造函数的 prototype 属性。

  3. 执行构造函数,将 this 绑定到新对象。

  4. 如果构造函数返回非对象值,则返回新对象;否则返回该值。 示例:

    function Car(model) {
    this.model = model;
    }
    const myCar = new Car('Toyota');
    console.log(myCar.model); // 输出 "Toyota"

11. JavaScript 是如何实现继承的?

JavaScript 主要通过原型链实现继承。常见方式:

  • 原型链继承:子类原型指向父类实例。

  • 构造函数继承:在子类构造函数中调用父类构造函数。

  • 组合继承:结合原型链和构造函数。

  • ES6 类继承 :使用 classextends 关键字(语法糖,底层基于原型)。 示例(ES6):

    class Animal {
    constructor(name) {
    this.name = name;
    }
    speak() {
    console.log(this.name + ' makes a sound.');
    }
    }
    class Dog extends Animal {
    speak() {
    console.log(this.name + ' barks.');
    }
    }
    const dog = new Dog('Rex');
    dog.speak(); // 输出 "Rex barks."

12. JavaScript 的设计原理是什么?

JavaScript 的设计原理基于:

  • 单线程事件循环:处理异步操作,避免阻塞。
  • 原型继承:替代传统类继承,更灵活。
  • 动态类型:变量类型在运行时确定。
  • 函数式编程支持:函数是一等公民,支持高阶函数。
  • ECMAScript 规范:由 ECMA International 标准化,确保跨平台兼容。

13. JavaScript 中关于 this 指向的问题?

this 指向当前执行上下文的对象,规则包括:

  • 全局环境:this 指向 window(浏览器)或 global(Node.js)。

  • 函数调用:默认指向全局对象,但在严格模式('use strict')下为 undefined

  • 方法调用:指向调用该方法的对象。

  • 构造函数:指向新创建的对象。

  • 显式绑定:使用 call()apply()bind() 改变 this。 示例:

    const obj = {
    value: 'obj',
    logValue() {
    console.log(this.value);
    }
    };
    obj.logValue(); // 输出 "obj",this 指向 obj
    const log = obj.logValue;
    log(); // 输出 undefined(严格模式下报错),this 指向全局

14. script 标签的 async 和 defer 有什么区别?

  • async:脚本异步加载,加载完成后立即执行,不保证顺序。适用于独立脚本。
  • defer :脚本异步加载,但延迟到文档解析完成后执行,保持顺序。适用于依赖 DOM 的脚本。 区别:async 可能阻塞渲染,执行顺序不确定;defer 不阻塞,执行顺序确定。

15. setTimeout 最小执行时间是多少?

setTimeout 的最小执行时间取决于浏览器和系统,通常为 4ms(根据 HTML5 规范)。但实际可能因浏览器优化或标签页后台运行而延长,不能保证精确。

16. ES5 和 ES6 有什么区别?

ES5(ECMAScript 5)和 ES6(ECMAScript 2015)的主要区别:

  • 语法增强 :ES6 引入 letconst(块级作用域)、箭头函数、模板字符串、解构赋值。
  • 新特性 :ES6 添加类(class)、模块(import/export)、Promise、Symbol 等。
  • 功能扩展:ES6 提供原生模块化、迭代器、生成器,而 ES5 依赖库实现类似功能。 ES6 是重大更新,提升了代码可读性和开发效率。

17. ES6 的新特性有哪些?

ES6 关键新特性包括:

  • 块级作用域变量:letconst
  • 箭头函数:() => {},简化语法,自动绑定 this
  • 模板字符串:${variable} 支持多行字符串。
  • 解构赋值:如 const {a, b} = obj
  • 类与继承:classextendssuper
  • 模块化:importexport
  • Promise:处理异步操作。
  • 新数据类型:Symbol(唯一值)、MapSet
  • 默认参数和剩余参数:function(a = 1, ...args)

18. call、apply、bind 三者的区别?

三者都用于改变函数 this 指向:

  • call :立即调用函数,参数逐个传递。如 func.call(thisArg, arg1, arg2)

  • apply :立即调用函数,参数以数组传递。如 func.apply(thisArg, [arg1, arg2])

  • bind :返回一个新函数,绑定 this 和部分参数,但不立即执行。如 const newFunc = func.bind(thisArg, arg1)。 示例:

    function greet(greeting) {
    console.log(greeting + ', ' + this.name);
    }
    const person = { name: 'Alice' };
    greet.call(person, 'Hello'); // 输出 "Hello, Alice"
    greet.apply(person, ['Hi']); // 输出 "Hi, Alice"
    const boundGreet = greet.bind(person, 'Hey');
    boundGreet(); // 输出 "Hey, Alice"

19. 用递归的时候有没有遇到什么问题?

使用递归时常见问题:

  • 栈溢出:递归深度过大导致调用栈溢出(如未设置基线条件)。
  • 性能问题:递归可能比迭代慢,尤其在多次调用时。
  • 内存消耗:每个递归调用占用栈空间。 解决方法:设置递归终止条件、改用循环(迭代)、使用尾递归优化(ES6 支持,但浏览器实现有限)。

20. 如何实现一个深拷贝?

深拷贝创建完全独立的副本,包括嵌套对象。实现方式:

  • JSON 方法 :简单但无法处理函数、Symbol 或循环引用。

    复制代码
    const obj = { a: 1, b: { c: 2 } };
    const deepCopy = JSON.parse(JSON.stringify(obj));
  • 递归函数 :手动处理所有类型。

    复制代码
    function deepClone(source) {
      if (source === null || typeof source !== 'object') return source;
      const target = Array.isArray(source) ? [] : {};
      for (let key in source) {
        if (source.hasOwnProperty(key)) {
          target[key] = deepClone(source[key]);
        }
      }
      return target;
    }
  • 库函数 :如 Lodash 的 _.cloneDeep()

21. 事件循环是什么?

事件循环(Event Loop)是 JavaScript 处理异步操作的机制。基于单线程,它管理调用栈、任务队列(宏任务和微任务)。工作流程:

  1. 同步代码执行。
  2. 异步任务(如 setTimeout)完成后,回调进入任务队列。
  3. 事件循环检查调用栈为空时,从队列中取出任务执行。 宏任务包括 setTimeoutsetInterval;微任务包括 Promise.thenMutationObserver。微任务优先于宏任务执行。

22. Ajax 是什么,怎么实现?

Ajax(Asynchronous JavaScript and XML)是一种异步 Web 开发技术,用于在后台与服务器交换数据,无需刷新页面。实现方式:

  • 使用 XMLHttpRequest 对象。

  • 现代方式:fetch API(ES6)。 示例(使用 fetch):

    fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));

23. GET 和 POST 有什么区别?

  • GET:请求数据,参数在 URL 中可见(长度受限),可缓存,幂等(多次请求结果相同)。
  • POST:提交数据,参数在请求体中(不可见,长度大),不可缓存,非幂等(可能改变服务器状态)。 用途:GET 用于获取数据(如搜索),POST 用于发送数据(如表单提交)。

24. Promise 的内部原理是什么,它的优缺点是什么?

  • 内部原理 :Promise 是异步编程解决方案,基于状态机(状态:pending、fulfilled、rejected)。通过 then 方法添加回调,支持链式调用。
  • 优点 :避免回调地狱、支持错误传播(catch)、易于组合(Promise.all)。
  • 缺点:无法取消、错误处理需显式捕获、可能导致微任务队列膨胀。

25. Promise 和 async/await 的区别是什么?

  • Promise :基于回调链,使用 then()catch() 处理异步。

  • async/await :ES8 语法糖,基于 Promise,用同步方式写异步代码。async 函数返回 Promise,await 暂停执行直到 Promise 解决。 区别:async/await 更简洁、可读性强,但需在函数内使用;Promise 更底层,适合简单链式调用。 示例:

    // Promise
    fetchData().then(data => console.log(data)).catch(err => console.error(err));

    // async/await
    async function getData() {
    try {
    const data = await fetchData();
    console.log(data);
    } catch (err) {
    console.error(err);
    }
    }

26. 浏览器的存储方式有哪些?

  • Cookie:小数据(约4KB),随请求发送到服务器,可设置过期时间。
  • Web Storage
    • localStorage:永久存储,同源可用。
    • sessionStorage:会话级存储,标签页关闭后清除。
  • IndexedDB:非关系型数据库,支持大容量和复杂查询。
  • Cache Storage:用于 Service Workers 缓存资源。

27. token 存在 sessionStorage 还是 localStorage?

token 通常存储在 localStorage 中,因为它持久化,适合长期保存登录状态。但 sessionStorage 更安全(会话结束清除),适用于敏感数据。选择取决于需求:localStorage 用于"记住我"功能,sessionStorage 用于临时会话。

28. token 的登录流程是什么?

典型 token 登录流程:

  1. 用户输入凭据(用户名/密码)。
  2. 客户端发送请求到服务器验证。
  3. 服务器验证通过后,生成 token(如 JWT)并返回。
  4. 客户端存储 token(如 localStorage)。
  5. 后续请求在 HTTP 头(如 Authorization: Bearer <token>)中包含 token。
  6. 服务器验证 token 并响应数据。

29. 页面渲染的过程是怎么样的?

页面渲染过程:

  1. 解析 HTML:构建 DOM 树。
  2. 解析 CSS:构建 CSSOM 树。
  3. 合并成渲染树:结合 DOM 和 CSSOM,生成渲染树(只包含可见元素)。
  4. 布局(Layout):计算元素位置和大小。
  5. 绘制(Paint):将元素渲染到屏幕。
  6. 合成(Composite):处理图层合并(如 CSS 动画)。 优化:减少重排(布局改变)和重绘(外观改变)。

30. DOM 树和渲染树有什么区别?

  • DOM 树:由 HTML 解析生成,表示文档结构,包括所有节点(如隐藏元素)。
  • 渲染树 :由 DOM 树和 CSSOM 树合并生成,只包含可见元素(如排除 display: none),用于布局和绘制。 区别:渲染树是 DOM 树的子集,仅用于视觉渲染。

31. 精灵图和 base64 的区别是什么?

  • 精灵图(Sprite) :多个小图标合并成一张大图,通过 CSS background-position 显示部分。减少 HTTP 请求,但需手动管理位置。
  • Base64:将图像编码为文本字符串,直接嵌入 CSS 或 HTML。减少 HTTP 请求,但增加文件大小(约30%),不适合大图。 用途:精灵图用于小图标;Base64 用于小图标或内联图像。

32. SVG 格式了解多少?

SVG(Scalable Vector Graphics)是一种矢量图像格式,基于 XML。特点:

  • 缩放不失真,适合图标和图形。
  • 可直接嵌入 HTML,支持 CSS 和 JavaScript 操作。
  • 文件小,支持动画(如 SMIL)和交互。 示例:<svg width="100" height="100"><circle cx="50" cy="50" r="40" fill="red"/></svg>

33. 了解 JWT 吗?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于安全传输信息作为 JSON 对象。结构:

  • Header:算法和类型。
  • Payload:数据(如用户 ID)。
  • Signature:签名验证完整性。 优点:无状态、可跨域;缺点:token 大小较大、无法直接废止。用于身份验证和授权。

34. npm 的底层环境是什么?

npm(Node Package Manager)的底层环境是 Node.js 运行时。它基于 JavaScript,提供包管理和脚本执行功能。核心组件包括:

  • 注册表(Registry):存储包。
  • CLI 工具:安装、发布包。
  • 依赖解析:处理 package.json 文件。

35. HTTP 协议规定的请求头和响应头有什么?

  • 请求头(Request Headers) :客户端发送,如:
    • Host:服务器域名。
    • User-Agent:客户端信息。
    • Accept:可接受的响应类型。
    • Authorization:认证信息。
    • Content-Type:请求体类型(如 application/json)。
  • 响应头(Response Headers) :服务器发送,如:
    • Content-Type:响应体类型。
    • Cache-Control:缓存策略。
    • Set-Cookie:设置 Cookie。
    • Status:HTTP 状态码。

36. 浏览器的缓存策略是什么?

缓存策略控制资源存储和重用:

  • 强缓存 :通过 Cache-ControlExpires 头,直接从本地缓存读取,不请求服务器。
  • 协商缓存 :通过 Last-Modified/If-Modified-SinceETag/If-None-Match 头,向服务器验证资源是否过期。 设置:服务器配置 HTTP 头,如 Cache-Control: max-age=3600

37. 防抖和节流是什么?

  • 防抖(Debounce) :事件触发后延迟执行,若在延迟内再次触发,则重新计时。用于输入框搜索(避免频繁请求)。

    复制代码
    function debounce(func, delay) {
      let timer;
      return function() {
        clearTimeout(timer);
        timer = setTimeout(() => func.apply(this, arguments), delay);
      };
    }
  • 节流(Throttle) :事件触发后,在指定时间内只执行一次。用于滚动事件(控制频率)。

    复制代码
    function throttle(func, limit) {
      let inThrottle;
      return function() {
        if (!inThrottle) {
          func.apply(this, arguments);
          inThrottle = true;
          setTimeout(() => inThrottle = false, limit);
        }
      };
    }

38. 什么是同源策略?

同源策略(Same-Origin Policy)是浏览器安全机制,限制不同源(协议、域名、端口相同)的脚本交互。防止恶意脚本窃取数据。跨源访问需 CORS(跨域资源共享)或 JSONP 解决。

39. 什么是 JSON?

JSON(JavaScript Object Notation)是一种轻量级数据交换格式,基于文本,易于读写。语法类似 JavaScript 对象,但独立于语言。用于前后端数据传输。 示例:{"name": "Alice", "age": 30}

40. 有没有做过无感登录?

无感登录(自动登录)实现方式:

  • 用户首次登录后,服务器返回 token 或 refresh token。
  • 客户端存储 token(如 localStorage)。
  • 后续访问时,自动发送 token 验证。
  • 加入 token 过期机制和刷新逻辑(如用 refresh token 获取新 token)。 关键点:安全处理 token 存储,避免 XSS 攻击。

41. 大文件上传是怎么做的?

大文件上传常用方法:

  • 分片上传:将文件切分成小块,分别上传,服务器合并。

  • 断点续传:记录上传进度,失败后从中断处继续。

  • 使用 Web Workers:后台处理分片,避免阻塞。

  • 库支持 :如 axios 或专门库(如 resumable.js)。 示例代码(分片上传):

    async function uploadFile(file) {
    const chunkSize = 5 * 1024 * 1024; // 5MB
    for (let start = 0; start < file.size; start += chunkSize) {
    const chunk = file.slice(start, start + chunkSize);
    await fetch('/upload', { method: 'POST', body: chunk });
    }
    }

42. 当数据没有请求过来的时候,该怎么做?

处理数据未请求到的情况:

  • 前端处理
    • 显示加载状态(如 spinner)或占位内容(Skeleton Screen)。
    • 设置超时机制(如 setTimeout),超时后提示用户。
    • 错误处理:使用 try/catch 或 Promise 的 catch 块,显示友好错误消息。
    • 重试逻辑:自动或手动重试请求。
  • 优化策略
    • 使用缓存(如 localStorage)显示旧数据。

    • 实现离线模式(Service Workers)。

    • 监控网络状态(如 navigator.onLine)。 示例:

      fetch('https://api.example.com/data')
      .then(response => {
      if (!response.ok) throw new Error('Network error');
      return response.json();
      })
      .then(data => renderData(data))
      .catch(error => {
      console.error(error);
      showError('数据加载失败,请重试');
      });

HTML5与CSS3相关问题

1. 语义化的理解

语义化指通过选择合适的HTML标签表达内容结构含义(而非仅样式),例如:

  • 使用<header>表示页眉,<nav>表示导航,<article>表示独立内容
  • 优点:
    • SEO优化:爬虫更易理解页面结构
    • 可访问性:屏幕阅读器能准确定位内容
    • 代码可维护性:开发者通过标签名即可理解区块功能
    • 未来兼容性:浏览器能更智能解析新特性
2. HTML5与CSS3新特性

HTML5 核心新特性:

  • 语义化标签<section>, <figure>, <time>
  • 多媒体支持<audio>, <video>原生嵌入
  • 图形处理<canvas>绘图和SVG矢量图支持
  • 本地存储localStorage/sessionStorage
  • 表单增强
    • 输入类型:email, date, range
    • 属性:placeholder, required, pattern

CSS3 核心新特性:

  • 选择器增强 :属性选择器[type="text"]、伪类:nth-child(n)
  • 视觉效果
    • 圆角:border-radius: 10px
    • 阴影:box-shadow: 2px 2px 5px #ccc
    • 渐变:background: linear-gradient(red, yellow)
  • 动画与过渡
    • 过渡:transition: width 0.5s ease
    • 动画:@keyframes + animation
  • 响应式布局@media媒体查询
  • 弹性盒子display: flex
3. rem适配原理

rem(root em)是相对于根元素<html>字体尺寸的单位,适配流程:

复制代码
/* 步骤1:设置基准值(设计稿750px宽时) */
html {
  font-size: 100px; /* 1rem = 100px */
}

/* 步骤2:元素使用rem单位 */
.box {
  width: 1.5rem; /* 实际宽度150px */
}

/* 步骤3:通过媒体查询动态调整根字体 */
@media (max-width: 480px) {
  html {
    font-size: 64px; /* 小屏缩小基准值 */
  }
}

数学关系

若设计稿元素宽度w,基准字体b,则CSS中尺寸为\\frac{w}{b} rem。

屏幕宽度变化时,通过JS或媒体查询更新b值,实现等比缩放。

4. 解决的移动端兼容问题
  1. 1像素边框问题
    现象 :高清屏物理像素比>1时,1px CSS边框变粗
    方案

    复制代码
    .thin-border {
      transform: scaleY(0.5); /* Y轴压缩50% */
    }
  2. 点击延迟300ms
    原因 :早期浏览器等待双击缩放判断
    方案

    复制代码
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    或使用touch-action: manipulation;

  3. 默认样式差异
    现象 :不同设备按钮/链接样式不一致
    方案

    复制代码
    /* 统一重置 */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box; /* 标准盒模型 */
    }
  4. REM适配兼容
    方案 :使用flexible.js动态计算根字体大小,覆盖Android 4.4以下版本兼容问题。

Vue.js 全面解析

一、Vue 基础概念
  1. v-if 和 v-show 的区别

    • v-if:条件渲染,元素在条件为真时渲染到DOM,为假时从DOM中移除。适用于切换频率低的场景,因为涉及DOM操作开销。
    • v-show:通过CSS的display属性控制显示/隐藏(display: none),元素始终在DOM中。适用于频繁切换的场景,性能更好。
  2. 如何理解 MVVM

    MVVM(Model-View-ViewModel)是一种架构模式:

    • Model:数据层(如API返回的数据)。
    • View:UI层(如Vue模板)。
    • ViewModel:Vue实例,负责数据绑定和事件处理,连接Model和View。自动同步数据变化,实现双向数据绑定。
  3. v-for 中的 key 值作用是什么
    key 属性用于标识列表项的唯一性,帮助Vue高效更新DOM:

    • 提供唯一标识(如ID),避免复用错误。
    • 优化diff算法性能,减少不必要的DOM操作。
  4. vue 生命周期

    Vue组件从创建到销毁的全过程:

    • 创建阶段beforeCreate(实例初始化)、created(数据观测完成)。
    • 挂载阶段beforeMount(模板编译)、mounted(DOM渲染完成)。
    • 更新阶段beforeUpdate(数据变化前)、updated(DOM更新后)。
    • 销毁阶段beforeUnmount(卸载前)、unmounted(卸载完成)。
  5. created 和 mounted 去请求数据,有什么区别

    • created:在实例创建后调用,此时DOM未渲染。适合初始化数据(如API请求),但无法访问DOM元素。
    • mounted:在DOM渲染完成后调用。适合需要操作DOM的场景(如基于元素尺寸的请求),但请求可能延迟页面显示。
  6. vue 中的修饰符有哪些

    修饰符用于简化事件或表单处理:

    • 事件修饰符 :如.stop(阻止冒泡)、.prevent(阻止默认行为)、.once(只触发一次)。
    • 表单修饰符 :如.lazy(输入完成后更新)、.number(输入转为数字)、.trim(去除首尾空格)。
二、组件与表单
  1. element ui 是怎么做表单验证

    基于Vue的v-model和Element UI的el-form组件:

    • 使用rules属性定义验证规则(如必填、长度)。
    • 通过validate方法触发验证,错误时显示提示信息。
  2. vue 如何进行组件通信

    常见方式:

    • Props/Events :父组件通过props传数据给子组件,子组件通过$emit触发事件。
    • Provide/Inject :祖先组件provide数据,后代组件inject获取。
    • Vuex:全局状态管理。
    • Event Bus :使用Vue实例作为中央事件总线($on, $emit)。
    • Refs :父组件通过ref访问子组件实例。
  3. keep-alive 是什么,怎么使用

    • 作用:缓存组件实例,避免重复渲染(如保留表单状态)。

    • 使用 :包裹动态组件或路由组件,例如:

      复制代码
      <keep-alive>
        <component :is="currentComponent"></component>
      </keep-alive>
  4. axios 是怎么做封装的

    封装以复用请求逻辑:

    • 创建实例:axios.create() 设置基础URL、超时时间。
    • 拦截器:interceptors处理请求/响应(如添加token、错误处理)。
    • 封装API模块:统一导出方法(如getUser())。
三、路由管理
  1. vue 路由怎么传参

    方式:

    • Paramsthis.$router.push({ name: 'user', params: { id: 1 } }),在路由中定义path: '/user/:id'
    • Querythis.$router.push({ path: '/user', query: { id: 1 } }),参数在URL中(/user?id=1)。
  2. vue 路由的 hash 模式和 history 模式的区别

    • Hash 模式 :URL带#(如http://example.com/#/home),基于浏览器hashchange事件,无需服务器配置。
    • History 模式 :URL无#(如http://example.com/home),基于HTML5 History API,需服务器支持(避免404错误)。
  3. 路由拦截是怎么实现的

    使用router.beforeEach全局前置守卫:

    复制代码
    router.beforeEach((to, from, next) => {
      if (to.meta.requiresAuth && !isAuthenticated) next('/login'); // 验证登录
      else next(); // 放行
    });
  4. vue 的动态路由

    基于参数匹配:

    • 定义:{ path: '/user/:id', component: User }
    • 访问:this.$route.params.id 获取参数。
  5. 如何解决刷新后二次加载路由

    在Vue Router中,确保路由持久化:

    • 使用addRoute动态添加路由。
    • 结合Vuex存储路由状态,避免刷新丢失。
四、状态管理(Vuex)
  1. vuex 刷新数据会丢失吗,怎么解决

    • 会丢失:Vuex状态存储在内存中,刷新页面时重置。
    • 解决
      • 使用localStoragesessionStorage持久化数据。
      • 插件如vuex-persistedstate自动同步。
  2. computed 和 watch 的区别

    • computed :计算属性,基于依赖数据缓存结果(如fullName = firstName + lastName),适合派生数据。
    • watch:侦听器,观察数据变化执行回调(如API请求),适合异步操作。
  3. vuex 在什么场景会去使用,属性有哪些

    • 场景:跨组件共享状态(如用户登录信息、全局配置)。
    • 属性
      • state:存储数据。
      • getters:计算状态(类似computed)。
      • mutations:同步修改状态(通过commit触发)。
      • actions:异步操作(通过dispatch触发)。
      • modules:模块化管理。
  4. vue 双向数据绑定原理

    基于Object.defineProperty(Vue2)或Proxy(Vue3):

    • Vue2:劫持数据属性的getter/setter,通知依赖更新视图。
    • Vue3 :使用Proxy代理对象,直接监听属性变化,性能更好。
  5. diff 算法和虚拟 DOM

    • 虚拟DOM:轻量级JS对象表示真实DOM,减少直接操作。
    • Diff算法 :比较新旧虚拟DOM树(如通过key优化),仅更新变化部分,提高渲染效率。
五、性能优化与高级特性
  1. vue 和 jquery 的区别

    • Vue:响应式框架,组件化开发,数据驱动视图。
    • jQuery:库,操作DOM为主,无数据绑定机制。
  2. vuex 的响应式处理

    Vuex 的响应式处理依赖于 Vue.js 的响应式系统,通过将 store 状态包装为响应式对象,并结合 mutations 和 actions 来管理状态变化。这确保了状态与视图的自动同步,提升了开发效率。在实际项目中,遵循 Vuex 的规则(如使用 mutations 修改状态)和 Vue 的响应式最佳实践,可以避免常见问题。

  3. 如何搭建脚手架

    使用Vue CLI:

    复制代码
    npm install -g @vue/cli
    vue create my-project
    cd my-project
    npm run serve
  4. 如何封装一个组件

    步骤:

    • 定义props接收数据。
    • 使用slots提供内容分发。
    • 暴露事件($emit)。
    • 示例:封装按钮组件。
  5. 封装一个可复用的组件,需要满足什么条件

    • 单一职责:只做一件事。
    • 可配置:通过props控制行为。
    • 可扩展:支持slots和事件。
    • 文档化:提供使用说明。
  6. vue 的过滤器怎么使用

    用于文本格式化:

    • 定义:Vue.filter('capitalize', value => value.toUpperCase())
    • 使用:{``{ message | capitalize }}
  7. vue 中如何做强制刷新

    • 组件级:this.$forceUpdate() 强制重新渲染。
    • 全局:修改key属性触发更新(如:key="reloadKey")。
  8. vue3 和 vue2 有哪些区别

    • 性能:Vue3使用Proxy,优化响应式。
    • API :Vue3引入Composition API(setup())。
    • 体积:Vue3更小(Tree-shaking支持)。
    • 其他:Fragment(多根节点)、Teleport(传送组件)。
  9. vue 的性能优化怎么做

    • 代码层面:懒加载组件、v-if/v-show合理使用。
    • 构建优化:代码分割、压缩资源。
    • 运行时:避免深层响应式对象、使用v-once。
  10. 首屏优化该怎么做

    • 懒加载:路由和组件按需加载。
    • SSR:使用Nuxt.js服务端渲染。
    • CDN:静态资源分发。
    • 预渲染:生成静态HTML。
  11. vue3 的性能为什么比 vue2 好

    • 响应式优化:Proxy 比 Object.defineProperty 更高效。
    • 编译优化:静态树提升、补丁标志减少diff开销。
    • Tree-shaking:按需引入代码,减小体积。
  12. vue3 为什么使用 proxy

    • 直接代理整个对象,无需递归劫持属性。
    • 支持数组和动态属性监听,解决Vue2的限制。
六、项目实践与扩展
  1. 说一下对组件的理解

    组件是可复用的UI单元,封装HTML、CSS和JS:

    • 提高代码复用性。
    • 便于团队协作。
    • 通过组合构建复杂应用。
  2. 如何规划项目文件

    标准结构:

    复制代码
    src/
      ├── assets/       # 静态资源
      ├── components/   # 通用组件
      ├── views/        # 页面组件
      ├── router/       # 路由配置
      ├── store/        # Vuex 状态
      ├── services/     # API 封装
      └── App.vue       # 根组件
  3. 是否使用过 nuxt.js

    Nuxt.js 是基于Vue的服务端渲染框架:

    • 支持SSR(首屏优化)。
    • 简化路由和状态管理。
    • 适合SEO密集型应用。
  4. SEO 如何优化

    • SSR/SSG:使用Nuxt.js服务端渲染或静态生成。
    • Meta 标签 :动态设置titledescription(如vue-meta插件)。
    • 语义化HTML:合理使用header、section等标签。
    • Sitemap:生成XML站点地图。

ECharts常用组件介绍

ECharts是一个基于JavaScript的开源数据可视化库,广泛应用于Web开发中,用于创建交互式图表。其核心功能通过一系列组件实现,这些组件协同工作来构建完整的图表。以下是一些常用组件的详细说明,帮助您逐步理解和使用:

  1. 标题(title)

    用于设置图表的标题,包括主标题和副标题。可以自定义位置、样式和链接。

    示例配置:

    复制代码
    title: {
        text: '销售数据统计',
        subtext: '2023年度报告'
    }
  2. 图例(legend)

    显示不同数据系列的标识(如颜色标记),便于用户区分多个系列。支持交互操作,如点击隐藏/显示系列。

    示例配置:

    复制代码
    legend: {
        data: ['产品A', '产品B', '产品C'],
        orient: 'horizontal'
    }
  3. 坐标轴(axis)

    包括x轴(xAxis)和y轴(yAxis),用于定义数据的坐标系。支持类型(如类目型、数值型)、刻度、标签格式等设置。

    示例配置:

    复制代码
    xAxis: {
        type: 'category',
        data: ['一月', '二月', '三月']
    },
    yAxis: {
        type: 'value'
    }
  4. 系列(series)

    核心组件,定义图表类型(如折线图、柱状图、饼图)和数据源。每个系列对应一组数据点,并可配置样式和动画。

    示例配置(柱状图):

    复制代码
    series: [{
        name: '销量',
        type: 'bar',
        data: [120, 200, 150]
    }]
  5. 提示框(tooltip)

    当用户悬停在数据点上时,显示详细信息。支持格式化内容、触发方式(如鼠标悬停或点击)和自定义样式。

    示例配置:

    复制代码
    tooltip: {
        trigger: 'axis',
        formatter: '{b}: {c}'
    }
  6. 工具栏(toolbox)

    提供实用工具,如保存图片、数据视图、数据缩放和重置。方便用户交互和导出图表。

    示例配置:

    复制代码
    toolbox: {
        feature: {
            saveAsImage: {},
            dataView: {}
        }
    }
  7. 数据缩放(dataZoom)

    允许用户缩放和漫游图表区域,适用于大数据集的可视化。支持滑动条或内置缩放控件。

    示例配置:

    复制代码
    dataZoom: [{
        type: 'slider',
        start: 0,
        end: 100
    }]
  8. 视觉映射(visualMap)

    将数据值映射到视觉元素(如颜色、大小),常用于热力图或散点图。支持连续或分段映射。

    示例配置:

    复制代码
    visualMap: {
        min: 0,
        max: 100,
        calculable: true
    }
  9. 网格(grid)

    定义图表的布局区域,包括位置、大小和边距。确保多个组件(如坐标轴)在画布上正确对齐。

    示例配置:

    复制代码
    grid: {
        left: '10%',
        right: '10%',
        containLabel: true
    }
  10. 其他辅助组件

    • 标记点(markPoint):在系列中添加特殊点(如最大值、最小值)。
    • 标记线(markLine):添加参考线(如平均值线)。
    • 数据标签(label):在数据点上直接显示数值标签。

UniApp分包

在UniApp中,分包(Subpackage)是一种优化策略,用于将代码拆分成多个独立的包,以减少启动加载时间并管理代码体积(尤其在小程序平台如微信小程序)。以下是详细步骤和实现方法,确保结构清晰、易于理解。

1. 理解分包概念

  • 目的:主包包含启动页面和核心资源,分包包含非关键页面(如二级页面),实现按需加载。
  • 适用平台:主要针对小程序平台(微信、支付宝等),在H5或App端可能无效或不需配置。
  • 限制:微信小程序要求主包不超过2MB,总包不超过8MB(具体以平台文档为准)。

2. 配置分包步骤

在UniApp项目中,通过修改配置文件实现分包。以下是核心步骤:

步骤1: 创建分包目录
  • 在项目根目录下创建分包文件夹,例如subpackageA(名称自定义)。

  • 将分包页面文件(如.vue文件)放入此目录,例如:

    复制代码
    project-root/
    ├── pages/          # 主包页面
    ├── subpackageA/    # 分包目录
    │   ├── pages/      # 分包页面文件夹
    │   │   ├── page1.vue
    │   │   └── page2.vue
    ├── pages.json      # 配置文件
    └── ...
步骤2: 修改pages.json文件
  • pages.json中添加subPackages字段,定义分包信息。

  • 关键字段

    • root: 分包根目录路径(字符串)。
    • pages: 分包页面列表,每个页面包括pathstyle(类似主包页面配置)。
  • 示例配置

    复制代码
    {
      "pages": [
        {
          "path": "pages/index/index",
          "style": { ... }  // 主包页面配置
        }
      ],
      "subPackages": [
        {
          "root": "subpackageA",  // 分包根目录
          "pages": [
            {
              "path": "pages/page1",  // 页面路径,相对于root
              "style": { ... }       // 页面样式配置
            },
            {
              "path": "pages/page2",
              "style": { ... }
            }
          ]
        }
      ],
      // 其他全局配置...
    }
步骤3: 编译和测试
  • 运行编译命令(如npm run dev:mp-weixin),检查控制台输出,确保分包成功。

  • 在开发者工具中查看包大小:主包应较小,分包单独加载。

  • 访问分包页面:在代码中使用路由跳转,例如:

    复制代码
    uni.navigateTo({
      url: '/subpackageA/pages/page1'  // 路径格式:/分包根目录/页面路径
    });

3. 完整代码示例

以下是一个简单的UniApp项目结构及pages.json配置示例:

复制代码
// pages.json
{
  "pages": [
    {
      "path": "pages/home",
      "style": {
        "navigationBarTitleText": "首页"
      }
    }
  ],
  "subPackages": [
    {
      "root": "userCenter",
      "pages": [
        {
          "path": "profile",
          "style": {
            "navigationBarTitleText": "个人中心"
          }
        },
        {
          "path": "settings",
          "style": { ... }
        }
      ]
    }
  ],
  "globalStyle": { ... }
}

4. 注意事项

  • 路径规则:分包页面路径必须从分包根目录开始,不能跨分包引用资源。

  • 资源加载:分包内的图片、组件等需放在分包目录内,避免主包过大。

  • 兼容性 :在非小程序平台(如H5),分包配置可能被忽略,建议使用条件编译:

    复制代码
    // #ifdef MP-WEIXIN
    // 分包相关代码
    // #endif
  • 优化建议

    • 将低频页面放入分包。
    • 使用uni.preloadSubpackage预加载分包(微信小程序支持)。
  • 调试工具:使用微信开发者工具的分包分析功能,检查包大小和依赖。

常见问题解决

  • 分包未生效 :检查pages.json语法是否正确,路径是否匹配实际目录。
  • 包大小超限:优化图片、移除未使用代码,或拆分更多分包。
  • 路由错误 :确保跳转路径包含分包根目录(如/userCenter/profile)。