普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月11日首页

Vue3 代码编写规范 | 避坑指南+团队协作标准

2026年4月10日 23:05

一、Vue3 通用基础规范(必看!统一编码底线)

1.1 编码格式规范:避免格式混乱,提升代码可读性

  • 缩进:统一使用4个空格缩进(禁止使用Tab),确保不同编辑器渲染一致。
  • 换行:每个独立代码块之间空1行,逻辑相关的代码块紧密排列,提升可读性。
  • 分号:语句结尾统一添加分号,避免因自动分号插入(ASI)导致的语法歧义。
  • 引号:模板内属性使用双引号(""),Script中字符串优先使用单引号(''),特殊场景(如嵌套引号)可灵活切换。
  • 注释:关键逻辑、复杂业务代码必须添加注释,注释需简洁明了,说明“为什么做”而非“做了什么”;组件开头可添加类注释,说明组件功能、Props、使用场景。

1.2 命名规范:一眼看懂用途,降低协作成本

核心原则:JS/TS领域遵循camelCase(小驼峰)/PascalCase(大驼峰),HTML领域使用kebab-case(连字符),保持项目内命名一致性,提升代码可读性与协作效率。

  • 变量/函数:使用camelCase,首字母小写,动词开头命名函数(如handleClick、fetchData),名词开头命名变量(如userInfo、goodsList)。
  • 常量:使用UPPER_SNAKE_CASE(全大写下划线分隔),如const API_BASE_URL = 'api.example.com'。
  • 类/组件:使用PascalCase,首字母大写,组件名需为多个单词(根组件App除外),避免与HTML原生元素冲突,如UserProfile、GoodsCard而非Todo、Button。
  • 自定义指令:使用kebab-case,如v-focus、v-scroll-to,符合HTML属性命名规范。

二、Vue3 单文件组件(SFC)规范(核心重点!避坑关键)

2.1 组件结构规范:固定结构,避免渲染异常

单文件组件(.vue)内部顺序固定为:template → script → style,每个部分独立成块,结构清晰;template内最多包含一个顶级元素,避免多根节点导致的渲染异常。

<!-- 正确示例 -->
<template>
    <div class="user-profile">
        <!-- 组件内容 -->
    </div>
</template>

<script setup>
// 逻辑代码
</script>

<style scoped>
// 样式代码
</style>

2.2 Template 规范:高效渲染,减少性能损耗

  • 指令使用:v-bind、v-on可使用简写(:、@),v-slot使用#简写;指令顺序统一为:v-for → v-if → v-bind → v-on,如<div v-for="item in list" :key="item.id" v-if="item.visible" @click="handleClick">
  • v-for 要求:必须搭配key,key值需为唯一标识(如id),禁止使用index作为key;避免在v-for内使用v-if,可通过计算属性过滤数据后再渲染,提升性能。
  • 组件引用:模板中使用组件时,优先使用PascalCase标签(如),明确区分原生HTML元素;DOM模板中必须使用kebab-case(如),因HTML不区分大小写。
  • 属性绑定:多个属性分行书写,每个属性占一行,提升可读性;布尔属性直接写属性名,如而非。

2.3 Script 规范:简洁高效,符合Vue3最佳实践

2.3.1 语法选择:优先<script setup>,拒绝混合语法

优先使用<script setup>语法(Vue3推荐),简洁高效;复杂组件(如需要生命周期钩子、Props验证、 emits定义)可结合Options API,但同一项目内语法需统一,禁止混合使用。

2.3.2 导入顺序:规范排序,提升代码可维护性

导入语句按以下顺序排列,不同类别之间空1行,提升可读性:

  1. Vue内置API(如ref、computed、watch);
  2. 第三方库(如Pinia、Axios、Element Plus);
  3. 项目内部组件(如子组件、基础组件);
  4. 工具函数、常量、样式文件;
  5. API接口请求函数。
<script setup>
// 1. Vue内置API
import { ref, computed, watch } from 'vue';
// 2. 第三方库
import { useUserStore } from 'pinia';
import axios from 'axios';
// 3. 内部组件
import BaseButton from './BaseButton.vue';
import UserCard from '@/components/UserCard.vue';
// 4. 工具函数/常量
import { formatDate } from '@/utils/format';
import { API_BASE_URL } from '@/constants';
// 5. API接口
import { fetchUserInfo } from '@/api/user';
</script>

2.3.3 Props 规范:严谨定义,避免传参异常

  • 命名:Props定义使用camelCase(如userName),模板中传递时使用kebab-case(如user-name),Vue会自动完成转换。
  • 定义:Props需详细定义,至少指定类型;必填项标注required: true,可选值通过validator验证,提升组件可维护性与容错性。
// 正确示例
const props = defineProps({
    // 基础类型定义
    userId: {
        type: Number,
        required: true,
        validator: (value) => value > 0 // 验证值为正整数
    },
    // 布尔类型,推荐前缀is
    isDisabled: {
        type: Boolean,
        default: false
    },
    // 数组/对象类型,默认值需用函数返回,避免引用共享
    goodsList: {
        type: Array,
        default: () => []
    },
    userInfo: {
        type: Object,
        default: () => ({
            name: '',
            age: 0
        })
    }
});

2.3.4 Emits 规范:明确声明,避免事件混乱

  • 命名:定义时使用camelCase(如updateValue),模板中监听时使用kebab-case(如@update-value),符合HTML属性命名习惯。
  • 定义:通过defineEmits明确声明组件触发的事件,禁止隐式触发事件;事件参数需清晰,避免传递过多参数,复杂参数建议封装为对象。
// 正确示例
const emit = defineEmits(['updateValue', 'deleteItem']);

// 触发事件(传递单个参数)
const handleValueChange = (value) => {
    emit('updateValue', value);
};

// 触发事件(传递复杂参数,封装为对象)
const handleDelete = (id, name) => {
    emit('deleteItem', { id, name });
};

2.3.5 异步逻辑规范:优雅处理,避免报错中断

  • 优先使用async/await语法,禁止使用Promise链式调用(then/catch),代码更易读且便于调试。
  • 所有async/await必须包裹try/catch,或在调用时用.catch()捕获错误,避免控制台报错和逻辑中断;错误处理需友好,可结合UI提示反馈给用户。
  • 高频触发的异步请求(如搜索输入框)必须加防抖,避免无效请求,推荐用组合式函数useDebounce封装复用。
// 正确示例(async/await + try/catch)
const fetchUser = async () => {
    try {
        const res = await fetchUserInfo(); // 调用异步接口
        return res.data;
    } catch (err) {
        console.error('获取用户信息失败:', err);
        ElMessage.error('加载失败,请重试');
        throw err; // 如需上层处理,可重新抛出错误
    }
};

// 错误示例(Promise链式调用)
const fetchUser = () => {
    return fetchUserInfo()
        .then(res => res.data)
        .catch(err => {
            console.error('获取用户信息失败:', err);
            ElMessage.error('加载失败,请重试');
            throw err;
        });
};

2.3.6 TypeScript 规范:强类型约束,减少类型报错

  • 禁止滥用any类型:除非明确兼容所有类型(如第三方库无类型声明),否则必须用具体类型、unknown或泛型;若用any,需加注释说明原因。
  • 接口(interface)与类型别名(type)区分:定义对象/类的结构用interface(支持扩展、实现);定义联合类型、交叉类型或简单类型别名用type。
  • Props/Emits 类型:使用TypeScript时,优先通过泛型定义Props和Emits类型,提升类型安全性。
// 正确示例(interface定义对象结构)
interface Goods {
    id: number;
    name: string;
    price: number;
    stock: number;
}
const goods: Goods = { id: 1, name: '手机', price: 5999, stock: 100 };

// 正确示例(type定义联合类型)
type GoodsCategory = 'electronics' | 'clothes' | 'food';

// Props类型定义
interface Props {
    userId: number;
    isDisabled?: boolean;
}
const props = defineProps<Props>();

// Emits类型定义
const emit = defineEmits<{
    (e: 'updateValue', value: string): void;
    (e: 'deleteItem', params: { id: number; name: string }): void;
}>();

2.4 Style 规范:避免污染,提升样式复用性

  • 作用域:组件样式优先使用scoped(如),避免样式污染;全局样式统一放在src/styles目录下,禁止在组件内写全局样式(除非特殊需求)。
  • 命名:样式类名使用kebab-case,与组件名、功能对应,如.user-profile、goods-card;避免使用无意义的类名(如box1、content2)。
  • 样式顺序:按“布局 → 尺寸 → 样式 → 交互”的顺序编写,如position → width → background → hover。
  • 复用:公共样式(如颜色、字体、间距)提取为变量,统一管理;重复使用的样式封装为Mixin或自定义样式类,提升复用性。

三、Vue3 组件设计规范(高复用+低耦合,团队必守)

3.1 组件拆分原则:拒绝大组件,提升可维护性

  • 单一职责:一个组件只负责一个功能,避免“大组件”(代码超过500行),复杂功能拆分为多个子组件,如将用户列表拆分为UserList(列表容器)、UserItem(列表项)、UserSearch(搜索框)。
  • 高复用低耦合:可复用组件(如按钮、输入框)提取为基础组件(放在src/components/base目录),组件间通过Props传递数据、Emits触发事件,禁止直接操作父/子组件数据。
  • 命名区分:基础组件统一前缀Base(如BaseButton、BaseInput),业务组件按功能命名(如OrderList、PaymentForm),布局组件前缀Layout(如LayoutHeader、LayoutSidebar)。

3.2 组件通信规范:清晰传参,避免数据混乱

  • 父子组件:父传子用Props,子传父用Emits,禁止子组件直接修改Props(单向数据流);复杂数据可通过v-model双向绑定(Vue3支持多v-model)。
  • 跨层级组件:优先使用Pinia状态管理,或使用provide/inject(适用于深层组件通信,需明确注入类型),禁止使用EventBus(易造成事件混乱)。
  • 同级组件:通过父组件中转(子传父 → 父传另一个子),或使用Pinia共享状态,避免直接通信。

四、Vue3 Pinia 状态管理规范(替代Vuex,简洁高效)

4.1 Store 设计原则:模块化拆分,避免冗余

  • 模块化:按业务模块拆分Store(如userStore、cartStore、goodsStore),避免单一Store过大;Store命名统一前缀use(如useUserStore),使用camelCase命名法。
  • 状态划分:State(状态)、Getters(计算属性)、Actions(异步/同步操作)分离,禁止在Getters中修改State,禁止在组件中直接修改Store的State(需通过Actions)。

4.2 状态操作规范:规范调用,避免状态异常

// stores/user.ts 正确示例
import { defineStore } from 'pinia';
import { fetchUserInfo } from '@/api/user';

export const useUserStore = defineStore('user', () => {
    // State:定义状态,使用ref/reactive
    const userInfo = ref({
        id: 0,
        name: '',
        avatar: ''
    });
    const isLogin = ref(false);

    // Getters:计算属性,依赖State,只读
    const userNickname = computed(() => userInfo.value.name || '未知用户');

    // Actions:处理同步/异步操作,修改State
    const setUserInfo = (info) => {
        userInfo.value = info;
        isLogin.value = true;
    };

    const logout = () => {
        userInfo.value = { id: 0, name: '', avatar: '' };
        isLogin.value = false;
    };

    // 异步Action,使用async/await
    const loadUserInfo = async (userId) => {
        try {
            const res = await fetchUserInfo(userId);
            setUserInfo(res.data);
        } catch (err) {
            console.error('加载用户信息失败:', err);
            throw err;
        }
    };

    return { userInfo, isLogin, userNickname, setUserInfo, logout, loadUserInfo };
});

五、Vue3 Vue Router 路由规范(优化体验,避免路由踩坑)

  • 路由命名:路由name使用kebab-case(如user-profile),与组件名、路径对应,提升可读性;路由path使用kebab-case(如/user/profile),符合URL命名规范。
  • 路由懒加载:所有路由组件均使用懒加载(() => import('组件路径')),减少首屏加载时间;基础组件无需懒加载。
  • 路由守卫:全局守卫用于权限控制(如登录验证),路由独享守卫用于单个路由的特殊控制,组件内守卫用于组件内的生命周期控制;避免在守卫中写复杂业务逻辑。
  • 参数传递:路径参数(params)用于必填参数(如/user/:id),查询参数(query)用于可选参数(如/list?page=1&size=10);接收参数时需做类型校验。
// router/index.ts 正确示例
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
    {
        path: '/',
        name: 'home',
        component: () => import('@/views/Home.vue'),
        meta: { title: '首页', requiresAuth: false }
    },
    {
        path: '/user/:id',
        name: 'user-profile',
        component: () => import('@/views/UserProfile.vue'),
        meta: { title: '用户详情', requiresAuth: true },
        props: true // 自动将params转为Props传递给组件
    },
    {
        path: '/404',
        name: '404',
        component: () => import('@/views/404.vue')
    },
    {
        path: '/:pathMatch(.*)*',
        redirect: '/404' // 路由匹配失败,重定向到404
    }
];

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes
});

// 全局前置守卫:登录验证
router.beforeEach((to, from, next) => {
    const userStore = useUserStore();
    if (to.meta.requiresAuth && !userStore.isLogin) {
        next('/login');
    } else {
        document.title = to.meta.title || 'Vue3 项目';
        next();
    }
});

export default router;

六、Vue3 工程化与协作规范(团队高效协作必备)

6.1 文件目录规范:结构清晰,便于维护

项目目录结构清晰,按功能模块划分,便于维护和协作,推荐目录结构如下:

src/
├── assets/          // 静态资源(图片、字体、图标等),命名使用kebab-case
│   ├── images/
│   ├── fonts/
│   └── icons/
├── components/      // 公共组件
│   ├── base/        // 基础组件(BaseButton、BaseInput等)
│   ├── layout/      // 布局组件(LayoutHeader、LayoutSidebar等)
│   └── business/    // 业务组件(OrderList、GoodsCard等)
├── views/           // 页面视图组件,命名使用PascalCase
│   ├── Home.vue
│   ├── UserProfile.vue
│   └── Order/
│       ├── OrderList.vue
│       └── OrderDetail.vue
├── stores/          // Pinia状态管理,命名使用useXXXStore.ts
│   ├── useUserStore.ts
│   └── useCartStore.ts
├── router/          // 路由配置
│   └── index.ts
├── api/             // API接口封装,按模块划分
│   ├── user.ts
│   └── goods.ts
├── utils/           // 工具函数,命名使用camelCase
│   ├── format.ts
│   └── request.ts
├── constants/       // 常量定义
│   └── index.ts
├── styles/          // 全局样式
│   ├── index.scss
│   └── variables.scss
├── composables/     // 组合式函数,复用逻辑
│   └── useDebounce.ts
└── App.vue          // 根组件

6.2 代码提交规范(Git Commit):清晰可追溯,便于审查

采用Conventional Commits标准,提交信息清晰,便于代码审查和版本回溯,格式为:(): 。

  • type(提交类型):feat(新功能)、fix(Bug修复)、docs(文档变更)、style(代码样式调整,不影响逻辑)、refactor(重构,不修复Bug也不增加功能)、test(测试相关)、chore(构建/工具变更)。
  • scope(范围):指定提交影响的模块(如user、router、goods),无明确范围可省略。
  • subject(描述):简洁明了,说明提交内容,首字母小写,结尾不加句号。
// 示例
feat(user): add password reset UI
fix(router): handle 404 redirect
chore(deps): upgrade axios to 1.2.0
docs: update component usage documentation

6.3 代码校验规范:统一格式,减少冲突

  • 工具配置:项目必须集成ESLint、Prettier,统一代码格式;安装依赖:npm install -D eslint prettier eslint-plugin-vue @typescript-eslint/parser eslint-config-prettier husky lint-staged。
  • 自动校验:配置pre-commit钩子(husky + lint-staged),提交代码时自动校验格式,不符合规范的代码禁止提交;开发过程中使用编辑器插件(如ESLint、Prettier)实时校验。
  • ESLint配置:继承vue3-recommended规范,结合项目需求调整规则,禁止禁用必要的校验规则(如禁止滥用any、禁止Props修改)。

七、Vue3 性能与安全规范(优化体验+规避风险)

7.1 性能优化规范:提速降耗,提升用户体验

  • 响应式优化:避免过度使用reactive,简单数据使用ref;大数据列表使用v-virtual-scroller(虚拟滚动),减少DOM渲染数量。
  • 计算属性与监听:computed用于依赖状态的计算(缓存结果),watch用于监听状态变化并执行副作用(如请求接口);避免在watch中写复杂逻辑,避免监听过多状态。
  • 资源优化:静态资源(图片)压缩,使用CDN加载;路由懒加载、组件懒加载;避免重复请求(添加请求缓存、防抖节流)。
  • DOM优化:减少DOM操作,避免在模板中使用复杂表达式;使用v-show替代v-if(频繁切换场景),v-if替代v-show(一次性渲染场景)。

7.2 安全规范:规避漏洞,保障项目稳定

  • XSS防护:避免直接插入HTML(如v-html),若必须使用,需对内容进行过滤;禁止使用eval、with等危险语法。
  • 接口安全:请求接口时添加token验证;敏感数据(如密码)加密后传输;接口返回数据需做类型校验,避免恶意数据导致的报错。
  • 依赖安全:定期更新项目依赖,避免使用存在安全漏洞的依赖包;安装依赖前检查依赖安全性(如使用npm audit)。

