Skip to content

Review 2025

阶段一

事件循环?

👉 渡一大师课 - 原理事件循环

javascript
console.log(1);

setTimeout(() => {
  console.log(3);
}, 0);

Promise.reject(() => {
  console.log(4);
}).catch((error) => {
  console.log(error);
});

console.log(2);
运行结果
javascript
1
2
() => {
  console.log(4);
}
3

阶段二

事件循环?

👉 阶段一 - 事件循环?

uni-app 页面跳转方式?

  • uni.navigateTo(options),保留当前页面,跳转到应用内的某个非 tabBar 页面,可以返回原页面。
  • uni.redirectTo(options),关闭当前页面,跳转到应用内的某个非 tabBar 页面,不能返回原页面。
  • uni.switchTab(options),跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面,不能携带参数(参数需用全局变量或 storage 实现)
  • uni.reLaunch(options),关闭所有页面,打开到应用内的某个页面,清空页面栈。
  • uni.navigateBack(options),关闭当前页面,返回上一页面或多级页面。
javascript
uni.navigateTo({
  url: "/pages/detail/detail?id=123",
});

uni.redirectTo({
  url: "/pages/detail/detail?id=456",
});

uni.switchTab({
  url: "/pages/home/home",
});

uni.reLaunch({
  url: "/pages/login/login",
});

uni.navigateBack({
  delta: 1, // 返回上一页
});

uni-app 的页面传参方式?

  1. URL 参数传参(最常用)
javascript
// 传参方式(页面跳转时带参数)
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=Tom'
})

// 接收参数方式(在目标页 onLoad 生命周期中)
onLoad(options) {
  console.log(options.id);   // 输出: 123
  console.log(options.name); // 输出: Tom
}
  1. 使用 eventChannel 传参(适合对象或复杂数据)
javascript
// 传参方式(仅 navigateTo 支持)
uni.navigateTo({
  url: "/pages/detail/detail",
  success: function (res) {
    res.eventChannel.emit("acceptDataFromOpenerPage", {
      id: 456,
      user: { name: "Alice", age: 25 },
    });
  },
});

// 接收方式(在目标页 onLoad 中)
onLoad() {
  const eventChannel = this.getOpenerEventChannel();
  eventChannel.on('acceptDataFromOpenerPage', (data) => {
    console.log(data.id);       // 456
    console.log(data.user.name); // Alice
  });
}
  1. 使用 uni.setStorage / uni.setStorageSync 传参
javascript
// 设置参数
uni.setStorageSync("userInfo", { name: "Jack", age: 30 });

// 获取参数
const user = uni.getStorageSync("userInfo");
console.log(user.name); // Jack
  1. 使用全局变量(不推荐,容易污染命名空间)
javascript
// 在 main.js 中定义全局变量
Vue.prototype.globalData = {
  userId: 789,
};

// 在页面中访问
console.log(this.globalData.userId); // 789

uni-app 的父子组件传参?

  1. 父组件向子组件传参(通过 props)
vue
<!-- 父组件中使用子组件,传入参数 -->
<template>
  <my-child :title="parentTitle" :user="userInfo" />
</template>
<script>
export default {
  data() {
    return {
      parentTitle: "欢迎来到UniApp",
      userInfo: { name: "Tom", age: 20 },
    };
  },
};
</script>

<!-- 子组件通过 props 接收 -->
<script>
export default {
  props: {
    title: String,
    user: Object,
  },
  mounted() {
    console.log(this.title); // 欢迎来到UniApp
    console.log(this.user.name); // Tom
  },
};
</script>
  1. 子组件向父组件传值(通过 $emit)
vue
<!-- 子组件中触发事件 -->
<template>
  <button @click="sendMsg">点击我</button>
</template>

<script>
export default {
  methods: {
    sendMsg() {
      this.$emit("childEvent", "我是子组件传来的消息");
    },
  },
};
</script>

<!-- 父组件中监听事件 -->
<template>
  <my-child @childEvent="getMsgFromChild" />
</template>

<script>
export default {
  methods: {
    getMsgFromChild(msg) {
      console.log(msg); // 我是子组件传来的消息
    },
  },
};
</script>
  1. 父组件调用子组件方法(通过 ref)
vue
<!-- 子组件定义方法 -->
<script>
export default {
  methods: {
    sayHello() {
      console.log("子组件的方法被调用了");
    },
  },
};
</script>

<!-- 父组件中通过 ref 调用 -->
<template>
  <my-child ref="childRef" />
</template>

<script>
this.$refs.childRef.sayHello();
</script>

注意:在 <script setup> 语法中需要使用 ref="xxx" + onMounted() 方式配合 defineExpose。

  1. 使用 Vuex / Pinia / 全局 EventBus(适合跨层级通信)
  • 如果数据要在多个不相关组件间共享,建议使用 Vuex 或 Pinia。
  • 或者简单场景可用一个 EventBus 来监听/发送事件。

uni-app 的生命周期?

在 UniApp 中,生命周期分为三类:

  • 应用级生命周期(App)
  • 页面级生命周期(Page)
  • 组件级生命周期(Component)

应用生命周期

管理整个 App 的启动、退出、后台、前台等状态。

应用生命周期函数在 App.vue 中定义,是整个应用的入口文件。

生命周期函数触发时机支持平台
onLaunch(options)应用初始化完成时触发(只触发一次)全平台
onShow(options)应用进入前台时触发全平台
onHide()应用进入后台时触发全平台
onUnhandledRejection捕获未处理的 Promise 异常H5、微信小程序、App
onThemeChange系统主题变化时触发微信小程序
场景生命周期用法
用户打开 App,检查登录状态onLaunch初始化、读取缓存 token、跳转登录页
App 从后台返回前台onShow检查是否有新消息、刷新数据等
App 被挂起onHide暂存数据、清理监听器
统一处理未 catch 的错误onUnhandledRejection错误上报等

页面生命周期

适用于页面(即 pages.json 中注册的页面)。

生命周期函数说明
onLoad(options)页面加载时触发,接收页面参数。初始化逻辑写在这里。
onShow()页面显示时触发,每次返回页面都会执行。
onReady()页面初次渲染完成时触发。只触发一次。
onHide()页面隐藏时触发,如 navigateTo 到另一个页面。
onUnload()页面卸载时触发,如 navigateBack 返回。
onPullDownRefresh()监听下拉刷新操作(需要配置)。
onReachBottom()页面滚动到底部时触发。
onShareAppMessage()用户点击右上角分享按钮时触发(仅小程序)。
onPageScroll(event)页面滚动时触发。
onResize()页面尺寸变化时触发(仅 H5)。
onTabItemTap(item)点击 tabBar 中的按钮时触发(仅 tab 页面)。
javascript
export default {
  onLoad(options) {
    console.log("页面加载", options);
  },
  onShow() {
    console.log("页面显示");
  },
  onReady() {
    console.log("页面渲染完成");
  },
  onHide() {
    console.log("页面隐藏");
  },
  onUnload() {
    console.log("页面卸载");
  },
};

