一、真实DOM渲染

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div><h2>哈哈哈</h2><h2>呵呵呵</h2><div>发发的说法</div><ul><li></li><li></li><li></li></ul></div></body>
</html>

二、vue的渲染器实现

  • html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"></div><script src="./renderer.js"></script><script>const increment = () => {console.log("+1");};const dcrement = () => {console.log("-1");};//1.通过h函数来创建一个vnodeconst vnode = h("div", { class: "title" }, [h("h2", null, "当前计数:0"),h("button", { onclick: increment }, "+1"),]);//2.通过mount函数,将vnode挂载到div#app上mount(vnode, document.querySelector("#app"));//3.创建新的vnodesetTimeout(() => {const vnode1 = h("div", { class: "why" }, [h("h2", null, "当前计数:222"),h("button", { onclick: dcrement }, "+222"),]);patch(vnode, vnode1);}, 2000);</script></body>
</html>
  • render.js
const h = (tag, props, children) => {//vnode -> javascript对象 -> {}return {tag,props,children,};
};const mount = (vnode, container) => {//vnode -> element//1.创建出真实元素,并在vnode上保留elconst el = (vnode.el = document.createElement(vnode.tag));//2.处理propsif (vnode.props) {for (const key in vnode.props) {const value = vnode.props[key];if (key.startsWith("on")) {//对事件监听的判断el.addEventListener(key.slice(2).toLowerCase(), value);} else {el.setAttribute(key, value);}}}//3.处理childrenif (vnode.children) {if (typeof vnode.children === "string") {el.textContent = vnode.children;} else {vnode.children.forEach((item) => {mount(item, el);});}}//4.将el挂载到container上container.appendChild(el);
};const patch = (n1, n2) => {if (n1.tag !== n2.tag) {const n1ElParent = n1.el.parentElement;n1ElParent.removeChild(n1.el);mount(n2, n1ElParent);} else {//1.取出element对象,并且在n2中进行保存const el = (n2.el = n1.el);//2.处理propsconst oldProps = n1.props || {};const newProps = n2.props || {};//2.1.获取所有的newProps添加到elfor (const key in newProps) {const oldValue = oldProps[key];const newValue = newProps[key];if (newValue !== oldValue) {if (key.startsWith("on")) {//对事件监听的判断el.addEventListener(key.slice(2).toLowerCase(), newValue);} else {el.setAttribute(key, newValue);}}}//2.2.删除旧的propsfor (const key in oldProps) {if (key.startsWith("on")) {const value = oldProps[key];el.removeEventListener(key.slice(2).toLowerCase(), value);}if (!(key in newProps)) {el.removeAttribute(key);}}//3.处理childrenconst oldChildren = n1.children || [];const newChildren = n2.children || [];if (typeof newChildren === "string") {//情况一:newChildren本身是一个 string//边界情况 (edge case)if (typeof oldChildren === "string") {if (newChildren !== oldChildren) {el.textContent = newChildren;}} else {el.innerHTML = newChildren;}} else {//情况二:newChildren本身是一个数组if (typeof oldChildren === "string") {el.innerHTML = "";newChildren.forEach((item) => {mount(item, el);});} else {//oldChildren [v1,v2,v3]//newChildren [v1,v5,v6,v8,v9]//1.前面有相同节点的元素进行patch操作const commonLength = Math.min(oldChildren.length, newChildren.length);for (let i = 0; i < commonLength; i++) {patch(oldChildren[i], newChildren[i]);}//2.newChildren.length > oldChildren.lengthif (newChildren.length > oldChildren.length) {newChildren.slice(oldChildren.length).forEach((item) => {mount(item, el);});}//3.newChildren.length < oldChildren.lengthif (newChildren.length < oldChildren.length) {oldChildren.slice(newChildren.length).forEach((item) => {el.removeChild(item.el);});}}}}
};

三、响应式系统实现

vue2的响应式

class Dep {constructor() {this.subscribers = new Set();}addEffect(effect) {this.subscribers.add(effect);}notify() {this.subscribers.forEach((effect) => {effect();});}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}
}let activeEffect = null;
function watchEffect(effect) {activeEffect = effect;effect();activeEffect = null;
}//Map({key:value}):key是一个字符串
//weakMap({key(对象):value})  key是一个对象 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {//1.根据(target)对象取出对应的Map对象let depsMap = targetMap.get(target);if (!depsMap) {depsMap = new Map();targetMap.set(target, depsMap);}//2.取出具体的dep对象let dep = depsMap.get(key);if (!dep) {dep = new Dep();depsMap.set(key, dep);}return dep;
}// vue2对raw进行数据劫持
function reactive(raw) {Object.keys(raw).forEach((key) => {const dep = getDep(raw, key);let value = raw[key];Object.defineProperty(raw, key, {get() {dep.depend();return value;},set(newValue) {if (value !== newValue) {value = newValue;dep.notify();}},});});return raw;
}//测试代码
const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.88 });info.name = "125454";
watchEffect(function () {console.log("effect1:", info.counter * 2, info.name);
});watchEffect(function () {console.log("effect2:", info.counter * info.counter);
});
watchEffect(function () {console.log("effect3:", info.counter + 10, info.name);
});watchEffect(function () {console.log("effect4:", foo.height);
});
info.counter++;
//info.name = "why";
//foo.height = 2;