八、Vue3 补充规范(细节拉满,避免踩坑)

  • 兼容性:兼容主流浏览器(Chrome、Edge、Firefox最新版本),如需兼容旧浏览器(如IE11),需添加相应的polyfill。
  • 可维护性:代码书写简洁,避免冗余(如重复代码封装为函数/组件);注释清晰,便于后续维护和他人理解。
  • 一致性:项目内所有代码严格遵循本规范,团队成员需统一认知;新增规范需团队讨论确认后补充,避免个人风格差异导致的代码混乱。
  • 废弃代码:禁止保留无用代码(如注释掉的代码、未使用的变量/函数/组件),提交代码前删除废弃内容,保持代码整洁。
昨天 — 2026年4月10日首页

前端必看!JS高频实用案例(单行代码+实战场景+十大排序)

2026年4月10日 17:14

一、JS单行高频实用案例(25个,直接复制可用)

1. 变量值交换(不使用临时变量)

适用场景:快速交换两个变量的值,无需额外声明临时变量,简化代码,适用于简单值交换场景。

// 核心单行代码(适用于数字、字符串等基本类型)
let a = 10, b = 20;
[a, b] = [b, a]; // 解构赋值实现交换
// 结果: a = 20, b = 10

// 补充案例(字符串交换)
let str1 = 'hello', str2 = 'world';
[str1, str2] = [str2, str1];
// 结果: str1 = 'world', str2 = 'hello'

总结:利用数组解构赋值,简洁实现两个变量值的交换,无需临时变量,代码简洁易读,仅适用于基本数据类型;若为引用类型(对象、数组),交换的是引用地址,原数据会受影响。

2. 快速生成随机数(指定范围)

适用场景:生成指定区间内的随机整数(如随机抽奖、随机排序、模拟随机数据),前端高频使用场景。

// 核心单行代码(min=最小值,max=最大值,包含min和max)
const getRandomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
// 案例1:生成1-10的随机整数
const random1 = getRandomNum(1, 10);
// 结果: 1-10之间的任意整数(如5、8、10)

// 案例2:生成100-200的随机整数
const random2 = getRandomNum(100, 200);
// 结果: 100-200之间的任意整数(如156、199、100)

总结:Math.random()生成0-1(不包含1)的随机小数,乘以(max-min+1)可将范围扩展到0-(max-min),Math.floor()取整后加min,即可得到指定区间内的随机整数,灵活适配各类随机场景。

3. 浅克隆对象(复制顶层属性)

适用场景:快速复制对象的顶层属性,生成新对象,适用于简单对象(无嵌套对象)的复制,避免修改原对象。

const originalObj = { name: '张三', age: 24 }; // 修正原文档拼写错误(original0bj→originalObj)
const clonedObj = { ...originalObj }; // 核心单行代码
// 结果: clonedObj = { name: '张三', age: 24 }
// 验证:修改clonedObj.age = 25,originalObj.age仍为24

总结:利用扩展运算符(...)复制对象的所有可枚举自身属性,生成新对象。注意:若对象包含嵌套对象,嵌套对象仍为引用类型,修改嵌套对象会影响原对象,此时需使用深克隆。

4. 合并对象

适用场景:将多个对象合并为一个新对象,重复属性会被后一个对象覆盖。

const obj1 = { name: '张三' };
const obj2 = { age: 22 };
const mergedObj = { ...obj1, ...obj2 }; // 核心单行代码
// 结果: mergedObj = { name: '张三', age: 22 }

总结:扩展运算符可快速合并多个对象,若存在重复属性,后面的对象属性会覆盖前面的。例如:const obj3 = { name: '李四', gender: '男' }; 合并后mergedObj = { name: '李四', age:22, gender: '男' }。

5. 清理数组(删除所有假值)

适用场景:快速过滤数组中的假值,保留有效数据。

const arr = [0, 1, false, 2, '', 3, null, NaN, undefined]; // 补充完整假值案例
const cleanedArray = arr.filter(Boolean); // 核心单行代码
// 结果: cleanedArray = [1, 2, 3]

总结:Array.prototype.filter() 结合Boolean函数,自动过滤所有假值(0、false、null、''、NaN、undefined),无需手动判断,高效简洁。

6. 将NodeList转换为数组

适用场景:获取DOM元素集合(NodeList)后,需使用数组方法(如map、filter)操作时。

// 修正原文档语法疏漏(补充扩展运算符包裹)
const nodesArray = [...document.querySelectorAll('div')]; 
// 结果: nodesArray 为包含所有div元素的数组,可使用map、filter等方法

总结:扩展运算符可将类数组(NodeList、arguments等)转换为真正的数组,从而使用数组的所有方法。例如:nodesArray.map(div => div.style.color = 'red'),可批量修改所有div的字体颜色。

7. 检查数组是否满足指定条件

适用场景:判断数组中是否存在满足条件的元素(some),或所有元素是否都满足条件(every)。

// 案例1:检查数组中是否存在负数
const arr1 = [1, 2, 3, -5, 4];&#xA;const hasNegativeNumbers = arr1.some(num => num < 0);
// 结果: hasNegativeNumbers = true

// 案例2:检查数组所有元素是否均为正数
const allPositive = arr1.every(num => num > 0);
// 结果: allPositive = false

// 补充案例:检查数组中是否有大于10的元素
const arr2 = [5, 8, 12, 3];
const hasGreaterThan10 = arr2.some(num => num > 10);
// 结果: hasGreaterThan10 = true

总结:some() 只要有一个元素满足条件就返回true,every() 需所有元素满足条件才返回true,两者均为短路操作(找到符合条件/不符合条件的元素后立即停止遍历)。

8. 将文本复制到剪贴板

适用场景:实现点击按钮复制文本、复制链接等功能。

// 核心单行代码(异步操作,可结合async/await使用)
navigator.clipboard.writeText('Text to copy'); // 修正原文档拼写(Textto→Text to)

// 完整示例(结合按钮点击)
document.querySelector('#copyBtn').addEventListener('click', async () => {
  await navigator.clipboard.writeText('要复制的文本');
  alert('复制成功!');
});

总结:使用Clipboard API实现文本复制,比传统的“创建input复制”更简洁,但需注意浏览器兼容性(IE不支持),且需在HTTPS协议或本地环境下使用。

9. 删除数组重复项

适用场景:快速去重,适用于基本数据类型(数字、字符串、布尔值等)的数组。

const arr = [1, 2, 2, 3, 4, 4, 5, 5, 5];
const unique = [...new Set(arr)]; // 核心单行代码
// 结果: unique = [1, 2, 3, 4, 5]

总结:利用Set对象“值唯一”的特性,结合扩展运算符将Set转换为数组,实现快速去重。注意:若数组包含对象,此方法无法去重(对象引用不同),需额外处理。

10. 取两个数组的交集

适用场景:获取两个数组中共同存在的元素。

const arr1 = [1, 2, 3, 4];
const arr2 = [2, 4, 6, 8];
const intersection = arr1.filter(value => arr2.includes(value)); // 核心单行代码
// 结果: intersection = [2, 4]

// 补充案例(字符串数组交集)
const arr3 = ['a', 'b', 'c'];
const arr4 = ['b', 'c', 'd'];
const strIntersection = arr3.filter(value => arr4.includes(value));
// 结果: strIntersection = ['b', 'c']

总结:通过filter()遍历第一个数组,使用includes()判断元素是否存在于第二个数组中,筛选出共同元素。若数组元素较多,可先将第二个数组转为Set,提升查询效率:const arr2Set = new Set(arr2); const intersection = arr1.filter(value => arr2Set.has(value))。

11. 求数组元素的总和

适用场景:快速计算数组中所有基本数据类型(数字)的总和。

const arr = [1, 2, 3, 4];
const sum = arr.reduce((total, value) => total + value, 0); // 核心单行代码
// 结果: sum = 10

// 补充案例(含负数和小数)
const arr2 = [1.5, 2.5, -3, 4];
const sum2 = arr2.reduce((total, value) => total + value, 0);
// 结果: sum2 = 5

总结:reduce() 方法接收回调函数和初始值,回调函数中的total为累加值,value为当前元素,遍历数组并累加所有元素。初始值设为0,避免数组为空时返回undefined。

12. 根据指定条件,给对象的属性赋值

适用场景:根据条件动态给对象添加属性,避免冗余的if-else语句。

const condition = true;
const value = '你好, 世界';
const newObject = { ...(condition && { key: value }) }; // 核心单行代码
// 结果: newObject = { key: '你好, 世界' }

// 补充案例(条件为false)
const condition2 = false;
const newObject2 = { ...(condition2 && { key: value }) };
// 结果: newObject2 = {}

总结:利用短路求值(&&),当条件为true时,返回{key: value},并通过扩展运算符添加到新对象中;当条件为false时,短路返回false,扩展运算符会忽略false,不添加任何属性。

13. 使用变量作为对象的键

适用场景:动态设置对象的键名(键名不确定,需通过变量指定)。

const dynamicKey = 'name';
const value = '张三';
const obj = { [dynamicKey]: value }; // 核心单行代码(计算属性名)
// 结果: obj = { name: '张三' }

// 补充案例(动态切换键名)
const dynamicKey2 = 'age';
const obj2 = { [dynamicKey2]: 24 };
// 结果: obj2 = { age: 24 }

总结:通过计算属性名(方括号包裹变量),可将变量的值作为对象的键名,灵活适配动态场景,例如根据接口返回值动态设置对象键名。

14. 离线状态检查器

适用场景:检测用户浏览器的网络连接状态,提示用户当前是否在线。

const isOnline = navigator.onLine ? '在线' : '离线'; // 核心单行代码
// 结果: 网络正常时返回'在线',断开时返回'离线'

// 完整示例(实时监听网络状态)
window.addEventListener('online', () => console.log('网络已连接,当前状态:在线'));
window.addEventListener('offline', () => console.log('网络已断开,当前状态:离线'));

总结:利用navigator.onLine属性结合三元运算符,快速判断网络状态。注意:navigator.onLine仅能检测是否有网络连接,无法判断网络是否能正常访问互联网(如连接了无网络的WiFi)。

15. 离开页面弹出确认对话框

适用场景:防止用户误操作关闭页面,导致未保存的数据丢失(如表单填写、编辑内容)。

// 核心单行代码
window.onbeforeunload = () => '你确定要离开吗?未保存的内容将丢失!'; // 补充提示信息

// 补充:现代浏览器对提示文本的限制(部分浏览器不显示自定义文本,仅显示默认提示)
window.onbeforeunload = (e) => {
  e.preventDefault();
  e.returnValue = '';
  return '你确定要离开吗?';
};

总结:监听window的onbeforeunload事件,当用户关闭页面、刷新页面或跳转页面时,会弹出确认对话框。注意:现代浏览器为了安全,可能会忽略自定义提示文本,仅显示浏览器默认提示。

16. 对象数组,根据对象的某个key求对应值的总和

适用场景:统计对象数组中,指定属性的所有值的总和(如统计订单金额、商品数量等)。

const arrayOfObjects = [{ x: 1 }, { x: 2 }, { x: 3 }];
// 核心单行函数(可复用)
const sumBy = (arr, key) => arr.reduce((acc, obj) => acc + obj[key], 0);
const total = sumBy(arrayOfObjects, 'x'); // 传入数组和指定key
// 结果: total = 6

// 补充案例(统计订单金额)
const orders = [{ amount: 100 }, { amount: 200 }, { amount: 150 }];
const totalAmount = sumBy(orders, 'amount');
// 结果: totalAmount = 450

总结:封装sumBy函数,利用reduce()遍历对象数组,累加指定key对应的属性值,可灵活复用,适用于各种对象数组的统计场景。

17. 将URL问号后面的查询字符串转为对象

适用场景:快速解析URL中的查询参数,方便获取参数值(如页面跳转传参、接口请求参数解析)。

const query = 'name=John&age=30&gender=male'; // 补充多参数案例
// 核心单行代码(修正原文档拼写错误:0bject→Object)
const parseQuery = query => Object.fromEntries(new URLSearchParams(query));
const queryObj = parseQuery(query);
// 结果: queryObj = { name: 'John', age: '30', gender: 'male' }

总结:URLSearchParams用于解析查询字符串,返回可迭代对象,再通过Object.fromEntries()将其转换为对象,解析后的参数值均为字符串,若需数字类型,需手动转换(如Number(queryObj.age))。

18. 将秒数转换为时间格式的字符串(HH:MM:SS)

适用场景:将秒数转换为标准时间格式,如视频时长、倒计时显示等。

const seconds = 3661; // 1小时1分1秒(修正原文档注释错误:3600秒为1小时)
// 核心单行代码(修正原文档拼写错误:toIsostring→toISOString、substr→slice)
const toTimeString = seconds => new Date(seconds * 1000).toISOString().slice(11, 19);
const timeStr = toTimeString(seconds);
// 结果: timeStr = '01:01:01'

// 补充案例(不足1小时、不足1分钟)
const seconds2 = 61; // 1分1秒
const timeStr2 = toTimeString(seconds2);
// 结果: timeStr2 = '00:01:01'

const seconds3 = 5; // 5秒
const timeStr3 = toTimeString(seconds3);
// 结果: timeStr3 = '00:00:05'

总结:将秒数乘以1000(转换为毫秒),创建Date对象,再通过toISOString()获取标准时间字符串,最后截取时间部分(HH:MM:SS),适用于所有正秒数的转换。

19. 求某对象所有属性值的最大值

适用场景:快速获取对象中所有属性值的最大值(如统计最高分、最大金额等)。

const scores = { math: 95, chinese: 99, english: 88 }; // 数学、语文、英语成绩
// 核心单行代码(修正原文档拼写错误:0bject→Object、max0bjectValue→maxObjectValue)
const maxObjectValue = obj => Math.max(...Object.values(obj));
const maxScore = maxObjectValue(scores);
// 结果: maxScore = 99

// 补充案例(数字属性值含小数)
const prices = { apple: 5.9, banana: 3.5, orange: 4.8 };
const maxPrice = maxObjectValue(prices);
// 结果: maxPrice = 5.9

总结:Object.values(obj)提取对象所有属性值,生成数组,再通过扩展运算符将数组元素作为Math.max()的参数,获取最大值。注意:对象属性值必须为数字类型,否则会返回NaN。

20. 判断对象的值中是否包含某个值

适用场景:检查对象的所有属性值中,是否存在指定的值。

const person = { name: '张三', age: 30, gender: '男' }; // 补充多属性案例
// 核心单行代码(修正原文档拼写错误:0bject→Object)
const hasValue = (obj, value) => Object.values(obj).includes(value);
// 案例1:判断是否包含30
const has30 = hasValue(person, 30); // 结果: true
// 案例2:判断是否包含'女'
const hasFemale = hasValue(person, '女'); // 结果: false

总结:Object.values(obj)获取对象所有属性值的数组,再通过includes()判断指定值是否在数组中,适用于快速检查对象值的存在性。

21. 安全访问深度嵌套的对象属性

适用场景:访问嵌套层级较深的对象属性,避免因中间属性不存在导致的TypeError错误。

// 案例1:中间属性存在
const user = { profile: { name: '张三' } };
const userName = user.profile?.name ?? '匿名'; // 核心单行代码
// 结果: userName = '张三'

// 案例2:中间属性不存在(profile为undefined)
const user2 = { };
const userName2 = user2.profile?.name ?? '匿名';
// 结果: userName2 = '匿名'

// 补充:区分??和||的差异
const user3 = { profile: { name: '' } };
const userName3 = user3.profile?.name ?? '匿名'; // 结果: ''(空字符串不是null/undefined,不触发默认值)
const userName4 = user3.profile?.name || '匿名'; // 结果: '匿名'(空字符串是假值,触发默认值)

总结:可选链运算符(?.):当中间属性为null/undefined时,短路返回undefined,避免报错;空值合并运算符(??):仅当左侧为null/undefined时,返回右侧默认值,不影响其他假值(如''、0)。两者结合,可安全访问深度嵌套属性并设置默认值。

22. 条件执行语句

适用场景:无需if语句,简洁实现“条件为真时执行函数/赋值”。

// 案例1:条件执行函数
const isEligible = true;
const performAction = () => console.log('执行操作');
isEligible && performAction(); // 核心单行代码(条件为真时执行)
// 结果: 输出'执行操作'

// 案例2:条件赋值(修正原文档语法,补充括号)
const isEligible2 = true;
let value = '';
isEligible2 && (value = '条件达成'); // 核心单行代码
// 结果: value = '条件达成'

// 补充案例:条件为false时不执行
const isEligible3 = false;
isEligible3 && performAction(); // 无输出

总结:利用逻辑AND(&&)的短路特性,左侧为true时,才执行右侧的函数或赋值语句;左侧为false时,直接短路,不执行右侧代码。适用于简单的条件判断场景,简化代码。

23. 创建包含指定数字范围的数组

适用场景:快速生成连续数字数组(如分页页码、循环计数等)。

// 案例1:创建1-5的数组
const range1 = Array.from({ length: 5 }, (_, i) => i + 1); // 核心单行代码
// 结果: range1 = [1, 2, 3, 4, 5]

// 案例2:创建5-10的数组
const range2 = Array.from({ length: 6 }, (_, i) => i + 5);
// 结果: range2 = [5, 6, 7, 8, 9, 10]

// 案例3:创建0-4的数组(简化)
const range3 = Array.from({ length: 5 }, (_, i) => i);
// 结果: range3 = [0, 1, 2, 3, 4]

总结:Array.from()接收两个参数:类数组对象(设置length指定数组长度)和映射函数,映射函数通过索引(i)生成指定范围的数字。下划线(_)表示未使用的参数(当前元素),是JS中的常用惯例。

24. 提取文件扩展名

适用场景:获取文件名中的扩展名(如判断文件类型、上传文件校验等)。

