基于JavaScript实现一个简单的Vue


Posted in Javascript onSeptember 26, 2018

Object.defineProperty()

实现之前我们得先看一下Object.defineProperty的实现,因为vue主要是通过数据劫持来实现的,通过get、set来完成数据的读取和更新。

var obj = {name:'wclimb'}
var age = 24
Object.defineProperty(obj,'age',{
enumerable: true, // 可枚举
configurable: false, // 不能再define
get () {
return age
},
set (newVal) {
console.log('我改变了',age +' -> '+newVal);
age = newVal
}
})
> obj.age
> 24
> obj.age = 25;
> 我改变了 24 -> 25
> 25

从上面可以看到通过get获取数据,通过set监听到数据变化执行相应操作,还是不明白的话可以去看看Object.defineProperty文档。

流程图

基于JavaScript实现一个简单的Vue 

html代码结构

<div id="wrap"> 
 <p v-html="test"></p>
 <input type="text" v-model="form">
 <input type="text" v-model="form">
 <button @click="changeValue">改变值</button>
 {{form}}
</div>

Vue结构

class Vue{
constructor(){}
proxyData(){}
observer(){}
compile(){}
compileText(){}
}
class Watcher{
constructor(){}
update(){}
}

Vue constructor 构造函数主要是数据的初始化

proxyData 数据代理

observer 劫持监听所有数据

compile 解析dom

compileText 解析dom里处理纯双花括号的操作

Watcher 更新视图操作

Vue constructor 初始化

class Vue{
constructor(options = {}){
this.$el = document.querySelector(options.el);
let data = this.data = options.data;
// 代理data,使其能直接this.xxx的方式访问data,正常的话需要this.data.xxx
Object.keys(data).forEach((key)=> {
this.proxyData(key);
});
this.methods = obj.methods // 事件方法
this.watcherTask = {}; // 需要监听的任务列表
this.observer(data); // 初始化劫持监听所有数据
this.compile(this.$el); // 解析dom
}
}

上面主要是初始化操作,针对传过来的数据进行处理

proxyData 代理data

class Vue{
constructor(options = {}){
......
}
proxyData(key){
let that = this;
Object.defineProperty(that, key, {
configurable: false,
enumerable: true,
get () {
return that.data[key];
},
set (newVal) {
that.data[key] = newVal;
}
});
}
}

上面主要是代理data到最上层,this.xxx的方式直接访问data

observer 劫持监听

class Vue{
constructor(options = {}){
......
}
proxyData(key){
......
}
observer(data){
let that = this
Object.keys(data).forEach(key=>{
let value = data[key]
this.watcherTask[key] = []
Object.defineProperty(data,key,{
configurable: false,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue !== value){
value = newValue
that.watcherTask[key].forEach(task => {
task.update()
})
}
}
})
})
}
}

同样是使用Object.defineProperty来监听数据,初始化需要订阅的数据。

把需要订阅的数据到push到watcherTask里,等到时候需要更新的时候就可以批量更新数据了。??下面就是;

遍历订阅池,批量更新视图。

set(newValue){
if(newValue !== value){
value = newValue
// 批量更新视图
that.watcherTask[key].forEach(task => {
task.update()
})
}
}

compile 解析dom

class Vue{
constructor(options = {}){
......
}
proxyData(key){
......
}
observer(data){
......
}
compile(el){
var nodes = el.childNodes;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if(node.nodeType === 3){
var text = node.textContent.trim();
if (!text) continue;
this.compileText(node,'textContent')
}else if(node.nodeType === 1){
if(node.childNodes.length > 0){
this.compile(node)
}
if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
node.addEventListener('input',(()=>{
let attrVal = node.getAttribute('v-model')
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'value'))
node.removeAttribute('v-model')
return () => {
this.data[attrVal] = node.value
}
})())
}
if(node.hasAttribute('v-html')){
let attrVal = node.getAttribute('v-html');
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
node.removeAttribute('v-html')
}
this.compileText(node,'innerHTML')
if(node.hasAttribute('@click')){
let attrVal = node.getAttribute('@click')
node.removeAttribute('@click')
node.addEventListener('click',e => {
this.methods[attrVal] && this.methods[attrVal].bind(this)()
})
}
}
}
},
compileText(node,type){
let reg = /{{(.*)}}/g, txt = node.textContent;
if(reg.test(txt)){
node.textContent = txt.replace(reg,(matched,value)=>{
let tpl = this.watcherTask[value] || []
tpl.push(new Watcher(node,this,value,type))
return value.split('.').reduce((val, key) => {
return this.data[key];
}, this.$el);
})
}
}
}

这里代码比较多,我们拆分看你就会觉得很简单了

首先我们先遍历el元素下面的所有子节点,node.nodeType === 3 的意思是当前元素是文本节点,node.nodeType === 1 的意思是当前元素是元素节点。因为可能有的是纯文本的形式,如纯双花括号就是纯文本的文本节点,然后通过判断元素节点是否还存在子节点,如果有的话就递归调用compile方法。下面重头戏来了,我们拆开看:

if(node.hasAttribute('v-html')){
let attrVal = node.getAttribute('v-html');
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
node.removeAttribute('v-html')
}

