全网超详细的vue双向数据绑定的原理
文章目录
- 1. 文章引言
- 2. vue如何实现数据的双向绑定
- 3. 什么是Object.defineProperty
- 4. 什么是setter和getter
1. 文章引言
假设,我在文本框中输入文字,在p
标签中动态展示我输入的文字,如下图所示:
实现上述效果的代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue的数据双向绑定</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div id="app" style="padding: 100px 100px 10px;">
<form class="bs-example bs-example-form" role="form">
<div class="input-group">
<input type="text" class="form-control" placeholder="twitterhandle" v-model="text">
<p style="color: red;margin-top: 50px;">{{ text }}</p>
</div>
</form>
</div>
<script src="../js/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>
基于我在文本框中输入文字,在p标签中动态展示我输入的文字
的效果,可以将其理解为vue
的数据双向绑定。
那么,接下来,我便详细介绍vue
的数据双向绑定。
如果你还没有安装vue
,可以参考博文:全网最新的vue.js下载和安装的3种方法(2023年)
2. vue如何实现数据的双向绑定
vue.js
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
和getter
。
在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。
具体步骤如下:
-
需要
observer
的数据对象进行递归遍历,包括子属性对象的属性,都加上setter
和getter
。如此,给这个对象的某属性赋值,就会触发setter
,那么就能监听到了数据变化。 -
compile
解析模板指令,将模板中的变量替换成数据,接着初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。 -
Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情如下:-
在自身实例化时往属性订阅器
(dep)
里面添加自己。 -
自身必须有一个
update()
方法。 -
待属性变动
dep.notice()
通知时,能调用自身的update()
方法,并触发Compile
中绑定的回调,则功成身退。
-
-
MVVM
作为数据绑定的入口,整合Observer
、Compile
和Watcher
三者。通过Observer
来监听自己的model
数据变化,接着通过Compile
来解析编译模板指令,最终利用Watcher
搭起Observer
和Compile
之间的通信桥梁,达到数据变化到视图的更新;视图交互变化(input)
到数据model
变更的双向绑定效果。
3. 什么是Object.defineProperty
3.1 语法如下
Object.defineProperty(obj, prop, descriptor)
3.2 参数说明
-
obj
:必需。目标对象 -
prop
:必需。需定义或修改的属性的名字 -
descriptor
:必需。目标属性所拥有的特性
3.3 返回值:
传入函数的对象,即第一个参数obj
。
针对属性,我们可以给属性设置一些特性,比如是否只读不可以写;是否可以被for…in
或Object.keys()
遍历。
给对象的属性添加特性描述,目前提供如下两种形式:
-
数据描述
-
存取器描述。
当修改或定义对象的某个属性的时候,给这个属性添加一些特性。
3.4 数据描述
var obj = {
test:"hello word"
}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
configurable:true | false,
enumerable:true | false,
value:"任意类型的值",
writable:true | false
});
//对象新添加的属性的特性描述
Object.defineProperty(obj,"newKey",{
configurable:true | false,
enumerable:true | false,
value:"任意类型的值",
writable:true | false
});
数据描述中的属性都是可选的,来看下设置每个属性的作用。
value
属性对应的值,可以使任意类型的值,默认为undefined
。
var obj = {}
//第一种情况:不设置value属性
Object.defineProperty(obj,"newKey",{
});
console.log( obj.newKey ); //undefined
------------------------------
//第二种情况:设置value属性
Object.defineProperty(obj,"newKey",{
value:"hello word"
});
console.log( obj.newKey ); //hello word
writable
属性的值是否可以被重写,取决于如下设置:
-
设置为
true
可以被重写; -
设置为
false
,不能被重写。
默认为false
。
var obj = {}
//第一种情况:writable设置为false,不能重写。
Object.defineProperty(obj,"newKey",{
value:"hello word",
writable:false
});
//更改newKey的值
obj.newKey = "change value";
console.log( obj.newKey ); //hello word
//第二种情况:writable设置为true,可以重写
Object.defineProperty(obj,"newKey",{
value:"hello word",
writable:true
});
//更改newKey的值
obj.newKey = "change value";
console.log( obj.newKey ); //ch
enumerable
此属性是否可以被枚举(使用for…in
或Object.keys()
),取决于如下设置:
-
设置为
true
,可以被枚举 -
设置为
false
,不能被枚举。
默认为false
。
var obj = {}
//第一种情况:enumerable设置为false,不能被枚举。
Object.defineProperty(obj,"newKey",{
value:"hello word",
writable:false,
enumerable:false
});
//枚举对象的属性
for( var attr in obj ){
console.log( attr ); //console不出来
}
//第二种情况:enumerable设置为true,可以被枚举。
Object.defineProperty(obj,"newKey",{
value:"hello word",
writable:false,
enumerable:true
});
//枚举对象的属性
for( var attr in obj ){
console.log( attr ); //newKey
}
configurable
是否可以删除目标属性,或是否可以再次修改属性的特性,比如writable, configurable, enumerable
,取决于如下设置:
-
设置为
true
,可以被删除,或可以重新设置特性; -
设置为
false
,不能被可以被删除,或不可以重新设置特性。
默认为false
。
这个属性起到如下两个作用:
-
目标属性是否可以使用delete删除
-
目标属性是否可以再次设置特性
//-----------------测试目标属性是否能被删除------------------------
var obj = {}
//第一种情况:configurable设置为false,不能被删除。
Object.defineProperty(obj,"newKey",{
value:"hello word",
writable:false,
enumerable:false,
configurable:false
});
//删除属性
delete obj.newKey;
console.log( obj.newKey ); //hello word
//第二种情况:configurable设置为true,可以被删除。
Object.defineProperty(obj,"newKey",{
value:"hello word",
});
除了可以给新定义的属性设置特性,也可以给已有的属性设置特性,如下代码所示:
//定义对象的时候添加的属性,是可删除、可重写、可枚举的。
var obj = {
test:"hello word"
}
//改写值
obj.test = 'change value';
console.log( obj.test ); //'change value'
Object.defineProperty(obj,"test",{
writable:false
});
//再次改写值
obj.test = 'change value again';
console.log( obj.test ); //依然是:'change value'
【注意】一旦使用Object.defineProperty
给对象添加属性,如果不设置属性的特性,那么configurable
、enumerable
、writable
这些值都为默认的false
。如下代码所示:
var obj = {};
//定义新属性后,这个属性的特性中configurable,enumerable,writable都为默认的值false
//这就意味着neykey是不能重写、不能枚举、不能再次设置特性
Object.defineProperty(obj,'newKey',{
});
//设置值
obj.newKey = 'hello word';
console.log(obj.newKey); //undefined
//枚举
for( var attr in obj ){
console.log(attr);
}
4. 什么是setter和getter
对象有两种属性:
-
数据属性: 就是我们经常使用的属性
-
访问器属性: 也称
存取器属性
存取器属性就是一组获取和设置值的函数。
再看如下的代码:
<script>
const obj = {"name":"super先生","address":"江苏省无锡市"};
console.log(obj);
</script>
log
打印出来的如下图:
数据属性就是username
和address
。
get
和set
就是关键字,它们后面各自对应一个函数——这个函数就是上面红字部分所讲的存储器属性
。
get
对应的方法称为getter
,负责获取值,它不带任何参数。
set
对应的方法为setter
,负责设置值,在它的函数体中,一切的return
都是无效的。