组件生命周期

适用于通过 components/ 引入的子组件。

生命周期函数说明
beforeCreate()实例初始化之后,数据观测之前。
created()实例创建完成,数据已设置。
beforeMount()模板编译前,尚未挂载到 DOM。
mounted()组件挂载到 DOM 后。最常用,用于获取 DOM 元素或发送请求。
beforeUpdate()数据更新前。
updated()数据更新后。
beforeDestroy()实例销毁前。
destroyed()实例销毁后。

在 vue3 + <script setup> 中,推荐使用 Composition API 的生命周期钩子:

javascript
// Vue 3 示例(使用 Composition API)
import { onMounted, onUnmounted } from "vue";

onMounted(() => {
  console.log("组件已挂载");
});

onUnmounted(() => {
  console.log("组件已卸载");
});

生命周期执行顺序

  1. 页面:onLoad → onShow → onReady
  2. 组件:beforeCreate → created → beforeMount → mounted

阶段三

vue 的数据绑定连接方式?

Vue 的核心特性之一是双向数据绑定,其本质是构建一个响应式系统,自动追踪数据变化并更新视图。

Vue 2 和 Vue 3 实现响应式的方式不同:

  • Vue 2 使用 Object.defineProperty 对属性进行劫持。
  • Vue 3 使用 Proxy 实现更全面的代理和性能优化。

响应式系统原理

Vue 2:基于 Object.defineProperty

Vue 在初始化时遍历 data 中每个属性,将它们转化为响应式对象。

  • 读取数据时:触发 getter → 收集依赖
  • 修改数据时:触发 setter → 通知视图更新
javascript
// 示例代码
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log("访问:", key); // 收集依赖
      return val;
    },
    set(newVal) {
      console.log("修改:", key, "为", newVal); // 通知更新
      val = newVal;
      // 触发视图更新逻辑
    },
  });
}

let data = { name: "Tom" };
defineReactive(data, "name", data.name);

data.name = "Jerry"; // 自动触发 setter → 更新视图

Vue 3 基于 Proxy 对整个对象进行一次性代理,能监听新增/删除属性和数组变化。

  • 读取属性时:触发 get → 收集依赖
  • 设置属性时:触发 set → 通知更新
javascript
// 示例代码
const handler = {
  get(target, key) {
    console.log("读取属性:", key); // 收集依赖
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log("设置属性:", key, "=", value); // 通知更新
    const result = Reflect.set(target, key, value);
    // 触发视图更新逻辑
    return result;
  },
};

const state = new Proxy({ count: 0 }, handler);
state.count++; // 触发 set → 更新视图

响应式更新流程

读取属性 → 收集依赖

修改属性 → 通知更新


Vue 2:Object.defineProperty → Dep(依赖管理)+ Watcher(视图更新)

Vue 3:Proxy → track() + trigger()

对比

