IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> vue源码系列4之虚拟dom和真实dom -> 正文阅读

[JavaScript知识库]vue源码系列4之虚拟dom和真实dom

vue源码系列4之虚拟dom和真实dom

  • 流程:
// 判断用户是否 传入了render , 没传入时,可能传入了template。template没有传入时候,就拿最外面的 <div id="#app"></div>作为模板
// 将我们的html -> 词法解析 (开始标签 , 结束标签,属性,文本)
// ast语法树 用来描述html语法的 stack = []
// codegen <div>hello</div> -> _c('div',{},'hello') -> 让字符串解析  _c创建元素
// 方式:
// 使用方式 01:模板引擎 new Function + with 来实现 -> render函数 
	  // + 02:字符串解析转化为代码后 eval 好性能 会有作用域问题 -> 不使用
// 之后生成虚拟dom

compiler / index.js

  • 经过编译之后,生成代码 compileToFunction 函数
  • 在code之中,需要使用到数据,数据又存放在vm实例对象上,因此使用with方法改变作用域 (类似于Vue之中的data数据,直接可以去data的数据)
    let code = generate(root);
      // console.log("code", code); 
      //code _c('div',{ id:"app",a:"1",style:{"color":"red","background":"pink"} },_v('_s(name)'),_v('"22heoolo"+_s(arr)'),_v('12'))
      // code 之中会用到 数据在 vm 上 -> 因此需要在vm上取数据
    
    • 模板引擎 new Function + with 使用模板引擎的转化为 render函数
import {
  generate
} from "./genrate.js";
import {
  parserHTML
} from "./parser.js";
// 模板的编译 渲染函数
export function compileToFunction(template) {
  let root = parserHTML(template);
  // html -> ast -> render函数 -> 虚拟dom (增加额外属性)

  // 生成代码
  let code = generate(root);
  let render = new Function(`with(this){return ${code}}`)
  return render;
  // 就是传入当前vue实例对象 vm因此在取数的时候 直接是 vm.name等形式 或者 name
  // console.log("render", render.toString());
  // render function anonymous(
  //   ) {
  //   with(this){return _c('div',{ id:"app",a:"1",style:{"color":"red","background":"pink"} },_v('_s(name)'),_v('"22heoolo"+_s(arr)'),_v('12'))}
  //   }
}

init.js

  • 在初始化函数之中,调用compileToFunction(template)函数,把模板变成渲染函数,返回渲染函数
  • 之后的话,就是调用render函数渲染为真实dom,替换掉页面的内容!
  • 替换掉页面的前提:需要借助组件的挂载
    • 并且挂载在Vue实例对象身上vm.$options,为之后访问vm实例能获取到 render函数
    • 有了renden函数之后,就是考虑渲染真实dom了,替换掉页面的内容
    • 就是通过mountComponent(vm, el);把组件的实例对象vm挂载在el身上;
import {
  compileToFunction
} from "./compiler/index.js";
import { mountComponent } from "./lifecycle.js";
import {
  initSate
} from "./state";

// 表示在vue的基础上 做一次混合操作
export function initMixin(Vue) {

  Vue.prototype._init = function (options) {
    // console.log("initMixin options", options);
    const vm = this;
    vm.$options = options; //options传入的Vue类的对象数据 后续对 options进行扩展操作

    // 对数据状态的 初始化 watch computed props等
    initSate(vm); // 也就是vm.$optiops.data的数据劫持

    if (vm.$options.el) {
      // 判断这个vm实例是否挂载模板 有模板时,把data数据挂载在改模板上
      // console.log("vm.$options.el",vm.$options.el);// #app
      vm.$mount(vm.$options.el);
    }
  }
  // 挂载模板 使得数据与模板进行挂钩
  Vue.prototype.$mount = function (el) {
    let vm = this;
    let options = vm.$options;
    el = document.querySelector(el);
    // console.log("el", el); // <div id="app"> {{ name }}</div>
    // 把模板转化为 对应的渲染函数 -> 演化出虚拟dom -> 最终使用diff算法 更新虚拟dom -> 产生真实dom
    // console.log("render", vm.$options.render);
    if (!options.render) { // 当传入Vue类中的options没有render函数时,就使用template模板
      let template = options.template;
      if (!template && el) { // 没有template模板,并且有el挂载dom节点,因此使用el作为模板
        template = el.outerHTML;
        // console.log("template",template);//<div id="app"> {{ name }}</div>
        let render = compileToFunction(template); // 把模板变成渲染函数
        options.render = render; //因此 vm实例对象内 有了渲染函数
      }
    }
    // 调用render方法 渲染为真实dom 替换掉页面的内容
    // console.log("options.render", options.render);
    // 组件的挂载 把组件实例vm 挂载到 el上
    mountComponent(vm, el);
  }
}

lifecycle.js

  • 关于组件的挂载,这时候就需要updataComponent函数了
  • 组件挂载的时候,先调用一次此函数 updataComponent
  • 数据一发生变化就调用此函数 updataComponent
  • 其中updataComponent函数的两大作用
    • 01:调用render函数,生成虚拟dom
    • 02:用虚拟dom 生成真实dom
      vm._updata(vm._render())
      • 也就是vm实例上原型的 更新虚拟dom方法,用于更新render渲染函数
  • 其中原型之中的Vue.prototype._updata函数,则目的:为了根据虚拟dom创建真实dom
  • 其根本需要在index.js之中传入Vue实例对象
  • 之后的话,就拿到虚拟dom,通过patch方法 把虚拟dom转化为真实dom
  • 注意点:就是vm._render()函数定义于 render.js之内,创建的一个render渲染函数,并且挂载于vm实例身上
