用ES6的class模仿Vue写一个双向绑定
最终效果如下:

构造器(constructor)
构造一个TinyVue对象,包含基本的el,data,methods
class TinyVue{
constructor({el, data, methods}){
this.$data = data
this.$el = document.querySelector(el)
this.$methods = methods
// 初始化
this._compile()
this._updater()
this._watcher()
}
}
编译器(compile)
用于解析绑定到输入框和下拉框的v-model和元素的点击事件@click。
先创建一个函数用来载入事件:
// el为元素tagName,attr为元素属性(v-model,@click)
_initEvents(el, attr, callBack) {
this.$el.querySelectorAll(el).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr)
callBack(i, key)
}
})
}
载入输入框事件
this._initEvents('input, textarea', 'v-model', (i, key) => {
i.addEventListener('input', () => {
Object.assign(this.$data, {[key]: i.value})
})
})
载入选择框事件
this._initEvents('select', 'v-model', (i, key) => {
i.addEventListener('change', () => Object.assign(this.$data, {[key]: i.options[i.options.selectedIndex].value}))
})
载入点击事件
点击事件对应的是methods中的事件
this._initEvents('*', '@click', (i, key) => {
i.addEventListener('click', () => this.$methods[key].bind(this.$data)())
})
视图更新器(updater)
同理先创建公共函数来处理不同元素中的视图,包括input、textarea的value,select的选择值,div的innerHTML
_initView(el, attr, callBack) {
this.$el.querySelectorAll(el, attr, callBack).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr),
data = this.$data[key]
callBack(i, key, data)
}
})
}
更新输入框视图
this._initView('input, textarea', 'v-model', (i, key, data) => {
i.value = data
})
更新选择框视图
this._initView('select', 'v-model', (i, key, data) => {
i.querySelectorAll('option').forEach(v => {
if(v.value == data) v.setAttribute('selected', true)
else v.removeAttribute('selected')
})
})
更新innerHTML
这里实现方法有点low,仅想到正则替换{{text}}
let regExpInner = /\{{ *([\w_\-]+) *\}}/g
this.$el.querySelectorAll("*").forEach(i => {
let replaceList = i.innerHTML.match(regExpInner) || (i.hasAttribute('vueID') && i.getAttribute('vueID').match(regExpInner))
if(replaceList) {
if(!i.hasAttribute('vueID')) {
i.setAttribute('vueID', i.innerHTML)
}
i.innerHTML = i.getAttribute('vueID')
replaceList.forEach(v => {
let key = v.slice(2, v.length - 2)
i.innerHTML = i.innerHTML.replace(v, this.$data[key])
})
}
})
监听器(watcher)
数据变化之后更新视图
_watcher(data = this.$data) {
let that = this
Object.keys(data).forEach(i => {
let value = data[i]
Object.defineProperty(data, i, {
enumerable: true,
configurable: true,
get: function () {
return value;
},
set: function (newVal) {
if (value !== newVal) {
value = newVal;
that._updater()
}
}
})
})
}
使用
<div id="app">
<input type="text" v-model="text1"><br>
<input type="text" v-model="text2"><br>
<textarea type="text" v-model="text3"></textarea><br>
<button @click="add">加一</button>
<h1>您输入的是:{{text1}}+{{text2}}+{{text3}}</h1>
<select v-model="select">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</select>
<select v-model="select">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</select>
<h1>您选择了:{{select}}</h1>
</div>
<script src="./TinyVue.js"></script>
<script>
let app = new TinyVue({
el: '#app',
data: {
text1: 123,
text2: 456,
text3: '文本框',
select: 'saab'
},
methods: {
add() {
this.text1 ++
this.text2 ++
}
}
})
</script>
TinyVue全部代码
class TinyVue{
constructor({el, data, methods}){
this.$data = data
this.$el = document.querySelector(el)
this.$methods = methods
this._compile()
this._updater()
this._watcher()
}
_watcher(data = this.$data) {
let that = this
Object.keys(data).forEach(i => {
let value = data[i]
Object.defineProperty(data, i, {
enumerable: true,
configurable: true,
get: function () {
return value;
},
set: function (newVal) {
if (value !== newVal) {
value = newVal;
that._updater()
}
}
})
})
}
_initEvents(el, attr, callBack) {
this.$el.querySelectorAll(el).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr)
callBack(i, key)
}
})
}
_initView(el, attr, callBack) {
this.$el.querySelectorAll(el, attr, callBack).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr),
data = this.$data[key]
callBack(i, key, data)
}
})
}
_updater() {
this._initView('input, textarea', 'v-model', (i, key, data) => {
i.value = data
})
this._initView('select', 'v-model', (i, key, data) => {
i.querySelectorAll('option').forEach(v => {
if(v.value == data) v.setAttribute('selected', true)
else v.removeAttribute('selected')
})
})
let regExpInner = /\{{ *([\w_\-]+) *\}}/g
this.$el.querySelectorAll("*").forEach(i => {
let replaceList = i.innerHTML.match(regExpInner) || (i.hasAttribute('vueID') && i.getAttribute('vueID').match(regExpInner))
if(replaceList) {
if(!i.hasAttribute('vueID')) {
i.setAttribute('vueID', i.innerHTML)
}
i.innerHTML = i.getAttribute('vueID')
replaceList.forEach(v => {
let key = v.slice(2, v.length - 2)
i.innerHTML = i.innerHTML.replace(v, this.$data[key])
})
}
})
}
_compile() {
this._initEvents('*', '@click', (i, key) => {
i.addEventListener('click', () => this.$methods[key].bind(this.$data)())
})
this._initEvents('input, textarea', 'v-model', (i, key) => {
i.addEventListener('input', () => {
Object.assign(this.$data, {[key]: i.value})
})
})
this._initEvents('select', 'v-model', (i, key) => {
i.addEventListener('change', () => Object.assign(this.$data, {[key]: i.options[i.options.selectedIndex].value}))
})
}
}
用ES6的class模仿Vue写一个双向绑定的更多相关文章
- 用vue写一个仿简书的轮播图
原文地址:用vue写一个仿简书的轮播图 先展示最终效果: Vue的理念是以数据驱动视图,所以拒绝通过改变元素的margin-top来实现滚动效果.写好css样式,只需改变每张图片的class即可实现轮 ...
- 用PHP写一个双向队列
PHP写一个双向队列,其实是在考察PHP几个内置数组的函数 用PHP写一个双向队列 <?php class Deque{ public $queue = array(); /** * 尾部入对 ...
- Vue的数据双向绑定和Object.defineProperty()
Vue是前端三大框架之一,也被很多人指责抄袭,说他的两个核心功能,一个数据双向绑定,一个组件化分别抄袭angular的数据双向绑定和react的组件化思想,咱们今天就不谈这种大是大非,当然我也没到达那 ...
- 原生js实现 vue的数据双向绑定
原生js实现一个简单的vue的数据双向绑定 vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时 ...
- Vue框架之双向绑定事件
Vue框架之双向绑定事件 首先介绍下Vue框架的语法 vue通过 {{temp}} 来渲染变量 {{count+100}} # 求和 v-text # 为标签插入text文本 v-html # 为标签 ...
- 剖析Vue原理&实现双向绑定MVVM-1
本文能帮你做什么?1.了解vue的双向数据绑定原理以及核心代码模块2.缓解好奇心的同时了解如何实现双向绑定为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,相对较简陋,并未考 ...
- es6的set和get实现数据双向绑定,监听变量变化。
直接上代码吧,这个用法真的是效仿了.net的枚举. vue的数据双向绑定就是用这个实现的. 代码: html: <input type="text" id="inp ...
- 组件的通信 :provide / inject 对象进入后,就等于不用props,然后内部对象,直接复制可以接受数组,属性不能直接复制,可以用Object.assgin覆盖对象,或者Vue的set 双向绑定数据
组件的通信 :provide / inject 对象进入后,就等于不用props,然后内部对象,直接复制可以接受数组,属性不能直接复制,可以用Object.assgin覆盖对象,或者Vue的set 双 ...
- 用vue写一个仿app下拉刷新的组件
如果你用vue弄移动端的页面,那么下拉刷新还是比较常见的场景,下面来研究如何写一个下拉刷新的组件(先上图); 由于节省大家的时间,样式就不贴出来了. html结构也不必介绍了,直接看代码吧-.- &l ...
随机推荐
- AngularJS:动画
ylbtech-AngularJS:动画 1.返回顶部 1. AngularJS 动画 AngularJS 提供了动画效果,可以配合 CSS 使用. AngularJS 使用动画需要引入 angula ...
- Python命令模块argparse学习笔记(二)
argparse模块可以设置两种命令参数,一个是位置参数,一个是命令参数 位置参数 import argparse parser = argparse.ArgumentParser(descripti ...
- LNMP 1.6 常见的502问题解决
在nginx上跑discuz,先修改配置文件 cd /usr/local/nginx/conf/vhosts/ vim test.conf server { listen ; server_name ...
- JAVA之数组队列
package xxj.datastructure0810; import java.util.Random; public class DataStructure { /** * @param ar ...
- 第3章_Java仿微信全栈高性能后台+移动客户端
当服务器构建完毕并且启动之后,我们通过网页URL地址就可以访问这台服务器,并且服务器会向网页输出Hello Netty这样几个字. Netty有三种线程模型:单线程.多线程.主从线程.Netty官方推 ...
- ES02 变量、数组、对象、方法
1 变量 1.1 变量的声明 利用var关键字来声明变量,例如: var a = 100; <!DOCTYPE html> <html> <head> <me ...
- 解析IFC数据并存储到关系型数据库
即系IFC数据并存储到关系型数据库中,目前解析的IFC文件是两亿多行,构件数量120万
- Arduino Serial库的使用
1 Serial.begin() 2 Serial.end() 3 Serial.available() 4 Serial.read() 5 Serial.peek() 6 Serial.flush( ...
- Flask框架 之 基本使用
初识Flask Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求 ...
- Spark的序列化
spark的序列化主要使用了hadoop的writable和java的Serializable. 说到底就是使用hadoop的writable替换了java的默认序列化实现方式. class Seri ...