我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Vue虚拟Dom到真实Dom的转换方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Vue虚拟Dom到真实Dom的转换方法

这篇文章主要介绍“Vue虚拟Dom到真实Dom的转换方法”,在日常操作中,相信很多人在Vue虚拟Dom到真实Dom的转换方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue虚拟Dom到真实Dom的转换方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

再有一颗树形结构的Javascript对象后, 我们需要做的就是讲这棵树跟真实Dom树形成映射关系。我们先回顾之前的mountComponnet 方法:

export function mountComponent(vm, el) {  vm.$el = el  ...  callHook(vm, 'beforeMount')  ...  const updateComponent = function () {    vm._update(vm._render())  }  ...}

我们已经执行完了vm._render 方法拿到了VNode, 现在将它作为参数传给vm._update 方法并执行。 vm._update这个方法的作用就是将VNode 转为真实的Dom, 不过它有两个执行时机:

首次渲染

当执行new Vue 到此时就是首次渲染了, 会将传入的Vnode对象映射为真实的Dom。

更新页面

数据变化会驱动页面发生变化, 这也是vue最独特的特性之一, 数据改变之前和之后生成两份VNode进行比较, 而怎么样在旧的VNode上做最小的改动去渲染页面,这样一个diff算法还是挺复杂的。 如果再没有先说清楚数据响应式是怎么回事之前,直接将diff对理解vue 的整体流程不太好。 所以这章分析首次渲染后, 下一章就是数据响应式, 之后才是diff比较。

先来看看vm._update方法的定义:

Vue.prototype._update = function(vnode) {  ... 首次渲染  vm.$el = vm.__patch__(vm.$el, vnode)  // 覆盖原来的vm.$el  ...}

这里的 vm. e l 是 之 前 在 = = m o u n t C o m p o n e n t = = 方 法 内 就 挂 载 的 , 一 个 真 实 的 = = D o m = = 元 素 。 首 次 渲 染 会 传 入 v m . el 是之前在 ==mountComponent== 方法内就挂载的, 一个真实的==Dom==元素。 首次渲染会传入 vm. el是之前在==mountComponent==方法内就挂载的,一个真实的==Dom==元素。首次渲染会传入vm.el 以及得到的VNode, 所以看下vm.patch 定义:

Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules })

patch 是createPatchFunction 方法内部返回的一个方法, 它接受一个对象:

nodeOps属性:封装了操作原生Dom 的一些方法的集合, 如:创建、插入,移除这些, 我们到使用的地方咋详解。

modules 属性: 创建真实Dom 也需要生成它的如class/attrs/style 等属性。 modules 是一个数组集合,数组的每一项都是这些属性对应的钩子方法, 这些属性的创建,更新,销毁等都有对应钩子方法。 当某一时刻需要做某件事,执行对应的钩子即可。 比如它们都有create 这个钩子方法, 如将这些create 钩子收集到一个数组内, 需要在真实Dom上创建这些属性时,依次执行数组的每一项,也就是依次创建了它们。

PS: 这里modules 属性内的钩子方法是区分平台的, web, weex 以及 SSR 它们调用VNode 方法方式并不相同, 所以vue在这里又使用了函数柯里化这个骚操作, 在createPatchFunction 内将平台的差异化磨平, 从而 patch 方法只用接收新旧node即可。

生成Dom

这里大家记住一句话即可, 无论VNode 是什么类型的节点, 只有三种类型的节点会被创建并插入到Dom中: 元素节点,注释节点, 和文本节点。

我们接着看下createPatchFunction 它返回一个怎样的方法:

export function createPatchFunction(backend) {  ...  const { modules, nodeOps } = backend  // 解构出传入的集合    return function (oldVnode, vnode) {  // 接收新旧vnode    ...    const isRealElement = isDef(oldVnode.nodeType) // 是否是真实Dom    if(isRealElement) {  // $el是真实Dom      oldVnode = emptyNodeAt(oldVnode)  // 转为VNode格式覆盖自己    }    ...  }}

首次渲染时没有oldVnode, oldVnode 就是 $el, 一个真实的dom, 经过emptyNodeAt(odVnode) 方法包装:

function emptyNodeAt(elm) {  return new VNode(    nodeOps.tagName(elm).toLowerCase(), // 对应tag属性    {},  // 对应data    [],   // 对应children    undefined,  //对应text    elm  // 真实dom赋值给了elm属性  )}包装后的:{  tag: 'div',  elm: '<div id="app"></div>' // 真实dom}-------------------------------------------------------nodeOps:export function tagName (node) {  // 返回节点的标签名  return node.tagName  }

在将传入的==$el== 属性转为了VNode 格式之后,我们继续:

export function createPatchFunction(backend) {   ...    return function (oldVnode, vnode) {  // 接收新旧vnode      const insertedVnodeQueue = []    ...    const oldElm = oldVnode.elm  //包装后的真实Dom <div id='app'></div>    const parentElm = nodeOps.parentNode(oldElm)  // 首次父节点为<body></body>      createElm(  // 创建真实Dom      vnode, // 第二个参数      insertedVnodeQueue,  // 空数组      parentElm,  // <body></body>      nodeOps.nextSibling(oldElm)  // 下一个节点    )        return vnode.elm // 返回真实Dom覆盖vm.$el  }}                                              ------------------------------------------------------nodeOps:export function parentNode (node) {  // 获取父节点  return node.parentNode }export function nextSibling(node) {  // 获取下一个节点  return node.nextSibing  }

createElm 方法开始生成真实的Dom, VNode 生成真实的Dom 的方式还是分为元素节点和组件两种方式, 所以我们使用上一章生成的VNode分别说明。

1. 元素节点生成Dom

{  // 元素节点VNode  tag: 'div',  children: [{      tag: 'h2',      children: [        {text: 'title h2'}      ]    }, {      tag: 'h3',      children: [        {text: 'title h3'}      ]    }, {      tag: 'h4',      children: [        {text: 'title h4'}      ]    }  ]}

大家可以先看下这个流程图有个印象即可, 再接下来看具体实现时思路会清晰很多(这里先借用网上的一张图):

Vue虚拟Dom到真实Dom的转换方法

开始Dom, 来看下它的定义:

function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {   ...  const children = vnode.children  // [VNode, VNode, VNode]  const tag = vnode.tag  // div    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {    return  // 如果是组件结果返回true,不会继续,之后详解createComponent  }    if(isDef(tag)) {  // 元素节点    vnode.elm = nodeOps.createElement(tag)  // 创建父节点    createChildren(vnode, children, insertedVnodeQueue)  // 创建子节点    insert(parentElm, vnode.elm, refElm)  // 插入      } else if(isTrue(vnode.isComment)) {  // 注释节点    vnode.elm = nodeOps.createComment(vnode.text)  // 创建注释节点    insert(parentElm, vnode.elm, refElm); // 插入到父节点      } else {  // 文本节点    vnode.elm = nodeOps.createTextNode(vnode.text)  // 创建文本节点    insert(parentElm, vnode.elm, refElm)  // 插入到父节点  }    ...}------------------------------------------------------------------nodeOps:export function createElement(tagName) {  // 创建节点  return document.createElement(tagName)}export function createComment(text) {  //创建注释节点  return document.createComment(text)}export function createTextNode(text) {  // 创建文本节点  return document.createTextNode(text)}function insert (parent, elm, ref) {  //插入dom操作  if (isDef(parent)) {  // 有父节点    if (isDef(ref)) { // 有参考节点      if (ref.parentNode === parent) {  // 参考节点的父节点等于传入的父节点        nodeOps.insertBefore(parent, elm, ref)  // 在父节点内的参考节点之前插入elm      }    } else {      nodeOps.appendChild(parent, elm)  //  添加elm到parent内    }  }  // 没有父节点什么都不做}这算一个比较重要的方法,因为很多地方会用到。

依次判断是否是元素节点, 注释节点,文本节点, 分别创建它们然后插入到父节点里面, 这里主要介绍创建元素节点, 另外两个并没有复杂的逻辑。 我们接下来看下:createChild 方法定义:

function createChild(vnode, children, insertedVnodeQueue) {  if(Array.isArray(children)) {  // 是数组    for(let i = 0; i < children.length; ++i) {  // 遍历vnode每一项      createElm(  // 递归调用        children[i],         insertedVnodeQueue,         vnode.elm,         null,         true, // 不是根节点插入        children,         i      )    }  } else if(isPrimitive(vnode.text)) {  //typeof为string/number/symbol/boolean之一    nodeOps.appendChild(  // 创建并插入到父节点      vnode.elm,       nodeOps.createTextNode(String(vnode.text))    )  }}-------------------------------------------------------------------------------nodeOps:export default appendChild(node, child) {  // 添加子节点  node.appendChild(child)}

开始创建子节点, 遍历VNode 的每一项, 每一项还是使用之前的createElm方法创建Dom。 如果某一项又是数组,继续调用createChild创建某一项的子节点; 如果某一项不是数组, 创建文本节点并将它添加到父节点内。 像这样使用递归的形式将嵌套的VNode全部创建为真实的Dom。

在看一遍流程图, 应该就能减少大家很多疑惑了(这里先借用网上一章图):

Vue虚拟Dom到真实Dom的转换方法

简单来说就是由里向外的挨个创建出真实的Dom, 然后插入到它的父节点内,最后将创建好的Dom插入到body内, 完成创建的过程, 元素节点的创建还是比较简单的, 接下来看下组件式怎么创建的。

组件VNode生成Dom

{  // 组件VNode  tag: 'vue-component-1-app',  context: {...},  componentOptions: {    Ctor: function(){...},  // 子组件构造函数    propsData: undefined,    children: undefined,    tag: undefined  },  data: {    on: undefined,  // 原生事件    hook: {  // 组件钩子      init: function(){...},      insert: function(){...},      prepatch: function(){...},      destroy: function(){...}    }  }}-------------------------------------------<template>  // app组件内模板  <div>app text</div></template>

首先看张简易流程图, 留个影响即可,方便理清之后的逻辑顺序(这里借用网上一张图):

Vue虚拟Dom到真实Dom的转换方法

使用上一章组件生成VNode , 看下在createElm 内创建组件Dom分支逻辑是怎么样的:

function createElm(vnode, insertedVnodeQueue, parentElm, refElm) {   ...  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { // 组件分支    return    }  ...

执行createComponent 方法, 如果是元素节点不会返回任何东西,所以是undefined , 会继续走接下来的创建元节点的逻辑。 现在是组件, 我们看下createComponent 的实现:

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {  let i = vnode.data  if(isDef(i)) {    if(isDef(i = i.hook) && isDef(i = i.init)) {      i(vnode)  // 执行init方法    }    ...  }}

首先会将组件的vnode.data赋值给i, 是否有这个属性就能判断是否是组件vnode。 之后的if(isDef(i = i.hook) && isDef(i = i.init)) 集判断和赋值为一体, if 内的i(vnode) 就是执行的组件init(vnode)方法。 这个时候我们来看下组件的init 钩子方法做了什么:

import activeInstance  // 全局变量const init = vnode => {  const child = vnode.componentInstance =     createComponentInstanceForVnode(vnode, activeInstance)  ...}

activeInstance 是一个全局的变量, 再update 方法内赋值为当前实例, 再当前实例做 patch 的过程中作为了组件的父实例传入, 在子组件的initLifecycle时构建组件关系。 将createComponentInsanceForVnode 执行的结果赋值给了vnode.componentInstance, 所以看下它的返回的结果是什么:

export  createComponentInstanceForVnode(vnode, parent) {  // parent为全局变量activeInstance  const options = {  // 组件的options    _isComponent: true,  // 设置一个标记位,表明是组件    _parentVnode: vnode,     parent  // 子组件的父vm实例,让初始化initLifecycle可以建立父子关系  }    return new vnode.componentOptions.Ctor(options)  // 子组件的构造函数定义为Ctor}

再组件的init 方法内首先执行craeeteComponentInstanceForVnode方法, 这个方法的内部就会将子组件的构造函数实例化, 因为子组件的构造函数继承了基类Vue的所有能力, 这个时候相当于执行new Vue({…}) , 接下来又会执行==_init方法进行一系列的子组件的初始化逻辑, 回到_init== 方法内, 因为他们之间还是有些不同的地方:

Vue.prototype._init = function(options) {  if(options && options._isComponent) {  // 组件的合并options,_isComponent为之前定义的标记位    initInternalComponent(this, options)  // 区分是因为组件的合并项会简单很多  }    initLifecycle(vm)  // 建立父子关系  ...  callHook(vm, 'created')    if (vm.$options.el) { // 组件是没有el属性的,所以到这里咋然而止    vm.$mount(vm.$options.el)  }}----------------------------------------------------------------------------------------function initInternalComponent(vm, options) {  // 合并子组件options  const opts = vm.$options = Object.create(vm.constructor.options)  opts.parent = options.parent  // 组件init赋值,全局变量activeInstance  opts._parentVnode = options._parentVnode  // 组件init赋值,组件的vnode   ...}

前面都还是执行的好好的, 最后却因为没有el属性, 所以没有挂载,createComponentInstanceForVnode 方法执行完毕。 这个时候我们回到组件的init方法, 补全剩下的逻辑:

const init = vnode => {  const child = vnode.componentInstance = // 得到组件的实例    createComponentInstanceForVnode(vnode, activeInstance)      child.$mount(undefined)  // 那就手动挂载呗}

我们在init 方法内手动挂载这个组件, 接着又会执行组件的==render()== 方法得到组件内元素节点VNode , 然后执行vm._update(), 执行组件的 patch 方法, 因为 $mount 方法传入的是 undefined, oldVnode 也是undefinned, 会执行__patch_ 内的这段逻辑:

return function patch(oldVnode, vnode) {  ...  if (isUndef(oldVnode)) {    createElm(vnode, insertedVnodeQueue)  }  ...}

这次执行createElm 是没有传入第三个参数父节点的, 那组件创建好的Dom放哪生效了? 没有父节点页要生成Dom不是, 这个时候执行的是组件的 patch , 所以参数vnode 就是组件内元素节点的vnode了:

<template> // app组件内模板  <div>app text</div></template>-------------------------{  // app内元素vnode  tag: 'div',  children: [    {text: app text}  ],  parent: {  // 子组件_init时执行initLifecycle建立的关系    tag: 'vue-component-1-app',    componentOptions: {...}  }}

很明显这个时候不是组件了, 即使是组件也没关系, 大不了还是执行一遍createComponent 创建组件的逻辑, 因为总会有组件是由元素节点组成的。 这个时候我们执行一遍创建元素节点的逻辑, 因为没有第三个参数父节点, 所以组件的Dom虽然创建好了, 并不会在这里插入。 请注意这个时候组件的init 已经完成, 但是组件的createComponent 方法并没有完成, 我们补全它的逻辑:

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {  let i = vnode.data;  if (isDef(i)) {    if (isDef(i = i.hook) && isDef(i = i.init)) {      i(vnode)  // init已经完成    }        if (isDef(vnode.componentInstance)) {  // 执行组件init时被赋值          initComponent(vnode)  // 赋值真实dom给vnode.elm            insert(parentElm, vnode.elm, refElm)  // 组件Dom在这里插入      ...      return true  // 所以会直接return    }  }}-----------------------------------------------------------------------function initComponent(vnode) {  ...  vnode.elm = vnode.componentInstance.$el  // __patch__返回的真实dom  ...}

无论是嵌套多么深的组件, 遇到组件后就执行 init, 在init 的 patch 过程中又遇到嵌套组件, 那就再执行嵌套组件的init, 嵌套组件完成 __patch__后将真是的Dom插入到它的父节点内, 接着执行完外层组件的 patch 又插入到它的父几点内, 最后插入到body 内, 完成嵌套组件的创建过程, 总之还是一个由里及外的过程。

在回过头看这张图, 相信会很好理解了:

Vue虚拟Dom到真实Dom的转换方法

再将本章最初的mountComponent 之后的逻辑补全:

export function mountComponent(vm, el) {  ...  const updateComponent = () => {    vm._update(vm._render())  }    new Watcher(vm, updateComponent, noop, {    before() {      if(vm._isMounted) {        callHook(vm, 'beforeUpdate')      }    }     }, true)    ...  callHook(vm, 'mounted')    return vm}

接下来会将 updateComponent 传入到一个Watcher 的类中, 这个类是干嘛的,我们下一章在介绍。 接下来执行mounted 钩子方法。 至此new vue 的整个流程就全部走完了。 我们回顾下从new Vue 开始执行的顺序:

new Vue ==> vm._init() ==> vm.$mount(el) ==> vm._render()  ==> vm.update(vnode)

最后我们以一个问题来结束本章的内容:

父子两个组件同时定义了 beforeCreate, created, beforeMounte, mounted 四个钩子, 它们的执行顺序是怎样的?

解答:

首先会执行父组件的初始化过程, 所以会依次执行beforeCreate, created, 在执行挂载前又会执行beforeMount钩子, 不过在生成真实dom 的 __patch__过程中遇到嵌套子组件后又会转为去执行子组件的初始化钩子beforeCreate, created, 子组件在挂载前会执行beforeMounte, 再完成子组件的Dom创建后执行 mounted。 这个父组件的 patch 过程才算完成, 最后执行父组件的mounted 钩子, 这就是它们的执行顺序。 如下:

parent beforeCreateparent createdparent beforeMounte    child beforeCreate    child created    child beforeMounte    child mountedparent mounted

到此,关于“Vue虚拟Dom到真实Dom的转换方法”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Vue虚拟Dom到真实Dom的转换方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Vue虚拟Dom到真实Dom的转换方法

这篇文章主要介绍“Vue虚拟Dom到真实Dom的转换方法”,在日常操作中,相信很多人在Vue虚拟Dom到真实Dom的转换方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue虚拟Dom到真实Dom的转换方法
2023-06-20

JS前端开发模拟虚拟dom转真实dom详解

这篇文章主要为大家介绍了JS前端开发模拟虚拟dom转真实dom详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-10

Vue虚拟dom被创建的方法

这篇文章主要介绍了Vue虚拟dom是如何被创建的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-11-13

Vue中的虚拟DOM和Diff算法实例分析

这篇文章主要介绍了Vue中的虚拟DOM和Diff算法实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue中的虚拟DOM和Diff算法实例分析文章都会有所收获,下面我们一起来看看吧。简单介绍一下 虚拟DO
2023-06-29

React vs Svelte:虚拟和真实DOM之间的战争

在本文中,我将比较Svelte与对React以及它们在幕后彼此的技术。

怎样深入理解vue中的虚拟DOM和Diff算法

怎样深入理解vue中的虚拟DOM和Diff算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。真实DOM的渲染在讲虚拟DOM之前,先说一下真实DOM的渲染。浏览器真实DOM渲
2023-06-22

剖析Vue实例:揭开虚拟DOM和渲染机制的神秘面纱

虚拟DOM和渲染机制是Vue.js框架的关键概念。理解它们对于优化应用程序性能和响应能力至关重要。本文将深入探讨Vue实例,揭开这些机制的神秘面纱,帮助您创建高效且可维护的应用程序。
剖析Vue实例:揭开虚拟DOM和渲染机制的神秘面纱
2024-02-18

android实现Uri获取真实路径转换成File的方法

本文实例讲述了android实现Uri获取真实路径转换成File的方法。分享给大家供大家参考。具体实现方法如下:Uri uri = data.getData(); String[] proj = { MediaStore.Images.Me
2022-06-06

在Win7 Hyper-v虚拟机中接真实机声卡的方法

1.真机为Win7 企业版,虚拟机也是Win7的企业版。(虚拟机中XP 只能支持播放不能编程客栈录音,微软文档说Win2008 R2能支持录音,但实际操作发现也是只支持播放,疑惑中)2.真机android中MSTSC的设置如下:python
2023-06-01

在交换机上划分虚拟局域网的实现方法

  虚拟局域网(VLAN)是一组逻辑上的设备和用户,这些设备和用户并不受物理位置的限制,可以根据功能、部门及应用等因素将它们组织起来,相互之间的通信就好像它们在同一个网段中一样,由此得名虚拟局域网。在这一篇教程里面,小编就为大家介绍一下在交换机上划分虚拟局域网的实现方法吧。  VLAN(也就是虚拟局域网)技术是允许一个
在交换机上划分虚拟局域网的实现方法
2024-04-18

自动驾驶车道线检测分类的虚拟-真实域适应方法

本文选择CARLA来生成模拟数据集,除灵活的Python API外,CARLA还包含丰富的覆盖城市、农村和公路场景的预先绘制地图内容。

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录