特性Vue 2 (Object.defineProperty)Vue 3 (Proxy)
响应方式遍历每个属性设置 getter/setter一次性代理整个对象
新增/删除属性不会响应(需用 Vue.set自动响应
数组支持有缺陷,需手动处理部分方法原生支持所有数组方法
性能中等,适合中小项目更好,适合中大型项目

数据视图更新机制

Vue 使用“依赖收集 + 发布-订阅模式”来追踪数据和视图之间的关系:

  1. 读取数据时 → 收集依赖

渲染组件时访问数据属性,Vue 会记录当前组件对该数据的依赖。

  1. 写入数据时 → 通知更新

数据变化触发 setter(Vue2)或 Proxy.set(Vue3),Vue 通知所有依赖组件重新执行更新逻辑。

javascript
// Vue 2 示例(模拟更新流程)
let name = "Tom";
Object.defineProperty(window, "name", {
  get() {
    console.log("视图用到了 name,收集依赖");
    return name;
  },
  set(val) {
    console.log("name 改了,要更新视图了");
    name = val;
    // 通知视图更新
  },
});

console.log(window.name); // 触发 getter → 收集依赖
window.name = "Jerry"; // 触发 setter → 更新视图
javascript
// Vue 3 示例(自动依赖追踪)
const state = reactive({ count: 0 });

watchEffect(() => {
  console.log(state.count); // 自动收集依赖
});

state.count++; // 修改数据 → 自动更新

总结

Vue 能实现视图自动更新,核心机制是:

读取属性时 → 自动记录依赖

修改属性时 → 自动触发更新

Vue 2 利用 getter/setter 完成依赖收集与通知更新 Vue 3 借助 Proxy 和响应式追踪系统(如 track 和 trigger)实现更优雅的响应式

vue 和 uni-app 的不同点?

对比项VueUniApp
路由配置vue-routerpages.json
路由跳转$router.pushuni.navigateTo
生命周期created/mountedonLoad/onShow/onReady
DOM 操作支持原生 DOM需使用 uni.createSelectorQuery()
请求方式axios/fetchuni.request
平台支持指令#ifdef 条件编译支持
模板语法完整 Vue 支持子集,平台兼容优先
实例

1. 页面结构配置

uni-appVue 的写法虽然大体一致(因为 UniApp 基于 Vue 2/3 开发),但 UniApp 针对 跨端适配页面配置平台特性 做了扩展和限制,导致语法上存在一些差异。

VueUniApp
路由使用 vue-router,手动注册页面组件。所有页面必须在 pages.json 中声明,自动注册页面。
javascript
// Vue 的路由结构
const routes = [
  { path: '/home', component: Home },
  ...
]
jsonc
// uni-app 中的 pages.json
{
  "pages": [
    {
      "path": "pages/home/index",
      "style": {
        "navigationBarTitleText": "首页"
      }
    }
  ]
}

2. 路由跳转方式不同

VueUniApp
使用 this.$router.push({ path: '/home' })使用 uni.navigateTo({ url: '/pages/home/index' })

3. 生命周期语法不同(页面级)

Vue 生命周期UniApp 页面生命周期
created / mounted 等onLoad / onShow / onReady / onUnload 等

4. 模板语法差异:平台兼容性考虑

UniApp 的模板语法 不能完全等同 Vue 语法,主要是为了支持小程序平台,限制了一些 Vue 语法特性:

特性VueUniApp
动态组件<component :is="comp" />支持有限,在部分平台不兼容
v-html支持(输出 HTML)H5 支持,小程序不支持或需借助 rich-text
自定义事件修饰符 .native支持小程序中无效
DOM 操作原生 DOM 支持无法直接访问 DOM,只能通过 uni.createSelectorQuery()

5. 事件绑定与调用方式

虽然语法相似,但 uni-app 针对微信小程序等平台对事件行为有额外限制:

vue
<!-- Vue -->
<button @click="doSomething">点击</button>

<!-- UniApp -->
<!-- 一样写法,但在小程序中不能使用某些 DOM 事件修饰符如 .stop、.prevent 组合使用 -->
<button @click.stop="doSomething">点击</button>
<!-- 小程序可能无效 -->

6. API 语法差异

UniApp 用了很多自有的 API,不同于 Vue 的传统写法。

功能Vue 语法UniApp 语法
请求接口axios.get(...)uni.request({...})
缓存localStorage.setItem(...)uni.setStorageSync(...)
页面跳转$router.push()uni.navigateTo()
获取元素原生 DOM / refuni.createSelectorQuery()

7. 第三方组件写法差异

  • Vue 可以自由注册/引入组件,支持异步组件等高级特性。
  • UniApp 支持 easycom 自动引入,也支持平台差异化写法(如条件编译 #ifdef APP-PLUS)

8. 平台差异指令语法(UniApp 专属)

UniApp 支持跨平台写法时,会加入特有语法:

vue
<!-- 仅在微信小程序中显示 -->
<view v-if="isWx">微信专属内容</view>

<!-- 条件编译写法 -->
<!-- #ifdef MP-WEIXIN -->
<view>微信小程序内容</view>
<!-- #endif -->

uni-app 的 sessionStorage 和 localStorage 缓存机制?

vue-dragger value 和 list 的区别?

value:

Input array to draggable component. Typically same array as referenced by inner element v-for directive.

This is the preferred way to use Vue.draggable as it is compatible with Vuex.

It should not be used directly but only though the v-model directive:

拖拽组件的输入数组。通常与内部元素 v-for 指令引用的数组相同。 这是使用 Vue.draggable 的推荐方式,因为它与 Vuex 兼容。 它不应直接使用,而只能通过 v-model 指令使用:

vue
<draggable v-model="myArray">

list:

Alternative to the value prop, list is an array to be synchronized with drag-and-drop.

The main difference is that list prop is updated by draggable component using splice method, whereas value is immutable.

Do not use in conjunction with value prop.

value 属性的替代方案,列表是一个用于与拖放同步的数组。 主要区别在于 list 属性是通过可拖动组件使用 splice 方法更新的,而 value 是不可变的。 不要与 value prop 同时使用。

阶段四(略)

阶段五

闭包产生的原因?如何避免?

闭包 Closure 是指引用了另外一个函数作用域中变量的函数。

javascript
function outer() {
  let count = 0;

  return function inner() {
    count++;
    console.log(count);
  };
}

const counter = outer();
counter(); // 输出 1
counter(); // 输出 2

会出现的问题:

  • 闭包可能导致 内存泄漏(变量被持久引用,无法释放)。
  • 使用时要注意避免不必要的闭包,尤其是在循环或大型项目中。

避免方法:

建议说明
使用 let 代替 var避免循环中变量引用异常
不滥用闭包尽量只在需要保留状态时使用闭包
及时释放引用用完闭包后设置为 null
注意事件监听器使用委托或解绑闭包引用
避免引用大型数据结构减少内存占用,防止泄漏

阶段六

微信小程序与原生区别?

模板

  • 微信小程序、H5 和原生 App 的主要区别在于运行环境和能力限制。
  • 微信小程序是运行在微信客户端内的轻应用,开发体验接近前端,具备一定的系统能力(如扫码、位置、授权登录),性能和体验优于 H5,但仍低于原生 App。
  • 原生 H5 是最轻量级的方式,只依赖浏览器就能运行,但功能受限,体验相对最差,更多用于活动页、落地页、分享页等场景。
  • 原生 App 则拥有最强大的功能权限和用户体验,适用于复杂系统类、强交互类或对性能要求高的应用,比如直播、电商、社交平台等,但开发和维护成本较高。
  • 所以三者通常是互补关系,可以根据业务类型、目标用户、成本预期灵活选型。例如:主应用用原生 App,部分推广页用 H5,引流/转化页用小程序。

  • 比如我在项目中做过一个在线商城系统,核心功能由原生 App 承担,保证性能和体验;但同时提供小程序入口用于分享、社群传播;推广活动页面用 H5 实现,嵌入在公众号菜单中。这三者共同协作,兼顾了体验、开发效率和传播效果。

微信小程序 vs 原生 H5 vs 原生 App ,应当从 运行环境开发方式功能权限性能场景适用 等方面系统对比。

维度微信小程序原生 H5(HTML/CSS/JS)原生 App(Android/iOS)
运行环境微信容器内浏览器(如 Chrome、Safari)操作系统(Android/iOS)
开发语言WXML + WXSS + JSHTML + CSS + JSJava/Kotlin、Swift/Obj-C
部署方式微信平台审核,上线即可使用网页部署在服务器,打开即用应用市场上架、用户下载安装
安装方式无需安装,扫码即用无需安装,浏览器访问需下载并安装
性能中等,介于 H5 与原生 App 之间较低,运行在浏览器中最优,系统级运行
系统 API 能力中等:微信提供的封装 API(如扫码、位置)很弱,仅能使用浏览器暴露的 API(如 localStorage)强:可调用系统所有功能(如蓝牙、传感器、推送等)
UI 控制由微信提供的组件系统控制自由度较高,但受浏览器限制完全可定制
开发成本较低:一次开发,全平台可用(微信生态)最低:通用 Web 技术高:需分别开发 iOS 和 Android
更新方式即时更新(由微信控制)即时更新(后台更新 HTML)需发布新版本,用户手动更新
用户留存一般(微信中打开即走)弱(网页打开即走)强(安装后可推送、沉淀用户)
典型场景微信内商城、工具、活动页轻应用、活动页、分享页重交互 App、电商、游戏

微信小程序发布流程?

步骤操作内容说明
① 注册账号登录 微信公众平台 → 注册小程序账号选择“小程序”类型,完成邮箱验证、企业认证(或个人)
② 设置小程序信息填写小程序名称、头像、服务类目、简介等建议填写清晰,方便审核
③ 配置开发者添加开发者、体验者账号在“开发管理”中配置,便于多人协作开发和测试
④ 本地开发与预览使用微信开发者工具开发 → 预览支持使用 npm 构建、真机调试、模拟器预览等
⑤ 提交代码审核在开发者工具中上传代码 → 公众平台提交审核提交前可生成体验版(体验二维码)供测试
⑥ 审核通过并发布上线审核成功后点击“发布”按钮正式上线,所有用户可访问小程序

模板

  • 微信小程序发布流程包括注册小程序账号、填写基本信息、使用微信开发者工具开发并调试代码,提交审核后由微信平台人工或机器审核,审核通过后可发布上线。
  • 审核期间需要特别注意类目、功能是否合规,接口权限是否开放、UI 设计是否符合规范。提交前建议先生成体验版,由产品和测试全面验证。

  • 代码审核时间:通常 1 个工作日内(部分类目如医疗/金融可能更久)
  • 体验版分享:支持配置“体验者”扫码体验(扫码有效期有限)
  • 类目不符会被驳回:功能必须和你选择的服务类目一致,否则审核不通过
  • 灰度发布功能:目前仅支持一些企业小程序进行灰度发布(需满足条件)

微信小程序生命周期?

微信小程序 & uni-app 生命周期对比表

生命周期类别UniApp微信小程序(原生)区别说明
应用级onLaunchonShowonHideonError同名(完全一致)基本一致
页面级onLoadonShowonReadyonHideonUnloadonPullDownRefreshonReachBottom同名(完全一致)基本一致
组件级createdattachedreadymoveddetacheddestroyed(Vue 语义)createdattachedreadymoveddetached命名基本一致,但 UniApp 多了 destroyed(Vue 风格)

👉 阶段二 - uni-app 的生命周期?

微信小程序首屏加载优化?

页面首屏渲染的优化,目的是让「首页渲染完成」(Page.onReady) 尽可能提前。但很多情况下「首页渲染完成」可能还是空白页面,因此更重要的是让用户能够更早的看到页面内容(First Paint 或 First Contentful Paint)。

  1. 使用「按需注入」和「用时注入」

除了优化代码注入的耗时外,「按需注入」和「用时注入」也可以减少需要初始化的组件数量,降低实际页面渲染的耗时,使「首页渲染完成」提前。

json
{
  "lazyCodeLoading": "requiredComponents"
}
  1. 启用「初始渲染缓存」

自基础库版本 2.11.1 起,小程序支持启用初始渲染缓存。开启后,可以在非首次启动时,使视图层不需要等待逻辑层初始化完毕,而直接提前将页面渲染结果展示给用户。

  1. 避免引用未使用的自定义组件

在页面渲染时,会初始化在当前页面配置和全局配置通过 usingComponents 引用的自定义组件,以及组件所依赖的其他自定义组件。未使用的自定义组件会影响渲染耗时。

  1. 精简首屏数据

首页渲染的耗时与页面的复杂程度正相关。对于复杂页面,可以选择进行渐进式的渲染,根据页面内容优先级,优先展示页面的关键部分,对于非关键部分或者不可见的部分可以延迟更新。

此外,与视图层渲染无关的数据应尽量不要放在 data 中,避免影响页面渲染时间。

  1. 提前首屏数据请求

很多小程序在渲染首页时,需要依赖服务端的接口数据(如商品列表等),此时小程序的首页可能是空白或者骨架屏。

由于网络请求需要相对较长的时间,我们建议开发者在 Page.onLoad 或更早的时机发起网络请求,而不应等待 Page.onReady 之后再进行。

  1. 缓存请求数据

小程序提供了 wx.setStorage、wx.getStorage 等读写本地缓存的能力,数据存储在本地,返回的会比网络请求快。如果开发者基于某些原因无法采用数据预拉取与周期性更新,我们推荐优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。

  1. 骨架屏

骨架屏通常用于在页面完全渲染之前,通过一些灰色的区块大致勾勒出轮廓,待数据加载完成后,再替换成真实的内容。

微信小程序使用过哪些 API?

常见 API 及应用场景如下:

  • 页面跳转 vs tabBar 跳转的区别(navigateTo / switchTab)
  • 本地缓存的同步与异步操作(区别、使用场景)
  • wx.login + code 获取 openid 的流程
  • 上传图片流程(wx.chooseImage + wx.uploadFile)
  • 网络请求封装(比如封装 wx.request 为 Promise 形式)
  • 权限授权和 getUserProfile vs getUserInfo 的变化(特别是 2021 年后政策变化)
  • 使用 Canvas 或图片压缩相关 API(如果你有上传优化相关项目)

阶段七

call、apply、bind 的区别?

模板

call()apply()bind() 都可以改变函数运行时的 this 指向,但区别在于是否立即执行,以及参数传递方式。

  • call()apply() 都会立即调用函数,区别是参数格式不同(call 逗号,apply 数组);
  • bind() 不会立即执行,而是返回一个新函数,可用于延迟执行、函数柯里化、事件回调绑定等场景。

在实际项目中,我通常在构造函数继承、事件回调中使用这几个方法来灵活控制函数的执行上下文。

方法含义是否立即执行参数传递方式
call调用函数并改变 this 指向✅ 是依次传入参数(逗号分隔)
apply调用函数并改变 this 指向✅ 是参数以数组形式传入
bind返回一个新函数,改变了 this 指向❌ 否(需手动调用)依次传入参数

基本用法对比

javascript
function greet(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: "Zan" };

// call
greet.call(person, "Hello", "!"); // Hello, I'm Zan!

// apply
greet.apply(person, ["Hi", "..."]); // Hi, I'm Zan...

// bind
const boundGreet = greet.bind(person, "Hey");
boundGreet("?"); // Hey, I'm Zan?

应用场景举例

call / apply

  • 多用于立即调用函数并绑定上下文
  • 比如:继承构造函数中的属性
javascript
function Animal(name) {
  this.name = name;
}

function Dog(name, color) {
  Animal.call(this, name); // 继承 Animal 的属性
  this.color = color;
}
  • 利用 apply 进行参数解构调用(如函数柯里化、Math 方法)
javascript
Math.max.apply(null, [1, 5, 3]); // 5

✅ bind

  • 多用于延迟调用,例如:事件绑定、定时器、函数柯里化
javascript
const person = { name: "zan" };
const sayName = function () {
  console.log(this.name);
}.bind(person);

setTimeout(sayName, 1000); // 输出 zan

怎么看事件循环机制?怎么看原型和原型链?

  • 事件循环: JS 通过事件循环区分同步任务、微任务、宏任务,以实现异步非阻塞执行。
  • 原型链: JS 中对象通过 __proto__ 指向其原型,逐层查找形成原型链,实现继承与属性共享。

模板

JavaScript 是单线程语言,它通过“事件循环(Event Loop)”机制来实现异步非阻塞操作。执行顺序是:先执行主线程同步任务,然后不断从任务队列中取出异步任务。

异步任务分为 宏任务(MacroTask)微任务(MicroTask)。每一个宏任务执行完成后,会先清空所有微任务队列,再执行下一个宏任务。

微任务常见于 Promise.then()MutationObserver,宏任务则包括 setTimeoutsetIntervalDOM 事件回调MessageChannel 等。

进阶:

JS 的事件循环机制本质上是通过执行栈 + 消息队列来调度任务。它使得浏览器在单线程下仍能执行 I/O、网络、定时器等异步任务而不会阻塞主线程。

在 Vue 和 React 中我们也常用微任务机制来控制异步更新,例如 Vue 的 nextTick() 本质就是基于 Promise 微任务的。

javascript
console.log("start");

setTimeout(() => {
  console.log("timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise");
});

console.log("end");

shell
# 输出
start
end
promise
timeout

解释:Promise.then() 是微任务,在当前宏任务结束后立即执行,而 setTimeout 是下一个宏任务。

模板

在 JavaScript 中,每个对象都有一个内部属性 [[Prototype]](在代码中可通过 __proto__ 访问),它指向创建它的构造函数的 prototype 对象。

当我们访问一个对象的属性时,如果找不到,就会沿着 __proto__ 向上查找,这条链路就叫做原型链(Prototype Chain)。

通过原型链,JavaScript 实现了对象的继承机制。

进阶:

原型和原型链是 JS 中实现继承和属性查找的基础机制。通过构造函数的 prototype 属性和实例对象的 __proto__ 链接,构成了一条查找路径。这条链路决定了 JS 的属性继承逻辑,是 instanceof、继承、多态的底层基础。

javascript
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function () {
  console.log("Hi, I am " + this.name);
};

const p = new Person("Zan");

p.sayHi(); // Hi, I am Zan

// 属性查找顺序:p → p.__proto__ (Person.prototype) → Object.prototype → null
关键词说明
__proto__实例对象的原型指针
prototype构造函数创建实例时的原型模板
constructor原型对象上的属性,指回构造函数
instanceof判断原型链中是否包含某构造函数的 prototype
javascript
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(p instanceof Person); // true

vue2 和 vue3 区别?vue 和 react 区别?

vue2 & vue3

模板

Vue3 是对 Vue2 的底层重构,最大的变化是引入了 Composition API 和基于 Proxy 的响应式系统。它更适合大型项目的模块化、逻辑复用,同时性能和 TypeScript 支持也有显著提升。

在实际开发中,如果项目较大或需要高性能、TS 支持,我会优先选择 Vue3。

维度Vue2Vue3(重大升级)
响应式原理Object.defineProperty✅ 使用 Proxy,支持更复杂的结构
语法风格Options API✅ 引入 Composition API(组合式 API)
性能较慢(尤其是大型组件)✅ 更快、更轻(更好的编译优化)
TypeScript 支持较差✅ 从底层开始支持 TS
Fragment❌ 不支持(必须有根元素)✅ 支持多个根节点
Teleport / Suspense❌ 不支持✅ 新增组件:传送门、异步组件更灵活
生命周期名称比如 createdmounted✅ 新增 onMounted() 等组合 API

vue & react

模板

Vue 更适合快速上手和中小型项目,语法直观、文档完善;而 React 更强调函数式思想、状态驱动,适合构建灵活、规模化的应用。

我个人更倾向 Vue 做项目初期快速开发,也熟悉 React 的 Hooks 和函数式组件,在跨团队协作中也能很好适配。

维度VueReact
设计理念渐进式框架(更关注视图)UI = f(state),函数式组件为主
语法模板语法(.vue 文件)JSX(JavaScript + XML)
数据响应响应式系统(Proxy)使用 useStateuseEffect 手动管理
状态管理Vuex / PiniaRedux / Zustand / Recoil
组件通信props / emit / provide / injectprops / context / hooks
学习曲线相对更易上手相对自由但复杂
性能优化手段watchcomputed、响应式依赖追踪手动依赖管理、memo、useCallback
生态适合快速开发中小项目更适合大型项目和企业级需求

如何实现大文件上传?

模板

大文件上传通常采用切片(分片)上传 + 并发控制 + 秒传校验等方式提升性能与稳定性,避免一次性上传带来的浏览器崩溃、网络中断等问题。

具体流程包括:将文件按大小切片、通过唯一标识记录进度、支持断点续传,上传完成后在服务端进行合并。常结合 FileReaderBlob.sliceFormDataaxios 实现,后端负责合并与校验。

完整流程

前端

1.文件切片(chunk)

将大文件按固定大小切割为多个小块(chunk),使用 Blob.prototype.slice

js
function createChunks(file, chunkSize = 10 * 1024 * 1024) {
  // 10MB
  const chunks = [];
  let cur = 0;

  while (cur < file.size) {
    chunks.push(file.slice(cur, cur + chunkSize));
    cur += chunkSize;
  }

  return chunks;
}

2. 生成文件唯一标识(hash)

可使用 MD5 或文件名 + 大小等方式,识别是否为同一文件。

js
const fileHash = `${file.name}-${file.size}`;

(更严谨方式是用 Web Worker + spark-md5 对文件内容做哈希)

3. 上传每个 chunk

js
async function uploadChunk(chunk, index, hash) {
  const formData = new FormData();
  formData.append("file", chunk);
  formData.append("filename", `${hash}-${index}`);

  return axios.post("/upload-chunk", formData);
}

可以使用 Promise.all 并发上传多个 chunk,但建议限制并发量:

js
const MAX_CONCURRENT = 5;
// 用 p-limit / concurrency 控制上传并发

4. 通知后端合并文件

所有 chunk 上传成功后,发送合并请求:

js
await axios.post("/merge", {
  hash: fileHash,
  chunkTotal: chunks.length,
});

5. 断点续传与秒传(可选优化)

前端上传前调用接口获取该 hash 的上传进度,如果某些 chunk 已上传,则跳过这些分片。

后端

  • 接收每个分片保存到临时目录(如 upload/tmp/文件 hash/分片名
  • 合并分片(按序号拼接)
  • 删除临时文件
  • 校验文件完整性(如 MD5)

可选进阶优化

优化方向方法
并发控制限制上传并发数,提高稳定性
秒传前端生成文件 hash,服务器判断是否已存在
断点续传前端记录上传进度;服务端保存已传分片
上传进度利用 axiosonUploadProgress
大文件预览上传前生成缩略图(图像)或视频首帧

前端性能优化的手段?

模板

在实际开发中,我会从加载速度、渲染效率、代码体积、请求优化等多个方面进行性能优化:

  1. 静态资源压缩:使用 Webpack/Gzip 压缩 JS 和 CSS 文件,减小包体积;
  2. 按需加载和代码分割:例如路由懒加载(Vue 的 defineAsyncComponent)、Ant Design 按需引入;
  3. 图片优化:使用 webp 格式、压缩图像体积、懒加载大图;
  4. 缓存策略:合理配置 HTTP 缓存、使用 localStorage / IndexedDB 缓存接口数据;
  5. DOM 渲染优化:使用虚拟滚动优化长列表、避免不必要的 DOM 更新;
  6. 请求层优化:接口合并、CDN 加速、减少 HTTP 请求次数;
  7. 构建优化:Tree-shaking 去除无用代码、使用 ESBuild 或 Vite 提升构建速度。

👉 大前端 - 前端性能优化之飞书文档

分类优化手段(关键点)
1. 加载优化- 资源压缩(JS/CSS/图片)
- 资源懒加载 / 预加载
- HTTP 缓存、CDN 加速
- Gzip / Brotli 压缩
2. 渲染优化- 减少重排重绘
- 合理使用 will-change / transform
- 虚拟列表、节流处理滚动事件
3. 请求优化- 合理拆分接口(避免 N+1)
- 接口合并 / 去重
- 使用 HTTP2/3 并发能力
4. 代码优化- Tree shaking、代码分割
- 延迟加载(如路由懒加载)
- 减少依赖包体积、使用轻量库
5. 数据处理优化- 防抖节流
- 缓存(localStorage / IndexedDB)
- 虚拟滚动、懒渲染
6. 构建优化- 按需引入组件
- 多线程构建(webpack thread-loader)
- 图片转 base64、小图雪碧图

如何看待 cookie、localStorage、pinia?

模板

cookie、localStorage 和 Pinia 分别代表了三个不同层面的存储和状态管理机制:

  • cookie 是用于前后端通信的传统方式,适合服务端身份校验;
  • localStorage 是前端本地存储解决方案,适合存储用户个性化配置、缓存数据;
  • Pinia 则是 Vue3 官方推荐的状态管理库,适合组件之间共享响应式状态。

实际开发中,它们往往是配合使用的:例如用户登录后,token 保存在 cookie 中用于接口鉴权,同时用户信息用 Pinia 管理用于响应式渲染,而一些本地缓存(如用户偏好、语言选择)可以存进 localStorage,页面刷新后可自动恢复。

javascript
// 登录后:token 存 cookie(用于接口携带),userInfo 存 Pinia,userTheme 存 localStorage

// cookie 存 JWT
document.cookie = "token=xxx; path=/; secure;";

// localStorage 存缓存
localStorage.setItem("theme", "dark");

// pinia 存状态
userStore.setUserInfo({ name: "tester" });
项目类型用途定位
cookie浏览器原生存储与服务器交互(如登录状态)
localStorage本地持久化存储存储用户偏好、缓存数据等
Pinia前端状态管理工具(Vue3)管理组件之间共享的响应式状态
  1. cookie

✅ 优点:

  • 可以自动随请求发送到服务器(携带身份标识)
  • 支持设置过期时间、作用域、HttpOnly、安全策略等

⚠️ 缺点:

  • 容量限制(约 4KB)
  • 每次请求自动携带,影响性能
  • 安全性差(易被 XSS 访问,除非设置 HttpOnly)

💡 适合:登录状态保存、跨页面登录验证、与后端交互的小型数据

  1. localStorage

✅ 优点:

  • 存储容量大(约 5MB)
  • 持久化强(除非主动清除,不会失效)
  • 简单易用,键值对结构

⚠️ 缺点:

  • 只能存储字符串
  • 无法响应式更新(不适合多组件共享状态)
  • 不会自动同步到服务器(仅前端可访问)

💡 适合:用户主题偏好、缓存搜索记录、减少接口请求

  1. Pinia(或 Vuex)

✅ 优点:

  • 响应式、组件共享状态简单高效
  • 插件机制、模块化、持久化支持(搭配插件)
  • 与 Vue3 深度集成,支持 TS 类型推导

⚠️ 缺点:

  • 学习曲线稍高(对初学者)
  • 页面刷新后状态会重置(需手动结合 localStorage)

💡 适合:组件间状态共享、复杂页面逻辑(如购物车、用户信息管理)

如何防御 XSS、CSRF 攻击?

模板

我了解 Web 常见攻击如 XSS 和 CSRF,日常开发中我主要通过以下方式防护:

  • 对用户输入进行转义和校验,防止注入 XSS;
  • 设置 CSP 策略 禁止内联脚本执行;
  • 对于 Cookie 设置 HttpOnly + SameSite 属性;
  • 所有敏感操作接口使用 CSRF Token 机制 进行验证;
  • 富文本类输入我会使用 DOMPurify 等库进行过滤处理。

在项目中,我也会配合后端设置响应头和接口权限,确保前后端协同防护。

XSS

跨站脚本攻击 Cross-Site Scripting,XSS 是攻击者在网页中注入恶意脚本代码,诱导用户执行,从而窃取用户信息(如 Cookie)、劫持会话、控制页面行为等。

举例:

js
<input value="<script>alert('xss')</script>" />

防御手段:

类型防御方式
1. 输入过滤永远不要相信用户输入,使用白名单限制可输入的字符
2. 输出编码对用户输入进行转义(如将 < 转成 &lt;),防止注入脚本
3. CSP 策略设置内容安全策略(Content Security Policy),禁止页面执行内联 JS 等
4. httpOnly将 Cookie 设置为 HttpOnly,防止被 JS 读取,保护 token
5. 第三方库防护使用 DOMPurify 等库对富文本、HTML 内容做清洗

如何使用 CSP 防 XSS?

设置 CSP 响应头:

http
Content-Security-Policy: default-src 'self'; script-src 'self';

效果:

  • 禁止加载第三方脚本
  • 禁止 eval、inline script 等

CSRF

跨站请求伪造 Cross-Site Request Forgery,利用用户已登录状态,通过伪造请求欺骗服务器执行未授权的操作。

举例:

用户登录银行网站后,访问恶意页面,页面偷偷发起:

html
<img src="https://bank.com/transfer?to=bad&amount=1000" />

浏览器自动带上了 Cookie,从而完成转账。

防御手段:

防御方式说明
1. Token 验证每次请求带上 CSRF token,后端校验是否有效
2. SameSite Cookie设置 Cookie 属性:SameSite=Strict/Lax,防止第三方站点携带 Cookie
3. Referer 校验后端检查请求头中 Referer 是否来自本站
4. 使用双重 Cookie设置 csrfToken 存在 Cookie + 请求头一并提交,前后端都校验
5. POST 替代 GET关键操作不使用 GET,避免通过 <img> 伪造

httpOnly 和 SameSite 作用总结

属性作用
HttpOnlyJS 无法访问此 Cookie,防止 XSS 窃取
SameSite控制 Cookie 跨站请求是否发送,防止 CSRF

GZip 没有生效,如何排查?

GZip 压缩是前端性能优化的重要手段

  1. 客户端请求头检查
html
Accept-Encoding: gzip, deflate, br
  1. 服务器端配置排查

服务器使用 nginx 演示

2.1 检查 nginx 配置

nginx
gzip on;                          # 是否开启GZip
gzip_types text/html text/css ...; # 是否包含text/html类型
gzip_vary on;                     # 是否允许代理服务器根据请求头选择压缩

2.2 确保服务器支持 GZip 静态文件

nginx
gzip_static on; # 启用静态压缩文件

确认服务器能访问到.html.gz 文件(如路径是否正确)。

  1. Webpack 构建产物检查

查看 Webpack 输出目录(如 dist),确认是否存在 index.html.gz 文件。

  1. 缓存检查

4.1 浏览器缓存检查 4.2 服务器缓存检查 CDN

阶段八

URL 到页面加载的过程

👉 从输入-url-到页面加载的全过程

阶段十

盒模型?

👉 标准盒模型与怪异盒模型的区别

positions 属性值?

👉 CSS Position 定位布局

this 指向?

👉 八股文 - JavaScript - this 的五种情况

BFC ?

👉 BFC 块级格式上下文

vue 的双向数据绑定?

👉 vue 的数据绑定连接方式

ref 和 reactive 的区别?如何实现的?

警告

refreactive 都是 Vue 3 中用于创建响应式数据的 API。区别在于:

  • ref 主要用于基本类型或 DOM 引用,返回一个带 .value 属性的对象;
  • reactive 用于对象和数组,基于 Proxy 实现深度响应式。

在实现上,ref 是通过 getter/setter 对 .value 做响应追踪,而 reactive 是通过 Proxy 拦截所有属性读写。两者都会自动进行依赖收集与触发更新。

在模板中 Vue 会自动解包 ref,所以使用上也很直观。


Vue 的响应式系统采用了「依赖收集(track) + 派发更新(trigger)」的机制。无论是 ref 还是 reactive,都是基于 effect 函数与依赖 Map 实现的精细化更新。

区别

对比项ref()reactive()
用法用于基本类型 & DOM 引用用于对象类型(数组、对象等)
返回值类型对象:{ value: xxx }代理后的响应式对象(Proxy)
解包行为模板中自动 .value 解包模板中自动访问属性,无需解包
适用场景数字、字符串、布尔、DOM、异步等复杂对象、状态管理
底层实现依赖 RefImpl 对象 + getter/setter依赖 Proxy 拦截属性访问

ref

ts
import { ref } from "vue";

const count = ref(0);

console.log(count.value); // 访问需要 .value
count.value++;

reactive

ts
import { reactive } from "vue";

const user = reactive({ name: "Zan", age: 25 });

console.log(user.name); // 直接访问属性
user.age++;

原理

  • ref() 的实现(基于对象包装)
ts
function ref(value) {
  return {
    __v_isRef: true,
    get value() {
      track(this, "value"); // 依赖收集
      return value;
    },
    set value(newVal) {
      value = newVal;
      trigger(this, "value"); // 派发更新
    },
  };
}

ref 本质是把基本类型值包裹在一个带有 .value getter/setter 的对象中。

  • reactive() 的实现(基于 Proxy)
ts
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key); // 依赖收集
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value);
      trigger(target, key); // 派发更新
      return result;
    },
  });
}