vue3的响应式系统实现

class Dep {constructor() {this.subscribers = new Set();}addEffect(effect) {this.subscribers.add(effect);}notify() {this.subscribers.forEach((effect) => {effect();});}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}
}let activeEffect = null;
function watchEffect(effect) {activeEffect = effect;effect();activeEffect = null;
}//Map({key:value}):key是一个字符串
//weakMap({key(对象):value})  key是一个对象 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {//1.根据(target)对象取出对应的Map对象let depsMap = targetMap.get(target);if (!depsMap) {depsMap = new Map();targetMap.set(target, depsMap);}//2.取出具体的dep对象let dep = depsMap.get(key);if (!dep) {dep = new Dep();depsMap.set(key, dep);}return dep;
}// vue3对raw进行数据劫持
function reactive(raw) {return new Proxy(raw, {get(target, key) {const dep = getDep(target, key);dep.depend();return target[key];},set(target, key, newValue) {const dep = getDep(target, key);target[key] = newValue;dep.notify();},});
}//测试代码
const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.88 });info.name = "125454";
watchEffect(function () {console.log("effect1:", info.counter * 2, info.name);
});watchEffect(function () {console.log("effect2:", info.counter * info.counter);
});
watchEffect(function () {console.log("effect3:", info.counter + 10, info.name);
});watchEffect(function () {console.log("effect4:", foo.height);
});
//info.counter++;
info.name = "why";
//foo.height = 2;

完整的miniVue

目录结构

  • index.js
function createApp(rootComponent) {return {mount(selecter) {const container = document.querySelector(selecter);let isMounted = false;let oldVnode = null;watchEffect(function () {if (!isMounted) {oldVnode = rootComponent.render();mount(oldVnode, container);isMounted = true;} else {const newVnode = rootComponent.render();patch(oldVnode, newVnode);oldVnode = newVnode;}});},};
}
  • index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"></div><script src="../02_渲染器实现/renderer.js"></script><script src="../03_响应式系统/04_响应式vue3实现.js"></script><script src="./index.js"></script><script>//1.创建根组件const App = {data: reactive({counter: 0,}),render() {return h("div", null, [h("h2", null, `当前计数:${this.data.counter}`),h("button",{onClick: ()=> {this.data.counter++;},},"+1"),]);},};//2.挂载根组件const app = createApp(App);app.mount("#app");</script></body>
</html>
  • mini-vue下 index.js
function createApp(rootComponent) {return {mount(selector) {const container = document.querySelector(selector);let isMounted = false;let oldVNode = null;watchEffect(function() {if (!isMounted) {oldVNode = rootComponent.render();mount(oldVNode, container);isMounted = true;} else {const newVNode = rootComponent.render();patch(oldVNode, newVNode);oldVNode = newVNode;}})}}
}
  • mini-vue下reactive.js
