前言

今天是个特别的日子 祝各位女神女神节快乐哈 封面我就放一张杀殿的帅照表达我的祝福 哈哈

此篇主要手写 Vue2.0 源码-初始渲染原理

上一篇咱们主要介绍了 Vue 模板编译原理 它是 Vue 生成虚拟 dom 的基础 模板编译最后转化成了 render 函数 之后又如何能生成真实的 dom 节点去替换掉 el 选项配置呢 那么通过此篇的学习就可以知道 Vue 初始渲染的流程 此篇主要包含虚拟 dom 以及真实 dom 的生成

适用人群: 没时间去看官方源码或者看源码看的比较懵而不想去看的同学


正文

1.组件挂载入口

// src/init.jsVue.prototype.$mount = function (el) {const vm = this;const options = vm.$options;el = document.querySelector(el);// 如果不存在render属性if (!options.render) {// 如果存在template属性let template = options.template;if (!template && el) {// 如果不存在render和template 但是存在el属性 直接将模板赋值到el所在的外层html结构(就是el本身 并不是父元素)template = el.outerHTML;}// 最终需要把tempalte模板转化成render函数if (template) {const render = compileToFunctions(template);options.render = render;}}// 将当前组件实例挂载到真实的el节点上面return mountComponent(vm, el);
};
复制代码

接着看$mount 方法 我们主要关注最后一句话 mountComponent 就是组件实例挂载的入口函数 这个方法放在源码的 lifecycle 文件里面 代表了与生命周期相关 因为我们组件初始渲染前后对应有 beforeMount 和 mounted 生命周期钩子

2.组件挂载核心方法 mountComponent

// src/lifecycle.js
export function mountComponent(vm, el) {// 上一步模板编译解析生成了render函数// 下一步就是执行vm._render()方法 调用生成的render函数 生成虚拟dom// 最后使用vm._update()方法把虚拟dom渲染到页面// 真实的el选项赋值给实例的$el属性 为之后虚拟dom产生的新的dom替换老的dom做铺垫vm.$el = el;//   _update和._render方法都是挂载在Vue原型的方法  类似_initvm._update(vm._render());
}
复制代码

新建 lifecycle.js 文件 表示生命周期相关功能 核心导出 mountComponent 函数 主要使用 vm._update(vm._render())方法进行实例挂载

3.render 函数转化成虚拟 dom 核心方法 _render

// src/render.jsimport { createElement, createTextNode } from "./vdom/index";export function renderMixin(Vue) {Vue.prototype._render = function () {const vm = this;// 获取模板编译生成的render方法const { render } = vm.$options;// 生成vnode--虚拟domconst vnode = render.call(vm);return vnode;};// render函数里面有_c _v _s方法需要定义Vue.prototype._c = function (...args) {// 创建虚拟dom元素return createElement(...args);};Vue.prototype._v = function (text) {// 创建虚拟dom文本return createTextNode(text);};Vue.prototype._s = function (val) {// 如果模板里面的是一个对象  需要JSON.stringifyreturn val == null? "": typeof val === "object"? JSON.stringify(val): val;};
}
复制代码

主要在原型定义了_render 方法 然后执行了 render 函数 我们知道模板编译出来的 render 函数核心代码主要 return 了 类似于_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))这样的代码 那么我们还需要定义一下_c _v _s 这些函数才能最终转化成为虚拟 dom

// src/vdom/index.js// 定义Vnode类
export default class Vnode {constructor(tag, data, key, children, text) {this.tag = tag;this.data = data;this.key = key;this.children = children;this.text = text;}
}// 创建元素vnode 等于render函数里面的 h=>h(App)
export function createElement(tag, data = {}, ...children) {let key = data.key;return new Vnode(tag, data, key, children);
}// 创建文本vnode
export function createTextNode(text) {return new Vnode(undefined, undefined, undefined, undefined, text);
}
复制代码

新建 vdom 文件夹 代表虚拟 dom 相关功能 定义 Vnode 类 以及 createElement 和 createTextNode 方法最后都返回 vnode

4.虚拟 dom 转化成真实 dom 核心方法 _update

