基于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实现的数值范围range2dslider选取插件特效多款代码分享
Aug 27 Javascript
AngularJS应用开发思维之依赖注入3
Aug 19 Javascript
浅析如何利用angular结合translate为项目实现国际化
Dec 08 Javascript
JavaScript中从setTimeout与setInterval到AJAX异步
Feb 13 Javascript
利用PM2部署node.js项目的方法教程
May 10 Javascript
JS 验证密码 不能为空,必须含有数字、字母、特殊字符,长度在8-12位
Jun 21 Javascript
Vue实现数字输入框中分割手机号码的示例
Oct 10 Javascript
微信小程序实现动态设置页面标题的方法【附源码下载】
Nov 29 Javascript
使用命令行工具npm新创建一个vue项目的方法
Dec 27 Javascript
微信小程序App生命周期详解
Jan 31 Javascript
vue项目中,main.js,App.vue,index.html的调用方法
Sep 20 Javascript
mpvue将vue项目转换为小程序
Sep 30 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
phpmyadmin3 安装配置图解教程
2012/03/29 PHP
PHP调用Linux的命令行执行文件压缩命令
2013/01/27 PHP
PHP的mysqli_query参数MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的区别
2014/09/29 PHP
Symfony2框架创建项目与模板设置实例详解
2016/03/17 PHP
自制PHP框架之路由与控制器
2017/05/07 PHP
Yii2框架中日志的使用方法分析
2017/05/22 PHP
PHP获取本周所有日期或者最近七天所有日期的方法
2018/06/20 PHP
一款js和css代码压缩工具[附JAVA环境配置方法]
2010/04/16 Javascript
javascript中window.event事件用法详解
2012/12/11 Javascript
JavaScript实现维吉尼亚(Vigenere)密码算法实例
2013/11/22 Javascript
javascript生成随机数的方法
2014/05/16 Javascript
用循环或if语句从json中取数据示例
2014/08/18 Javascript
tuzhu_req.js 实现仿百度图片首页效果
2015/08/11 Javascript
基于jQuery倾斜打开侧边栏菜单特效代码
2015/09/15 Javascript
IE8 内存泄露(内存一直增长 )的原因及解决办法
2016/04/06 Javascript
Bootstrap3.0建站教程(一)之bootstrap表单元素排版
2016/06/01 Javascript
第一次接触神奇的Bootstrap网格系统
2016/07/27 Javascript
JavaScript算法系列之快速排序(Quicksort)算法实例详解
2016/09/04 Javascript
纯JS打造网页中checkbox和radio的美化效果
2016/10/13 Javascript
详解AngularJS验证、过滤器、指令
2017/01/04 Javascript
JavaScript限定范围拖拽及自定义滚动条应用(3)
2017/05/17 Javascript
AngularJS中的作用域实例分析
2018/05/16 Javascript
webpack公共组件引用路径简化小技巧
2018/06/15 Javascript
vue组件之间通信实例总结(点赞功能)
2018/12/05 Javascript
小程序获取当前位置加搜索附近热门小区及商区的方法
2019/04/08 Javascript
python实现封装得到virustotal扫描结果
2014/10/05 Python
Python中getattr函数和hasattr函数作用详解
2016/06/14 Python
Python Socket使用实例
2017/12/18 Python
利用pyshp包给shapefile文件添加字段的实例
2019/12/06 Python
超级实用的8个Python列表技巧
2020/08/24 Python
德国大型箱包和皮具商店:Koffer
2019/10/01 全球购物
护士自我评价范文
2014/01/25 职场文书
《燕子》教学反思
2014/02/18 职场文书
晨会主持词
2014/03/17 职场文书
先进教育工作者事迹材料
2014/12/23 职场文书
Mysql中存储引擎的区别及比较
2021/06/04 MySQL