基于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中的变量使用说明
May 18 Javascript
实现网页页面跳转的几种方法(meta标签、js实现、php实现)
May 20 Javascript
JavaScript运行机制之事件循环(Event Loop)详解
Oct 10 Javascript
基于jQuery实现网页进度显示插件
Mar 04 Javascript
javascript jquery对form元素的常见操作详解
Jun 12 Javascript
javascript实现获取图片大小及图片等比缩放的方法
Nov 24 Javascript
Vue form 表单提交+ajax异步请求+分页效果
Apr 22 Javascript
简单实现js进度条加载效果
Mar 25 Javascript
Vue-cli Eslint在vscode里代码自动格式化的方法
Feb 23 Javascript
vue中关闭eslint的方法分析
Aug 04 Javascript
elementUI select组件value值注意事项详解
May 29 Javascript
使用vue-element-admin框架从后端动态获取菜单功能的实现
Apr 29 Vue.js
微信小程序授权登录及解密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
mac下使用brew配置环境的步骤分享
2011/05/23 PHP
php在文件指定行中写入代码的方法
2012/05/23 PHP
关于PHP中Session文件过多的问题及session文件保存位置
2016/03/17 PHP
Yii2中添加全局函数的方法分析
2017/05/04 PHP
javascript支持firefox,ie7页面布局拖拽效果代码
2007/12/20 Javascript
javascript AOP 实现ajax回调函数使用比较方便
2010/11/20 Javascript
Jquery判断IE6等浏览器的代码
2011/04/05 Javascript
javascript图像处理—仿射变换深度理解
2013/01/16 Javascript
本地图片预览(支持IE6/IE7/IE8/Firefox3)经验总结
2013/03/25 Javascript
js的匿名函数使用介绍
2013/12/11 Javascript
一个判断抢购时间是否到达的简单的js函数
2014/06/23 Javascript
原生js结合html5制作简易的双色子游戏
2015/03/30 Javascript
jQuery动态移除和添加背景图片的方法详解
2017/03/07 Javascript
JS ES6中setTimeout函数的执行上下文示例
2017/04/27 Javascript
jQuery插件imgAreaSelect基础讲解
2017/05/26 jQuery
详解vue数据渲染出现闪烁问题
2017/06/29 Javascript
微信小程序非swiper组件实现的自定义伪3D轮播图效果示例
2018/12/11 Javascript
详解如何在Javascript和Sass之间共享变量
2019/11/13 Javascript
小程序中使用css var变量(使js可以动态设置css样式属性)
2020/03/31 Javascript
JavaScript中clientWidth,offsetWidth,scrollWidth的区别
2021/01/25 Javascript
[11:27]《一刀刀一天》之DOTA全时刻20:TI4总奖金突破920W TS赛事分析
2014/06/18 DOTA
[51:07]VGJ.S vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
如何在django中添加日志功能
2020/02/06 Python
浅析python 动态库m.so.1.0错误问题
2020/05/09 Python
Blancsom美国/加拿大:服装和生活用品供应商
2018/07/27 全球购物
英国奢侈皮具品牌:Aspinal of London
2018/09/02 全球购物
一套比较完整的软件测试人员面试题
2012/05/13 面试题
如何用Java判断一个文件或目录是否存在
2012/11/19 面试题
餐饮商业计划书范文
2014/04/29 职场文书
单位委托函范文
2015/01/29 职场文书
介绍信样本
2015/01/31 职场文书
工程竣工验收申请报告
2015/05/15 职场文书
2016秋季运动会前导词
2015/11/25 职场文书
高中历史教学反思
2016/02/19 职场文书
用Python实现一个打字速度测试工具来测试你的手速
2021/05/28 Python
python实现手机推送 代码也就10行左右
2022/04/12 Python