相思资源网 Design By www.200059.com

为什么写这篇vue的分析文章?

对于天资愚钝的前端(我)来说,阅读源码是件不容易的事情,毕竟有时候看源码分析的文章都看不懂。每次看到大佬们用了1~2年的vue就能掌握原理,甚至精通源码,再看看自己用了好几年都还在基本的使用阶段,心中总是羞愧不已。如果一直满足于基本的业务开发,怕是得在初级水平一直待下去了吧。所以希望在学习源码的同时记录知识点,可以让自己的理解和记忆更加深刻,也方便将来查阅。

目录结构

本文以vue的第一次 commit a879ec06 作为分析版本

├── build
│  └── build.js        // `rollup` 打包配置
├── dist            
│  └── vue.js  
├── package.json
├── src            // vue源码目录
│  ├── compiler        // 将vue-template转化为render函数
│  │  ├── codegen.js     // 递归ast提取指令,分类attr,style,class,并生成render函数
│  │  ├── html-parser.js   // 通过正则匹配将html字符串转化为ast
│  │  ├── index.js      // compile主入口
│  │  └── text-parser.js   // 编译{{}}
│  ├── config.js       // 对于vue的全局配置文件
│  ├── index.js        // 主入口
│  ├── index.umd.js      // 未知(应该是umd格式的主入口)
│  ├── instance        // vue实例函数
│  │  └── index.js      // 包含了vue实例的初始化,compile,data代理,methods代理,watch数据,执行渲染
│  ├── observer        // 数据订阅发布的实现
│  │  ├── array.js      // 实现array变异方法,$set $remove 实现
│  │  ├── batcher.js     // watch执行队列的收集,执行
│  │  ├── dep.js       // 订阅中心实现
│  │  ├── index.js      // 数据劫持的实现,收集订阅者
│  │  └── watcher.js     // watch实现,订阅者
│  ├── util          // 工具函数
│  │  ├── component.js
│  │  ├── debug.js
│  │  ├── dom.js
│  │  ├── env.js       // nexttick实现
│  │  ├── index.js
│  │  ├── lang.js
│  │  └── options.js
│  └── vdom
│    ├── dom.js       // dom操作的封装
│    ├── h.js        // 节点数据分析(元素节点,文本节点)
│    ├── index.js      // vdom主入口
│    ├── modules      // 不同属性处理函数
│    │  ├── attrs.js    // 普通attr属性处理
│    │  ├── class.js    // class处理
│    │  ├── events.js   // event处理
│    │  ├── props.js    // props处理
│    │  └── style.js    // style处理
│    ├── patch.js      // node树的渲染,包括节点的加减更新处理,及对应attr的处理
│    └── vnode.js      // 返回最终的节点数据
└── webpack.config.js     // webpack配置

从template到html的过程分析

我们的代码是从new Vue()开始的,Vue的构造函数如下:

constructor (options) {
 // options就是我们对于vue的配置
 this.$options = options
 this._data = options.data
 // 获取元素html,即template
 const el = this._el = document.querySelector(options.el)
 // 编译模板 -> render函数
 const render = compile(getOuterHTML(el))
 this._el.innerHTML = ''
 // 实例代理data数据
 Object.keys(options.data).forEach(key => this._proxy(key))
 // 将method的this指向实例
 if (options.methods) {
  Object.keys(options.methods).forEach(key => {
   this[key] = options.methods[key].bind(this)
  })
 }
 // 数据观察
 this._ob = observe(options.data)
 this._watchers = []
 // watch数据及更新
 this._watcher = new Watcher(this, render, this._update)
 // 渲染函数
 this._update(this._watcher.value)
}

当我们初始化项目的时候,即会执行构造函数,该函数向我们展示了vue初始化的主线:编译template字符串 => 代理data数据/methods的this绑定 => 数据观察 => 建立watch及更新渲染

1. 编译template字符串

const render = compile(getOuterHTML(el))

其中compile的实现如下:

export function compile (html) {
 html = html.trim()
 // 对编译结果缓存
 const hit = cache[html]
 // parse函数在parse-html中定义,其作用是把我们获取的html字符串通过正则匹配转化为ast,输出如下