const fileName1 = 'example.png';
const fileName2 = 'document.pdf';
const fileName3 = 'image.jpg';
const fileName4 = 'text'; // 无扩展名
// 核心单行代码(修正原文档拼写错误:lastIndex0f→lastIndexOf、getFileExtension返回值错误)
const getFileExtension = str => str.slice(((str.lastIndexOf(".") - 1) >>> 0) + 2);
// 案例验证
console.log(getFileExtension(fileName1)); // 结果: 'png'
console.log(getFileExtension(fileName2)); // 结果: 'pdf'
console.log(getFileExtension(fileName3)); // 结果: 'jpg'
console.log(getFileExtension(fileName4)); // 结果: ''(无扩展名返回空字符串)

总结:通过lastIndexOf(".")找到最后一个点号的位置,使用位运算符(>>>)确保即使未找到点号(返回-1),操作也安全,最终截取点号后面的字符串作为扩展名。

25. 切换元素的class

适用场景:动态添加/移除元素的class(如菜单显示/隐藏、按钮选中/取消、表单状态切换等)。

// 核心代码(修正原文档语法错误,补充括号)
const element = document.querySelector('.myelement');
const toggleClass = (el, className) => el.classList.toggle(className);
// 切换class(存在则删除,不存在则添加)
toggleClass(element, 'active');

// 完整示例(点击按钮切换元素class)
document.querySelector('#toggleBtn').addEventListener('click', () => {
  toggleClass(element, 'active');
});

总结:classList.toggle()方法自动判断元素是否包含指定class,包含则删除,不包含则添加,无需手动判断,简洁高效,是前端开发中动态控制元素样式的常用方法。

二、JS实战高频案例(18个,覆盖前端常用场景)

1. 防抖函数(避免高频事件频繁触发)

适用场景:防止输入框输入、窗口resize、滚动等高频事件频繁触发函数,适用于搜索联想、输入验证等场景。

// 核心单行防抖函数(简化版)
const debounce = (fn, delay = 500) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
};
// 使用示例(输入框搜索)
const search = (value) => console.log('搜索:', value);
const debouncedSearch = debounce(search, 300);
// 输入框输入时调用,仅在停止输入300ms后触发
document.querySelector('#searchInput').addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

2. 节流函数(限制函数触发频率)

适用场景:限制函数在指定时间内只能触发一次,适用于滚动加载、按钮点击防重复提交等场景。

// 核心单行节流函数(简化版)
const throttle = (fn, interval = 1000) => {
  let lastTime = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
};
// 使用示例(滚动加载)
const loadMore = () => console.log('加载更多数据');
const throttledLoadMore = throttle(loadMore, 2000);
// 滚动时调用,每2000ms只能触发一次
window.addEventListener('scroll', throttledLoadMore);

3. 深克隆对象(解决嵌套对象复制问题)

适用场景:复制包含嵌套对象的复杂对象,确保修改克隆对象不影响原对象,适用于数据备份、复杂数据处理。

// 核心单行深克隆(简单场景,不支持函数、Symbol等特殊类型)
const deepClone = obj => JSON.parse(JSON.stringify(obj));
// 示例
const originalObj = {
  name: '张三',
  age: 24,
  address: { city: '北京', area: '朝阳' } // 嵌套对象
};
const clonedObj = deepClone(originalObj);
clonedObj.address.city = '上海'; // 修改克隆对象的嵌套属性
console.log(originalObj.address.city); // 结果: '北京'(原对象不受影响)

// 补充:复杂场景深克隆(支持函数、Symbol,需使用递归)
const deepCloneAdvanced = (obj) => {
  if (obj === null || typeof obj !== 'object') return obj;
  const clone = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    clone[key] = deepCloneAdvanced(obj[key]);
  }
  return clone;
};

4. 精准检查数据类型

适用场景:精准判断数据类型(如区分数组、对象、null、函数等),避免typeof的局限性,适用于数据校验场景。

// 核心单行函数
const getType = (data) => Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
// 案例验证
console.log(getType(123)); // 结果: 'number'
console.log(getType('abc')); // 结果: 'string'
console.log(getType([])); // 结果: 'array'
console.log(getType({})); // 结果: 'object'
console.log(getType(null)); // 结果: 'null'
console.log(getType(() => {})); // 结果: 'function'
console.log(getType(new Date())); // 结果: 'date'

5. 数组扁平化(多维转一维)

适用场景:将多维数组转为一维数组,适用于数据处理、数组遍历等场景,简化数据操作。

// 案例1:二维数组扁平化(核心单行代码)
const arr1 = [1, [2, 3], [4, [5, 6]]];
const flatArr1 = arr1.flat(1); // 参数1表示扁平化1层
// 结果: [1, 2, 3, 4, [5, 6]]

// 案例2:多维数组扁平化(不限层级)
const flatArr2 = arr1.flat(Infinity); // Infinity表示扁平化所有层级
// 结果: [1, 2, 3, 4, 5, 6]

// 补充案例(手动实现扁平化,不使用flat方法)
const flatArr3 = arr1.reduce((acc, item) => acc.concat(Array.isArray(item) ? flatArr3(item) : item), []);
// 结果: [1, 2, 3, 4, 5, 6]

6. 数组排序(数字/字符串通用)

适用场景:对数组进行升序/降序排序,适用于数据展示、排名统计等场景,解决sort默认字符串排序的问题。

// 案例1:数字数组升序排序
const nums = [3, 1, 4, 1, 5, 9, 2, 6];
const sortedNumsAsc = [...nums].sort((a, b) => a - b); // 展开数组避免修改原数组
// 结果: [1, 1, 2, 3, 4, 5, 6, 9]

// 案例2:数字数组降序排序
const sortedNumsDesc = [...nums].sort((a, b) => b - a);
// 结果: [9, 6, 5, 4, 3, 2, 1, 1]

// 案例3:字符串数组排序(按字母顺序)
const strs = ['banana', 'apple', 'cherry', 'date'];
const sortedStrs = [...strs].sort();
// 结果: ['apple', 'banana', 'cherry', 'date']

7. 日期格式化(指定格式)

适用场景:将Date对象转换为指定格式的日期字符串(如YYYY-MM-DD、HH:MM:SS),适用于时间展示、日志记录等。

// 核心单行函数(格式化YYYY-MM-DD HH:MM:SS)
const formatDate = (date = new Date()) => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,补0
  const day = String(date.getDate()).padStart(2, '0');
  const hour = String(date.getHours()).padStart(2, '0');
  const minute = String(date.getMinutes()).padStart(2, '0');
  const second = String(date.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
};
// 调用函数
console.log(formatDate()); // 结果: 2026-04-10 14:30:00(当前时间)
console.log(formatDate(new Date(2026, 3, 10))); // 结果: 2026-04-10 00:00:00

8. 字符串数组与数字数组互换

适用场景:快速转换数组元素类型,适用于数据格式转换(如接口返回字符串数组,需转为数字进行计算)。

// 案例1:数字数组转为字符串数组
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const strArr = arr.map(String);
// 结果: ['1', '2', '3', '4', '5', '6', '7', '8', '9']

// 案例2:字符串数组转为数字数组
var a = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
const numArr = a.map(Number);
// 结果: [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 补充案例(处理异常值)
var b = ['1', '2', 'abc', '4'];
const numArr2 = b.map(Number);
// 结果: [1, 2, NaN, 4](无法转换的字符串会转为NaN)

9. 对象数组匹配更新指定元素

适用场景:根据条件更新对象数组中的指定元素,适用于待办状态修改、数据编辑等前端高频场景。

const todos = [
  { id: '001', name: '吃饭', done: true },
  { id: '002', name: '睡觉', done: true }
];
const id = '002';
const done = false;
const newTodos = todos.map((todoObj) => {
  // 匹配id,更新done状态,其余元素不变
  if (todoObj.id === id) return { ...todoObj, done };
  else return todoObj;
});
// 结果: newTodos = [
//   { id: '001', name: '吃饭', done: true },
//   { id: '002', name: '睡觉', done: false }
// ]

10. 对象数组删除指定元素

适用场景:过滤删除对象数组中指定条件的元素,适用于待办删除、无效数据清理等场景。

const todos = [
  { id: '001', name: '吃饭', done: true },
  { id: '002', name: '睡觉', done: true }
];
const id = '002';
// 核心代码:过滤掉id为002的元素
const newTodos = todos.filter((todoObj) => todoObj.id !== id);
// 结果: newTodos = [
//   { id: '001', name: '吃饭', done: true }
// ]

11. 对象数组查找指定元素

适用场景:根据key快速查找对象数组中符合条件的元素,适用于详情页数据获取、数据查询等场景。

const items = [
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' },
  { id: 3, name: 'Item 3' }
];
const key = 2; // 要查找的id
// 核心代码:找到id为2的对象
const item = items.find(item => item.id === key);
console.log(item); // 结果: { id: 2, name: 'Item 2' }

12. 对象数组查找指定元素索引

适用场景:快速获取符合条件的元素在数组中的索引,适用于修改、删除指定位置的元素。

const contents = [
  { language: 'zh_CN', alias: "", description: "" },
  { language: 'td_CN', alias: "", description: "" },
  { language: 'en_US', alias: "", description: "" },
  { language: 'ja_JP', alias: "", description: "" }
];
// 核心代码:查找language为zh_CN的元素索引
const index = contents.findIndex(item => item.language === 'zh_CN');
console.log(index); // 结果: 0(数组索引从0开始)

13. 对象数组过滤非空属性元素

适用场景:筛选出对象数组中指定属性不为空的元素,适用于有效数据筛选、表单数据校验等场景。

const contents = [
  { language: 'zh_CN', alias: "1", description: "" },
  { language: 'td_CN', alias: "", description: "" },
  { language: 'en_US', alias: "", description: "" },
  { language: 'ja_JP', alias: "", description: "" }
];
// 核心代码:过滤出alias值不为空的元素
const filteredContents = contents.filter(item => item.alias !== "");
console.log(filteredContents);
// 结果: [{ "language": "zh_CN", "alias": "1", "description": "" }]

14. 对象数组求交集(根据属性匹配)

适用场景:获取两个对象数组中属性匹配的元素,适用于数据对比、重复数据筛选等场景。

let todos1 = [
  { id: "001", name: "吃饭", done: true },
  { id: "002", name: "睡觉", done: true },
];
let todos2 = [
  { id: "001", name: "吃饭", done: true },
  { id: "002", name: "睡觉", done: true },
  { id: "003", name: "学习", done: true },
];
// 核心代码:找出todos2中与todos1 id匹配的元素
todos2 = todos2.filter((item1) =>
  todos1.some((item2) => item2.id === item1.id)
);
console.log(todos2);
// 结果: [
//   { id: "001", name: "吃饭", done: true },
//   { id: "002", name: "睡觉", done: true }
// ]

15. 数组头部添加元素(不修改原数组)

const todos = [
  { id: '001', name: '吃饭', done: true },
  { id: '002', name: '睡觉', done: true }
];
const todoObj = { id: '003', name: '敲码', done: true };
const newTodos = [todoObj, ...todos]; // 核心代码
// 结果: newTodos = [
//   { id: '003', name: '敲码', done: true },
//   { id: '001', name: '吃饭', done: true },
//   { id: '002', name: '睡觉', done: true }
// ]

16. 删除数组指定下标的元素(2种常用方式)

适用场景:明确知道数组中要删除元素的下标,需删除指定位置元素(如删除列表指定索引项、清理数组特定位置无效数据),前端开发高频场景。

// 方式1:splice方法(修改原数组,简洁高效,最常用)
const arr1 = [10, 20, 30, 40, 50];
const index1 = 2; // 要删除的下标(删除元素30)
arr1.splice(index1, 1); // 核心代码:参数1=下标,参数2=删除个数
// 结果: arr1 = [10, 20, 40, 50](原数组被修改)

// 方式2:slice方法(不修改原数组,生成新数组,推荐需保留原数组场景)
const arr2 = [10, 20, 30, 40, 50];
const index2 = 2;
const newArr2 = arr2.slice(0, index2).concat(arr2.slice(index2 + 1)); // 核心代码
// 结果: newArr2 = [10, 20, 40, 50],arr2仍为[10, 20, 30, 40, 50](原数组不变)

// 补充:边界处理(下标越界时,两种方式均不报错,splice无操作,slice返回原数组)
const arr3 = [10, 20];
const index3 = 5; // 下标越界
arr3.splice(index3, 1); // 无操作,arr3仍为[10, 20]
const newArr3 = arr3.slice(0, index3).concat(arr3.slice(index3 + 1)); // newArr3 = [10, 20]

// 补充案例(对象数组删除指定下标元素)
const objArr = [
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' }
];
const objIndex = 1; // 删除下标为1的“香蕉”
// 方式1(修改原数组)
objArr.splice(objIndex, 1);
// 结果: objArr = [{ id: 1, name: '苹果' }, { id: 3, name: '橙子' }]
// 方式2(不修改原数组)
const newObjArr = objArr.slice(0, objIndex).concat(objArr.slice(objIndex + 1));
// 结果: newObjArr = [{ id: 1, name: '苹果' }, { id: 3, name: '橙子' }]

总结:两种方式各有适用场景——splice方法修改原数组,代码简洁,适合无需保留原数组的场景;slice方法不修改原数组,避免污染原始数据,适合需保留原数组(如数据备份、回滚)的场景。注意:下标从0开始,需做好边界判断,避免下标越界导致无效操作。

17. 对象数组多层数据,只保留两层结构

适用场景:处理多层嵌套的对象数组(如接口返回的复杂数据),需简化结构、只保留两层数据(顶层对象 + 一层子对象/子数组),适用于数据展示、表格渲染等无需深层数据的场景,避免冗余数据影响性能。

// 核心函数:递归/遍历处理,只保留两层数据(顶层 + 一层子级)
// 思路:遍历顶层对象数组,仅保留顶层属性和第一层子级,删除子级中的嵌套数据
const keepTwoLayers = (arr) => {
  // 遍历顶层数组,处理每个顶层对象
  return arr.map(item => {
    // 复制顶层对象(避免修改原数据)
    const newItem = {...item};
    // 遍历顶层对象的每个属性,判断是否为对象/数组(即子级)
    for (let key in newItem) {
      const value = newItem[key];
      // 若子级是对象(非null)或数组,仅保留其自身属性,删除嵌套层级
      if (typeof value === 'object' && value !== null) {
        // 数组:保留数组元素,但元素若为对象,仅保留其自身属性(不嵌套)
        if (Array.isArray(value)) {
          newItem[key] = value.map(subItem => {
            return typeof subItem === 'object' && subItem !== null ? {...subItem} : subItem;
          });
        } else {
          // 普通对象:仅保留自身属性,删除嵌套属性
          newItem[key] = {...value};
        }
      }
    }
    return newItem;
  });
};

// 示例:多层嵌套对象数组(模拟接口返回的复杂数据)
const complexArr = [
  {
    id: 1,
    name: '商品分类1',
    info: {
      desc: '电子产品',
      detail: { // 三层嵌套,需删除
        createTime: '2026-01-01',
        updateTime: '2026-04-10'
      },
      tags: [
        { name: '热门', type: { id: 1, name: '推荐' } }, // 三层嵌套,需删除
        { name: '新品', type: { id: 2, name: '新品' } }
      ]
    }
  },
  {
    id: 2,
    name: '商品分类2',
    info: {
      desc: '生活用品',
      detail: { // 三层嵌套,需删除
        createTime: '2026-02-01',
        updateTime: '2026-04-05'
      },
      tags: [
        { name: '热销', type: { id: 3, name: '热销' } }
      ]
    }
  }
];

// 调用函数,只保留两层结构
const twoLayerArr = keepTwoLayers(complexArr);
console.log(twoLayerArr);
// 结果:
// [
//   {
//     id: 1,
//     name: '商品分类1',
//     info: { desc: '电子产品', detail: {}, tags: [ { name: '热门' }, { name: '新品' } ] }
//   },
//   {
//     id: 2,
//     name: '商品分类2',
//     info: { desc: '生活用品', detail: {}, tags: [ { name: '热销' } ] }
//   }
// ]

总结:keepTwoLayers函数通过map遍历顶层对象数组,复制每个顶层对象,再遍历其属性,对对象/数组类型的子级进行处理——数组元素若为对象则仅保留自身属性,普通对象仅保留自身属性,删除所有三层及以上的嵌套数据。该方法可灵活处理各类多层嵌套对象数组,简化数据结构,避免冗余嵌套影响前端渲染性能,适用于表格展示、列表渲染等无需深层数据的场景。

18. 批量修改对象数组的指定属性(批量更新)

适用场景:批量修改对象数组中所有元素的指定属性,或根据条件批量更新属性值,适用于批量操作(如批量修改状态、批量设置默认值),前端开发高频场景。

// 案例1:批量修改所有元素的指定属性(统一设置默认值)
const products = [
  { id: 1, name: '手机', stock: 100, isSale: false },
  { id: 2, name: '电脑', stock: 50, isSale: false },
  { id: 3, name: '平板', stock: 80, isSale: false }
];
// 核心代码:批量将isSale设为true,stock统一减10
const updatedProducts1 = products.map(item => ({
  ...item,
  isSale: true,
  stock: item.stock - 10
}));
// 结果:所有商品isSale为true,stock均减少10

// 案例2:根据条件批量修改属性(满足条件的元素才更新)
// 批量将stock>60的商品isSale设为true,其余不变
const updatedProducts2 = products.map(item => {
  if (item.stock > 60) {
    return {...item, isSale: true};
  }
  return item; // 不满足条件则返回原对象
});
// 结果:id为1、3的商品isSale为true,id为2的商品保持不变

// 补充:批量修改多个不同属性(按需设置)
const updatedProducts3 = products.map(item => ({
  ...item,
  price: item.id * 1000, // 新增属性并赋值
  stock: item.stock > 60 ? item.stock - 10 : item.stock, // 条件赋值
  isSale: item.stock > 60 // 条件赋值(布尔值)
}));
console.log(updatedProducts3);
// 结果:所有商品新增price属性,stock按需减少,isSale按条件设置

