Vue的实现原理(数据双向绑定)
Vue数据双向绑定的原理:1、实例化一个app对象——定义一个Vue类(类的参数是一个对象,包括id属性、数据和方法等)id属性:为了获取原生DOM对象2、循环通过set,get方法实现app对象的数据代理3、观察数据动态,对更新的数据进行管理——设置一个劫持事件4、设置一个观察对象,对节点的内容进行更新——定义一个watch类5、把view的数据和事件进行...
·
一、Vue数据双向绑定的原理:
1、实例化一个app对象——定义一个Vue类(类的参数是一个对象,包括id属性、数据和方法等)
id属性:为了获取原生DOM对象
2、循环通过set,get方法实现app对象的数据代理
3、观察数据动态,对更新的数据进行管理——设置一个劫持事件
4、设置一个观察对象,对节点的内容进行更新——定义一个watch类
5、把view的数据和事件进行绑定(参数是DOM对象)
二、结果展示:
1、文本框输入内容,全部数据同时被修改。
2、点击修改内容,全部数据同时被修改。
三、代码展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#app{
width: 200px;
height: 200px;
margin: 100px auto;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="msg">
<h1>{{msg}}</h1>
<!-- v-html 绑定的是h1对象上的innerHtml -->
<h1 v-html="msg"></h1>
<button type="button" @click="changeEvent">修改内容</button>
</div>
<script>
class Vue {
constructor(options) { //options是传入的对象
// 通过选择器获取根对象
this.$el = document.querySelector(options.el);
// 数据创造之前
if(typeof options.beforeCreate == 'function') {
options.beforeCreate.bind(this)();
}
this.$options = options;
// 设置一个对象用来修改更新事件
this.$watchEvent = {};
// 代理options的data数据
this.proxyData();
// 劫持事件,一旦修改则触发某件事情
this.observe();
// 数据创造之后
if(typeof options.created == 'function') {
options.created.bind(this)();
}
// 挂载前
if(typeof options.beforeMount == 'function') {
options.beforeMount.bind(this)();
}
// 把view的数据和事件进行绑定
this.compile(this.$el);
// 挂载后
if(typeof options.mounted == 'function') {
options.mounted.bind(this)();
}
}
// 劫持事件
observe() {
for (let key in this.$options.data) {
// 获取此处value保存
let value = this.$options.data[key];
let that = this;
Object.defineProperty(this.$options.data, key, {
configurable: false,
enumerable: true,
// value:"定义值"
// writeable:true,false 定义是否可以进行修改
set(val) {
value = val;
// 触发以这个key值得更新事件
if (that.$watchEvent[key]) {
that.$watchEvent[key].forEach((item, index) => {
item.updata();
})
}
},
get() {
// 获取this[key]时,即返回options的data[key]
// 触发获取内容事件
return value;
}
});
}
}
// 循环通过set,get方法来实现代理数据
proxyData() {
for (let key in this.$options.data) {
// console.log(key); //msg username
// 定义属性的方法 参数:对象、属性、配置的信息
Object.defineProperty(this, key, {
configurable: false, //是否可再定义
enumerable: true, //是否可迭代,即使用for循环
// value:"定义值"
// writeable:true,false 定义是否可以进行修改
set(val) {
this.$options.data[key] = val;
},
get() {
// 获取this[key]时,即返回options的data[key]
return this.$options.data[key];
}
});
}
}
// 把view的数据和事件进行绑定
compile(cNode) {
// 获取所有节点进行循环
cNode.childNodes.forEach((node, index) => {
if (node.nodeType == 1) {
// 元素类型
if (node.hasAttribute('v-html')) {
let vmKey = node.getAttribute('v-html').trim(); // msg
if (this.hasOwnProperty(vmKey)) {
node.innerHTML = this[vmKey];
let watcher = new Watch(this, vmKey, node, 'innerHTML');
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher);
} else {
this.$watchEvent[vmKey] = [];
this.$watchEvent[vmKey].push(watcher);
}
// 删除节点事件
node.removeAttribute('v-html');
}
}
// 判断是否有v-model属性
if (node.hasAttribute('v-model')) {
let vmKey = node.getAttribute('v-model').trim();
if (this.hasOwnProperty(vmKey)) {
node.value = this[vmKey];
let watcher = new Watch(this, vmKey, node, 'value');
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher);
} else {
this.$watchEvent[vmKey] = [];
this.$watchEvent[vmKey].push(watcher);
}
}
node.addEventListener('input', (event) => {
this[vmKey] = node.value;
})
// 删除节点事件
node.removeAttribute('v-model');
}
// 判断是否绑定@click属性
if (node.hasAttribute('@click')) {
let vmKey = node.getAttribute('@click').trim(); // changeEvent
node.addEventListener('click', (event) => {
this.eventFn = this.$options.methods[vmKey].bind(this);
this.eventFn(event);
})
}
// 如果还有子节点
if (node.childNodes.length > 0) {
this.compile(node);
}
}
if (node.nodeType == 3) {
// 文本类型
let reg = /\{\{(.*?)\}\}/g;
let text = node.textContent;
node.textContent = text.replace(reg,(match,vmKey)=>{
vmKey = vmKey.trim(); // 去除空格
if (this.hasOwnProperty(vmKey)) {
node.innerHTML = this[vmKey];
let watcher = new Watch(this, vmKey, node, 'textContent');
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher);
} else {
this.$watchEvent[vmKey] = [];
this.$watchEvent[vmKey].push(watcher);
}
}
return this[vmKey];
});
}
})
}
}
// 设置一个watch对象
class Watch {
//属性:DOM对象、key、绑定的节点对象、节点属性、节点类型
constructor(vm, key, node, attr, nType) {
// 实例化的app对象
this.vm = vm;
// 绑定的vm触发的属性
this.key = key;
// 此vm[key]数据绑定的HTML结点
this.node = node;
// vm数据所绑定的HTML结点的属性名称
this.attr = attr;
}
updata() {
// 更新前
if(typeof options.beforeUpdate == 'function') {
options.beforeUpdate.bind(this)();
}
this.node[this.attr] = this.vm[this.key];
// 更新后
if(typeof options.updated == 'function') {
options.updated.bind(this)();
}
}
}
</script>
<script>
let options = {
el: "#app",
data: {
msg: "hello zzs",
username: "zzs"
},
methods: {
changeEvent: function () {
this.msg = "hello vue"
}
},
// 创造之前
beforeCreate() {
console.log("beforeCreate")
},
// 创造
created() {
console.log("created")
},
// 渲染之前
beforeMount() {
console.log("beforeMount")
},
// 渲染之后
mounted() {
console.log("mounted")
},
beforeUpdate() {
// 数据更改,但内容未更改之前
console.log("beforeUpdata")
},
updated() {
// 内容更新完毕
console.log("updated")
}
};
let app = new Vue(options);
console.log(app);
</script>
</body>
</html>
更多推荐
已为社区贡献2条内容
所有评论(0)