核心逻辑是对对象属性的读取和修改进行拦截,从而实现依赖收集和更新触发。

假设我给 ref 传入一个对象,vue 是怎么进行处理的?

当你给 ref() 传入一个对象时,Vue 会自动调用 reactive() 将它变成响应式对象,然后包裹在 .value 里返回。

Vue 的源码(简化)中是这么处理的:

ts
function ref(rawValue) {
  // 如果是对象,先转成 reactive
  const value = isObject(rawValue) ? reactive(rawValue) : rawValue;

  const r = {
    __v_isRef: true,
    get value() {
      track(r, "value");
      return value;
    },
    set value(newVal) {
      value = isObject(newVal) ? reactive(newVal) : newVal;
      trigger(r, "value");
    },
  };

  return r;
}
ts
const obj = ref({ name: "Zan" });

console.log(obj.value.name); // ✅ 是响应式的
obj.value.name = "ZanXiao"; // ✅ 会触发更新

这时候你用的是 .value.name,而不是直接 obj.name,因为它还是包裹在 ref 对象里面。

computed 和 watch 的区别?

警告

它们都是用来响应数据变化、执行副作用的工具,但在使用方式、依赖收集机制、触发时机等方面有明显区别。

computedwatch 都是 Vue 中的响应式工具,但适用场景不同:

  • computed 适合用来计算派生状态,有缓存特性,依赖没变就不会重新计算;
  • watch 更适合监听数据变化后执行副作用,如发送请求、手动处理逻辑;