上面这个首先判断node节点上是否有v-html这种指令,如果存在的话,我们就发布订阅,怎么发布订阅呢?只需要把当前需要订阅的数据push到watcherTask里面,然后到时候在设置值的时候就可以批量更新了,实现双向数据绑定,也就是下面的操作

that.watcherTask[key].forEach(task => {
task.update()
})

然后push的值是一个Watcher的实例,首先他new的时候会先执行一次,执行的操作就是去把纯双花括号 -> 1,也就是说把我们写好的模板数据更新到模板视图上。

最后把当前元素属性剔除出去,我们用Vue的时候也是看不到这种指令的,不剔除也不影响

至于Watcher是什么,看下面就知道了

Watcher

that.watcherTask[key].forEach(task => {
task.update()
})

之前发布订阅之后走了这里面的操作,意思就是把当前元素如:node.innerHTML = '这是data里面的值'、node.value = '这个是表单的数据'

那么我们为什么不直接去更新呢,还需要update做什么,不是多此一举吗?

其实update记得吗?我们在订阅池里面需要批量更新,就是通过调用Watcher原型上的update方法。

总结

以上所述是小编给大家介绍的基于JavaScript实现一个简单的Vue,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jquery简单实现鼠标经过导航条改变背景图
Dec 17 Javascript
Angularjs全局变量被作用域监听的正确姿势
Feb 06 Javascript
jQuery绑定事件的几种实现方式
May 09 Javascript
jQuery根据ID、CLASS、等获取对象的实例
Dec 04 Javascript
javascript滚轮事件基础实例讲解(37)
Feb 14 Javascript
Webpack实现按需打包Lodash的几种方法详解
May 08 Javascript
Vue中定义全局变量与常量的各种方式详解
Aug 23 Javascript
微信小程序6位或多位验证码密码输入框功能的实现代码
May 29 Javascript
js canvas实现二维码和图片合成的海报
Nov 19 Javascript
产制造追溯系统之通过微信小程序实现移动端报表平台
Jun 03 Javascript
解决layer.confirm选择完之后消息框不消失的问题
Sep 16 Javascript
javascript设计模式 ? 适配器模式原理与应用实例分析
Apr 13 Javascript
微信小程序授权登录及解密unionId出错的方法
Sep 26 #Javascript
vue根据进入的路由进行原路返回的方法
Sep 26 #Javascript
vue-router之nuxt动态路由设置的两种方法小结
Sep 26 #Javascript
Vue 配合eiement动态路由,权限验证的方法
Sep 26 #Javascript
react-navigation之动态修改title的内容
Sep 26 #Javascript
React项目动态设置title标题的方法示例
Sep 26 #Javascript
Vue resource三种请求格式和万能测试地址
Sep 26 #Javascript
You might like
ThinkPHP表单自动提交验证实例教程
2014/07/18 PHP
PHP中PDO的事务处理分析
2016/04/07 PHP
基于jquery ajax 用户无刷新登录方法详解
2012/04/28 Javascript
jquery自定义属性(类型/属性值)
2013/05/21 Javascript
js 得到文件后缀(通过正则实现)
2013/07/08 Javascript
js实现图片在未加载完成前显示加载中字样
2014/09/03 Javascript
浅谈javascript回调函数
2014/12/07 Javascript
JS仿Windows开机启动Loading进度条的方法
2015/02/26 Javascript
javascript比较两个日期相差天数的方法
2015/07/24 Javascript
你不知道的高性能JAVASCRIPT
2016/01/18 Javascript
jq stop()和:is(:animated)的用法及区别(详解)
2017/02/12 Javascript
详谈jQuery Ajax(load,post,get,ajax)的用法
2017/03/02 Javascript
使用bootstrap-paginator.js 分页来进行ajax 异步分页请求示例
2017/03/09 Javascript
JS解析url查询参数的简单代码
2017/08/06 Javascript
利用JS测试目标网站的打开响应速度
2017/12/01 Javascript
react-redux中connect的装饰器用法@connect详解
2018/01/13 Javascript
VUE 配置vue-devtools调试工具及安装方法
2018/09/30 Javascript
JavaScript实现表单注册、表单验证、运算符功能
2018/10/15 Javascript
JavaScript实现点击图片换背景
2020/11/20 Javascript
Python多线程编程(一):threading模块综述
2015/04/05 Python
Python基于回溯法子集树模板解决找零问题示例
2017/09/11 Python
python Gunicorn服务器使用方法详解
2019/07/22 Python
Python CSV文件模块的使用案例分析
2019/12/21 Python
Pycharm最常用的快捷键及使用技巧
2020/03/05 Python
基于python SMTP实现自动发送邮件教程解析
2020/06/02 Python
澳大利亚婴儿喂养品牌:Cherub Baby
2018/11/01 全球购物
css animation配合SVG制作能量流动效果
2021/03/24 HTML / CSS
计算机网络专业个人的自我评价
2013/10/17 职场文书
年检委托书
2014/08/30 职场文书
大学学生个人总结
2015/02/15 职场文书
2015年学生资助工作总结
2015/05/25 职场文书
早会开场白台词大全
2015/06/01 职场文书
食堂管理制度范本
2015/08/04 职场文书
Python NumPy灰度图像的压缩原理讲解
2021/08/04 Python
MYSQL如何查看操作日志详解
2022/05/30 MySQL
Java实现简单小画板
2022/06/10 Java/Android