用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 ...
随机推荐
- MySQL mysqldump备份与恢复
1 用户权限 grant select,RELOAD,PROCESS,SUPER, REPLICATION CLIENT ON *.* TO 'bak'@'192.168.%' IDENTIFIED ...
- 基于mjpg-streamer网络视频服务器移植
MJPG简介: MJPG是MJPEG的缩写,但是MJPEG还可以表示文件格式扩展名. MJPEG 全名为 "Motion Joint Photographic Experts Group&q ...
- C Primer Plus学习笔记(六)- C 控制语句:分支和跳转
if 语句: if 语句被称为分支语句(branching statement)或选择语句(selection statement) if 语句的通用形式: if (expression) state ...
- 一个hitbernate配置文件,带几个表(注意mapping);如果连接字符串没有设置utf-8,向insert mysql 会产生乱码(utf8 或 utf-8)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuratio ...
- leetcode328
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNo ...
- 前端seo小结,网页代码优化
seo的目的:提高网站流量 search engine optimization 搜索引擎优化seo search engine marketing 搜索引擎营销sem 权重10个等级 等级越大,权重 ...
- LNMP 1.6 常见的502问题解决
在nginx上跑discuz,先修改配置文件 cd /usr/local/nginx/conf/vhosts/ vim test.conf server { listen ; server_name ...
- 根据URL下载文件
commons-io 包中已经封装好了,直接可以使用 一.添加依赖 <dependency> <groupId>org.apache.commons</groupId&g ...
- TextView下划线,部分文字并响应点击事件(SpannableString)
TextView useInfo = (TextView) findViewById(R.id.info); useInfo.setText("开始即表示您同意遵守"); Stri ...
- 关于static的继承问题
今天研究了一下被static修饰的变量和方法,在子类中继承的问题,网上也看了别人的博客,自己也动手试了一下 代码如下 //父类 package com.xujingyang.test; public ...