// src/lifecycle.jsimport { patch } from "./vdom/patch";
export function lifecycleMixin(Vue) {// 把_update挂载在Vue的原型Vue.prototype._update = function (vnode) {const vm = this;// patch是渲染vnode为真实dom核心patch(vm.$el, vnode);};
}
复制代码
// src/vdom/patch.js// patch用来渲染和更新视图 今天只介绍初次渲染的逻辑
export function patch(oldVnode, vnode) {// 判断传入的oldVnode是否是一个真实元素// 这里很关键  初次渲染 传入的vm.$el就是咱们传入的el选项  所以是真实dom// 如果不是初始渲染而是视图更新的时候  vm.$el就被替换成了更新之前的老的虚拟domconst isRealElement = oldVnode.nodeType;if (isRealElement) {// 这里是初次渲染的逻辑const oldElm = oldVnode;const parentElm = oldElm.parentNode;// 将虚拟dom转化成真实dom节点let el = createElm(vnode);// 插入到 老的el节点下一个节点的前面 就相当于插入到老的el节点的后面// 这里不直接使用父元素appendChild是为了不破坏替换的位置parentElm.insertBefore(el, oldElm.nextSibling);// 删除老的el节点parentElm.removeChild(oldVnode);return el;}
}
// 虚拟dom转成真实dom 就是调用原生方法生成dom树
function createElm(vnode) {let { tag, data, key, children, text } = vnode;//   判断虚拟dom 是元素节点还是文本节点if (typeof tag === "string") {//   虚拟dom的el属性指向真实domvnode.el = document.createElement(tag);// 解析虚拟dom属性updateProperties(vnode);// 如果有子节点就递归插入到父节点里面children.forEach((child) => {return vnode.el.appendChild(createElm(child));});} else {//   文本节点vnode.el = document.createTextNode(text);}return vnode.el;
}// 解析vnode的data属性 映射到真实dom上
function updateProperties(vnode) {let newProps = vnode.data || {};let el = vnode.el; //真实节点for (let key in newProps) {// style需要特殊处理下if (key === "style") {for (let styleName in newProps.style) {el.style[styleName] = newProps.style[styleName];}} else if (key === "class") {el.className = newProps.class;} else {// 给这个元素添加属性 值就是对应的值el.setAttribute(key, newProps[key]);}}
}
复制代码

_update 核心方法就是 patch 初始渲染和后续更新都是共用这一个方法 只是传入的第一个参数不同 初始渲染总体思路就是根据虚拟 dom(vnode) 调用原生 js 方法创建真实 dom 节点并替换掉 el 选项的位置

5._render 和_update 原型方法的混入

// src/index.jsimport { initMixin } from "./init.js";
import { lifecycleMixin } from "./lifecycle";
import { renderMixin } from "./render";
// Vue就是一个构造函数 通过new关键字进行实例化
function Vue(options) {// 这里开始进行Vue初始化工作this._init(options);
}
// _init方法是挂载在Vue原型的方法 通过引入文件的方式进行原型挂载需要传入Vue
// 此做法有利于代码分割
initMixin(Vue);// 混入_render
renderMixin(Vue);
// 混入_update
lifecycleMixin(Vue);
export default Vue;
复制代码

最后就是把定义在原型的方法引入到 Vue 主文件入口 这样所有的实例都能共享方法了

6.模板编译的思维导图

本文首发于前端黑洞网,csdn同步跟新