class Dep {constructor() {this.subscribers = new Set();}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}notify() {this.subscribers.forEach(effect => {effect();})}
}let activeEffect = null;
function watchEffect(effect) {activeEffect = effect;effect();activeEffect = null;
}// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {// 1.根据对象(target)取出对应的Map对象let depsMap = targetMap.get(target);if (!depsMap) {depsMap = new Map();targetMap.set(target, depsMap);}// 2.取出具体的dep对象let dep = depsMap.get(key);if (!dep) {dep = new Dep();depsMap.set(key, dep);}return dep;
}// vue3对raw进行数据劫持
function reactive(raw) {return new Proxy(raw, {get(target, key) {const dep = getDep(target, key);dep.depend();return target[key];},set(target, key, newValue) {const dep = getDep(target, key);target[key] = newValue;dep.notify();}})
}// const proxy = reactive({name: "123"})
// proxy.name = "321";// // 测试代码
// const info = reactive({counter: 100, name: "why"});
// const foo = reactive({height: 1.88});// // watchEffect1
// watchEffect(function () {//   console.log("effect1:", info.counter * 2, info.name);
// })// // watchEffect2
// watchEffect(function () {//   console.log("effect2:", info.counter * info.counter);
// })// // watchEffect3
// watchEffect(function () {//   console.log("effect3:", info.counter + 10, info.name);
// })// watchEffect(function () {//   console.log("effect4:", foo.height);
// })// info.counter++;
// info.name = "why";// foo.height = 2;
  • mini-vue下render.js
const h = (tag, props, children) => {// vnode -> javascript对象 -> {}return {tag,props,children}
}const mount = (vnode, container) => {// vnode -> element// 1.创建出真实的原生, 并且在vnode上保留elconst el = vnode.el = document.createElement(vnode.tag);// 2.处理propsif (vnode.props) {for (const key in vnode.props) {const value = vnode.props[key];if (key.startsWith("on")) { // 对事件监听的判断el.addEventListener(key.slice(2).toLowerCase(), value)} else {el.setAttribute(key, value);}}}// 3.处理childrenif (vnode.children) {if (typeof vnode.children === "string") {el.textContent = vnode.children;} else {vnode.children.forEach(item => {mount(item, el);})}}// 4.将el挂载到container上container.appendChild(el);
}const patch = (n1, n2) => {if (n1.tag !== n2.tag) {const n1ElParent = n1.el.parentElement;n1ElParent.removeChild(n1.el);mount(n2, n1ElParent);} else {// 1.取出element对象, 并且在n2中进行保存const el = n2.el = n1.el;// 2.处理propsconst oldProps = n1.props || {};const newProps = n2.props || {};// 2.1.获取所有的newProps添加到elfor (const key in newProps) {const oldValue = oldProps[key];const newValue = newProps[key];if (newValue !== oldValue) {if (key.startsWith("on")) { // 对事件监听的判断el.addEventListener(key.slice(2).toLowerCase(), newValue)} else {el.setAttribute(key, newValue);}}}// 2.2.删除旧的propsfor (const key in oldProps) {if (key.startsWith("on")) { // 对事件监听的判断const value = oldProps[key];el.removeEventListener(key.slice(2).toLowerCase(), value)} if (!(key in newProps)) {el.removeAttribute(key);}}// 3.处理childrenconst oldChildren = n1.children || [];const newChidlren = n2.children || [];if (typeof newChidlren === "string") { // 情况一: newChildren本身是一个string// 边界情况 (edge case)if (typeof oldChildren === "string") {if (newChidlren !== oldChildren) {el.textContent = newChidlren}} else {el.innerHTML = newChidlren;}} else { // 情况二: newChildren本身是一个数组if (typeof oldChildren === "string") {el.innerHTML = "";newChidlren.forEach(item => {mount(item, el);})} else {// oldChildren: [v1, v2, v3, v8, v9]// newChildren: [v1, v5, v6]// 1.前面有相同节点的原生进行patch操作const commonLength = Math.min(oldChildren.length, newChidlren.length);for (let i = 0; i < commonLength; i++) {patch(oldChildren[i], newChidlren[i]);}// 2.newChildren.length > oldChildren.lengthif (newChidlren.length > oldChildren.length) {newChidlren.slice(oldChildren.length).forEach(item => {mount(item, el);})}// 3.newChildren.length < oldChildren.lengthif (newChidlren.length < oldChildren.length) {oldChildren.slice(newChidlren.length).forEach(item => {el.removeChild(item.el);})}}}}
}

vue源码解析-实现相关推荐

  1. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  2. [Vue源码解析] patching算法

    [Vue源码解析] patching算法 pathching算法:通过对比新旧VNode的不同,然后找出需要更新的节点进行更新 操作:1.创建新增节点 2.删除废弃节点 3.修改需要更新的节点 创建节 ...

  3. Vue源码解析(尚硅谷)

    视频地址:Vue源码解析系列课程 一.Vue源码解析之mustache模板引擎 1. 什么是模板引擎 模板引擎是将数据要变为视图最优雅的解决方案 历史上曾经出现的数据变为视图的方法 2. mustac ...

  4. Vue源码解析(一)

    前言:接触vue已经有一段时间了,前面也写了几篇关于vue全家桶的内容,感兴趣的小伙伴可以去看看,刚接触的时候就想去膜拜一下源码~可每次鼓起勇气去看vue源码的时候,当看到几万行代码的时候就直接望而却 ...

  5. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  6. Vue源码解析:虚拟dom比较原理

    通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写: 模版字符串转AST语法树 AST语法树转render函数 Vue双向绑定原理 V ...

  7. Vue源码解析之Template转化为AST的实现方法

    什么是AST 在Vue的mount过程中,template会被编译成AST语法树,AST是指抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree) ...

  8. Vue源码解析之函数入口

    从入口开始看起 写博客就是记录自己撸码的过程和问题,好了~废话就不多说了,直接源码撸起,通过上一篇博客咱们大致知道了Vue源码目录设计,下面我们要一步步找到vue的入口 通过查看package.jso ...

  9. Vue源码解析(笔记)

    github vue源码分析 认识flow flow类型检查 安装flow sudo npm install -g flow-bin 初始化flow flow init 运行flow命令 命令: fl ...

  10. Vue源码解析-$mount

    前言 Vue实例是通过mount方法进行挂载的,上一篇说了new Vue这篇分析一下mount挂载方法.mount的定义在Vue源码中有多处,这是因为Vue需要适配不同的平台以及构建方式,本文这里主要 ...