总结:利用map方法遍历对象数组,通过对象扩展运算符(...)保留原对象属性,同时修改指定属性值,可实现统一批量修改或条件批量修改。该方法不修改原数组,生成新数组,避免污染原始数据,适用于批量操作场景,代码简洁且可复用,是前端批量处理对象数组的常用方式。

三、十大排序算法(前端实战版)

排序算法是前端数据处理的核心基础,以下整理十大常用排序算法,先通过表格对比各算法关键信息,再详细说明核心原理、JS实现代码(简洁可复制)、适用场景及优缺点,贴合前端开发实际需求,避免复杂冗余,重点适配数组排序场景。

排序算法 核心特点 时间复杂度 空间复杂度 稳定性 前端适用场景
冒泡排序 相邻元素对比,逐步冒泡至末尾 O(n²) O(1) 稳定 少量数据,简单场景
选择排序 每次选最小/大元素,与首位交换 O(n²) O(1) 不稳定 少量数据,对稳定性无要求
插入排序 分已排序/未排序,逐个插入合适位置 O(n²)(接近有序时O(n)) O(1) 稳定 少量数据、数据接近有序(如表单排序)
希尔排序 插入排序优化,按步长分组排序 O(nlogn) O(1) 不稳定 中量数据
快速排序 分治法,选基准值分组递归排序 O(nlogn) O(logn) 不稳定 大量数据,前端最常用
归并排序 分治法,拆分后合并有序子数组 O(nlogn) O(n) 稳定 大量数据,对稳定性有要求
堆排序 利用大顶堆特性,逐步取出堆顶元素 O(nlogn) O(1) 不稳定 大量数据,对空间要求严格
计数排序 非比较排序,统计元素次数后重构数组 O(n + k)(k为元素范围) O(k) 稳定 元素为整数、范围较小(如分数排序)
桶排序 非比较排序,分桶排序后合并 O(n + k) O(n + k) 稳定 元素分布均匀、大量数据(如数据统计)
基数排序 非比较排序,按位数依次排序 O(n * k)(k为最大数位数) O(n + k) 稳定 整数、字符串,大量数据

1. 冒泡排序(Bubble Sort)

核心原理:重复遍历数组,每次比较相邻两个元素,将较大元素“冒泡”到数组末尾,逐步完成排序。

// 优化版:添加标志位,无交换时直接退出(提升效率)
const bubbleSort = (arr) => {
  const newArr = [...arr]; // 不修改原数组
  const len = newArr.length;
  for (let i = 0; i < len - 1; i++) {
    let hasSwap = false; // 标志位:是否发生交换
    for (let j = 0; j < len - 1 - i; j++) {
      if (newArr[j] > newArr[j + 1]) {
        [newArr[j], newArr[j + 1]] = [newArr[j + 1], newArr[j]]; // 交换元素
        hasSwap = true;
      }
    }
    if (!hasSwap) break; // 无交换,说明已排序完成
  }
  return newArr;
};
// 示例
const arr1 = [3, 1, 4, 1, 5, 9];
console.log(bubbleSort(arr1)); // 结果:[1, 1, 3, 4, 5, 9]

优缺点:简单易理解,空间复杂度低(O(1));时间复杂度O(n²),数据量大时效率极低,适用于少量数据排序。

2. 选择排序(Selection Sort)

核心原理:每次遍历未排序部分,找到最小(或最大)元素,与未排序部分的第一个元素交换,逐步缩小未排序范围。

const selectionSort = (arr) => {
  const newArr = [...arr];
  const len = newArr.length;
  for (let i = 0; i < len - 1; i++) {
    let minIndex = i; // 记录最小元素下标
    // 找到未排序部分的最小元素
    for (let j = i + 1; j < len; j++) {
      if (newArr[j] < newArr[minIndex]) {
        minIndex = j;
      }
    }
    // 交换最小元素与未排序部分第一个元素
    [newArr[i], newArr[minIndex]] = [newArr[minIndex], newArr[i]];
  }
  return newArr;
};
// 示例
const arr2 = [7, 2, 5, 0, 3];
console.log(selectionSort(arr2)); // 结果:[0, 2, 3, 5, 7]

优缺点:实现简单,空间复杂度O(1);时间复杂度O(n²),不稳定(相同元素可能改变相对位置),适用于数据量小、对稳定性无要求的场景。

3. 插入排序(Insertion Sort)

核心原理:将数组分为“已排序”和“未排序”两部分,每次从无排序部分取一个元素,插入到已排序部分的合适位置。

const insertionSort = (arr) => {
  const newArr = [...arr];
  const len = newArr.length;
  for (let i = 1; i < len; i++) {
    const current = newArr[i]; // 未排序部分的当前元素
    let j = i - 1; // 已排序部分的最后一个下标
    // 找到插入位置
    while (j >= 0 && newArr[j] > current) {
      newArr[j + 1] = newArr[j]; // 元素后移
      j--;
    }
    newArr[j + 1] = current; // 插入当前元素
  }
  return newArr;
};
// 示例
const arr3 = [6, 3, 8, 2, 9];
console.log(insertionSort(arr3)); // 结果:[2, 3, 6, 8, 9]

优缺点:稳定排序,数据接近有序时效率极高(时间复杂度接近O(n));时间复杂度O(n²),适用于少量数据、数据接近有序的场景(如表单排序)。

4. 希尔排序(Shell Sort)

核心原理:插入排序的优化版,将数组按“步长”分组,对每组进行插入排序,逐步缩小步长,最终步长为1时完成排序。

const shellSort = (arr) => {
  const newArr = [...arr];
  const len = newArr.length;
  // 步长初始为数组长度的一半,逐步缩小为1
  for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
    // 对每组进行插入排序
    for (let i = gap; i < len; i++) {
      const current = newArr[i];
      let j = i - gap;
      while (j >= 0 && newArr[j] > current) {
        newArr[j + gap] = newArr[j];
        j -= gap;
      }
      newArr[j + gap] = current;
    }
  }
  return newArr;
};
// 示例
const arr4 = [10, 5, 12, 3, 7, 1];
console.log(shellSort(arr4)); // 结果:[1, 3, 5, 7, 10, 12]

优缺点:效率高于插入/冒泡/选择排序,时间复杂度O(nlogn);不稳定,适用于中量数据排序。

5. 快速排序(Quick Sort)

核心原理:分治法,选择一个“基准值”,将数组分为“小于基准”“等于基准”“大于基准”三部分,递归对左右两部分排序,效率极高。

// 简洁版:递归实现,基准值选数组中间元素
const quickSort = (arr) => {
  if (arr.length <= 1) return arr; // 递归终止条件
  const newArr = [...arr];
  const midIndex = Math.floor(newArr.length / 2);
  const pivot = newArr.splice(midIndex, 1)[0]; // 基准值(删除并获取)
  const left = []; // 小于基准的元素
  const right = []; // 大于基准的元素
  // 分组
  for (let item of newArr) {
    item < pivot ? left.push(item) : right.push(item);
  }
  // 递归排序左右两部分,合并结果
  return [...quickSort(left), pivot, ...quickSort(right)];
};
// 示例
const arr5 = [8, 3, 1, 7, 0, 10, 2];
console.log(quickSort(arr5)); // 结果:[0, 1, 2, 3, 7, 8, 10]

优缺点:效率极高,时间复杂度O(nlogn);不稳定,空间复杂度O(logn),适用于大量数据排序(前端最常用的排序算法)。

6. 归并排序(Merge Sort)

核心原理:分治法,将数组递归拆分为两个子数组,直到每个子数组只有一个元素,再逐步合并两个有序子数组,最终得到有序数组。

// 合并两个有序数组
const merge = (left, right) => {
  const result = [];
  let i = 0, j = 0;
  // 对比两个数组,按顺序合并
  while (i < left.length && j < right.length) {
    left[i] < right[j] ? result.push(left[i++]) : result.push(right[j++]);
  }
  // 合并剩余元素
  return [...result, ...left.slice(i), ...right.slice(j)];
};

// 归并排序主函数
const mergeSort = (arr) => {
  if (arr.length <= 1) return arr; // 递归终止条件
  const mid = Math.floor(arr.length / 2);
  const left = arr.slice(0, mid); // 左子数组
  const right = arr.slice(mid); // 右子数组
  // 递归拆分 + 合并
  return merge(mergeSort(left), mergeSort(right));
};
// 示例
const arr6 = [5, 2, 9, 1, 5, 6];
console.log(mergeSort(arr6)); // 结果:[1, 2, 5, 5, 6, 9]

优缺点:稳定排序,时间复杂度O(nlogn);空间复杂度O(n),适用于对稳定性有要求、大量数据的排序场景。

7. 堆排序(Heap Sort)

核心原理:利用堆(大顶堆/小顶堆)的特性,将数组构建为大顶堆(最大值在堆顶),每次取出堆顶元素,再调整堆结构,重复直至排序完成。

// 调整堆结构(大顶堆)
const adjustHeap = (arr, parentIndex, len) => {
  const temp = arr[parentIndex]; // 父节点
  let childIndex = 2 * parentIndex + 1; // 左子节点下标
  while (childIndex < len) {
    // 找到左右子节点中较大的一个
    if (childIndex + 1 < len && arr[childIndex + 1] > arr[childIndex]) {
      childIndex++;
    }
    // 父节点大于子节点,无需调整
    if (temp >= arr[childIndex]) break;
    // 子节点上移
    arr[parentIndex] = arr[childIndex];
    parentIndex = childIndex;
    childIndex = 2 * parentIndex + 1;
  }
  arr[parentIndex] = temp; // 插入父节点到正确位置
};

// 堆排序主函数
const heapSort = (arr) => {
  const newArr = [...arr];
  const len = newArr.length;
  // 1. 构建大顶堆(从最后一个非叶子节点开始调整)
  for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
    adjustHeap(newArr, i, len);
  }
  // 2. 逐步取出堆顶元素,调整堆结构
  for (let i = len - 1; i > 0; i--) {
    [newArr[0], newArr[i]] = [newArr[i], newArr[0]]; // 堆顶与末尾元素交换
    adjustHeap(newArr, 0, i); // 调整剩余堆结构
  }
  return newArr;
};
// 示例
const arr7 = [3, 9, 2, 10, 4, 7];
console.log(heapSort(arr7)); // 结果:[2, 3, 4, 7, 9, 10]

优缺点:效率高,时间复杂度O(nlogn);不稳定,空间复杂度O(1),适用于大量数据、对空间要求严格的场景。

8. 计数排序(Counting Sort)

核心原理:非比较排序,统计数组中每个元素出现的次数,根据元素大小顺序,依次输出对应次数的元素,适用于元素范围较小的整数数组。

const countingSort = (arr) => {
  if (arr.length <= 1) return arr;
  const newArr = [...arr];
  const max = Math.max(...newArr); // 找到数组最大值
  const min = Math.min(...newArr); // 找到数组最小值
  const countArr = new Array(max - min + 1).fill(0); // 计数数组

  // 统计每个元素出现的次数
  for (let item of newArr) {
    countArr[item - min]++;
  }

  // 构建排序后的数组
  let index = 0;
  for (let i = 0; i < countArr.length; i++) {
    while (countArr[i] > 0) {
      newArr[index++] = i + min;
      countArr[i]--;
    }
  }
  return newArr;
};
// 示例(元素范围较小的整数数组)
const arr8 = [2, 0, 2, 1, 1, 0];
console.log(countingSort(arr8)); // 结果:[0, 0, 1, 1, 2, 2]

优缺点:稳定排序,时间复杂度O(n + k)(k为元素范围);空间复杂度O(k),仅适用于元素为整数、范围较小的场景(如考试分数排序)。

9. 桶排序(Bucket Sort)

核心原理:非比较排序,将数组元素按范围分到不同的“桶”中,对每个桶内的元素进行排序(可使用其他排序算法),最后合并所有桶的元素。

// 桶排序主函数,默认分5个桶
const bucketSort = (arr, bucketCount = 5) => {
  if (arr.length <= 1) return arr;
  const newArr = [...arr];
  const max = Math.max(...newArr);
  const min = Math.min(...newArr);
  const bucketSize = Math.ceil((max - min + 1) / bucketCount); // 每个桶的范围大小
  const buckets = new Array(bucketCount).fill(0).map(() => []); // 初始化桶

  // 将元素分到对应桶中
  for (let item of newArr) {
    const bucketIndex = Math.floor((item - min) / bucketSize);
    buckets[bucketIndex].push(item);
  }

  // 对每个桶排序,合并结果(这里使用插入排序,也可替换为快速排序)
  return buckets.reduce((result, bucket) => {
    return [...result, ...insertionSort(bucket)]; // 复用前面的插入排序
  }, []);
};
// 示例
const arr9 = [4, 2, 8, 1, 5, 7, 3, 6];
console.log(bucketSort(arr9)); // 结果:[1, 2, 3, 4, 5, 6, 7, 8]

优缺点:稳定排序,时间复杂度O(n + k);空间复杂度O(n + k),适用于元素分布均匀、大量数据的排序场景(如数据统计)。

10. 基数排序(Radix Sort)

核心原理:非比较排序,按元素的“位数”(个位、十位、百位...)依次排序,从低位到高位,每次排序后元素按当前位数有序,最终得到完整有序数组。

// 获取数字的某一位(个位=0,十位=1,百位=2...)
const getDigit = (num, digit) => {
  return Math.floor(Math.abs(num) / Math.pow(10, digit)) % 10;
};

// 基数排序主函数
const radixSort = (arr) => {
  if (arr.length <= 1) return arr;
  const newArr = [...arr];
  // 找到数组中最大数的位数
  const maxDigit = Math.max(...newArr).toString().length;

  // 按每一位排序,从个位到最高位
  for (let digit = 0; digit< maxDigit; digit++) {
    const buckets = new Array(10).fill(0).map(() => []); // 0-9共10个桶
    // 按当前位数将元素分到对应桶中
    for (let item of newArr) {
      const bucketIndex = getDigit(item, digit);
      buckets[bucketIndex].push(item);
    }
    // 合并桶,更新数组
    newArr.splice(0, newArr.length, ...buckets.flat());
  }
  return newArr;
};
// 示例(正整数数组)
const arr10 = [123, 45, 6, 789, 10, 23];
console.log(radixSort(arr10)); // 结果:[6, 10, 23, 45, 123, 789]

优缺点:稳定排序,时间复杂度O(n * k)(k为最大数的位数);空间复杂度O(n + k),适用于整数、字符串等可按“位”排序的大量数据场景。

排序算法总结(前端选型参考)

  1. 少量数据(n < 100):优先用插入排序、冒泡排序(简单易实现);

  2. 中大量数据(n > 100):优先用快速排序(效率最高)、归并排序(稳定);

  3. 元素范围小的整数数组:用计数排序、桶排序(效率高于比较排序);

  4. 对稳定性有要求:用归并排序、插入排序、计数排序、桶排序、基数排序;

  5. 对空间有要求:用堆排序、快速排序、冒泡排序、选择排序、插入排序、希尔排序。

  6. 所有代码均为前端实战高频场景,可直接复制到项目中使用,部分代码需根据实际需求(如DOM选择器、属性名)微调;

  7. 单行代码优先简化实现,兼顾简洁性和实用性,复杂场景补充完整示例,适配不同开发需求;

  8. 注意浏览器兼容性:部分API(如Clipboard API、可选链运算符??、扩展运算符)不支持IE浏览器,若需兼容IE,需额外做兼容处理;

  9. 对象/数组操作均优先采用不修改原数据的方式(如扩展运算符、map、filter),避免意外污染原始数据,提升代码可维护性。

Sass与Less全面对比(含语法+场景+选型)

2026年4月10日 10:31

Sass(Syntactically Awesome Style Sheets)和 Less(Leaner Style Sheets)是目前最主流的两款CSS预处理器,二者核心目标一致——扩展CSS的功能,解决原生CSS无变量、无嵌套、无复用性等痛点,让样式开发更高效、代码更易维护。但两者在起源、语法细节、功能特性、生态支持等方面存在显著差异,选择时需结合项目规模、团队习惯和需求场景综合判断。

一、核心差异总览(表格清晰对比)

