首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Vue.js 第一章学习笔记

Vue.js 第一章学习笔记

原创
作者头像
临在linzai
修改2025-11-02 10:37:55
修改2025-11-02 10:37:55
1020
举报

## 前言

最近在深入学习 **Vue.js设计与实现** ,深感“理解设计理念”比“记忆API用法”更为重要。

我决定通过文章来记录和输出学习内容,这不仅是为了检验自己的理解程度,更是为了梳理Vue.js背后的核心思想和实现机制。希望这种“知其所以然”的探索,能为你带来启发。

### 1.1命令式和声明式

首先什么是声明式和命令式,看接下来我们要做的事

代码语言:txt
复制
 获取 id 为 app 的 div 标签 
 它的文本内容为 hello world 
 为其绑定点击事件 
 当点击时弹出提示:ok

**命令式代码**:你如何去做,自己手动操作DOM,核心概念就是 **关注过程**

代码语言:txt
复制
 const div = document.querySelector('#app') // 获取 div 
 div.innerText = 'hello world' // 设置文本内容 
 div.addEventListener('click', () => { alert('ok') }) // 绑定点击事件

**声明式代码**:你描述想要的结果,Vue自动帮你实现DOM操作,核心概念就是 **关注结果**

代码语言:txt
复制
 <div @click="() => alert('ok')">hello world</div>

更通俗一点的说法:

* **命令式代码**是厨师,你需要洗菜、切菜、炒菜,每个步骤都需要你来

* **声明式代码**是顾客,我只需要跟你说一声我要吃什么,接下来就交给你了

### 1.2 性能和可维护性的权衡

声明式代码好是好,但他会有**更多的性能消耗**,借助书中的一句结论:**声明式代码的性能不优于命令式代码的性能**

看上面的例子 要求:把div中的文本要求改成 **hzh 真帅**。

**命令式代码**:

代码语言:txt
复制
div.textContent = 'hzh 真帅'

**声明式代码**:

代码语言:txt
复制
<div @click="handleClick">{{ message }}</div>

const app = {
  data() {
    return {
      message: 'hello world'
    }
  },
  methods: {
    handleClick() {
      // 声明式更新:只改变数据,Vue自动更新DOM
      this.message = 'hzh 真帅';
    }
  }
}

//当我们的this.message = 'hzh 真帅'的时候,vue自动做了这么一步
div.textContent = 'hzh 真帅'

书中结论:

如果我们把**直接修改**的性能消耗定义为 **A**,把**找出差异**的性能消耗定义为 **B**,那么有: <br/>

● 命令式代码的更新性能消耗 = A <br/>

● 声明式代码的更新性能消耗 = B + A

那既然声明式代码会消耗更多的性能,那我们是不是要更多的使用命令式代码?答案是不一定,直接上截图

其实对于大多数业务应用,声明式的可维护性优势远大于其性能开销。而且随着框架优化技术的进步,这个性能差距正在不断缩小。这也是为什么vue,react这些声明式框架会成为主流

### 1.3 虚拟DOM的性能到底如何

首先,虚拟DOM是什么?先看看真实DOM是什么

**真实DOM**

代码语言:txt
复制
// 真实DOM - 浏览器中的实际节点
<div class="title" id="app">Hello World</div>

**虚拟DOM**

代码语言:txt
复制
// 对应的虚拟DOM
const vnode = {
  type: 'div',
  props: {
    class: 'title',
    id: 'app'
  },
  children: 'Hello World'
}

可以看出,虚拟DOM就是一个js对象,为什么要有虚拟DOM这个东西呢,回想之前的**声明式代码**,把**找出差异**的性能消耗定义为 **B**,虚拟DOM就是为了优化这个'找出差异(B)'的性能消耗(书中原话:虚拟DOM就是为了最小化找到差异这一步性能消耗而出现的)

DOM的更新方式以下有几种

1. 原生的js操作

2. innerHTML

3. 虚拟DOM

**innerHTML**更新需要触发需要**删除以前的元素**,在**重新更新新的元素**

**innerHTML**创建页面的性能:**HTML 字符串拼接的计算量** + **innerHTML 的 DOM 计算量**

代码语言:txt
复制
<div id="list">
        <li>项目1</li>
        <li>项目2</li>
</div>

// 更新列表 - 重新渲染整个列表
const list = document.getElementById('list');
list.innerHTML = `
  <li>项目1</li>
  <li>项目2</li>
  <li>新项目3</li>  <!-- 只增加了这一项 -->
`;

**虚拟DOM**创建js对象,这个对象可以**理解**成真实DOM,并使用**递归地遍历**虚拟 DOM 树并创建真实 DOM

**虚拟DOM**创建页面的性能:**创建 JavaScript 对象的计算量** + **创建真实 DOM 的计算量**

代码语言:txt
复制
// 旧的虚拟DOM
const oldVNodes = [
  { type: 'li', children: '项目1' },
  { type: 'li', children: '项目2' }
];

