前言
本文旨在让小白大概了解一下怎么实现响应式(即:双向绑定 v-model),像源码中核心部分的观察者模式,模板渲染注册观察者等操作统统略过,大佬看到这就可以忽略此文了。
文章分两部分来讲,基础补充(Object.defineProperty)和代码的简单实现。
1、响应式核心 – Object.defineProperty
可能很多小白不知道这个原生的js方法,这是正常的,这里作者希望小白们在将来研究任何源码之前,先认真学习一下JavaScript,这是现在普遍的现象,一毕业出来实习,你可能JavaScript都没有学多少,公司 ‘啪’的一下扔一个Vue框架给你开发,对,确实Vue很好用,但是如果你局限于使用,你会在工作中越走越难。
推荐一个学习JavaScript的网站:https://zh.javascript.info/ 比MDN好在知识没有那么杂乱。
回归正题
Vue.js实现响应式的核心时利用了ES5的Object.defineProperty,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因.
**Object.defineProperty()**方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
而Vue的响应式利用的正是该方法的存取描述符中的get/set,来实现的.
代码演示:
let user = {
name: "YC",
surname: "Z"
};
// 在User对象中新增一个fullName的key
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // 取值调用get方法: YC Z
for(let key in user) alert(key); // name, surname 遍历默认不不可见,可看上文表格中
user.fullName ="L LL"; // 赋值调用set方法
console.log(user.name); // LL
console.log(user.surname); // L
2、Demo – 我的垃圾响应式代码
我们现在知道了Object.defineProperty的基本使用,那么来一步步写一个简单的响应式demo
Html部分
我们以一个input输入框来做触发条件,来控制div中的内容双向绑定显示
<div id="app">
<input id="input1" oninput="model()" />
<div id="text"></div>
</div>
JavaScript部分
<script>
// 1:获取id为app的div
var app = document.getElementById("app");
// 2:初始化被监听的对象data
var data = {
'message':''
};
// 3:使用Object.defineProperty设置监听数据message
Object.defineProperty(data, 'changeMessage', {
enumerable: true,
configurable: true,
get() {
console.log('message属性被读取了');
return
},
set(newVal) {
console.log('message属性被修改了');
this.message= newVal;
render();
}
}
)
// 4:获取input的值赋值给监听数据
function change() {
var input = document.querySelector('#input1');
// 触发set事件
data.changeMessage = input.value
}
// 5:重写div内容的方法
function render() {
// 获取显示内容的div
var testDiv = document.querySelector('#text');
// 置空内容
testDiv.innerHTML = "";
// 创建 p 标签
var ele = document.createElement('p');
// 给 p 标签赋值
ele.innerText = data.message;
// 把 p 标签加入div中
testDiv.appendChild(ele);
}
</script>
到这里我想思路清晰的人肯定会有疑问为什么不把change方法和render方法合并起来,让input标签触发呢,如下:
function change(){
var input = document.querySelector('#input1');
var testDiv = document.querySelector('#text');
testDiv.innerHTML = "";
var ele = document.createElement('p');
ele.innerText = input.value;
testDiv.appendChild(ele);
}
我们可以思考一下,现在我们只是一个div绑定了值,但是在实际代码中,我们data里面的值可能被七八个地方绑定到,如果是不同的标签呢?
其实不用纠结,因为Vue实现中不止是只有一个双向绑定,还有虚拟dom,diff算法,template解析等等,相互配合,不用纠结于一个地方,这里只是单独拿出一个小点来讲,所以才会引发这些思考。