一、Vue2数据响应式原理
通过数据劫持 defineProperty[dɪˈfaɪnˈprɑːpərti]+ 发布订阅者模式,当 vue 实例初始化后 observer[əbˈzɜːrvər]会针对实例中的 data 中的每一个属性进行劫持,并通过 defineProperty() 设置值后,在 get() 中向发布者添加该属性的订阅者,这里在编译模板时就会初始化每一属性的 watcher [ˈwɑːtʃər]在数据发生更新后调用 set 时会通知发布者 notify[ˈnoʊtɪfaɪ]通知对应的订阅者做出数据更新,同时将新的数据根性到视图上显示。
二、Object.defineProperty方法介绍
创建一个对象:var obj = { name: “sun” }
Object.defineProperty[dɪˈfaɪnˈprɑːpərti]这个方法接收三个参数:
1、属性所在的对象(obj);
2、属性的名字(name、age);
3、一个描述符对象:描述符对象分为:数据属性 或 访问器属性
2.1、数据属性:
①configurable[kənˈfɪgjərəbl]表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
Object.defineProperty(obj,"name",{
configurable : false,
})
console.log(p1); //{ name: 'sun' }
delete p1.name;
console.log(p1); //{ name: 'sun' }
通过这个方法设置好configurable 这个属性,delete就不能把name属性给删除掉了
②enumerable:表示能否通过for in循环访问属性,默认值为false;
Object.defineProperty(obj,"age",{
enumerable:false
})
for(var i in obj){
console.log(obj[i]);
} // sun
通过这个方法给enumerable设置为false,这样对象就不能通过迭代器遍历出age这个属性的值
③writable:表示能否修改属性的值,默认值为false;
④value:包含这个属性的数据值,默认值为undefined。
Object.defineProperty(obj,"age",{
writable :false,
value : 15,
})
console.log(p1.age); //15
p1.age = 20;
console.log(p1.age); //15
给 obj 这个对象新加了一个age属性,并且设置成只读的。就无法修改这个age属性了。
2.2、访问器属性:
①get:get是读取属性,get不带参数,get必须用return返回;
②set:set是修改属性,set有且仅有一个参数,set不需要返回;
var book = {
_year : 2004,
edition : 1
}
Object.defineProperty(book,"year",{
get: function(){
return this._year
},
set: function(newYear){
if(newYear > 2004){
this._year = newYear;
this.edition += newYear - 2004
}
}
})
book.year = 2005;
console.log(book.edition); // 2
console.log(book._year); //2005
由于get方法返回_year的值,set方法通过计算来确定正确的版本。
因此把year的值设置为2005会导致edition的值变为2.
三、数据劫持
数据劫持指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
比较典型的是基于ES5 Object.defineProperty() 和 ES2016 中新增的 Proxy对象。数据劫持最著名的应用当属双向绑定;例如 Vue 2.x 使用的是Object.defineProperty(),Vue 在 3.x 版本之后改用 Proxy 进行实现
Object.defineProperty()
通过 Object.defineProperty 遍历对象的每一个属性,把每一个属性变成一个 getter 和 setter 函数,读取属性的时候调用 getter,给属性赋值的时候就会调用 setter; 当运行 render 函数的时候,发现用到了响应式数据,这时候就会运行 getter 函数,然后 watcher(发布订阅)就会记录下来。当响应式数据发生变化的时候,就会调用 setter 函数,watcher 就会再记录下来这次的变化,然后通知 render 函数,数据发生了变化,然后就会重新运行 render 函数,重新生成虚拟 dom 树。
简单原理:利用 Object.defineProperty(),并且把内部解耦为 Observer, Dep, 并使用 Watcher 相连。
Object.defineProperty() 的问题主要有三个:
- 1、不能监听数组的变化,导致通过数组添加元素,不能实时响应;(无法实时响应方法:push、pop、shift、unshift、splice、sort、reverse)
- 2、object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
- 3、Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
v-model 源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
#myInput {
width: 400px;
height: 50px;
font-size: 40px;
color: red;
}
#contain {
margin-top: 20px;
width: 400px;
height: 200px;
border: 1px solid salmon;
}
</style>
</head>
<body>
<input id="myInput" type="text" />
<div id="contain"></div>
<script>
var text;
window.data = {};
var oIn = document.getElementById("myInput");
var oDiv = document.getElementById("contain");
oIn.addEventListener("input", function (e) {
text = e.target.value;
console.log(text);
window.data.value = text;
});
// Object.defineProperty 方法
Object.defineProperty(window.data, "value", {
get() {
return "";
},
set(v) {
oDiv.innerHTML = v;
},
});
</script>
</body>
</html>