最新文章

  1. 虚拟DOM和Diff算法 - 入门级
  2. *2-3-7-加入field_automation机制
  3. Shiro之从数据库初始化角色权限信息
  4. 160 - 26 Colormaster
  5. 来及Java空间的传送门2
  6. 无法获取保存在session中的验证码
  7. 想开发微信小游戏,先看看腾讯是如何制定规则的
  8. mybatis一简单one2one关系xml配置
  9. lock concurrence
  10. 《道德经》里的世界观(一种解读,仅供参考)
  11. 接口快速开发框架 magic-api 2.x初体验
  12. 产品研发中存在的问题和缺陷
  13. Facebook加好友被禁止,请问什么时候被解禁
  14. 刺激战场 枪支性能雷达图分析
  15. 关于 nscd,nslcd 和 sssd 套件的综述
  16. xmind可以画流程图吗_怎样用XMind方便地制作流程图
  17. 色温,色阶,色调,色调
  18. 浅谈前端SPA(单页面应用)
  19. 【STM32CubeMX学习】I2C读写24C02
  20. Excel函数 - 多条件统计

热门文章

  1. 10个不错的编程等宽字体
  2. matlab有限元分析杆单元,有限元实验1-杆单元有限元分析
  3. Android - 浅谈 Handler 机制
  4. 神奇宝贝服务器服务器修改器,pkhex修改器最新版
  5. Meshlab模型对齐
  6. 正规矩阵 酉矩阵 对角矩阵
  7. 终于有人把分布式系统架构讲明白了
  8. imp命令导入dmp文件问题
  9. 科比数据集分析与预测
  10. 谈谈核心网UPF和开放