// 新的虚拟DOM  
const newVNodes = [
  { type: 'li', children: '项目1' },
  { type: 'li', children: '项目2' },
  { type: 'li', children: '新项目3' }
];

那么他两的区别在哪呢?

**innerHTML**的更新过程:innerHTML的更新过程会销毁所有现有DOM元素,然后重新创建新的DOM元素

代码语言:txt
复制

大概意思是:

原本:list.innerHTML = `
  <li>项目1</li>
  <li>项目2</li>
`;
->
销毁之后:list.innerHTML = ``
->
重新更新:list.innerHTML = `
  <li>项目1</li>
  <li>项目2</li>
  <li>新项目3</li>
`;

**虚拟DOM**的更新过程:javaScript对象+diff,只会更新更改的元素

代码语言:txt
复制
    // 虚拟DOM如何工作
    const oldVNode = {
      type: 'ul',
      children: [
        { type: 'li', children: 'Item 1' },
        { type: 'li', children: 'Item 2' }
      ]
    };

    const newVNode = {
      type: 'ul', 
      children: [
        { type: 'li', children: 'Item 1' },
        { type: 'li', children: 'Updated Item 2' }, // 只有这里变了
        { type: 'li', children: 'Item 3' } // 新增
      ]
    };

    // Diff算法发现:
    // - 第一个li没变,复用
    // - 第二个li文本变了,只更新文本
    // - 新增第三个li,创建新元素

虚拟DOM的优势在于:无论页面有多大,我**只更新变化**的地方,而innerHTML则是**全部销毁更新**,这对性能消耗很大

书中原话:innerHTML、虚拟 DOM 以及原生 JavaScript 在更新页面时的性能我们分了几个维度:**心智负担**、**可维护性**和**性能**

### 1.4编译时和运行时

当设计一个框架的时候,我们有三种选择:**纯运行时**的、**运行时** +**编译时**的或**纯编译时**的

**运行时**

假设一个框架,提供一个render函数,我们可以提供一个树型结构的对象

代码语言:txt
复制

const obj = { 
tag: 'div', 
children: [ 
{ tag: 'span', children: 'hello world' } 
] 
}

tag代表标签名称,children即可以是一个数组(代表子元素),也可以是一段文本内容,接下来实现一下render函数

代码语言:txt
复制
function Render(obj, root) {
   const el = document.createElement(obj.tag)
   if (typeof obj.children === 'string') {
     const text = document.createTextNode(obj.children)
     el.appendChild(text)
   } else if (obj.children) {
     // 数组,递归调用 Render,使用 el 作为 root 参数
     obj.children.forEach((child) => Render(child, el))
   }

   // 将元素添加到 root
   root.appendChild(el)
 }

有了这个函数之后,我们可以这样使用来渲染到body下

代码语言:txt
复制
 Render(obj, document.body)

**编译时**

有时我们觉得,直接写一个树型结构的对象好麻烦啊,我能不能**获取一下html标签把他变成树型结构的对象**呢?,为此写了一个Compiler函数,可以实现上述效果

代码语言:txt
复制

const html = `
 <div>
   <span>hello world</span>
 </div>
 `
 // 调用 Compiler 编译得到树型结构的数据对象
 const obj = Compiler(html)

Compiler函数做的事:将html编译成命令式代码的过程

那么这三种选择分别有哪些优缺点呢?

**运行时**:

* ✅ **无构建步骤**:直接引入即可使用

* ✅ **灵活性高**:可以在运行时动态创建任何结构

* ✅ **调试简单**:没有编译过程,代码就是最终代码

* ❌ **性能较差**:无法进行编译时优化

* ❌ **代码冗长**:需要手动创建DOM和绑定事件

* ❌ **无语法糖**:没有模板、JSX等便捷语法

**编译时**:

* ✅ **性能极佳**:编译时完成所有优化,运行时几乎零开销

* ✅ **包体积小**:不需要包含运行时框架代码

* ✅ **无虚拟DOM**:直接操作DOM,减少内存占用

* ❌ **灵活性差**:难以在运行时动态创建复杂结构

* ❌ **调试困难**:编译后代码与源代码差异大

* ❌ **构建依赖**:必须使用构建工具,无法直接运行

**编译时 + 运行时**:

* ✅ **开发体验好**:提供模板、JSX等友好语法

* ✅ **性能优化**:编译时可以进行静态分析优化

* ✅ **灵活性**:支持运行时动态创建

* ✅ **渐进式**:可以选择使用编译特性

* ⚠️ **需要构建工具**:需要配置webpack、vite等

* ⚠️ **包体积较大**:包含编译器和运行时

* ⚠️ **复杂度高**:需要处理编译和运行时的协调

## 总结

通过对比命令式与声明式、分析虚拟DOM原理、了解编译时与运行时的选择,我们看到了Vue.js在性能与可维护性之间的智慧平衡。这种"知其所以然"的理解,能让我们更好地使用和欣赏这个优秀的框架。

希望这篇文章对你有所启发!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档