Vue--双向绑定的原理--手写双向绑定(模仿vue)
原文网址:Vue—双向绑定的原理—手写双向绑定(模仿vue)_IT利刃出鞘的博客-CSDN博客
简介
说明
本文用示例介绍Vue的双向绑定的原理(实现方式)。
什么是双向绑定
双向绑定:数据和视图任意一个变化,则另一个也跟着变化。也就是:数据变化更新视图,视图变化更新数据。
Vue是MVVM模型。数据属于Model,视图属于View。本文要介绍的双向绑定就是:VM(ViewModel)。
MVVM表示的是 Model-View-ViewModel
- Model:模型层。负责处理业务逻辑以及和服务器端进行交互
- View:视图层:负责将数据模型转化为UI展示出来,可以简单理解为HTML页面
- ViewModel:视图模型层。用来连接Model和View,是Model和View之间的通信桥梁
Vue双向绑定的实现方案
官网
深入响应式原理 — Vue.js
简介
Vue是通过数据劫持和观察者模式(订阅者模式)来实现双向绑定的。
项 | 数据劫持 | 观察者模式 |
目的 | 监视数据的变化。 用于数据变化时更新视图。 | 监视视图的变化。 用于视图变化时更新数据。 |
实现方案 | Vue2: Object.defineProperty() Vue3: Proxy(ES6的新特性) | 对每个元素节点进行扫描和解析并绑定更新函数。 |
Object.defineProperty
优点
- 兼容性好。(ES5就已支持)
缺点
不能监听数组;
- 因为数组没有getter和setter。(数组长度不确定,如果太长性能负担太大)
- 只能监听属性,而不是整个对象;需要遍历属性;
- 只能监听属性变化(set、get),不能监听属性的删减;
Proxy
优点
- 可以直接监听数组的变化
- 可以直接监听整个对象,,而非是对象的某个属性
- 拦截方法丰富: 多达13种, 不限于 get、set、deleteProperty、has等。
缺点
- 兼容性差。(ES6的新特性)
详述
双向绑定需要三个部分:监听器(Observer),解析器(Compile),订阅者(Watcher)。
监听器(Observer)
- 监听数据的变化。
- 对数据的属性进行递归遍历,都加上setter和getter。给数据赋值时,会触发setter;读数据时,会触发getter。
解析器(Compile)
作用1:初始化页面。
- 对每个元素节点进行扫描和解析,利用正则将页面中的”{ {xxx}}“替换成data中对应的数据
作用2:监听视图的变动。
- 将每个指令对应的节点绑定更新函数,添加订阅者。一旦订阅者发出通知,就执行相应的更新函数。
订阅者(Watcher)
- 连接Observer和Compile 之间的桥梁。
- 通过Observer监听数据变化,更新视图。
- 通过Compile监听视图变化,更新数据。
手写双向绑定(简单实现)
需求:将输入的内容(“视图”内容)保存到对象(“数据”内容)中,对象保存时,同时更新
代码
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This is title</title>
</head>
<body>
<input type="text" id="id-input"><br/>
<div id="id-div"></div>
<script>
let user = {};
let input = document.querySelector("#id-input");
let text = document.querySelector("#id-div");
// 数据到视图 model => view
Object.defineProperty(user,"name",{
get:function(){
console.log('获取user的name');
return this._name;
},
set:function(val){
console.log('修改user的name为:' + val);
this._name = val;
text.textContent = this._name;
}
})
// 视图到数据 view => model
input.addEventListener('input',function(e){
user.name = e.target.value;
console.log("-----------------------")
console.log("监听器中将user的name设置为:" + user.name);
})
</script>
</body>
</html>
测试
手写双向绑定(模仿vue2)
代码
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This is title</title>
</head>
<body>
<!-- 实现vue -->
<div id="app">
<input type="text" v-model="text">
{
{ text }}
</div>
<script type="text/javascript">
function defineReactive(obj, key, value) {
let dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
if (Dep.target) {
dep.addSub(Dep.target);
}
return value
},
set: function (newVal) {
// 数据没有改变则不需要通知
if (newVal === value) {
return
}
value = newVal;
console.log('新值:' + value);
// 数据改变,通知订阅者
dep.notify();
}
})
}
// 观察者函数
function observe(obj, vm) {
for (let key of Object.keys(obj)) {
defineReactive(vm, key, obj[key]);
}
}
/*编译函数*/
function compile(node, vm) {
let reg = /\{\{(.*)\}\}/; // 来匹配 {
{ xxx }} 中的xxx
// 如果是元素节点
if (node.nodeType === 1) {
let attr = node.attributes;
// 解析元素节点的所有属性
for (let i = 0; i < attr.length; i++) {
if (attr[i].nodeName === 'v-model') {
let name = attr[i].nodeValue; // 看看是与哪一个数据相关
node.addEventListener('input', function (e) {
vm[name] = e.target.value; // 将实例的text 修改为最新值
});
node.value = vm[name]; // 将data的值赋给该node
node.removeAttribute('v-model');
}
}
}
// 如果是文本节点
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
let name = RegExp.$1; // 获取到匹配的字符串
name = name.trim();
// node.nodeValue = vm[name]; // 将data的值赋给该node
new Watcher(vm, node, name); // 不直接通过赋值的操作,而是通过绑定一个订阅者
}
}
}
// Watcher构造函数
function Watcher(vm, node, name) {
Dep.target = this; // Dep.target 是一个全局变量
this.vm = vm;
this.node = node;
this.name = name;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update() {
this.get();
this.node.nodeValue = this.value; // 注意,这是更改节点内容的关键
},
get() {
this.value = this.vm[this.name]; // 触发相应的get
}
}
// dep构造函数
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub(sub) {
this.subs.push(sub);
},
notify() {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
function nodeToFragment(node, vm) {
let fragment = document.createDocumentFragment();
let child;
while (child = node.firstChild) {
compile(child, vm);
fragment.appendChild(child);
}
return fragment
}
// Vue构造函数
function MyVue(options) {
this.data = options.data;
let data = this.data;
observe(data, this);
let id = options.el;
let dom = nodeToFragment(document.getElementById(id), this);
// 处理完所有dom节点后,重新将内容添加回去
document.getElementById(id).appendChild(dom);
}
let vm = new MyVue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>
测试
其他网址
通俗易懂了解Vue双向绑定原理及实现 - osc_2x36yftz的个人空间 - OSCHINA - 中文开源技术交流社区
手写JS(七)—实现VUE的双向绑定 - 掘金
手写VUE mvvm双向数据绑定_Alinachanchan的博客-CSDN博客_手写vue双向绑定
还没有评论,来说两句吧...