主要理解、实现如下方法:

  • Observe :监听器 监听属性变化并通知订阅者

  • Watch :订阅者 收到属性变化,更新视图

  • Compile :解析器 解析指令,初始化模板,绑定订阅者,绑定事件

  • Dep :存放对应的所有 watcher 实例

主要执行流程

右键点击图片,在新标签页打开,可查看更清晰图片

将watcher装入对应的dep实例的订阅列表中的过程

相关html代码,用于被解析绑定数据

这里的代码即是compile中要解析的模板,解析其中的 v-model {{}} v-on 等命令

<div id="app">
<p> 姓名:<input type="text" v-model="name"></p>
<p>学号:<input type="text" v-model="number"></p>
<p><span>学号:</span> <span>{{ number }}</span></p>
<p><span>computed实现:</span> <span>{{ getStudent }}</span></p> <p>
<button v-on:click="setData">事件绑定</button>
</p>
</div>

observer代码

为data数据添加 get、set 以便添加 watcher,和创建 Dep 实例,通知更新视图

const defineProp = function(obj, key, value) {
observe(value) /*
* 预先为每一个不同属性创建一个独有的dep
*/
const dep = new Dep()
Object.defineProperty(obj, key, {
get: function() {
/*
* 根据不同的属性创建且只在创建Watcher时调用
*/
if(Dep.target) {
dep.targetAddDep()
}
return value
},
set: function(newVal) { if(newVal !== value) {
/*
* 这里的赋值操作,是以便于get 方法中返回value,因为都是赋值后马上就会调用get方法
*/
value = newVal
/*
* 通知监听的属性的所有订阅者
*/
dep.notify()
}
}
})
} const observe = function(obj) {
if(!obj || typeof obj !== 'object') return Object.keys(obj).forEach(function(key) {
defineProp(obj, key, obj[key])
})
}

Dep代码

主要是将 watcher 放入 对应的 dep 订阅列表

let UUID = 0
function Dep() {
this.id = UUID++
// 存放当前属性的所有的监听watcher
this.subs = []
}
Dep.prototype.addSub = function(watcher) {
this.subs.push(watcher)
}
// 目的是将当前 dep 实例 传入 watcher
Dep.prototype.targetAddDep = function() {
// 这里 this 是实例化后的 dep
Dep.target.addDep(this)
}
Dep.prototype.notify = function() {
// 触发当前属性的所有 watcher
this.subs.forEach(_watcher => {
_watcher.update()
})
}
Dep.target = null

Watcher 代码

数据更新后,更新视图

function Watcher(vm, prop, callback) {
this.vm = vm
this.prop = prop
this.callback = callback
this.depId = {}
this.value = this.pushWatcher()
} Watcher.prototype = {
update: function() {
/* 更新值的变化 */
const value = this.vm[this.prop]
const oldValue = this.value
if (value !== oldValue) {
this.value = value
this.callback(value)
}
},
// 目的是接收 dep 实例,用于将当前watcher实例放入 subs
addDep: function(dep) {
if(!this.depId.hasOwnProperty(dep.id)) {
dep.addSub(this)
this.depId[dep.id] = dep.id
} else {
console.log('already exist');
}
},
pushWatcher: function() {
// 存贮订阅器
Dep.target = this
// 触发对象的get监听,将上面赋值给 target 的this 加入到subs
var value = this.vm[this.prop]
// 加入完后就删除
Dep.target = null
return value
}
}

Compile 代码

解析html模板,创建代码片段,绑定数据事件