对比维度 Sass Less
起源与底层实现 2007年诞生,最初基于Ruby实现,目前官方推荐使用Dart Sass(更易维护、性能更优),Node Sass已停更废弃。 2009年诞生,基于JavaScript实现,依赖Node.js环境编译,学习门槛相对较低,可在浏览器端直接解析(不推荐生产环境)。
语法风格 支持两种语法:① SCSS(.scss后缀):兼容原生CSS,使用大括号和分号,目前最常用;② 缩进式(.sass后缀):无大括号和分号,靠缩进区分代码块,格式要求严格。 仅支持一种语法(.less后缀),完全兼容原生CSS,必须使用大括号和分号,写法与原生CSS高度一致,上手更轻松。
变量声明 使用$符号定义变量,支持!default设置默认值(仅在变量未定义时生效),作用域严格,局部变量不影响全局,重定义未加!default的变量会报错。 使用@符号定义变量,采用“懒求值”机制,同名变量后声明会覆盖前声明(无论是否在嵌套块内),局部变量可直接覆盖全局变量,无报错提示。
嵌套与父选择器 支持选择器嵌套,父选择器&解析严格,要求符号与选择器间无多余空格(如&:hover合法),带空格会编译报错,避免隐性错误。 支持选择器嵌套,父选择器&解析宽松,允许省略空格或多余空格(如&.disabled& .disabled均合法),易出现“编译成功但结果异常”的情况。
混合(Mixins) @mixin定义混合宏,@include调用,支持参数、默认值和可变参数,功能灵活,可配合逻辑控制实现复杂复用逻辑。 用类选择器(可加括号,加括号不输出到CSS)定义混合,直接通过类名调用,支持参数和默认值,功能相对基础,无复杂逻辑支持。
继承 @extend实现继承,支持“占位符选择器”(%开头),仅用于继承,不生成冗余CSS,复用效率更高。 @extend实现继承,但不支持占位符选择器,被继承的类会被编译到最终CSS中,易产生冗余代码。
逻辑控制 支持完整的逻辑控制:@if/@else条件判断、@for/@each/@while循环,还可通过@function自定义函数,适合复杂动态样式生成。 逻辑控制能力较弱,仅支持简单的when条件判断和递归循环(需手动终止),无原生自定义函数功能,复杂逻辑需通过混合模拟。
模块化机制 采用现代化模块系统,通过@use(直接使用模块)和@forward(转发模块成员)实现模块化,自动单例加载,支持命名空间和私有成员,彻底避免命名冲突和重复加载。 依赖@import实现模块化,无命名空间和单例加载机制,多次导入同一文件会重复编译,易造成全局污染和代码冗余,无原生私有成员支持。
内置函数 内置函数丰富,涵盖颜色处理、字符串操作、数学计算等,支持颜色对象运算,类型安全,边界值处理更严谨(如纯黑颜色调整),还可通过内置模块(如sass:math、sass:color)扩展功能。 内置函数相对基础,主要支持简单的颜色调整(如lighten、darken),函数参数和返回值类型不统一,颜色操作仅支持字符串拼接,无法参与复杂运算,易出现解析异常。
生态与框架支持 生态更成熟,社区活跃,插件丰富,主流框架(Bootstrap 4+、Angular、Vue CLI)均优先支持,与Webpack、Vite等构建工具集成流畅,Dart Sass编译速度快,适合大型项目。 生态相对小众,早期用于Bootstrap 3,目前在部分企业级老项目中仍有应用,与构建工具集成时存在配置限制(如Vite不支持javascriptEnabled配置),适合小型项目或老项目维护。
学习门槛 中等,SCSS语法兼容CSS,基础用法易上手,但高级特性(逻辑控制、模块化)需额外学习,缩进式语法对格式要求较高。 低,语法完全贴近原生CSS,无额外格式要求,基础功能简单易懂,适合刚接触预处理器的开发者快速上手。

二、核心语法对比(附代码示例)

以下针对最常用的核心功能,对比两者的语法差异,所有示例均采用各自最主流的语法(Sass用SCSS,Less用默认语法)。

1. 变量声明与使用

// 定义全局变量,设置默认值(未定义时生效)
$primary-color: #2563eb !default;
$font-size: 16px;

// 局部变量(仅在.box内生效,不影响全局)
.box {
  $local-color: #6c757d;
  color: $primary-color; // 全局变量:#2563eb
  background: $local-color; // 局部变量:#6c757d
  font-size: $font-size; // 全局变量:16px
}

// 重定义带!default的变量(合法,覆盖默认值)
$primary-color: #1d4ed8;
.text {
  color: $primary-color; // 覆盖后:#1d4ed8
}
// 定义全局变量
@primary-color: #2563eb;
@font-size: 16px;

// 局部变量(覆盖全局变量)
.box {
  @primary-color: #6c757d;
  color: @primary-color; // 局部变量:#6c757d(覆盖全局)
  font-size: @font-size; // 全局变量:16px
}

// 重定义变量(直接覆盖,无报错)
@primary-color: #1d4ed8;
.text {
  color: @primary-color; // 覆盖后:#1d4ed8
}

2. 选择器嵌套与父选择器

.nav {
  width: 100%;
  height: 60px;
  
  // 子选择器嵌套
  > li {
    float: left;
    margin: 0 10px;
    
    // 父选择器&(严格解析,无空格)
    &:hover {
      color: $primary-color;
    }
    &.active {
      font-weight: bold;
    }
  }
}
// 编译后无冗余,&解析准确
.nav {
  width: 100%;
  height: 60px;
  
  // 子选择器嵌套(与Sass一致)
  > li {
    float: left;
    margin: 0 10px;
    
    // 父选择器&(宽松解析,允许空格)
    & :hover { // 多余空格,编译为.nav li :hover(非预期)
      color: @primary-color;
    }
    &.active {
      font-weight: bold;
    }
  }
}
// 易因空格问题导致样式异常,需格外注意

3. 混合(Mixins)用法

// 定义带参数、默认值的混合
@mixin flex-center($direction: row) {
  display: flex;
  flex-direction: $direction;
  justify-content: center;
  align-items: center;
}

// 调用混合(传递参数)
.box {
  @include flex-center(column);
  width: 300px;
  height: 200px;
}

// 调用混合(使用默认值)
.card {
  @include flex-center;
  background: #fff;
}
// 定义带参数、默认值的混合(加括号不输出到CSS)
.flex-center(@direction: row) {
  display: flex;
  flex-direction: @direction;
  justify-content: center;
  align-items: center;
}

// 调用混合(直接使用类名,传递参数)
.box {
  .flex-center(column);
  width: 300px;
  height: 200px;
}

// 调用混合(使用默认值)
.card {
  .flex-center;
  background: #fff;
}

4. 继承用法

// 占位符选择器(仅用于继承,不输出到CSS)
%button-base {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

// 继承占位符样式
.primary-btn {
  @extend %button-base;
  background: $primary-color;
  color: #fff;
}

// 继承占位符样式
.success-btn {
  @extend %button-base;
  background: #16a34a;
  color: #fff;
}
// 编译后无%button-base相关样式,无冗余
// 普通类选择器(会被编译到CSS中)
.button-base {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

// 继承普通类样式
.primary-btn {
  @extend .button-base;
  background: @primary-color;
  color: #fff;
}

// 继承普通类样式
.success-btn {
  @extend .button-base;
  background: #16a34a;
  color: #fff;
}
// 编译后会保留.button-base样式,产生冗余

5. 逻辑控制用法

// 条件判断
$theme: dark;
.box {
  @if $theme == dark {
    background: #111;
    color: #fff;
  } @else {
    background: #fff;
    color: #333;
  }
}

// 循环(生成col-1到col-4)
@for $i from 1 to 5 {
  .col-#{$i} {
    width: 100% / $i;
    float: left;
  }
}
// 条件判断(通过when)
@theme: dark;
.box when (@theme = dark) {
  background: #111;
  color: #fff;
}
.box when not (@theme = dark) {
  background: #fff;
  color: #333;
}

// 递归循环(需手动终止)
.loop(@n) when (@n > 0) {
  .col-@{n} {
    width: 100% / @n;
    float: left;
  }
  .loop(@n - 1); // 递归调用,直到@n<=0
}
.loop(4); // 生成col-4到col-1

6. 模块化导入用法

// 1. 拆分模块:_variables.scss(局部文件,下划线开头不单独编译)
$primary-color: #2563eb;
$-private-var: 10px; // 私有成员(-/_开头,外部无法访问)

// 2. 主文件导入:main.scss
@use "./variables"; // 默认命名空间:variables,单例加载
@use "./variables" as v; // 自定义命名空间:v(重复导入仅加载一次)

.box {
  color: variables.$primary-color; // 通过命名空间访问
  padding: v.$primary-color;
  // margin: variables.$-private-var; // 报错:私有成员无法访问
}

// 3. 转发模块(供其他文件使用)
@forward "./variables" as var-*; // 转发所有成员,加前缀var-
// 1. 拆分模块:variables.less
@primary-color: #2563eb;
@private-var: 10px; // 无私有成员,外部可直接访问

// 2. 主文件导入:main.less
@import "./variables.less"; // 无命名空间,全局注入
@import "./variables.less"; // 重复导入,重复编译,产生冗余

.box {
  color: @primary-color; // 直接访问,无隔离
  padding: @private-var; // 无私有限制,可访问所有成员
}

三、编译环境与工具集成

1. Sass 编译环境

  • 主流实现:目前推荐使用Dart Sass(npm安装:npm install -g sass),替代已废弃的Node Sass,编译速度快、兼容性好,支持所有新特性。
  • 工具集成:与Webpack(sass-loader)、Vite(内置支持)、VS Code(Live Sass Compiler插件)集成流畅,支持source map调试,additionalData选项可注入全局变量,支持函数回调配置。
  • 编译命令:sass input.scss output.css(实时监听:sass --watch input.scss:output.css)。

2. Less 编译环境

  • 安装方式:基于Node.js,npm安装:npm install -g less,可通过less.js在浏览器端直接解析(仅适合开发调试,生产环境不推荐)。
  • 工具集成:与Webpack(less-loader)、Vite(内置支持)集成,但存在配置限制(如Vite不支持javascriptEnabled: true,无法运行JS表达式),additionalData选项仅支持字符串配置,不支持函数回调。
  • 编译命令:lessc input.less output.css(实时监听需借助第三方工具)。

四、项目选型建议

选型核心:结合项目规模、团队技术栈、功能需求,而非单纯追求“更强大”,优先保证开发效率和可维护性。

1. 优先选择 Sass(SCSS)的场景

  • 中大型项目/团队协作:需要复杂逻辑控制(如动态主题、批量样式生成)、严格的模块化隔离,避免命名冲突,Sass的@use/@forward、私有成员、逻辑控制等特性可大幅提升可维护性和协作效率。
  • 新项目开发:追求长期可维护性,希望适配主流技术生态,Sass的社区支持更完善、框架兼容性更好,后续扩展更便捷,是目前官方和行业推荐的首选方案。
  • 需要丰富的内置函数和高级特性:如复杂颜色处理、自定义函数、灵活的变量配置(!default),适合搭建设计系统或多主题项目,Sass的类型安全和函数链式调用更稳定可靠。
  • 使用主流前端框架:如Bootstrap 4+、Angular、Vue 3,这些框架均优先支持Sass,集成更流畅,减少配置成本。

2. 优先选择 Less 的场景

  • 小型项目/快速原型开发:需求简单,仅需变量、嵌套、基础混合等功能,Less语法贴近原生CSS,上手快、配置简单,可快速完成开发任务。
  • 维护旧项目:项目已基于Less开发,短期内无法迁移,继续使用Less可降低迁移成本,避免影响项目正常运行,Less的兼容性可保证旧代码稳定编译。
  • 团队成员不熟悉预处理器:团队以原生CSS开发为主,Less学习成本低,无需额外学习复杂语法,可快速过渡到预处理器开发模式。
  • 简单的浏览器端调试需求:Less可通过less.js直接在浏览器端解析,无需搭建复杂的编译环境,适合快速调试样式

五、常见问题与避坑指南

实际开发中,无论是Sass还是Less,都容易遇到语法、编译或集成相关的问题,以下梳理高频坑点及解决方案,帮助规避不必要的麻烦。

1. Sass 常见避坑点

  • 坑点1:混淆Node Sass与Dart Sass,导致编译报错。解决方案:彻底卸载Node Sass(npm uninstall node-sass),安装Dart Sass(npm install sass),确保项目依赖中无node-sass,避免版本冲突。
  • 坑点2:@use导入路径错误,提示“找不到模块”。解决方案:导入时省略下划线和文件后缀(如导入_variables.scss,写@use "./variables"),路径以当前文件为基准,避免绝对路径,跨目录导入需正确拼接相对路径(如@use "../utils/variables")。
  • 坑点3:误将SCSS语法用在缩进式Sass文件中,导致编译失败。解决方案:统一项目语法风格,优先使用SCSS(.scss后缀),若使用缩进式Sass(.sass后缀),需严格遵循“无大括号、无分号、靠缩进区分代码块”的规则。
  • 坑点4:重定义未加!default的变量,导致报错。解决方案:全局公共变量建议加!default(方便后续覆盖),局部变量仅在当前模块内使用,避免与全局变量重名,若需重定义全局变量,确保先导入变量文件,再重定义。

2. Less 常见避坑点

  • 坑点1:父选择器&添加多余空格,导致样式解析异常。解决方案:严格控制&与后续选择器的空格(如&:hover而非& :hover),避免编译后生成非预期的选择器(如.nav li :hover)。
  • 坑点2:Vite项目中启用javascriptEnabled: true,导致编译报错。解决方案:Vite内置的Less编译器不支持该配置,若需运行JS表达式,可改用Webpack+less-loader,或避免在Less中写入JS逻辑。
  • 坑点3:多次导入同一文件,导致CSS冗余。解决方案:尽量减少重复导入,可将公共模块(如变量、混合)集中在一个入口文件导入,再引入该入口文件,避免多文件重复导入同一模块。
  • 坑点4:变量覆盖导致样式异常,难以排查。解决方案:规范变量命名(如加模块前缀@btn-primary-color),避免全局变量与局部变量重名,复杂项目可按模块拆分变量文件,减少覆盖风险。

3. 通用避坑点

  • 避免嵌套过深:无论是Sass还是Less,嵌套层级建议不超过3层,否则会编译出冗长的选择器,影响CSS性能,且不利于代码维护。
  • 规范文件命名:局部模块文件(不单独编译的文件)建议以下划线开头(如_variables.scss_mixins.less),区分全局入口文件,避免编译生成多余的CSS文件。
  • 慎用!important:预处理器中尽量避免使用!important,若需提高样式优先级,可通过调整选择器权重(如增加父选择器)实现,否则会导致样式优先级混乱,难以调试。

六、实战对比总结

Sass和Less本质上都是为了解决原生CSS的痛点,提升样式开发效率,但两者的定位和适用场景有明显区分,无需纠结“谁更好”,只需结合自身需求选择即可,核心总结如下:

  • 从功能强大度来看:Sass > Less,Sass的模块化、逻辑控制、内置函数等高级特性,更适合复杂项目和设计系统搭建,能解决更多场景下的开发痛点。
  • 从学习成本来看:Less < Sass,Less语法与原生CSS高度一致,上手门槛极低,适合新手或原生CSS开发者快速过渡,Sass的高级特性需要额外投入时间学习。
  • 从生态和未来趋势来看:Sass更具优势,官方持续更新维护,主流框架和构建工具优先支持,Node Sass的废弃也推动了Dart Sass的普及,而Less生态相对停滞,仅适合维护旧项目或小型项目。
  • 从团队协作来看:Sass更适合团队协作,严格的作用域、命名空间和私有成员机制,能有效避免命名冲突,清晰的依赖关系也便于代码维护和迭代;Less无模块化隔离,大型团队协作易出现问题。

最后补充一句:无论是选择Sass还是Less,核心是“规范使用”——统一语法风格、合理拆分模块、规范变量命名,才能真正发挥预处理器的优势,让样式代码更高效、更易维护。如果是新建项目,优先选择Sass(SCSS),贴合行业主流;如果是维护旧项目或快速开发,Less也是不错的选择。

七、快速选型对照表(便捷参考)

项目/团队情况 推荐选择 核心原因
中大型项目、团队协作 Sass(SCSS) 模块化强、无命名冲突、支持复杂逻辑,可维护性高
小型项目、快速原型开发 Less 上手快、配置简单,满足基础需求,开发效率高
新建项目、追求长期维护 Sass(SCSS) 生态成熟、官方推荐,适配主流框架,扩展便捷
旧项目维护(基于Less) Less 降低迁移成本,保证旧代码稳定编译,无需额外学习
新手开发者、原生CSS过渡 Less 语法贴近原生CSS,学习成本低,快速上手无压力
搭建设计系统、多主题项目 Sass(SCSS) 内置函数丰富、变量配置灵活,支持复杂动态样式生成

SCSS中@use与@import的区别

2026年4月10日 10:16

SCSS(Sassy CSS)中@use@import均用于实现样式模块化,实现代码复用,但二者在作用域、加载机制、命名空间等核心特性上差异显著。其中@import是SCSS早期的导入语法,存在全局污染、重复加载等问题,而@use是Sass 3.8+推出的新版模块化语法,旨在解决@import的缺陷,目前已被官方推荐作为首选导入方式,未来@import将逐步被弃用。

一、核心差异对比(表格清晰呈现)

对比维度 @import(旧版语法) @use(新版推荐)
作用域 全局作用域,导入的变量、混合宏(mixin)、函数会直接注入当前文件的全局作用域,易造成命名冲突和变量污染。 局部作用域,导入的内容被封装在独立模块中,需通过命名空间访问,从根本上避免全局污染和命名冲突。
加载机制 多次导入同一文件时,会重复加载、重复编译,增加编译时间,可能导致输出CSS冗余。 自动实现单例加载,无论导入多少次同一文件,仅加载、编译一次,提升编译效率,避免冗余代码。
命名空间 无命名空间,导入的所有成员(变量、mixin等)可直接访问,无需前缀,易引发命名冲突,需通过冗长命名规避冲突。 默认以导入文件的文件名作为命名空间,也可自定义命名空间;访问成员时需加上命名空间前缀,可通过as *省略前缀(慎用)。
私有成员支持 不支持私有成员,导入文件中所有定义的变量、mixin均可被外部访问,无法实现成员隐藏。 支持私有成员,以-_为前缀的变量、mixin视为私有,仅能在定义文件内部使用,外部无法访问,实现更好的封装性。
依赖关系 依赖关系混乱,无法清晰判断变量、mixin的来源,不利于大型项目维护和团队协作。 依赖关系显式化,通过命名空间可明确知道每个成员的来源,代码可维护性大幅提升,适合大型项目和团队协作。
变量配置 通过重定义变量覆盖默认值(需在@import前定义),但全局变量易被意外修改,配置逻辑不清晰。 支持通过with语句针对性配置模块变量,不影响全局,配置逻辑更严谨、可追溯。
官方支持 已被官方不推荐使用,计划逐步弃用,仅为兼容旧代码保留,部分新特性不支持。 官方推荐首选语法,支持所有新特性,是SCSS模块化开发的标准方案,与@forward配合实现更完善的模块化体系。

上述表格已清晰列出@use@import的所有核心区别,接下来我们重点拆解最影响开发效率和代码质量的两个特性——重复加载和命名空间。

二、重点特性详解(重复加载+命名空间)

2.1 重复加载(性能与冗余核心差异)

重复加载是@import最突出的问题之一,会直接影响样式文件性能和代码冗余度,而@use通过单例加载机制完美解决了这一问题。

  • 当使用 @import 导入模块时,如果在多个文件中多次导入同一个文件,会导致重复加载的问题。
  • 这意味着被导入的文件将在每个使用了 @import 的文件中都被加载一次,导致样式表中包含多份相同的样式,从而影响性能和增加文件大小。

我们通过一个实际示例,直观感受重复加载的问题:

// _variables.scss(被重复导入的模块)
$primary-color: #007bff;
$secondary-color: #6c757d;

// styles1.scss
@import 'variables';
body {
  background-color: $primary-color;
}

// styles2.scss
@import 'variables';
button {
  background-color: $secondary-color;
}

示例解析:我们有两个样式文件 styles1.scss 和 styles2.scss,它们分别使用 @import 导入了同一个 _variables.scss 文件。由于 styles1.scss 和 styles2.scss 都导入了 _variables.scss,在编译这两个样式文件时,_variables.scss 将被加载两次。

编译后的结果如下所示:

// 编译后的 styles1.cssbody {
  background-color: #007bff;
}

