Skip to content

Review 2025

javascript

事件循环

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

vue

双向数据绑定?

Vue 2 的数据绑定原理(基于 Object.defineProperty

核心步骤

  1. 数据劫持:Vue 在初始化时会遍历 data 中的每一个属性,使用 Object.defineProperty 将其变成“响应式的”。
  2. 依赖收集:视图在渲染时,会“读取”这些属性。Vue 追踪哪些组件依赖了哪些属性。
  3. 数据变动 → 通知更新:当数据发生变化时,触发 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"; // 自动触发更新

Vue 3 的数据绑定原理(基于 Proxy

相比 Vue 2,Vue 3 使用 Proxy 替代了 Object.defineProperty,性能更高且支持数组/对象的新增/删除属性。

核心逻辑

  • 使用 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++; // 自动追踪和更新

响应式更新过程简图(简化)

data -> Observer -> Dep(依赖收集) -> Watcher(视图绑定)

修改 data -> setter -> 通知 Dep -> 执行 Watcher -> 更新视图

总结

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

数据视图更新机制?

依赖收集 + 发布订阅

当访问数据时,Vue 会记录“哪些地方使用了这个数据”;当你修改数据时,Vue 就会通知“这些地方”重新执行,从而更新视图。

详细过程分解(以 Vue 2 为例)

  1. 数据变成响应式:劫持属性

Vue 初始化时,会遍历 data 中的属性,使用 Object.defineProperty将它们变成响应式的。

javascript
Object.defineProperty(obj, "name", {
  get() {
    // 做依赖收集
    return val;
  },
  set(newVal) {
    // 通知 watcher 执行更新
    val = newVal;
  },
});
  1. 组件渲染 → 执行 render → 访问数据 → 自动收集依赖
  • 当 Vue 执行渲染函数时(如模版编译后的 render()),访问了 this.name
  • 此时触发 getter,Vue 就记录下当前组件“依赖了这个属性”

这就是所谓的 依赖收集(Dep -> Watcher)

  1. 数据修改 → 触发 setter → 通知更新
  • 当你执行 this.name = 'NewName'
  • Vue 的 setter 被触发
  • 它会通知与这个属性相关的所有 Watcher(如组件)重新执行 render() → 视图更新
javascript
// 实现示例
let name = "Tom";
Object.defineProperty(window, "name", {
  get() {
    console.log("视图用到了 name,收集依赖");
    return name;
  },
  set(val) {
    console.log("name 改了,要更新视图了");
    name = val;
    // 通知依赖这个 name 的视图更新
  },
});

// 模拟组件渲染
console.log(window.name); // -> 触发 getter

// 修改数据
window.name = "Jerry"; // -> 触发 setter,更新视图

Vue 3 中是如何实现的?

Vue 3 用了更强大的 Proxy 来劫持数据访问:

javascript
const state = reactive({ count: 0 });

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

// 修改值
state.count++; // 自动触发更新

Vue 3 的核心是:

  • 使用 依赖追踪器(effect & track)
  • 修改数据 → 触发 trigger() → 重新执行依赖函数(如 watchEffect

总结

Vue 知道视图需要更新,是因为它在读取数据时做了依赖收集,在写入数据时做了通知更新。这一切是通过 getter/setter(Vue2) 或 Proxy(Vue3)完成的。

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, // 返回上一页
});

页面传参方式?

  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

父子组件传参?

  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 来监听/发送事件。

生命周期?

在 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

sessionStorage 和 localStorage 缓存机制?

vue & uni app 对比

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 -->

总结

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

Released under the MIT License.