function Compile(vm) {
this._vm = vm
this._el = vm._el
this.methods = vm._methods
this.fragment = null
this.init()
}
Compile.prototype = {
init: function() {
this.fragment = this.createFragment()
this.compileNode(this.fragment) // 当代码片段中的内容编译完了之后,插入DOM中
this._el.appendChild(this.fragment)
}, // 根据真实的DOM节点,创建文档片段
createFragment: function() {
const fragment = document.createDocumentFragment()
let child = this._el.firstChild
while(child) {
// 将节点加入到文档片段中后,该节点会被从原来的位置删除,相当于移动节点位置
fragment.appendChild(child)
child = this._el.firstChild
} return fragment
}, compileNode: function(fragment) {
let childNodes = fragment.childNodes;
[...childNodes].forEach(node =>{
if(this.isElementNode(node)) {
this.compileElementNode(node)
} let reg = /\{\{(.*)\}\}/
// 获取节点下的所有文本内容
let text = node.textContent // 判断是否已是纯文本节点,且文本内容是否有{{}}
if(this.isTextNode(node) && reg.test(text)) {
let prop = reg.exec(text)[1].trim()
this.compileText(node, prop)
} if(node.childNodes && node.childNodes.length) {
// 递归编译子节点
this.compileNode(node)
}
})
}, compileElementNode: function(element) {
// 获取属性,只有元素节点有如下方法
let nodeAttrs = element.attributes;
[...nodeAttrs].forEach(attr => {
let name = attr.name if(this.isDirective(name)) {
/*
* v-model 放在可接受input事件的标签上
*/
let prop = attr.value
if (name === 'v-model') {
/*
* 获取到的value 即为需要绑定的data
*/
this.compileModel(element, prop)
} else if(this.isEvent(name)) {
/*
* 绑定事件
*/
this.bindEvent(element, name, prop)
}
}
})
}, compileModel: function(element, prop) {
let val = this._vm[prop]
this.updateElementValue(element, val)
new Watcher(this._vm, prop, value => {
this.updateElementValue(element, value)
}) element.addEventListener('input', event => {
let newValue = event.target.value
if (val === newValue) return
this._vm[prop] = newValue
})
}, compileText: function(textNode, prop) {
let text = ''
if(/\./.test(prop)) {
var props = prop.split('.')
text = this._vm[props[0]][props[1]]
} else {
text = this._vm[prop]
} this.updateText(textNode, text) console.log(text); new Watcher(this._vm, prop, (value) => {
this.updateText(textNode, value)
})
}, bindEvent: function(element, name, prop) {
var eventType = name.split(':')[1]
var fn = this._vm._methods[prop]
element.addEventListener(eventType, fn.bind(this._vm))
}, /*
* 判断属性是否为指令
*/
isDirective: function (text) {
return /v-/.test(text)
}, isEvent: function(text) {
return /v-on/.test(text)
}, isElementNode: function(node) {
// 元素节点返回1 文本节点(元素或属性中的文字)3 属性节点2(被废弃)
return node.nodeType === 1
}, isTextNode: function(node) {
return node.nodeType === 3
}, updateElementValue: function(element, value) {
element.value = value || ''
}, updateText: function(textNode, value) {
textNode.textContent = value || ''
}
}

vue 简要构造函数

主要实现了数据的双向绑定,自定义事件,computed

function FakeVue(options) {
this._data = options.data
this._methods = options.methods
this._computed= options.computed
this._el = document.querySelector(options.el) // 将 _data 中的属性代理到外层的vm上,这里只代理了_data的第一层属性
Object.keys(this._data).forEach(key => {
this._proxyData(key)
})
this._initComputed()
this._init()
}
FakeVue.prototype._init = function() {
// 开始递归监听对象的所有属性,直到属性值为值类型
observe(this._data)
new Compile(this)
}
FakeVue.prototype._proxyData = function(key) {
Object.defineProperty(this, key, {
get: function() {
return this._data[key]
},
set: function (value) {
this._data[key] = value
}
})
}
FakeVue.prototype._initComputed = function() {
// 简单的实现: 将值挂载到跟上即可
const computed = this._computed
Object.keys(computed).forEach((v) => {
Object.defineProperty(this, v, {
get: computed[v],
set: function (value) {}
})
})
}

创建vue实例

try{
let vm = new FakeVue({
el: '#app',
data: {
name: 'warren',
number: '10011',
score: {
math: 90
}
}, computed: {
getStudent: function() {
return `${this.name}:学号是 ${this.number}`
}
}, methods:{
// 通过在compile中给元素绑定事件实现
setData: function() {
alert('name:'+this.name);
}
}
});
} catch(error) {
console.error(error)
}

结语

这是从作者的理解角度,阐述的一个简单的vue实现原理示例,希望对正在探索的你有所帮助

在这个示例中,主要的复杂点在于对 html 模板的解析,数据的双向绑定。

建议跟着代码的执行顺序了解整个过程,关键点的代码都有必要的注释,若发现问题请指正。

最后附上 vue 源码地址,主要关注其中的 corecompiler 文件;

欢迎交流 Github

