基于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 相关文章推荐
javascript globalStorage类代码
Jun 04 Javascript
JQuery实现动态添加删除评论的方法
May 18 Javascript
全面解析Bootstrap中transition、affix的使用方法
May 30 Javascript
利用jquery获取select下拉框的值
Nov 23 Javascript
Angular的MVC和作用域
Dec 26 Javascript
jQuery实现遍历复选框的方法示例
Mar 06 Javascript
Node.js利用debug模块打印出调试日志的方法
Apr 25 Javascript
Vue.js简易安装和快速入门(第二课)
Oct 17 Javascript
关于在LayUI中使用AJAX提交巨坑记录
Oct 25 Javascript
el-table树形表格表单验证(列表生成序号)
May 31 Javascript
浅析我对JS延迟异步脚本的思考
Oct 12 Javascript
React-vscode使用jsx语法的问题及解决方法
Jun 21 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
基于Snoopy的PHP近似完美获取网站编码的代码
2011/10/23 PHP
PHP+FFMPEG实现将视频自动转码成H264标准Mp4文件
2014/09/24 PHP
在Win7 中为php扩展配置Xcache
2014/10/08 PHP
PHP实现简单用户登录界面
2019/10/23 PHP
PHP防止sql注入小技巧之sql预处理原理与实现方法分析
2019/12/13 PHP
兼容FireFox 的 js 日历 支持时间的获取
2009/03/04 Javascript
Jquery中显示隐藏的实现代码分析
2011/07/26 Javascript
JavaScript:Div层拖动效果实例代码
2013/08/06 Javascript
使用js声明数组,对象在jsp页面中(获得ajax得到json数据)
2013/11/05 Javascript
深入理解jQuery中的事件冒泡
2016/05/24 Javascript
jQuery+PHP实现微信转盘抽奖功能的方法
2016/05/25 Javascript
Jquery Easyui搜索框组件SearchBox使用详解(19)
2016/12/17 Javascript
jquery实现百叶窗效果
2017/01/12 Javascript
基于jQuery实现咖啡订单管理简单应用
2017/02/10 Javascript
小程序tab页无法传递参数的方法
2018/08/03 Javascript
js实现图片放大并跟随鼠标移动特效
2019/01/18 Javascript
python实现域名系统(DNS)正向查询的方法
2016/04/19 Python
Django学习笔记之Class-Based-View
2017/02/15 Python
python Selenium爬取内容并存储至MySQL数据库的实现代码
2017/03/16 Python
python自动12306抢票软件实现代码
2018/02/24 Python
详解python:time模块用法
2019/03/25 Python
Python代码实现http/https代理服务器的脚本
2019/08/12 Python
python 下划线的不同用法
2020/10/24 Python
Python截图并保存的具体实例
2021/01/14 Python
德国咖啡批发商:Coffeefair
2019/08/26 全球购物
使用索引(Index)有哪些需要考虑的因素
2016/10/19 面试题
成功经营餐厅的创业计划书范文
2013/12/26 职场文书
技能竞赛活动方案
2014/02/21 职场文书
创文明城市标语
2014/06/16 职场文书
师德师风的心得体会
2014/09/02 职场文书
2014大学班主任工作总结
2014/11/08 职场文书
2015年城市管理工作总结
2015/05/23 职场文书
功夫熊猫观后感
2015/06/10 职场文书
nginx优化的六点方法
2021/03/31 Servers
django注册用邮箱发送验证码的实现
2021/04/18 Python
苹果可能正在打击不进行更新的 App
2022/04/24 数码科技