比如在项目中,我用 computed 生成视图所需的展示数据,而用 watch 来监听用户输入进行接口请求防抖处理。


  • computed:
    • 基于 effect() 包装
    • 有依赖追踪(track)与缓存机制
    • 实现了懒执行 + 自动更新
  • watch:
    • 本质是手动注册一个副作用函数
    • 没有缓存
    • 可配置 deep、immediate 等选项
对比点computedwatch
用法目的计算属性(值)监听响应式数据的变化,执行副作用(回调)
返回值返回一个响应式“值”返回 undefined,执行副作用函数
是否缓存✅ 缓存(依赖未变则不重新计算)❌ 不缓存,触发一次执行一次
适合场景基于多个状态派生出一个“新状态”执行异步请求 / watch 路由变化 / 手动监听数据
默认行为初始化时不执行,只有依赖变才重新计算默认不立即执行(可配置),依赖变化触发函数
是否手动控制❌ 完全依赖 Vue 依赖收集✅ 可自定义监听项和选项(deep、immediate)

示例:

computed 计算一个派生状态

ts
const firstName = ref("Zan");
const lastName = ref("Xiao");

const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`;
});
  • 优点:firstName 或 lastName 改变时自动更新 fullName
  • ✅ 具有缓存功能

watch 监听数据变化做副作用操作

ts
// 监听一个值
watch(fullName, (newVal, oldVal) => {
  console.log("Full name changed:", newVal);
});

// 监听多个值
watch([firstName, lastName], ([newFirst, newLast]) => {
  console.log(`New name: ${newFirst} ${newLast}`);
});

watch 和 watchEffect 的区别?

警告

watch 和 watchEffect 都用于监听响应式数据变化,但有核心差异:

  • watch 是显式监听,适合监听特定变量、需要旧值的场景(如监听某个字段变动时调接口);
  • watchEffect 是自动依赖收集,更适合快速声明响应式副作用,比如在组件初始化时处理逻辑;

项目中我一般用 watchEffect 处理 UI 初始化逻辑,用 watch 处理精确控制的接口请求场景。

使用场景:

场景推荐方式
监听单个变量变化并获取旧值watch
需要监听多个响应式数据watch
快速响应所有相关数据变化执行副作用watchEffect
模拟生命周期,如请求初始数据watchEffect
深度监听嵌套对象属性watch + deep: true
对比项watchwatchEffect
依赖指定方式显式传入:如 refreactive、getter 函数自动收集依赖(只要你在 effect 中访问它)
是否立即执行默认不执行,需 immediate: true 配置会立即执行一次(组件挂载前)
副作用函数明确参数 (newVal, oldVal)无需参数,自动运行
适合场景精确控制监听哪个变量,适合监听单个/多个数据简洁声明式副作用,依赖会自动跟踪,适合逻辑初始化等
支持 deep✅ 可以配置 { deep: true }❌ 不支持 deep 配置(但会跟踪所有访问的属性)
调试/可控性✅ 更可控❌ 更自动,灵活但可能更“隐式”

示例:

watch 监听指定数据

ts
const count = ref(0);

watch(
  count,
  (newVal, oldVal) => {
    console.log("count changed:", newVal, oldVal);
  },
  { immediate: true, deep: false }
);
  • 明确指定要监听哪个值
  • 有 newVal、oldVal 参数
  • 可配置 deep / immediate

watchEffect 自动依赖收集

ts
const count = ref(0);

watchEffect(() => {
  console.log("count is:", count.value);
});
  • 自动追踪 count.value
  • 没有 newVal / oldVal
  • 一进页面就执行一次

低代码可视化实现思路?

可以分为 前端可视化编辑器 + 中后台渲染引擎 两大部分:

  1. 拖拽搭建 →
  2. 结构数据保存 →
  3. 渲染引擎解析生成页面

警告

低代码可视化平台的核心思路是:

  • 构建一个基于拖拽的页面编辑器,通过组件配置生成页面 DSL;
  • 将 DSL 结构转化为真实可运行的页面(Vue/React 渲染);
  • 并支持属性配置、事件编排、数据绑定等功能;

技术上通常会基于 Vue + 可视化库(如 VueDraggable),配合 JSON Schema 管理组件结构,并使用动态组件实现渲染。也可以扩展为支持数据联动、权限控制、发布系统等。

① 拖拽组件系统(可视化编辑器)

  • 使用 Vue 或 React 实现一个画布 + 组件库
  • 每个组件是一个配置驱动的模块化组件(封装好样式和逻辑)
  • 拖拽时生成一个组件树结构(DSL)
json
{
  "type": "Button",
  "props": { "text": "提交", "color": "primary" },
  "style": { "marginTop": "20px" }
}

这就是低代码平台的“页面 DSL(Domain Specific Language)”

② 属性配置面板

  • 拖到画布的组件被选中后,右侧出现属性编辑器
  • 使用 el-input, el-switch, el-color-picker 控制组件属性
  • 修改后实时更新组件配置(双向绑定)

③ 页面 DSL 结构管理

  • 当前页面的组件结构以树形方式存在内存中 / 存入数据库
  • 每次拖动/修改属性都同步修改 DSL
  • 可以导出为 JSON 存储,也可导出代码(如 Vue / JSX)

④ 实时预览 & 渲染引擎

  • 将组件 DSL 转换成真实的 Vue / React 组件
  • 渲染方式可用动态 render()、或 Vue.compile()(SSR 可用服务端注册)

⑤ 拓展支持(进阶)

  • 数据源绑定:支持接入接口(RESTful、GraphQL)、mock 数据
  • 事件系统:组件支持 onClick → triggerAction 等行为抽象
  • 权限控制:可控制页面、字段、组件的权限逻辑
  • 版本发布:可将 DSL 打包、发布为独立页面或 iframe 接入

移动端适配方案?

警告

移动端适配常见有 rem 方案、vw 方案和媒体查询等,其中我最常用的是 rem + lib-flexible 的方式:

  • 通过设置根元素 font-size,实现等比缩放;
  • 页面中用 rem 单位写布局,配合 postcss-pxtorem 插件自动转换;

项目中也会在特定场景(如全屏轮播、嵌入 iframe)使用 vw 单位适配。

对于更复杂的响应式需求,还可以结合媒体查询、Tailwind CSS 的断点类进行更精细的控制。

方案名称原理说明适用场景
1. 百分比布局使用 % 计算宽高、margin、padding 等简单自适应页面
2. rem + flexible.js根据屏幕宽度动态设置 <html> 的 font-size常见于 Vue2 + H5 项目
3. vw / vh使用视口单位设置元素宽度 / 高度全屏布局、banner、容器等
4. 媒体查询(media)针对不同屏幕宽度设置不同样式精细控制多个断点的样式
5. PostCSS + px2rem开发写 px,构建时自动转成 rem结合方案 2,一起使用
6. Vant + lib-flexibleVant UI 默认 rem 方案,需要配合 flexible.js使用 Vant UI 时的默认推荐
7. Tailwind CSS原子化样式 + media query 内置Vue3 / React 项目强推荐

阶段十一

webpack 和 vite 的区别?

警告

Webpack 是传统的打包构建工具,采用的是“打包构建再运行”的机制,而 Vite 是新一代构建工具,开发时利用浏览器原生 ESModule 实现“按需加载即编译”,极大提升了启动速度和热更新性能。在我开发 Vue3 或 React 项目时,通常会选择 Vite,它配置更简单、性能更好。Webpack 依然适用于一些需要复杂构建逻辑的项目或已有代码迁移场景。

对比维度WebpackVite
开发模式先构建所有模块,启动慢按需加载模块,启动极快
构建原理基于 JavaScript 打包(Bundle)开发时基于原生 ES 模块 + 构建时 Rollup 打包
热更新速度(HMR)慢:全部重新编译模块快:按模块热更新(模块级 HMR)
打包速度构建慢,尤其是大型项目构建快,基于 Rollup,且有现代优化策略
插件生态成熟,插件数量多插件数量逐步完善,大多兼容 Rollup 插件
配置复杂度配置复杂(webpack.config.js)配置简单(vite.config.ts),约定优于配置
默认支持语法需配置 loader原生支持 TS、JSX、CSS 模块等现代语法
构建产物一整包输出Rollup 优化构建产物,按需拆包优化
静态资源支持需配置 file-loader/url-loader默认支持,自动处理
适合场景大型复杂项目,或已有 Webpack 项目中小型新项目、Vue3/React 项目,开发效率优先

本质区别:

  • Webpack
    • 构建前:把所有依赖打包成一个或多个 bundle 文件,然后启动 dev server
    • 即使修改一个文件,也要重新打包大部分模块
  • Vite
    • 开发时:使用浏览器原生的 ES Module 机制,只在访问模块时再编译,修改哪个编译哪个
    • 构建时:基于 Rollup 进行优化打包,产物更轻

Released under the MIT License.