基于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 操作table,可以新增行和列并且隔一行换背景色代码分享
Jul 05 Javascript
Jquery通过Ajax访问XML数据的小例子
Nov 18 Javascript
JavaScript格式化日期时间的方法和自定义格式化函数示例
Apr 04 Javascript
关于JavaScript命名空间的一些心得
Jun 07 Javascript
jQuery窗口、文档、网页各种高度的精确理解
Jul 02 Javascript
jQuery中 attr() 方法使用小结
May 03 Javascript
AngularJS基于ngInfiniteScroll实现下拉滚动加载的方法
Dec 14 Javascript
js 监控iframe URL的变化实例代码
Jul 12 Javascript
Vue+Mock.js模拟登录和表格的增删改查功能
Jul 26 Javascript
jQuery实现表格的增、删、改操作示例
Jan 27 jQuery
微信小程序实现时间进度条功能
Nov 17 Javascript
vue keep-alive列表页缓存 详情页返回上一页不刷新,定位到之前位置
Nov 26 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
php adodb连接mssql解决乱码问题
2009/06/12 PHP
浅谈PHP中静态方法和非静态方法的相互调用
2016/10/04 PHP
jquery的flexigrid无法显示数据提示获取到数据
2013/07/19 Javascript
js实现右下角提示框的方法
2015/02/03 Javascript
JavaScript面向对象之私有静态变量实例分析
2016/01/14 Javascript
JavaScript计算器网页版实现代码分享
2016/07/15 Javascript
js实现简单的碰壁反弹效果
2016/08/30 Javascript
总结Javascript中数组各种去重的方法
2016/10/04 Javascript
Angular2入门--架构总览
2017/03/29 Javascript
jquery-file-upload 文件上传带进度条效果
2017/11/21 jQuery
mockjs,json-server一起搭建前端通用的数据模拟框架教程
2017/12/18 Javascript
基于wordpress的ajax写法详解
2018/01/02 Javascript
详解NODEJS的http实现
2018/01/04 NodeJs
如何用webpack4带你实现一个vue的打包的项目
2018/06/20 Javascript
JS逻辑运算符短路操作实例分析
2018/07/09 Javascript
详解react-refetch的使用小例子
2019/02/15 Javascript
vue项目中使用particles实现粒子背景效果及遇到的坑(按钮没有点击响应)
2020/02/11 Javascript
python脚本实现查找webshell的方法
2014/07/31 Python
python实现批量获取指定文件夹下的所有文件的厂商信息
2014/09/28 Python
Python实现将DOC文档转换为PDF的方法
2015/07/25 Python
Python for循环生成列表的实例
2018/06/15 Python
详解python中的json和字典dict
2018/06/22 Python
Python中顺序表原理与实现方法详解
2019/12/03 Python
Keras框架中的epoch、bacth、batch size、iteration使用介绍
2020/06/10 Python
python使用ctypes库调用DLL动态链接库
2020/10/22 Python
英国Flybe航空官网:欧洲最大的独立支线廉价航空公司
2019/07/15 全球购物
C语言面试题
2015/10/30 面试题
机电一体化专业推荐信
2013/12/03 职场文书
大学四年个人自我小结
2014/03/05 职场文书
中药学自荐信
2014/06/15 职场文书
2014年社区民政工作总结
2014/12/02 职场文书
任长霞观后感
2015/06/16 职场文书
餐厅服务员管理制度
2015/08/05 职场文书
培训感想范文
2015/08/07 职场文书
咖啡厅里的创业计划书
2019/08/21 职场文书
利用Python实现模拟登录知乎
2022/05/25 Python