vue 实现原理及简单示例实现的更多相关文章

  1. Optaplanner规划引擎的工作原理及简单示例(2)

    开篇 在前面一篇关于规划引擎Optapalnner的文章里(Optaplanner规划引擎的工作原理及简单示例(1)),老农介绍了应用Optaplanner过程中需要掌握的一些基本概念,这些概念有且于 ...

  2. Optaplanner规划引擎的工作原理及简单示例(1)

    在之前的文章中,老猿已介绍过APS及规划的相关内容,也对Optaplanner相关的概念和一些使用示例进行过介绍,接下来的文章中,我会自己做一个规划小程序 - 一个关于把任务分配到不同的机台上进行作来 ...

  3. RMI原理及简单示例

    分布式对象 在学习 RMI 之前,先来分布式对象(Distributed Object):分布式对象是指一个对象可以被远程系统所调用.对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程序( ...

  4. Ajax -- 原理及简单示例

    1. 什么是Ajax •Ajax被认为是(AsynchronousJavaScript and XML的缩写).现在,允许浏览器与服务器通信而无须刷新当前页面的技术都被叫做Ajax. 2. Ajax ...

  5. Vue+NodeJS+ElementUI 的简单示例

    演示所使用到的工具:谷歌浏览器.HBuilder.cmd命令窗口.nodejs(自带npm). 1.先使用 vue create 命令创建一个项目,等待创建完成. 2.切换到项目中. 3.使用 vue ...

  6. vue mvvm原理与简单实现 -- 上篇

    Object.defineProperty介绍-- let obj = {}; Object.defineProperty(obj,'school',{ configurable : true, // ...

  7. pureMVC简单示例及其原理讲解五(Facade)

    本节将讲述Facade,Proxy.Mediator.Command的统一管家.自定义Facade必须继承Facade,在本示例中自定义Facade名称为ApplicationFacade,这个名称也 ...

  8. pureMVC简单示例及其原理讲解四(Controller层)

    本节将讲述pureMVC示例中的Controller层. Controller层有以下文件组成: AddUserCommand.as DeleteUserCommand.as ModelPrepCom ...

  9. pureMVC简单示例及其原理讲解三(View层)

    本篇说的是View层,即视图层,在本示例中包括两个部分:MXML文件,即可视控件:Mediator. 可视控件 可视控件由UserForm.mxml(图1)和UserList.mxml(图2)两个文件 ...

随机推荐

  1. 2020-07-28:已知sqrt (2)约等于 1.414,要求不用数学库,求sqrt (2)精确到小数点后 10 位。

    福哥答案2020-07-28: 1.二分法.2.手算法.3.牛顿迭代法.基础是泰勒级数展开法.4.泰勒级数法.5.平方根倒数速算法,卡马克反转.基础是牛顿迭代法. golang代码如下: packag ...

  2. C#LeetCode刷题之#746-使用最小花费爬楼梯( Min Cost Climbing Stairs)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4016 访问. 数组的每个索引做为一个阶梯,第 i个阶梯对应着一个 ...

  3. 后端排序时去掉element表格排序的null状态

    经常会遇到远程排序,需要去掉null状态的排序,当设置sortable='custom'时,设置sort-orders为['ascending', 'descending']是不生效的.然后查到了一种 ...

  4. Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?

    花下猫语:在上一篇<Python 为什么能支持任意的真值判断? >文章中,我们分析了 Python 在真值判断时的底层实现,可以看出 Python 在对待布尔值时,采用了比较宽泛的态度.官 ...

  5. 同事不太懂负载均衡,我直接把阿里架构师的这份Nginx笔记甩给他

    Nginx功能强大,架构复杂,学习.维护和开发的门槛较高. 本份笔记深入最新的Nginx源码,详细剖析了模块体系.动态插件.功能框架.进程模型.事件驱动.线程池.TCP/UDP/HTTP 处理等Ngi ...

  6. 图论算法(二)最短路算法:Floyd算法!

    最短路算法(一) 最短路算法有三种形态:Floyd算法,Shortset Path Fast Algorithm(SPFA)算法,Dijkstra算法. 我个人打算分三次把这三个算法介绍完. (毕竟写 ...

  7. ybt1107题解和方法总结

    今天花了三个小时的时间刷了些基础题,虽说是简单题,但是有一些还是有点难度的 比如ybt1107,我死嗑了半个小时. [题目描述] 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米. ...

  8. 微信小程序内置组件web-view的缓存问题探讨

    前言:博客或者论坛上面,还有自习亲身经历,发现微信小程序的webview组件的页面缓存问题相当严重,对开发H5的小童鞋来说应该困扰了不少.很多小童鞋硬是抓破脑袋也没有办法解决这个问题,那我们今天就来探 ...

  9. 基础知识、DOS命令

    一.信息安全 1.信息源认证   https 访问控制   ACL   :不能有非法软件驻留   :不能含有未授权的操作等 2.2017-OWASP-TOP5 注入  :失效的身份认证和回话配置 :跨 ...

  10. golang map 声明,赋值

    参考链接:https://blog.csdn.net/wide288/article/details/84303511 // 先声明map var m1 map[string]string// 再使用 ...