// 编译后的 styles2.cssbutton {
  background-color: #6c757d;
}

可以看到,虽然编译后的CSS中未直接显示重复的变量定义,但 _variables.scss 中的内容在编译过程中被加载了两次,不仅增加了编译时间,若模块中包含实际样式(而非仅变量),会导致CSS文件中出现多份相同样式,增加文件大小、影响页面加载性能,还可能引发潜在的样式冲突。

而使用 @use 导入方式可以避免重复加载问题,因为它会确保每个模块只加载一次,即使在多个文件中导入。这样可以优化编译性能,并保持样式表的精简和一致性。

2.2 命名空间(避免冲突的核心机制)

@import 没有命名空间机制,这是导致其命名冲突的核心原因;而 @use 内置命名空间功能,可灵活隔离模块成员,提升代码可读性和可维护性,具体分为三种使用场景。

2.2.1 不使用as:直接以文件名作为命名空间

当在 @use 后面直接跟上文件路径,且不使用 as 关键字指定命名空间时,会将导入的模块整体作为一个命名空间,且使用被导入文件的名称作为命名空间标识(省略下划线前缀)。

// _variables.scss
$primary-color: #007bff;
$secondary-color: #6c757d;

// styles.scss
@use 'variables.scss';  // 省略下划线,默认命名空间为variables
body {   
  background-color: variables.$primary-color; // 通过命名空间访问变量
}  
button {   
  background-color: variables.$secondary-color;
}

示例解析:styles.scss 使用 @use 直接导入了 variables.scss 文件,未指定自定义命名空间,因此 _variables.scss 中的所有内容被封装在 variables 命名空间下,访问时需加上 variables.前缀,避免与当前文件的其他变量冲突。

2.2.2 使用as xxx:自定义命名空间

通过 as 关键字可以为导入的模块自定义命名空间,让命名更简洁、贴合业务场景,进一步提升代码可读性。

// _variables.scss(与上例一致)
$primary-color: #007bff;
$secondary-color: #6c757d; 

// styles.scss
@use 'variables.scss' as customVars;  // 自定义命名空间为customVars
body {   
  background-color: customVars.$primary-color; 
}  
button {   
  background-color: customVars.$secondary-color; 
}

示例解析:通过 as customVars 为导入的模块创建了自定义命名空间 customVars,后续访问模块中的变量时,需通过 customVars. 前缀,既避免了命名冲突,又让变量来源更清晰。

2.2.3 特殊情况:使用as * 导入(无命名空间)

如果在 @use 后面使用 as *,表示将导入的模块的所有内容直接合并到当前文件中,不创建任何命名空间,导入的变量、mixin、函数等可直接使用。

// _variables.scss(与上例一致)
$primary-color: #007bff;
$secondary-color: #6c757d;

// styles.scss
@use 'variables.scss' as *;  // 无命名空间,直接合并内容
body {   
  background-color: $primary-color; // 直接访问变量,无需前缀
}  
button {   
  background-color: $secondary-color;
}

注意:这种方式会丧失命名空间的隔离优势,与 @import 类似,易引发命名冲突,仅建议在变量统一管理、无冲突风险的简单场景使用。

三、语法用法补充(其他核心用法)

3.1 @import 其他常见问题

除了重复加载,@import 还存在全局污染、依赖混乱等问题,以下是基础用法回顾及问题总结:

// _variables.scss
$color: red;
$font-size: 16px;

// _utils.scss
$color: blue; // 与variables.scss中的$color重名
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

// main.scss
@import "./variables";
@import "./utils"; // 重名变量被覆盖
@import "./variables"; // 重复加载,增加编译冗余

.box {
  color: $color; // 输出blue(被utils.scss中的$color覆盖,意外污染)
  font-size: $font-size; // 输出16px
  @include flex-center;
}

编译后CSS:

.box {
  color: blue;
  font-size: 16px;
  display: flex;
  justify-content: center;
  align-items: center;
}

问题总结:@import导入的变量会全局覆盖,重复导入同一文件会重复编译,无法区分成员来源,维护难度高。

3.2 @use 其他优势用法

除了命名空间和单例加载,@use 还支持私有成员、变量配置等优势特性,以下是补充示例:

// _theme.scss
$-private-var: 10px; // 私有变量(仅文件内可用,前缀-/_)
$primary-color: #3498db !default; // 默认变量,可被配置覆盖
$secondary-color: #2ecc71 !default;

// main.scss
// 自定义命名空间为t,并通过with配置变量
@use "./theme" as t with (
  $primary-color: #e74c3c, // 覆盖默认主色
  $secondary-color: #f39c12
);

.box {
  background: t.$primary-color; // 输出#e74c3c(配置后的值)
  color: t.$secondary-color; // 输出#f39c12(配置后的值)
  // margin: t.$-private-var; // 报错:私有变量无法访问
}

编译后CSS:

.box {
  background: #e74c3c;
  color: #f39c12;
}

四、实际开发场景选择建议

1. 优先使用@use的场景

  • 新建SCSS项目:全程使用@use,配合@forward实现模块化拆分(如按变量、mixin、组件拆分文件),提升代码可维护性和可扩展性。
  • 大型项目/团队协作:通过命名空间隔离和显式依赖,避免命名冲突,清晰区分成员来源,降低协作成本和维护难度。
  • 需要封装私有成员:当部分变量、mixin仅需在当前文件使用,无需暴露给外部时,使用@use的私有成员特性,实现代码封装。
  • 使用Sass新特性:@use支持所有Sass新特性(如内置模块导入),而@import不支持部分新特性,无法适配未来升级需求。

2. 临时使用@import的场景

  • 维护旧项目:当项目中大量使用@import,短期内无法全部迁移时,可临时保留,逐步替换为@use,避免影响项目正常运行。
  • 导入纯CSS文件:虽然@use也可导入纯CSS文件,但@import语法更简洁,且无需处理命名空间(仅适用于简单场景)。

五、补充注意事项

  • @use导入文件时,可省略文件扩展名(.scss、.sass),也可省略下划线前缀(如导入_variables.scss,可写为@use "./variables")。
  • @use的as *语法可省略命名空间,直接访问导入的成员,但会丧失命名空间的隔离优势,易引发冲突,仅建议在变量统一管理的简单场景使用。
  • 迁移旧项目时,需注意:@use中以-_开头的变量视为私有,若旧代码中存在此类命名的变量,导入后会无法访问,需修改变量命名或调整访问方式。
  • @use与@forward的配合:@forward用于转发模块成员(不直接使用),适合库开发或入口文件整合;@use用于直接使用模块成员,二者配合可实现更灵活的模块化体系。
  • Sass内置模块(如sass:math、sass:color)需通过@use导入才能使用,无法通过@import导入,这也是官方推荐@use的重要原因之一。

六、总结

推荐使用 @use 来导入模块,以获得更好的模块化支持、性能优化和避免全局污染问题。其核心优势在于局部作用域、命名空间隔离、单例加载、私有成员支持,彻底解决了@import的全局污染、重复加载、依赖混乱等问题,提升了代码的可维护性、可扩展性和协作效率。而 @import 在新版本 Sass 中已不再推荐使用,并且未来可能会被废弃,仅适合临时维护旧项目,新建项目或项目升级时,应优先采用@use + @forward的模块化方案,遵循官方推荐的开发规范,避免后续维护成本增加。

学习Less,看这篇就够了(从入门到实战)

2026年4月10日 09:58

Less(Leaner Style Sheets)是CSS预处理器,在原生CSS基础上增加变量、嵌套、混合、函数、运算、模块化等编程特性,让CSS更易维护、复用、扩展,最终编译成标准CSS运行。本文从环境搭建、核心语法、进阶技巧、实战规范全流程覆盖,新增多个优雅使用案例(含Less源码与编译后CSS对比),直接上手可用。

一、Less基础:是什么、为什么、怎么用

1.1 核心优势(为什么用Less)

  • 变量统一管理:颜色、尺寸、字体等全局配置,一改全改
  • 嵌套结构:完全匹配HTML层级,代码更直观、减少重复选择器
  • 混合(Mixin):复用样式片段,像函数一样传参
  • 运算/函数:支持加减乘除、颜色明暗、单位转换
  • 模块化:拆分文件、导入合并,便于团队协作与维护
  • 兼容原生CSS:所有CSS代码可直接写在Less中,零学习门槛

1.2 环境搭建(3种方式,最快1分钟)

方式1:浏览器直接运行(学习/原型)


<!-- 1. 引入Less文件,rel必须是stylesheet/less -->
<link rel="stylesheet/less" type="text/css" href="styles.less" />
<!-- 2. 引入Less编译器(CDN,已替换为可用链接) -->
<script src="https://cdn.bootcdn.net/ajax/libs/less.js/4.2.0/less.min.js"></script>

⚠️ 仅适合开发调试,生产环境禁止使用(性能差、依赖JS);原CDN链接报错“link dead”,已替换为稳定可用版本

方式2:VSCode自动编译(推荐,日常开发)

  1. 安装插件:Easy LESS
  2. 新建.less文件,保存时自动生成同名.css
  3. 配置(可选,settings.json):

"less.compile": {
  "out": "../css/", // 输出到css文件夹
  "compress": true, // 压缩CSS
  "sourceMap": false
}

方式3:命令行编译(项目构建)

  1. 安装Node.js,全局安装Less:

npm install -g less
lessc -v # 验证安装
  1. 编译命令:

lessc styles.less styles.css # 基础编译
lessc styles.less styles.min.css --compress # 压缩输出

二、Less核心语法(必掌握,直接套用)

2.1 变量(@变量名:值)—— 统一管理,一改全改

变量以@开头,可存颜色、尺寸、字体、路径等,支持插值(选择器、属性、URL)。


// 1. 基础变量定义(语义化命名,便于维护)
@primary: #2563eb; // 主色
@success: #16a34a; // 成功色
@font-size: 16px; // 基础字体
@spacing: 20px; // 基础间距
@img-path: "../images"; // 图片路径

// 2. 变量使用(结合运算,减少硬编码)
.btn {
  background: @primary;
  font-size: @font-size;
  padding: @spacing/2; // 10px,无需手动计算
  margin: @spacing;
}

// 3. 变量插值(复用选择器、属性、URL,避免重复书写)
@selector: card;
@prop: width;
.@{selector} { // 编译为 .card
  @{prop}: 300px; // 编译为 width:300px
  background: url("@{img-path}/bg.png");
}

编译后CSS:


.btn {
  background: #2563eb;
  font-size: 16px;
  padding: 10px;
  margin: 20px;
}
.card {
  width: 300px;
  background: url("../images/bg.png");
}

优雅要点:变量语义化命名,通过插值复用选择器和路径,运算替代硬编码,后续修改主色、间距时,仅需修改变量值,无需逐行修改样式。

2.2 嵌套规则(&父选择器)—— 匹配HTML结构,告别重复

Less允许选择器嵌套,&代表当前父选择器,用于伪类、交集选择器、兄弟选择器,避免重复书写父选择器。


// HTML结构:<div class="header"><nav class="nav"><a href="#" class="active">首页</a></nav></div>
.header {
  width: 100%;
  height: 60px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1); // 增加阴影,提升质感
  .nav {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    a {
      color: #333;
      text-decoration: none;
      padding: 0 @spacing/2; // 复用间距变量
      margin: 0 @spacing;
      // & 代表父选择器 .nav a,避免书写 .nav a:hover
      &:hover { 
        color: @primary;
        transition: color 0.3s ease; // 过渡效果,更优雅
      }
      &.active { 
        font-weight: bold; 
        color: @primary;
        border-bottom: 2px solid @primary;
      }
    }
  }
}

编译后CSS(对比:选择器自动拼接,无需手动重复):