前端进阶-手写Vue2.0源码(三)|技术点评相关推荐

  1. 【手写 Vue2.x 源码】第二十八篇 - diff 算法-问题分析与 patch 优化

    一,前言 首先,对 6 月的更文内容做一下简单回顾: Vue2.x 源码环境的搭建 Vue2.x 初始化流程介绍 对象的单层.深层劫持 数组的单层.深层劫持 数据代理的实现 对象.数组数据变化的观测 ...

  2. 【手写 Vue2.x 源码】第二十二篇 - dep 和 watcher 关联

    一,前言 上篇,主要介绍了 Vue 依赖收集的过程分析: 介绍了 Vue 的响应式特性 介绍了 Vue 的依赖收集过程 介绍了 dep 和 watcher 以及观察者模式: 本篇,Vue 依赖收集的实 ...

  3. 【手写 Vue2.x 源码】第三十一篇 - diff 算法 - 比对优化(下)

    一,前言 上篇,diff 算法-比对优化(上),主要涉及以下几个点: 介绍了如何对儿子节点进行比对: 新老儿子节点可能存在的 3 种情况及代码实现: 新老节点都有儿子时,diff 的方案介绍与处理逻辑 ...

  4. 【手写 Vue2.x 源码】第十八篇 - 根据 render 函数,生成 vnode

    一,前言 上篇,介绍了 render 函数的生成,主要涉及以下两点: 使用 with 对生成的 code 进行一次包装 将包装后的完整 code 字符串,通过 new Function 输出为 ren ...

  5. 【手写 Vue2.x 源码】第十九篇 - 根据 vnode 创建真实节点

    一,前言 上篇,根据 render 函数,生成 vnode,主要涉及以下几点: 封装 vm._render 返回虚拟节点 _s,_v,_c的实现 本篇,根据 vnode 虚拟节点渲染真实节点 二,根据 ...

  6. Vue2.0源码解析——编译原理

    Vue2.0源码解析--编译原理 前言:本篇文章主要对Vue2.0源码的编译原理进行一个粗浅的分析,其中涉及到正则.高阶函数等知识点,对js的考察是非常的深的,因此我们来好好啃一下这个编译原理的部分. ...

  7. 【卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10)】

    卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10) 在上一章已经完成了卷积神经网络的结构分析,并通过各个模块理解 ...

  8. android米聊手写和涂鸦源码,Android访米聊手写和涂鸦源码

    Android访米聊手写和涂鸦源码 \请下载源代码,只上传Android访米聊手写和涂鸦源码源程序列表内容,如果需要此程序,请点击-下载,下载需要资料源代码. Android访米聊手写和涂鸦源码.ra ...

  9. Vue2.0源码解析 - 知其然知其所以然之Vue.use

    前言 小伙伴们大家好.用过Vue的小伙伴都知道,在我们进行Vue开发时,避免不了会使用一些第三方的库,比如说ElementUI组件库.当我们导入好这些组件库后会执行一个Vue.use函数,然后把导进来 ...

最新文章

  1. 2022-2028年中国UTM市场投资分析及前景预测报告
  2. mysql 113_Centos 下 can't connect mysql server 113
  3. 通俗易懂的生产环境Web应用架构介绍
  4. 面试官:有没有比读写锁更快的锁?
  5. 2017.8.22坐标问题
  6. ESRI产品框架和PostgreSQL对比及GIS技术前景
  7. [转] 各大著名公司音效小评(DNSe/BBE/SRS/PlayFX/杜比/Full Sound…)
  8. k8s-身份认证与权限
  9. [XMAN2018排位赛]AutoKey
  10. html刮刮乐百分比,jQuery+html5实现彩票刮刮乐效果
  11. MES系统是什么?如何搭建一个科学高效的MES报表系统?
  12. 交通一卡通二维码支付技术要求
  13. php如修改登陆后连接地址,两种wordpress更换后台登录界面logo图标方法
  14. 【小程序】使用font-awesome字体图标的解决方案(图文)
  15. Word:论文排版专题——多级列表与插入题注
  16. [问题解决] socket 10053
  17. 【js特效】图片循环滚动代码
  18. linux下怎么运行2048,如何在 Linux 中安装 2048 游戏
  19. 小程序上传音频失败_微信小程序实现录音后上传文件方法详细
  20. 还不知道 YUV 是什么?科普一下

热门文章

  1. Windows Mobile 5.0
  2. 序列化(串行化)- 使用BinaryFormatter进行序列化
  3. 如何使用Windows OVERLAPPED优化你的应用
  4. KafkaController 分区Rebalance平衡机制
  5. (59)FPGA面试题-什么是有限的状态机?有限状态机可分为几类,分别是什么?
  6. linux界面更改用户名,linux跳过用户名密码登陆界面方法
  7. 水下自动循迹机器人_水下避碰!国内首艘自主航行系统实验船试水
  8. Particle Filter Tutorial 粒子滤波:从推导到应用(三)
  9. IO多路复用(select,poll,epoll)详解
  10. oracle 数据库bak文件怎么打开,Oracle数据库的参数文件备份与恢复