热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JS】如何实现一个极简版Vue(初始化)

Vue.js是通过数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,

Vue.js 是通过数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
《【JS】如何实现一个极简版Vue (初始化)》

第一步 – 实现一个指令解析器(Compile)

初始化页面模版,包括基本的程序入口和DOM结构


<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content=">
<title>Documenttitle>
head>
<body>
<div id="app">
<h1>{
{ msg }}h1>
div>
<script type="module"> import Vue from './vue.js' new Vue({ el: '#app', data: { msg: 'hello world' } }) script>
body>
html>

处理组件配置项,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,而在 Vue 源码中,初始化根组件时会进行选项合并操作,将全局配置合并到根组件的局部配置上

// vue.js
export default class Vue {
constructor (options) {
this.el = options.el
this.$data = options.data
this.$options = options;

new Compile(options.el, this) // 指令解析器
}
}

Compile 主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图
基本思路: 根据el创建文档片段,nodeType分别处理元素和文本节点,然后将解析后的文档片段附加到DOM树

// compile.js
import compileUtils from './compileUtils.js'
export class Compile {
constructor (el, vm) {
this.el = document.querySelector(el)
this.vm = vm
const fragment = this.node2Fragment(this.el)
this.compile(fragment)
this.el.appendChild(fragment)
}
node2Fragment (el) {
const fragment = document.createDocumentFragment() let firstChild
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild)
}
return fragment
}
compile (fragment) {
const childNodes = [...fragment.childNodes]
childNodes.forEach((child) => {
if (this.isElementNode(child)) {
// this.compileElement(child)
} else {
this.compileText(child)
}
// 递归遍历子节点对象
if (child.childNodes && child.childNodes.length) {
this.compile(child)
}
})
}
compileText (node) {
const content = node.textContent

// 匹配 { {}} 模版字符串
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtils.text(node, content, this.vm)
}
}
isElementNode(node) {
return node.nodeType === 1
}
}

工具类根据指令执行对应方法,集中处理DOM的CRUD操作

// compileUtils.js
const compileUtils = {
/* * node 当前元素节点 * expr 当前指令的value * vm 当前Vue实例, * eventName 当前指令事件名称 */
text (node, expr, vm) {
let value;
if (expr.indexOf('{ {') !== -1) {
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], vm)
})
this.upDater.textUpDater(node, value)
}
},
getValue(expr, vm) {
return expr.split('.').reduce((data, currentVal) => {
return data[currentVal]
}, vm.$data)
},
upDater: {
textUpDater(node, value) {
node.textContent = value
}
}
}
export default compileUtils

{ {msg}}已经能出识别出,接下来就可以处理元素节点,例如与之等价的v-text="msg"
基本思路: 获取 node 节点的属性集合,识别自定义指令方法,删除指令属性

export class Compile {
...
compileElement (node) {
const attrs = [...node.attributes]
attrs.forEach((attr) => {
const { name, value } = attr
if (this.isDirective(name)) {
const directive = name.split('-')[1]
// 处理 v-bind:属性 v-on:事件再次分割
const [dirName, eventName] = directive.split(':')
compileUtils[dirName](node, value, this.vm, eventName)
node.removeAttribute('v-' + directive)
}
})
}
isDirective(attrName) {
return attrName.startsWith('v-')
}
}

补充 v-text 判断逻辑,并初始化赋值,v-html 实现逻辑同理

const compileUtils = {
text (node, expr, vm) {
let value;
if (expr.indexOf('{ {') !== -1) {
...
} else {
value = this.getValue(expr, vm)
}
this.upDater.textUpDater(node, value)
}
}

数据响应式,处理自定义方法,并与 $data 对象进行关联

//
new Vue({
...
methods: {
btnClick() {
console.log(this.$data.msg)
}
}
})

工具类新增 on 方法,负责DOM元素的事件绑定

const compileUtils = {
...
on (node, expr, vm, name) {
const handle = vm.$options.methods[expr]
node.addEventListener(name, handle.bind(vm), false)
},
}

代理数据对象, this.msg 映射为 this.$data.msg,通过defineProperty数据劫持实现

export default class Vue {
constructor (options) {
...
this.proxyData(this.$data)
}
proxyData (data) {
for (const key in data) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(val) {
data[key] = val
}
}}
}
}
}

处理 v-on:click@click 的语法糖,有兴趣的小伙伴可以自己实现原生属性的识别和绑定~

export class Compile {
...
compileElement (node) {
if (this.isDirective(name)) {
...
} else if (this.isEventName(name)) {
const directive = name.split('@')[1]
compileUtils['on'](node, value, this.vm, directive)
node.removeAttribute(name)
}
}
isEventName (attrName) {
return attrName.startsWith('@')
}
}

至此,我们已经基本实现了指令解析器的基础功能,包括指令与数据的绑定和事件处理,下一篇会讲解如何通过发布者-订阅者、数据劫持实现双向数据绑定

参考资料

  • Vue高级指南-01 Vue源码解析之手写Vue源码
  • Vue 源码解读 —— Vue 初始化过程
  • 实现最简 vue3 模型

推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
author-avatar
aRuis
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有