import { patch } from "./vnode/patch";

// 生命周期
export function leftcycleMixin(Vue) {
  Vue.prototype._updata = function (vnode) {
    console.log("updata 拿到虚拟dom", vnode);
    // 既有初始化,又有更新
    const vm = this;
    // console.log("vm.$el", vm.$el, vnode);
    patch(vm.$el, vnode);
    // 根据虚拟dom创建真实dom
  }
}

export function mountComponent(vm, el) {

  // 跟新函数 数据一发生变化就调用此函数
  let updataComponent = () => {
    // 调用render函数,生成虚拟dom
    vm._updata(vm._render()); // 后续更新 可以调用updataComponent函数
    // 用虚拟dom 生成真实dom
  }
  updataComponent();
}

index.js

import {
  initMixin
} from "./init";
import {
  leftcycleMixin
} from "./lifecycle";
import { renderMixin } from "./render";
// Vue 类 -> options 为用户传入的选项
function Vue(options) {
  this._init(options); //初始化操作
}

// 扩展原型的
initMixin(Vue)
renderMixin(Vue); // render函数 _render
leftcycleMixin(Vue); // _updata
export default Vue;

render.js

  • 创建的render渲染函数,并且挂载在vm实例身上
  • 其中vm.$options.render就是我们解析出来的render方法,同时也有可能是用户书写的!
  • 针对render函数之中的创建 标签 文本 等虚拟节点,特此在Vue原型内定义了 _c 、_v 、_s等原型方法,皆可用于之后的创建虚拟dom!
import {
  createElement,
  createTextElement
} from "./vnode/index.js";

export function renderMixin(Vue) {
  // 产生虚拟节点 标签
  Vue.prototype._c = function (tag, data, ...children) { // createElement
    // console.log("tag,data,...children", tag, data, ...children);
    return createElement(this, ...arguments)
  }
  // 产生虚拟节点 文本节点
  Vue.prototype._v = function (text) { // createTextElement
    // console.log("text", text);
    return createTextElement(this, text);
  }
  // 对对象进行 转化为字符串形式存储,否则会出息 [object,object]现象
  VisualViewport.prototype._s = function (val) { // JSon.stringify
    // console.log("val", val);
    if (typeof val == "object") return JSON.stringify(val);
    return val;
  }

  Vue.prototype._render = function () {
    // console.log("render");
    const vm = this;
    const render = vm.$options.render;

    let vnode = render.call(vm);
    return vnode;
  }
}

vnode / index.js

  • 创建虚拟dom
    • 有创建标签
    • 有创建文本
// vnode 虚拟dom操作
// 创建标签
export function createElement(vm, tag, data = {}, ...children) {
  // console.log("createElement", vm, tag, data = {}, ...children);
  return vnode(vm, tag, data, data.key, children, undefined)
}

// 创建文本节点
export function createTextElement(vm, text) {
  // console.log("createTextElement", vm, text);
  return vnode(vm, undefined, undefined, undefined, undefined, text)
}

// 虚拟dom
function vnode(vm, tag, data, key, children, text) {
  return {
    vm,
    tag,
    data,
    key,
    children,
    text
  }
}

vnode / patch.js

  • 比较前后dom的差异,监听数据的变化,进行重写渲染dom节点(初始化的时候,也会渲染dom节点)
  • patch函数把虚拟dom转化为真实dom
    • patch(vm.$el, vnode); 在挂载的时候 -> liftcycle.js生命周期.js文件内,就是挂载时候,传入当前挂载的模板el,还有虚拟节点;
  • 作用:就是把旧节点,替换为新的节点
  • 根据传入的旧节点 与 虚拟节点 进行逻辑处理
    • 首先先寻找到当前旧节点的父元素,然后移除掉当前当前父元素下的所有节点(标签、文本、空格等)
    • 之后,通过动态创建标签的形式,以虚拟dom上的节点,区分不同的元素,标签、文本等来创建出对应的标签、还有子元素、还有文本节点等
    • 紧接着,就是插入到当前旧节点之前
    • 最后删除掉旧节点,也就实现 旧 -> 新 (根据虚拟dom来渲染出真实的dom节点)
export function patch(oldVnode, vnode) {
  if (oldVnode.nodeType == 1) {
    // 用虚拟dom 来生成真实的dom 替换掉原本的dom元素
    const parentElm = oldVnode.parentNode; // 找到 oldVnode 的父元素
    let elm = createElm(vnode); // 根据虚拟节点创建元素
    parentElm.insertBefore(elm, oldVnode.nextSibling); // 插入到旧的元素前面
    parentElm.removeChild(oldVnode) // 然后删除掉 当前oldVnode 的子元素
  }
}

function createElm(vnode) {
  let {
    tag,
    data,
    children,
    text,
    vm
  } = vnode;
  if (typeof vnode.tag === "string") { // 元素
    vnode.el = document.createElement(tag); //虚拟节点会有一个el属性 对应的真实的dom -> 父节点
    // 创建对应的子节点
    children.forEach(child => {
      vnode.el.appendChild(createElm(child))
    });
  } else { // 文本节点
    vnode.el = document.createTextNode(text)
  }
  return vnode.el;
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:32:40  更:2021-11-19 17:33:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 19:09:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码