Pinia不酸,保甜
为什么是Pinia
怎么说呢,其实在过往的大部分项目里面,我并没有引入过状态管理相关的库来维护状态。因为大部分的业务项目相对来说比较独立,哪怕自身功能复杂的时候,可能也仅仅是通过技术栈自身的提供的状态管理能力来处理业务场景问题,比如React中的context,基本都能解决我遇到的问题。
针对Redux或者Vuex这类状态管理的库,我认为在较为复杂的大型业务场景下才能发挥他们的真实作用,在场景较为简单单一,数据状态相对不复杂的时候,引入他们可能并不能带来那么多的便捷。
说回Pinia,接触使用到它主要是因为有一次手里的发布功能需要进行重构。虽然仅仅是一个发布页面,但是梳理起来发现,里面涉及几类数据需要维护,包括主表单信息、错误校验信息、公共弹窗信息以及关联用户的各种状态信息等。想起之前有看到过关于小🍍的介绍,抱着试试看的心态接入试试。(不行我就继续Provide/Inject了[捂脸])
认识Pinia
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
上面这个是官网对Pinia的一个定义,从定义上我们其实可以看出来,它可能比Vuex要精炼一些(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。)具体如何我们还是得具体从使用情况来看一下。
Pinia的常规用法
安装
通过常用的包管理器进行安装:
yarn add pinia
// 或者
npm install pinia
安装完成后,我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程式。我们需要修改main.js,引入pinia提供的cteatePinia方法:
import { createApp } from 'vue';
import { createPinia } from 'pinia';const pinia = createPinia();
const app = createApp(App);
app.use(pinia).mount('#app');
上述安装引入基于Vue3,如果使用Vue2的话,轻参照官网相关说明即可。
Store
store简单来说就是数据仓库的意思,我们 的数据都放在store里面。当然你也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。它有三个概念,state、getters和actions,我们可以将它们等价于组件中的“数据”、“计算属性”和“方法”。
store中应该包含可以在整个应用中访问的数据、全局性数据,我们应该避免将可以管理在具体组件内部的数据放到store中。
我们需要使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据。我们可以在项目中创建store目录存储我们定义的各种store:
// src/store/formInfo.js
import { defineStore } from 'pinia';// 第一个参数是应用程序中 store 的唯一 id
const useFormInfoStore = defineStore('formInfo', {// 其他配置项,后面逐一说明
})export default useFormInfoStore;
defineStore接收两个参数:
- name:一个字符串,必传项,该store的唯一id。
- options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
将返回的函数命名为 use... 是组合式开发的约定,使其符合使用习惯。我们可以根据项目情况定义任意数量的store存储不同功能模块的数据,一个store就是一个函数,它和Vue3的实现思想也是一致的。
使用store
我们可以在任意组件中引入定义的store来进行使用
<script setup>
// 引入定义
import useFormInfoStore = '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
</script>
store 被实例化后,你就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性。
解构store
store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构,如果我们想要提取store中的属性同时保持其响应式的话,我们需要使用storeToRefs(),它将为响应式属性创建refs。
<script setup>
import { storeToRefs } from 'pinia';
// 引入定义
import useFormInfoStore = '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
const { name, age } = formInfoStore; // ❌ 此时解构出来的name和age不具有响应式
const { name, age } = storeToRefs(formInfoStore); // ✅ 此时解构出来的name和age是响应式引用
</script>
State
store是用来存储全局状态数据的仓库,那自然而然需要有地方能够保存这些数据,它们就保存在state里面。defineStore传入的第二个参数options配置项里面,就包括state属性。
// src/store/formInfo.js
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {// 推荐使用 完整类型推断的箭头函数state: () => {return {// 所有这些属性都将自动推断其类型name: 'Hello World',age: 18,isStudent: false}}// 还有一种定义state的方式,不太常见,了解即可// state: () => ({// name: 'Hello World',// age: 18,// isStudent: false// })
})
export default useFormInfoStore;
访问state
默认情况下,您可以通过 store 实例来直接读取和写入状态:
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
formInfoStore.age++; // 19
</script>
pinia还提供了几个常见场景的方法供我们使用来操作state:$reset、$patch、$state、$subscribe:
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
// 直接修改state中的属性
formInfoStore.age++; // 19
// 1.$reset 重置状态,将状态重置成为初始值
formInfoStore.$reset();
console.log(formInfoStore.age); // 18// 2.$patch 支持对state对象的部分批量修改
formInfoStore.$patch({name: 'hello Vue',age: 198
});// 3.$state 通过将其 $state 属性设置为新对象来替换 Store 的整个状态
formInfoStore.$state = {name: 'hello Vue3',age: 100,gender: '男'
}
// 4.$subscribe 订阅store中的状态变化
formInfoStore.$subscribe((mutation, state) => {// 监听回调处理
}, {detached: true // 💡如果在组件的setup中进行订阅,当组件被卸载时,订阅会被删除,通过detached:true可以让订阅保留
})
</script>
针对上面示例,有几点需要关注一下:
- 1.同直接修改state中的属性不同,通过$patch方法更新多个属性时,在devtools的timeline中,是合并到一个条目中的。