.header {
  width: 100%;
  height: 60px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.header .nav {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
.header .nav a {
  color: #333;
  text-decoration: none;
  padding: 0 10px;
  margin: 0 20px;
}
.header .nav a:hover {
  color: #2563eb;
  transition: color 0.3s ease;
}
.header .nav a.active {
  font-weight: bold;
  color: #2563eb;
  border-bottom: 2px solid #2563eb;
}

优雅要点:嵌套结构与HTML完全对应,可读性极强;&的使用避免重复书写父选择器(如.header .nav a),同时结合过渡效果,提升交互质感。

2.3 混合(Mixin)—— 复用样式,支持传参(核心)

Mixin是可复用的样式片段,带()不输出到CSS,不带()会输出;支持参数、默认值、条件判断,可封装公共样式,避免重复编码。

(1)基础Mixin(无参)—— 封装公共样式


// 定义:清除浮动(公共Mixin,可全局调用)
.clearfix() {
  &::after {
    content: "";
    display: block;
    clear: both;
    height: 0;
    visibility: hidden;
  }
}

// 定义:居中布局(公共Mixin,复用性强)
.center() {
  display: flex;
  justify-content: center;
  align-items: center;
}

// 使用:多个容器复用,无需重复书写样式
.container {
  .clearfix(); // 调用清除浮动
  width: 1200px;
  margin: 0 auto;
}
.modal {
  .center(); // 调用居中布局
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
}

编译后CSS(对比:Mixin样式自动注入,无需重复书写):


.container {
  width: 1200px;
  margin: 0 auto;
}
.container::after {
  content: "";
  display: block;
  clear: both;
  height: 0;
  visibility: hidden;
}
.modal {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
}

(2)带参数Mixin(默认值、多参数)—— 动态生成样式


// 定义:按钮样式(带参数+默认值,灵活适配不同场景)
.btn(@bg: @primary, @color: #fff, @radius: 4px, @padding: 8px 16px) {
  display: inline-block;
  padding: @padding;
  background: @bg;
  color: @color;
  border-radius: @radius;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.3s ease; // 统一过渡效果
  &:hover { 
    filter: brightness(0.9); // hover变暗,无需单独写样式
    transform: translateY(-2px); // 轻微上浮,提升交互
  }
  &:active {
    transform: translateY(0);
  }
}

// 使用:按需传参,无需重复书写按钮基础样式
.btn-primary { .btn(); } // 使用默认值(主色按钮)
.btn-success { .btn(@success); } // 传单个参数(成功色按钮)
.btn-round { .btn(@primary, #fff, 50%, 10px 20px); } // 传全部参数(圆形按钮)
.btn-small { .btn(@primary, #fff, 4px, 4px 8px); } // 传部分参数(小尺寸按钮)

编译后CSS(对比:自动生成不同样式的按钮,代码简洁):


.btn-primary {
  display: inline-block;
  padding: 8px 16px;
  background: #2563eb;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.3s ease;
}
.btn-primary:hover {
  filter: brightness(0.9);
  transform: translateY(-2px);
}
.btn-primary:active {
  transform: translateY(0);
}

.btn-success {
  display: inline-block;
  padding: 8px 16px;
  background: #16a34a;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.3s ease;
}
.btn-success:hover {
  filter: brightness(0.9);
  transform: translateY(-2px);
}
.btn-success:active {
  transform: translateY(0);
}

.btn-round {
  display: inline-block;
  padding: 10px 20px;
  background: #2563eb;
  color: #fff;
  border-radius: 50%;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.3s ease;
}
.btn-round:hover {
  filter: brightness(0.9);
  transform: translateY(-2px);
}
.btn-round:active {
  transform: translateY(0);
}

.btn-small {
  display: inline-block;
  padding: 4px 8px;
  background: #2563eb;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.3s ease;
}
.btn-small:hover {
  filter: brightness(0.9);
  transform: translateY(-2px);
}
.btn-small:active {
  transform: translateY(0);
}

(3)命名空间(组织Mixin,避免冲突)


// 命名空间:统一管理公共Mixin,避免与业务样式冲突
#utils() { // 带(),不输出到CSS
  .clearfix() { 
    &::after {
      content: "";
      display: block;
      clear: both;
      height: 0;
      visibility: hidden;
    }
  }
  .center() { 
    display: flex;
    justify-content: center;
    align-items: center; 
  }
  .shadow() { // 新增阴影Mixin
    box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  }
}

// 调用:通过命名空间调用,清晰区分公共样式与业务样式
.box { 
  #utils.center(); 
  #utils.shadow();
  width: 300px;
  height: 200px;
  background: #fff;
}

编译后CSS:


.box {
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  width: 300px;
  height: 200px;
  background: #fff;
}

2.4 运算(+ - * /)—— 自动计算,减少手动计算

支持颜色、数值、单位运算,Less自动处理单位(优先左侧单位),避免手动计算错误,代码更优雅。


@base: 20px;
@width: 1000px;
@card-width: @width / 5; // 200px,自动计算栅格宽度
@light-primary: lighten(@primary, 10%); // 主色变亮10%,无需手动计算色值

.box {
  width: @width - 40px; // 960px,自适应宽度
  padding: @base * 1.5; // 30px,间距按比例调整
  margin: (@base / 2); // 10px,统一间距
}
.card {
  width: @card-width;
  background: @light-primary;
  margin: @base;
}

⚠️ 除法建议加括号:(100px / 2),避免与CSS语法冲突

编译后CSS(对比:自动计算数值和颜色,无需手动计算):


.box {
  width: 960px;
  padding: 30px;
  margin: 10px;
}
.card {
  width: 200px;
  background: #3b82f6; // 自动计算的亮主色
  margin: 20px;
}

2.5 内置函数—— 颜色、字符串、数学处理(常用)

Less提供大量内置函数,无需定义直接用,提升效率,让样式更优雅。


// 颜色函数(最常用,自动处理色值)
@primary: #2563eb;
@dark-primary: darken(@primary, 10%); // 主色变暗10%
@fade-primary: fade(@primary, 50%); // 主色半透明
@saturate-primary: saturate(@primary, 20%); // 主色增加饱和度

// 数学函数(自动处理数值)
@base-font: 16px;
@title-font: ceil(@base-font * 1.5); // 24px,向上取整
@sub-font: floor(@base-font * 0.8); // 12px,向下取整

// 使用:结合函数和变量,样式更灵活
.title {
  font-size: @title-font;
  color: @dark-primary;
}
.sub-title {
  font-size: @sub-font;
  color: @fade-primary;
}
.btn {
  background: @saturate-primary;
  &:hover {
    background: @dark-primary;
  }
}

编译后CSS:


.title {
  font-size: 24px;
  color: #1d4ed8; // 变暗后的主色
}
.sub-title {
  font-size: 12px;
  color: rgba(37, 99, 235, 0.5); // 半透明主色
}
.btn {
  background: #1d4ed8; // 增加饱和度后的主色
}
.btn:hover {
  background: #1d4ed8;
}

2.6 导入(@import)—— 模块化拆分,代码解耦

拆分变量、Mixin、公共样式,通过@import合并,支持省略.less后缀,让代码结构更清晰,便于维护。


// 1. 拆分文件(按功能拆分,各司其职)
// variables.less → 全局变量(单独管理,一改全改)
@primary: #2563eb;
@font-size: 16px;
@spacing: 20px;

// mixins.less → 公共混合(单独管理,全局复用)
.clearfix() { ... }
.btn(@bg: @primary) { ... }
.center() { ... }

// base.less → 基础样式(reset、全局样式)
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: "Microsoft YaHei", sans-serif;
  font-size: @font-size;
  color: #333;
}

// 2. 主文件导入(统一入口,结构清晰)
@import "variables"; // 省略.less
@import "mixins";
@import "base";

// 编写业务样式(仅关注业务,无需关注公共样式)
.header {
  .clearfix();
  height: 60px;
  .nav {
    .center();
    a {
      color: #333;
      &:hover { color: @primary; }
    }
  }
}

⚠️ @import (reference) "mixins.less"; → 仅导入Mixin,不输出到CSS

编译后CSS(对比:所有导入的样式自动合并,结构清晰):


* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: "Microsoft YaHei", sans-serif;
  font-size: 16px;
  color: #333;
}
.header {
  height: 60px;
}
.header::after {
  content: "";
  display: block;
  clear: both;
  height: 0;
  visibility: hidden;
}
.header .nav {
  display: flex;
  justify-content: center;
  align-items: center;
}
.header .nav a {
  color: #333;
}
.header .nav a:hover {
  color: #2563eb;
}

2.7 作用域与注释

  • 作用域:就近原则,局部变量覆盖全局变量,类似JS,可灵活控制变量作用范围

// 全局变量(整个项目可用)
@color: red;

// 局部变量(仅在.box内可用,不影响全局)
.box {
  @color: blue;
  color: @color; // blue(局部优先)
}
// 其他模块仍使用全局变量
.text {
  color: @color; // red
}

编译后CSS:


.box {
  color: blue;
}
.text {
  color: red;
}
  • 注释:

    • 单行注释:// 注释 → 编译后不保留(用于开发备注,不污染生产CSS)
    • 多行注释:/* 注释 */ → 编译后保留(用于生产环境备注,如版权信息)

三、Less进阶技巧(提升效率,避坑)

3.1 父选择器&高级用法


// 1. 前缀拼接(批量生成同类样式,避免重复)
.btn {
  display: inline-block;
  padding: 8px 16px;
  border-radius: 4px;
  &-primary { background: @primary; color: #fff; } // .btn-primary
  &-success { background: @success; color: #fff; } // .btn-success
  &-warning { background: #f59e0b; color: #fff; } // .btn-warning
  &-disabled { background: #ccc; color: #666; cursor: not-allowed; } // .btn-disabled
}

// 2. 多层嵌套&(精准定位子元素,避免冗长选择器)
.list {
  width: 100%;
  &-item {
    padding: @spacing;
    border-bottom: 1px solid #eee;
    &:last-child { border-bottom: none; } // .list-item:last-child
    &-title { font-weight: bold; color: #333; } // .list-item-title
    &-content { color: #666; margin-top: 8px; } // .list-item-content
  }
}

编译后CSS(对比:自动拼接选择器,批量生成样式,代码简洁):


.btn {
  display: inline-block;
  padding: 8px 16px;
  border-radius: 4px;
}
.btn-primary {
  background: #2563eb;
  color: #fff;
}
.btn-success {
  background: #16a34a;
  color: #fff;
}
.btn-warning {
  background: #f59e0b;
  color: #fff;
}
.btn-disabled {
  background: #ccc;
  color: #666;
  cursor: not-allowed;
}

.list {
  width: 100%;
}
.list-item {
  padding: 20px;
  border-bottom: 1px solid #eee;
}
.list-item:last-child {
  border-bottom: none;
}
.list-item-title {
  font-weight: bold;
  color: #333;
}
.list-item-content {
  color: #666;
  margin-top: 8px;
}

3.2 条件Mixin(when)—— 动态生成样式


// 定义:根据尺寸生成不同按钮(条件判断,灵活适配)
.btn(@size) when (@size = large) {
  padding: 12px 24px;
  font-size: 18px;
  border-radius: 6px;
}
.btn(@size) when (@size = small) {
  padding: 4px 8px;
  font-size: 14px;
  border-radius: 3px;
}
// 新增条件:根据主题生成不同颜色
.btn(@size, @theme) when (@theme = dark) {
  .btn(@size);
  background: #333;
  color: #fff;
}

// 使用:按需传入条件,自动生成对应样式
.btn-lg { .btn(large); } // 大尺寸按钮
.btn-sm { .btn(small); } // 小尺寸按钮
.btn-lg-dark { .btn(large, dark); } // 大尺寸深色按钮

编译后CSS:


.btn-lg {
  padding: 12px 24px;
  font-size: 18px;
  border-radius: 6px;
}
.btn-sm {
  padding: 4px 8px;
  font-size: 14px;
  border-radius: 3px;
}
.btn-lg-dark {
  padding: 12px 24px;
  font-size: 18px;
  border-radius: 6px;
  background: #333;
  color: #fff;
}

3.3 循环(for)—— 批量生成样式(Less 3.9+)


// 生成1-5列栅格(循环遍历,无需手动写5个样式)
.generate-columns(@n, @i: 1) when (@i =< @n) {
  .col-@{i} {
    width: (@i * 100% / @n);
    float: left;
    padding: @spacing/2;
    box-sizing: border-box;
  }
  .generate-columns(@n, @i + 1); // 递归循环
}
.generate-columns(5); // 生成col-1到col-5

// 生成不同尺寸的margin样式(批量生成,复用性强)
.generate-margin(@n, @i: 1) when (@i =< @n) {
  .mt-@{i} { margin-top: @i * 8px; }
  .mb-@{i} { margin-bottom: @i * 8px; }
  .generate-margin(@n, @i + 1);
}
.generate-margin(5); // 生成mt-1~mt-5、mb-1~mb-5

编译后CSS(对比:自动生成10个margin样式+5个栅格样式,无需手动书写):


.col-1 {
  width: 20%;
  float: left;
  padding: 10px;
  box-sizing: border-box;
}
.col-2 {
  width: 40%;
  float: left;
  padding: 10px;
  box-sizing: border-box;
}
.col-3 {
  width: 60%;
  float: left;
  padding: 10px;
  box-sizing: border-box;
}
.col-4 {
  width: 80%;
  float: left;
  padding: 10px;
  box-sizing: border-box;
}
.col-5 {
  width: 100%;
  float: left;
  padding: 10px;
  box-sizing: border-box;
}

.mt-1 { margin-top: 8px; }
.mb-1 { margin-bottom: 8px; }
.mt-2 { margin-top: 16px; }
.mb-2 { margin-bottom: 16px; }
.mt-3 { margin-top: 24px; }
.mb-3 { margin-bottom: 24px; }
.mt-4 { margin-top: 32px; }
.mb-4 { margin-bottom: 32px; }
.mt-5 { margin-top: 40px; }
.mb-5 { margin-bottom: 40px; }

3.4 映射(Maps)—— 像对象一样取值(Less 3.5+)


// 定义颜色映射(类似JS对象,统一管理所有颜色,便于查找和修改)
@colors: {
  primary: #2563eb;
  success: #16a34a;
  warning: #f59e0b;
  danger: #ef4444;
  dark: #333;
  light: #f5f5f5;
};

// 定义尺寸映射(统一管理尺寸,避免硬编码)
@sizes: {
  small: 14px;
  base: 16px;
  large: 18px;
  xlarge: 24px;
};

// 使用:通过映射取值,代码更简洁,维护更方便
.btn {
  font-size: @sizes[base];
  &-primary { background: @colors[primary]; }
  &-success { background: @colors[success]; }
  &-warning { background: @colors[warning]; }
}
.title {
  font-size: @sizes[xlarge];
  color: @colors[dark];
}

编译后CSS:


.btn {
  font-size: 16px;
}
.btn-primary {
  background: #2563eb;
}
.btn-success {
  background: #16a34a;
}
.btn-warning {
  background: #f59e0b;
}
.title {
  font-size: 24px;
  color: #333;
}

四、实战规范与常见问题(避坑指南)

4.1 项目规范(推荐)

  1. 文件结构:

src/
├── less/
│   ├── variables.less   # 全局变量(颜色、尺寸、字体)
│   ├── mixins.less      # 公共混合(清除浮动、按钮、居中)
│   ├── base.less        # 基础样式(reset、全局)
│   ├── components/      # 组件(按钮、卡片、导航)
│   └── main.less        # 主入口(导入所有)
  1. 命名:变量用@xxx-xxx(@primary-color),Mixin用小驼峰/短横线,语义化

4.2 常见问题与解决

  1. 嵌套过深:最多3层,避免编译后选择器过长、性能差
  2. 变量污染:全局变量放单独文件,局部变量仅在模块内使用
  3. 编译报错:检查括号、分号、变量定义,优先用Easy LESS实时提示
  4. 单位冲突:运算时统一单位,或用unit()函数转换

五、Less vs Sass(快速对比,选择更合适)

特性 Less Sass
变量符号 @ $
编译环境 Node.js/浏览器 Ruby/Node.js
语法 接近CSS,易上手 缩进/花括号两种
循环/条件 支持(Less 3.9+) 原生支持,更强大
生态 轻量,适合中小型项目 功能全,适合大型项目

六、总结与下一步

Less核心就是把CSS变成可维护的代码:变量统一、嵌套清晰、Mixin复用、函数简化、模块化拆分。新增的优雅案例均贴合实际开发,通过Less源码与编译后CSS对比,可清晰看到Less如何简化编码、提升效率。

CSS3 position 属性全面理解(实战版)

2026年4月9日 23:07

CSS3 的 position 属性,核心作用是控制元素在页面中的定位方式,决定元素如何脱离正常文档流、如何相对于父元素/视口进行定位,是布局中实现“悬浮、固定、层叠”等效果的核心属性,常用且易混淆,本篇将从基础到实战,彻底讲懂。

核心原则:position 的取值决定元素的定位模式,配合 toprightbottomleft(以下简称“方位属性”)控制具体位置,未设置方位属性时,元素默认保持原有位置。

一、position 5个取值(必学,按常用度排序)

position 有5个核心取值,其中 static 是默认值,relativeabsolutefixed 是高频常用值,sticky 是粘性定位(场景化常用),逐一拆解如下。

1. static(默认值:静态定位)

最基础的定位方式,元素遵循正常文档流,自上而下、从左到右排列,不受方位属性(top/right等)的影响,也不会脱离文档流。

/* 默认无需主动设置,所有元素默认都是 static */
.box {
  position: static;
  top: 20px; /* 无效,static 不识别方位属性 */
  left: 20px; /* 无效 */
  width: 100px;
  height: 100px;
  background: #00aaff;
}

特点:无法通过方位属性调整位置,完全遵循正常布局,一般用于“取消已设置的定位”(如覆盖父元素的定位)。

2. relative(相对定位)

元素不脱离正常文档流,保留自身在文档流中的原有位置,方位属性(top/right等)相对于自身原有位置进行偏移,不会影响其他元素的布局。

.box {
  position: relative;
  top: 20px; /* 相对于自身原有位置,向下偏移20px */
  left: 30px; /* 相对于自身原有位置,向右偏移30px */
  width: 100px;
  height: 100px;
  background: #ff7d00;
}

关键要点:

  • 不脱离文档流,自身原有位置会被保留(其他元素不会填充进来);
  • 偏移量相对于“自身原本的位置”(而非父元素);
  • 常用场景:作为 absolute 定位的“参考容器”(子绝父相),或微调元素位置。

3. absolute(绝对定位)【重点】

元素完全脱离正常文档流,不再保留自身原有位置(其他元素会填充其空位),方位属性相对于最近的已定位祖先元素(position 不为 static 的祖先)定位;若没有已定位祖先,则相对于根元素(html)定位。

/* 父容器设置 relative(作为参考) */
.parent {
  position: relative;
  width: 300px;
  height: 300px;
  background: #f5f5f5;
}
/* 子元素设置 absolute(相对于父容器定位) */
.child {
  position: absolute;
  top: 50px; /* 相对于父容器顶部,向下50px */
  right: 50px; /* 相对于父容器右侧,向左50px */
  width: 100px;
  height: 100px;
  background: #00cc66;
}

关键要点(避坑核心):

  • 脱离文档流,不占据页面空间,会“悬浮”在其他元素上方;
  • 定位参考物:优先找“最近的、position 为 relative/absolute/fixed/sticky”的祖先元素,没有则相对于 html(页面);
  • 高频场景:弹窗、下拉菜单、元素悬浮、精准定位(如按钮右上角的角标);
  • 易错点:若父元素未设置定位,子元素 absolute 会相对于页面定位,导致布局错乱。

4. fixed(固定定位)

元素完全脱离正常文档流,方位属性相对于浏览器视口(viewport) 定位,无论页面如何滚动,元素始终固定在视口的指定位置,不随页面滚动而移动。

/* 页面右下角固定按钮 */
.fixed-btn {
  position: fixed;
  bottom: 30px; /* 相对于视口底部,向上30px */
  right: 30px; /* 相对于视口右侧,向左30px */
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #ff4444;
  color: white;
  text-align: center;
  line-height: 60px;
}

关键要点:

  • 脱离文档流,不占据页面空间,始终悬浮在视口固定位置;
  • 定位参考物是“浏览器视口”,与父元素无关;
  • 常用场景:固定导航栏、回到顶部按钮、悬浮客服按钮。

5. sticky(粘性定位)

“相对定位 + 固定定位”的结合体,元素默认遵循正常文档流,当页面滚动到指定位置时,自动切换为固定定位,固定在视口的指定位置;滚动超出范围后,恢复为相对定位。

/* 粘性导航栏 */
.sticky-nav {
  position: sticky;
  top: 0; /* 滚动到顶部距离为0时,固定 */
  width: 100%;
  height: 60px;
  background: #333;
  color: white;
  line-height: 60px;
  padding: 0 20px;
}

关键要点:

  • 未滚动到指定位置时,遵循正常文档流;滚动到阈值(top/right等设置的值)时,变为固定定位;
  • 必须设置方位属性(top/right/bottom/left),否则粘性效果无效;
  • 常用场景:粘性导航栏、列表标题悬浮(滚动时标题固定在顶部)。

二、核心对比(快速区分,避坑关键)

取值 是否脱离文档流 定位参考物 核心场景
static 无(不识别方位属性) 默认布局、取消定位
relative 自身原有位置 作为absolute参考容器、微调位置
absolute 最近的已定位祖先 / html 弹窗、悬浮元素、精准定位
fixed 浏览器视口 固定导航、回到顶部按钮
sticky 视滚动状态(未固定时否,固定时是) 正常流时自身,固定时视口 粘性导航、悬浮标题

三、实战案例(直接复制套用,覆盖高频场景)

案例1:子绝父相(最常用,精准定位)

场景:卡片内部的按钮、角标,相对于卡片精准定位,不影响卡片布局。

/* 父卡片 - 相对定位(参考容器) */
.card {
  position: relative;
  width: 280px;
  height: 380px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  padding: 20px;
}
/* 子元素 - 绝对定位(相对于卡片) */
.card-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  background: #ff4444;
  color: white;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
}
.card-btn {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%); /* 水平居中 */
  width: 80%;
  height: 40px;
  background: #00aaff;
  color: white;
  border: none;
  border-radius: 4px;
}

案例2:固定回到顶部按钮

场景:页面滚动时,右下角固定显示回到顶部按钮,点击可返回页面顶部。

.back-top {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: rgba(0, 170, 255, 0.8);
  color: white;
  text-align: center;
  line-height: 50px;
  cursor: pointer;
  transition: all 0.3s;
}
.back-top:hover {
  background: #00aaff;
  transform: scale(1.1);
}

案例3:粘性导航栏

场景:页面顶部导航栏,滚动页面时,导航栏固定在视口顶部,不随页面滚动消失。

/* 页面头部 */
.header {
  height: 100px;
  background: #f5f5f5;
  line-height: 100px;
  text-align: center;
  font-size: 24px;
}
/* 粘性导航 */
.sticky-nav {
  position: sticky;
  top: 0;
  width: 100%;
  height: 60px;
  background: #333;
  color: white;
  padding: 0 20px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* 导航菜单 */
.nav-menu {
  display: flex;
  gap: 30px;
  line-height: 60px;
}
/* 页面内容(用于测试滚动) */
.content {
  height: 1500px;
  padding: 20px;
}

四、避坑注意事项(必看)

  • absolute 定位的元素,若父元素未设置定位(position 为 static),会相对于 html 定位,容易导致布局错乱,建议遵循“子绝父相”原则。
  • fixed 定位的元素,会脱离文档流且相对于视口定位,若父元素有 transform 属性(如 scale、translate),会导致 fixed 定位失效(参考物变为父元素)。
  • sticky 定位必须设置方位属性(top/right等),否则无法触发粘性效果;且父元素不能有 overflow: hidden 属性,否则粘性效果失效。
  • 脱离文档流的元素(absolute、fixed),会覆盖在未脱离文档流的元素上方,可通过 z-index 属性调整层级(z-index 值越大,层级越高)。
  • relative 定位的元素,虽然不脱离文档流,但会创建“层叠上下文”,其内部的绝对定位元素会相对于它定位,而非相对于更外层的元素。

五、总结(快速记忆)

  1. 默认 static:不脱离流,方位无效;

  2. relative:不脱离流,相对于自身,做参考容器;

  3. absolute:脱离流,相对于已定位祖先,精准定位;

  4. fixed:脱离流,相对于视口,固定不动;

  5. sticky:混合定位,滚动触发固定,需设方位属性。

记住“子绝父相”“fixed 相对视口”“sticky 需设方位”这3个关键点,就能解决90%的定位场景,剩下的靠实战熟练即可。

零基础学会 Flex 布局(实战版)

2026年4月9日 23:00

Flex 布局(弹性布局)是 CSS 中最常用、最灵活的布局方式,核心作用是快速实现元素的对齐、分布、自适应,替代传统的 float、position 布局,适配所有现代浏览器,上手简单且实用性极强。

核心原则:给父容器设置 display: flex,父容器称为「flex 容器」,其直接子元素称为「flex 项目」,通过控制容器和项目的属性,实现各种布局效果。

一、核心基础(必学,3分钟掌握)

1. 开启 Flex 布局(第一步)

给需要布局的父容器添加 display: flex,即可开启弹性布局,项目会默认横向排列(水平主轴)。

/* 父容器 */
.flex-container {
  display: flex; /* 开启flex布局 */
  width: 100%;
  height: 300px;
  background: #f5f5f5;
}

/* 子项目 */
.flex-item {
  width: 100px;
  height: 100px;
  background: #00aaff;
  margin: 10px;
}

效果:3个项目横向排列,默认在容器顶部对齐,自动填满水平方向(未填充满时,项目之间有间隙)。

2. 两个核心概念(必记)

  • 主轴:项目排列的主要方向(默认「水平方向」,从左到右)
  • 交叉轴:与主轴垂直的方向(默认「垂直方向」,从上到下)

后续所有属性,都是围绕「主轴」和「交叉轴」的对齐、分布来设置。

二、容器属性(控制整体布局,最常用6个)

所有属性均设置在「父容器」上,控制子项目的排列方式,重点记前4个。

1. flex-direction(控制主轴方向,核心)

决定项目是横向、纵向排列,解决“水平/垂直布局”问题。

属性值 效果 常用场景
row(默认) 主轴水平,项目从左到右 导航栏、横向卡片
column 主轴垂直,项目从上到下 侧边栏、纵向列表
row-reverse 主轴水平,项目从右到左 反向排列(少见)
column-reverse 主轴垂直,项目从下到上 反向排列(少见)

2. justify-content(主轴对齐,最常用)

控制项目在「主轴」上的对齐方式,解决“水平/垂直居中、分布”问题。

属性值 效果(主轴水平时) 高频场景
flex-start(默认) 项目靠左对齐 普通列表
center 项目水平居中 登录框、卡片居中
flex-end 项目靠右对齐 右侧按钮组
space-between 两端对齐,项目之间间距相等 导航栏(左右分布)
space-around 项目两侧间距相等,整体间距均匀 卡片布局、商品列表

3. align-items(交叉轴对齐,核心)

控制项目在「交叉轴」上的对齐方式,解决“垂直/水平居中”的另一半问题。

属性值 效果(主轴水平时) 高频场景
stretch(默认) 项目高度拉伸至与容器一致 等高卡片、导航栏
center 项目垂直居中 文字+图标对齐、卡片内容居中
flex-start 项目靠上对齐 顶部列表
flex-end 项目靠下对齐 底部按钮组

4. flex-wrap(控制换行,避免溢出)

默认情况下,项目会强制在一行显示,超出容器会溢出,用这个属性控制换行。

属性值 效果
nowrap(默认) 不换行,项目会被压缩
wrap 自动换行,超出一行时,新行在下
wrap-reverse 自动换行,超出一行时,新行在上

5. align-content(多行交叉轴对齐,少见)

只有当项目换行(flex-wrap: wrap)时才生效,控制多行项目在交叉轴上的整体对齐方式,用法和 justify-content 类似(center、space-between 等)。

6. flex-flow(简写属性,偷懒必备)

flex-direction + flex-wrap 的简写,顺序任意,默认值:flex-flow: row nowrap。

/* 等价于 flex-direction: column; flex-wrap: wrap; */
.flex-container {
  flex-flow: column wrap;
}

三、项目属性(控制单个项目,常用3个)

属性设置在「子项目」上,控制单个项目的大小、对齐方式,按需使用。

1. flex(核心,控制项目占比)

最常用属性,控制项目在主轴上的占比,替代 width/height,实现自适应,语法:flex: 数字(数字越大,占比越大)。

.flex-container {
  display: flex;
  width: 100%;
  height: 200px;
  background: #f5f5f5;
}
/* 三个项目,占比 1:2:1 */
.item1 { flex: 1; background: #00aaff; }
.item2 { flex: 2; background: #ff7d00; }
.item3 { flex: 1; background: #00cc66; }

效果:容器宽度被分成 4 份,item2 占 2 份,其余各占 1 份,自适应容器宽度。

补充:flex 的完整写法是 flex: flex-grow flex-shrink flex-basis,日常用简写(数字)即可。

2. align-self(单个项目交叉轴对齐)

覆盖父容器的 align-items 属性,单独控制某个项目的交叉轴对齐方式。

.flex-container {
  display: flex;
  align-items: center; /* 所有项目垂直居中 */
}
.item-special {
  align-self: flex-end; /* 单个项目靠下对齐 */
}

补充:flex 的完整写法是 flex: flex-grow flex-shrink flex-basis,日常用简写(数字)即可。

3. order(控制项目顺序)

默认所有项目 order: 0,数字越小,项目越靠前;可以用负数,实现“不改变HTML结构,调整显示顺序”。

.item1 { order: 2; } /* 第3个显示 */
.item2 { order: 1; } /* 第2个显示 */
.item3 { order: 0; } /* 第1个显示 */

四、实战案例(直接复制套用,覆盖80%场景)

案例1:水平居中 + 垂直居中(最常用)

适用于登录框、弹窗内容、卡片居中,一步到位。

.container {
  display: flex;
  justify-content: center; /* 主轴居中(水平) */
  align-items: center; /* 交叉轴居中(垂直) */
  width: 100vw; /* 视口宽度 */
  height: 100vh; /* 视口高度 */
  background: #f5f5f5;
}
.center-box {
  width: 300px;
  height: 200px;
  background: white;
  border-radius: 8px;
  text-align: center;
  line-height: 200px;
}

案例2:导航栏(两端对齐)

适用于网站导航,左侧logo、右侧菜单,两端分布。

.nav {
  display: flex;
  justify-content: space-between; /* 两端对齐 */
  align-items: center; /* 垂直居中 */
  padding: 0 20px;
  height: 60px;
  background: #333;
  color: white;
}
.nav-logo {
  font-size: 20px;
  font-weight: bold;
}
.nav-menu {
  display: flex; /* 菜单横向排列 */
  gap: 20px; /* 菜单之间间距 */
}

案例3:自适应卡片布局(换行)

适用于商品列表、卡片展示,自适应屏幕宽度,自动换行。

.card-container {
  display: flex;
  flex-wrap: wrap; /* 自动换行 */
  gap: 20px; /* 卡片之间间距 */
  padding: 20px;
  background: #f5f5f5;
}
.card {
  flex: 1; /* 自适应占比 */
  min-width: 250px; /* 最小宽度,避免太窄 */
  height: 200px;
  background: white;
  border-radius: 8px;
  padding: 15px;
}

案例4:垂直布局(侧边栏)

适用于侧边导航、垂直列表,纵向排列。

.sidebar {
  display: flex;
  flex-direction: column; /* 垂直排列 */
  width: 200px;
  height: 500px;
  background: #333;
  color: white;
}
.sidebar-item {
  padding: 15px 20px;
  border-bottom: 1px solid #444;
}
.sidebar-item:last-child {
  border-bottom: none;
}

五、注意事项(避坑关键)

  • 开启 flex 布局后,子项目的 float、clear、vertical-align 属性会失效,无需再用。
  • flex: 1 等价于 flex: 1 1 0%,会让项目自适应填充剩余空间,优先于固定 width/height。
  • justify-content 控制主轴,align-items 控制交叉轴,方向由 flex-direction 决定(别搞反)。
  • 项目换行时,用 gap 控制间距(比 margin 更简洁,不会出现最后一个项目多余间距)。
  • 兼容问题:Flex 布局支持 IE10+,现代浏览器(Chrome、Edge、Firefox、Safari)均完美支持,无需加前缀。

六、总结(快速记忆)

  1. 开启:父容器 display: flex

  2. 方向:flex-direction(row/column);

  3. 对齐:主轴 justify-content,交叉轴 align-items;

  4. 换行:flex-wrap: wrap;

  5. 占比:项目 flex: 数字;

记住这5点,就能解决80%的布局问题,剩下的靠实战熟练即可。

Vue 中实现文字滚动(跑马灯)的多种方式

2026年4月9日 22:51

在 Vue 里实现文字滚动(跑马灯) ,最常用、最稳的就两种:

  1. CSS 动画纯实现(简单、性能好)
  2. JS 控制滚动(可暂停、可控制速度)

下面直接给你可复制粘贴的 Vue 组件代码


方式1:纯 CSS 跑马灯(推荐)

Marquee.vue

<template>
  <div class="marquee-wrap">
    <div class="marquee-content">
      {{ text }}
    </div>
  </div>
</template>

<script setup>
const text = '这里是需要滚动的文字,Vue 跑马灯效果,从右向左无限滚动~';
</script>

<style scoped>
.marquee-wrap {
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  background: #f5f5f5;
  padding: 8px 16px;
  border-radius: 8px;
}

.marquee-content {
  display: inline-block;
  animation: marquee 15s linear infinite;
}

@keyframes marquee {
  0% {
    transform: translateX(100%);
  }
  100% {
    transform: translateX(-100%);
  }
}
</style>

特点:

  • 一行无限滚动
  • 无 JS,性能最好
  • 鼠标悬浮暂停版往下看

方式2:hover 暂停 + 无缝滚动(更常用)

<template>
  <div class="box">
    <div class="marquee" @mouseenter="pause" @mouseleave="play">
      <div class="text" :style="{ animationPlayState }">
        {{ content }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const content = 'Vue3 无缝跑马灯,鼠标移入暂停,移出继续滚动~';
const animationPlayState = ref('running');

const pause = () => {
  animationPlayState.value = 'paused';
};
const play = () => {
  animationPlayState.value = 'running';
};
</script>

<style scoped>
.box {
  width: 100%;
  overflow: hidden;
  background: #f9f9f9;
  padding: 10px;
  border-radius: 6px;
}
.marquee {
  white-space: nowrap;
}
.text {
  display: inline-block;
  animation: move 12s linear infinite;
}
@keyframes move {
  0% { transform: translateX(100%); }
  100% { transform: translateX(-100%); }
}
</style>

方式3:真正无缝(无空白,首尾衔接)

适合公告、长文本:

<template>
  <div class="wrap">
    <div class="box">
      <span class="txt1">{{ text }}</span>
      <span class="txt2">{{ text }}</span>
    </div>
  </div>
</template>

<script setup>
const text = '这里是真正无缝跑马灯,没有空白间隔,一直循环滚动';
</script>

<style scoped>
.wrap {
  width: 100%;
  overflow: hidden;
  background: #fff8e1;
  padding: 8px 0;
}
.box {
  display: flex;
  width: max-content;
  animation: scroll 10s linear infinite;
}
.txt1, .txt2 {
  padding: 0 20px;
}
@keyframes scroll {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
</style>

方式4:JS 控制滚动(可变速、可停止)

<template>
  <div class="box" style="overflow: hidden">
    <div class="text" :style="{ marginLeft: `${left}px` }">
      JS 控制跑马灯,可随时停止、加速、减速
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const left = ref(300);
let timer = null;

onMounted(() => {
  timer = setInterval(() => {
    left.value -= 1;
    if (left.value < -300) left.value = 300;
  }, 20);
});
onUnmounted(() => clearInterval(timer));
</script>

CSS3 阴影完全指南:box-shadow、text-shadow 详解

2026年4月9日 22:48

shadow 里最常用的两个阴影属性:

  • text-shadow:文字阴影(简单)
  • box-shadow:盒子/元素阴影(常用、功能强)

我用最通俗、能直接上手的方式给你讲清楚,附带复制即用的代码。


一、text-shadow 文字阴影

专门给文字加阴影,语法超级简单。

基础语法

text-shadow: 水平偏移 垂直偏移 模糊度 颜色;

参数解释

  1. 水平偏移:正数向右,负数向左
  2. 垂直偏移:正数向下,负数向上
  3. 模糊度:值越大越模糊(不能为负)
  4. 颜色:阴影颜色(可省略,默认文字颜色)

示例代码

/* 基础阴影 */
text-shadow: 2px 2px 4px #333;

/* 发光效果(最常用!) */
text-shadow: 0 0 8px #00aaff;

/* 多重阴影(用逗号分隔) */
text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue;

二、box-shadow 盒子阴影(重点!)

div、图片、按钮、卡片加阴影,90% 网页美化都用它。

完整语法

box-shadow: 水平偏移 垂直偏移 模糊度 扩散半径 颜色 内外阴影;

6 个参数(必须记)

  1. h-shadow:水平偏移(必需)
  2. v-shadow:垂直偏移(必需)
  3. blur:模糊半径(越大越柔)
  4. spread:扩散半径(扩大/缩小阴影)
  5. color:阴影颜色
  6. inset内阴影(不加就是外阴影)

最常用写法(外阴影)

/* 卡片柔和阴影(推荐!) */
box-shadow: 0 2px 12px rgba(0,0,0,0.1);

内阴影(inset)

/* 凹陷效果 */
box-shadow: inset 0 0 10px #000;

多重阴影

box-shadow: 
  0 0 10px red,
  0 0 20px blue,
  0 0 30px green;

三、最实用的 6 种阴影效果(直接复制)

1. 标准卡片阴影(最常用)

box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);

2. 悬浮抬起效果(hover)

div{
  transition: 0.3s;
}
div:hover{
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
  transform: translateY(-3px);
}

3. 发光效果

box-shadow: 0 0 12px #00aaff;

4. 内阴影(凹陷)

box-shadow: inset 0 2px 8px rgba(0,0,0,0.2);

5. 细边框阴影(代替 border)

box-shadow: 0 0 0 1px #ccc;

6. 文字发光

text-shadow: 0 0 6px #fff, 0 0 12px #00aaff;

四、快速区分 & 记忆

属性 作用 最常用格式
text-shadow 文字阴影 2px 2px 4px #333
box-shadow 盒子阴影 0 2px 12px rgba(0,0,0,0.1)
  • 偏移:正右下,负左上
  • 模糊:越大越柔
  • 扩散:越大越大
  • 内阴影:加 inset

五、小技巧(高手必备)

  1. 阴影用 rgba 更自然:rgba(0,0,0,0.1)
  2. 垂直偏移 > 水平偏移更符合视觉习惯
  3. 配合 transition 做 hover 动画超好看
  4. 多重阴影 = 高级质感

总结

  • text-shadow:给文字加阴影,4个参数,简单
  • box-shadow:给盒子加阴影,6个参数,支持内外、多层
  • 最实用:0 2px 12px rgba(0,0,0,0.1) 卡片阴影
  • 想凹陷就加 inset

需要我给你做一个可在线调试的阴影演示页面吗?直接复制就能用!

❌
❌