- 2.通过实验得知,$state在进行替换时,会更新已有的属性,新增原来state不存在的属性,针对不在替换范围内的,则保持不变。

如上图,针对gender属性,执行的是"add"操作,然后这个替换过程我们没有设置isStudent属性,它仍然保持原状态在state中不变。
- 3.$subscribe只会订阅到patches引起的变化,即上面的通过$patch方法和$state覆盖时会触发到状态订阅,直接修改state的属性则不会触发。
Getters
pinia中的getters可以完全等同于Store状态的计算属性,通常在defineStore中的getters属性中定义。同时支持组合多个getter,此时通过this访问其他getter。
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {state: () => {return {name: 'Hello World',age: 18,isStudent: false,gender: '男'};},getters: {// 仅依赖state,通过箭头函数方式isMan: (state) => {return state.gender === '男';},isWoman() {// 通过this访问其他getter,此时不可以用箭头函数return !this.isMan;}}
});
export default useFormInfoStore;
在使用时,我们可以直接在store实例上面访问getter:
<template><div>The person is Man: {{ formInfoStore.isMan }} or is Woman: {{ formInfoStore.isWoman }}</div>
</tempalte>
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>
通常getter是不支持额外传参的,但是我们可以从getter返回一个函数的方式来接收参数:
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {state: () => {return {name: 'Hello World',age: 18,isStudent: false,gender: '男'};},getters: {isLargeBySpecialAge: (state) => {return (age) => {return state.age > age}}}
});
export default useFormInfoStore;
在组件中使用时即可传入对应参数,注意,在这种方式时,getter不再具有缓存性。
<template><div>The person is larger than 18 years old? {{ formInfoStore.isLargeBySpecialAge(18) }}</div>
</tempalte>
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>
Actions
actions相当于组件中的methods,它们定义在defineStore中的actions属性内,常用于定义业务逻辑使用。action可以是异步的,可以在其中await 任何 API 调用甚至其他操作
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {state: () => {return {name: 'Hello World',age: 18,isStudent: false,gender: '男'};},getters: {isMan: (state) => {return state.gender === '男';},isWoman() {return !this.isMan;}},actions: {incrementAge() {this.age++;},async registerUser() {try {const res = await api.post({name: this.name,age: this.age,isStudent: this.isStudent,gender: this.gender});showTips('用户注册成功~');} catch (e) {showError('用户注册失败!');}}}
});
export default useFormInfoStore;
使用起来也非常方便
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();const registerUser = () => {formInfoStore.registerUser();
}
</script>
$onAction()
可以使用 store.$onAction() 订阅 action 及其结果。传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数,onError 允许您在处理中抛出错误。
const unsubscribe = formInfoStore.$onAction(({name, // action 的名字store, // store 实例args, // 调用这个 action 的参数after, // 在这个 action 执行完毕之后,执行这个函数onError, // 在这个 action 抛出异常的时候,执行这个函数}) => {// 记录开始的时间变量const startTime = Date.now()// 这将在 `store` 上的操作执行之前触发console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。// 它将等待任何返回的 promiseafter((result) => {console.log(`Finished "${name}" after ${Date.now() - startTime}ms.\nResult: ${result}.`)})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发onError((error) => {console.warn(`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`)})}
)
// 手动移除订阅
unsubscribe()
和$subscribe类似,在组件中使用时,组件卸载,订阅也会被删除,如果希望保留的话,需要传入true作为第二个参数。
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
formInfoStore.$onAction(callback, true);
</script>
Pinia的基础使用我们暂时介绍到这里,其他使用场景大家可以参照官方文档进一步学习。
Pinia VS Vuex
回过头来,我们再来看一下,Pinia为什么现在这么受到推崇。和我们过往常用的Vuex相比,它到底好在哪里呢?
对于Vuex,我们知道,它的背后基本思想借鉴了Flux。Flux 是 Facebook 在构建大型 Web 应用程序时为了解决数据一致性问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构,或者以它为参考原型。Vuex在它的基础上进行了一些设计上的优化:

Vuex主要有五部分核心内容:
- 📦 state:整个应用的状态管理单例,等效于 Vue 组件中的 data,对应了 Flux 架构中的 store。
- 🧮 getter:可以由 state 中的数据派生而成,等效于 Vue 组件中的计算属性。它会自动收集依赖,以实现计算属性的缓存。
-
🛠 mutation:类似于事件,包含一个类型名和对应的回调函数,在回调函数中可以对 state 中的数据进行同步修改。
- Vuex 不允许直接调用该函数,而是需要通过
store.commit方法提交一个操作,并将参数传入回调函数。 - commit 的参数也可以是一个数据对象,正如 Flux 架构中的 action 对象一样,它包含了类型名
type和负载payload。 - 这里要求 mutation 中回调函数的操作一定是同步的,这是因为同步的、可序列化的操作步骤能保证生成唯一的日志记录,才能使得 devtools 能够实现对状态的追踪,实现 time-travel。
- Vuex 不允许直接调用该函数,而是需要通过
-
🔨 action:action 内部的操作不受限制,可以进行任意的异步操作。我们需要通过
dispatch方法来触发 action 操作,同样的,参数包含了类型名type和负载payload。- action 的操作本质上已经脱离了 Vuex 本身,假如将它剥离出来,仅仅在用户(开发者)代码中调用
commit来提交 mutation 也能达到一样的效果。
- action 的操作本质上已经脱离了 Vuex 本身,假如将它剥离出来,仅仅在用户(开发者)代码中调用
-
📁 module:store 的分割,每个 module 都具有独立的 state、getter、mutation 和 action。
- 可以使用
module.registerModule动态注册模块。 - 支持模块相互嵌套,可以通过设置命名空间来进行数据和操作隔离。
- 可以使用
通过和我们上面学习到的Pinia的基础内容对比可以看出,Pinia舍弃了mutation和module两部分,这样我们在使用时就更加的便捷。

与Vuex3.x/4.x对比,主要区别如下:
- mutations 不再存在。他们经常被认为是 非常 冗长。他们最初带来了 devtools 集成,但这不再是问题。
- 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
- 不再需要注入、导入函数、调用函数、享受自动完成功能!
- 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
- 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系。
- 没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。
其实对于我来说,之所以选择Pinia,甚至是喜欢上它,是因为它和Vue3的组合是API形式更加贴合,只需要把它当作一个特殊的数据状态组件来使用就好,不需要那么复杂的流程。
小结
通过对Pinia的基本功能的使用介绍,以及和Vuex进行对比,让我们比较清晰的来认识Pinia,使我们能够入门使用。在具体业务场景中,有效的划分Store,合理的组合使用,可以帮助我们完成复杂的业务逻辑。
参考内容:
pinia中文文档
Pinia or Vuex?
Vuex 与 Pinia 的设计实现对比
相关文章:
Pinia不酸,保甜
为什么是Pinia 怎么说呢,其实在过往的大部分项目里面,我并没有引入过状态管理相关的库来维护状态。因为大部分的业务项目相对来说比较独立,哪怕自身功能复杂的时候,可能也仅仅是通过技术栈自身的提供的状态管理能力来处理业务场景…...
uniapp生命周期
uniapp生命周期 uniapp生命周期不同于vue生命周期,uniapp生命周期分为: 应用生命周期 页面生命周期 组件生命周期 应用生命周期(官网) 注意 应用生命周期仅可在App.vue中监听,在其它页面监听无效。 onlaunch里进行页面跳转,如遇白…...
经典卷积模型回顾11—Xception实现图像分类(matlab)
Xception是一种深度卷积神经网络,它采用了分离卷积来实现深度神经网络的高准确性和高效率。Xception的名称来自“extreme inception”,意思是更加极致的Inception网络。 在传统的卷积神经网络中,每个卷积层都有若干个滤波器(即卷…...
移动App性能测试包含哪些内容?App性能测试工具有哪些?
随着互联网高科技的蓬勃发展,移动app的的需求量和供给量都较大。但一款好app的成功上线以及为用户带来高效体验,性能测试起着关键性的作用。性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试࿰…...
AI测试的迷思
近年来,我一直关注AI相关的测试,并积极参与多个全国性测试社区和社群。在这些社区中,我与不同公司和领域的测试专家交流探讨AI测试相关话题,包括业界顶尖公司的专家和国内知名测试学者。我也参加了多个大会,聆听了许多…...
[ 红队知识库 ] 一些常用bat文件集合
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
Qt广告机服务器(上位机)
目录功能结构adSever.promain.cpptcp_MSG.h 共用Tcp传输信息adsever.h 服务器adsever.cpp 服务器addate.h 时间处理addate.cpp 时间处理adtcp.h 客户端Socket处理adtcp.cpp 客户端Socket处理client.h 客户端信息类client.cpp 客户端信息类admsglist.h 信息记录模块admsglist.cp…...
SOA架构的理解
1. SOA概述 SOA(Service-Oriented Architecture,面向服务的架构)是一种在计算机环境中设计、开发、部署和管理离散模型的方法。SOA不是一种新鲜事物,它是在企业内部IT系统重复构建以及效率低下的背景下提出的。在SOA模型中&#x…...
如何选择一款数据库?
1主流数据库技术介绍常见的数据库模型主要分为SQL关系型数据库和NoSQL非关系型数据库。其中关系型数据库分为传统关系数据库和大数据数据库,非关系型数据库分为键值存储数据库、列存储数据库、面向文档数据库、图形数据库、时序数据库、搜索引擎存储数据库及其他&am…...
week2
蓝桥2 递归*树的遍历约数之和分形之城并查集亲戚连通块中点的数量*食物链银河英雄传说哈希笨拙的手指模拟散列表单调队列剪裁序列滑动窗口最大子序和KMP周期递归 *树的遍历 中序遍历: 遍历左子树,根节点,右子树 后序遍历:遍历左子树,右子树,根节点 一个二叉树,树中每个…...
JavaScript的学习
一、引言 1.1 JavaScript简介 JavaScript一种解释性脚本语言,是一种动态类型、弱类型、基于原型继承的语言,内置支持类型。它的解释器被称为JavaScript引擎,作为浏览器的一部分,广泛用于客户端的脚本语言,用来给HTML网…...
用gin写简单的crud后端API接口
提要使用gin框架(go的web框架)来创建简单的几个crud接口)使用技术: gin sqlite3 sqlx创建初始工程新建文件夹,创建三个子文件夹分别初始化工程 go mod如果没有.go文件,执行go mod tidy可能报错(warning: "all" matched no packages), 可以先不弄,只初始化模块就行(…...
CF大陆斗C战士(三)
文章目录[C. Good Subarrays](https://codeforces.com/problemset/problem/1398/C)题目大意题目分析code[C. Boboniu and Bit Operations](https://codeforces.com/problemset/problem/1395/C)题目大意题目分析code[C. Rings](https://codeforces.com/problemset/problem/1562/…...
TTS | 语音合成论文概述
综述系列2021_A Survey on Neural Speech Synthesis论文:2106.15561.pdf (arxiv.org)论文从两个方面对神经语音合成领域的发展现状进行了梳理总结(逻辑框架如图1所示):核心模块:分别从文本分析(textanalysi…...
HTML第5天 HTML新标签与特性
新标签与特性文档类型设定前端复习帮手W3Schoool常用新标签datalist标签,与input元素配合,定义选项列表fieldset元素新增input表单文档类型设定 document – HTML: 开发环境输入html:4s – XHTML: 开发环境输入html:xt – HTML5: 开发环境输入html:5 前…...
java ee 之进程
目录 1.进程的概念 2.进程管理 3.进程属性(pcb) 3.1pid 3.2内存指针 3.3文件描述符 3.4进程调度 3.4.1进程状态 3.4.2 进程的优先级 3.4.3进程的上下文 3.4.4进程的记账信息 5.进程间通信 1.进程的概念 一个运行起来的程序,就是进程 .exe是一个可执行文件(程序),双…...
Linux学习记录——십사 进程控制(1)
文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建 1、fork函数 fork函数从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。 #include <unistd.h> pid_t fork(vo…...
使用 create-react-app 脚手架搭建React项目
❀官网 1、安装脚手架:npm install -g create-react-app 2、查看版本:create-react-app -V !!!注意 Node版本必须是14以上,不然会报以下错误。 3、创建react项目(项目名不能包含大写字母&…...
inquirerjs
inquirerjs inquirerjs是一个用来实现命令行交互界面的工具集合。它帮助我们实现与用户的交互交流,比如给用户一个提醒,用户给我们一个答案,我们根据用户的答案来做一些事情,典型应用如plop等生成器工具。 npm install inquirer…...
[数据库]内置函数
●🧑个人主页:你帅你先说. ●📃欢迎点赞👍关注💡收藏💖 ●📖既选择了远方,便只顾风雨兼程。 ●🤟欢迎大家有问题随时私信我! ●🧐版权:本文由[你帅…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
