转载声明:原文链接:https://blog.csdn.net/weixin_45792953/article/details/112472338
数据响应式是vue的特性之一,在面试过程中也会常常被问起响应式原理,现在就让我们深入了解一下vue2.0中如何实现响应式,
下图是Vue2.0中对响应式原理的描述,其核心就是使用Object.defineProperty中的get/set进行数据劫持,
虽然Vue3.0中使用Proxy(代理)去实现响应式,其实原理都差不多,在3.0中主要是使用Proxy的get和set实现响应式,如果理解defineProperty,Proxy也会很快理解的
Object.defineProperty是什么?
define是定义的意思 Property是属性/描述的意思
其实defineProperty就是定义属性
主要有value、writable、configurable、enumerable、get、set这几个配置项
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
let object1={
property1:0
}
//例如
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false, //是否可写
configurable: false, //是否可删除
enumerable: false, //是否可枚举(遍历)
});
//get set配置项
Object.defineProperty(object1, 'property1', {
get() {//get方法在进行获取的时候触发
return property1
},
set(newVal) {//get方法在进行设置的时候触发,主要在此实现数据劫持
property1 = newVal
}
});
//我这边常用一种复数形式Object.defineProperties
Object.defineProperties(object1, {
property1: {
get() { //get方法在进行获取的时候触发
return property1
},
set(newVal) { //get方法在进行设置的时候触发,主要在此实现数据劫持
property1 = newVal
}
}
})
使用上述的内容实现Vue2.0中的双向绑定
实现出来的效果
下面使用Object.defineProperty实现一个响应式计算器,
核心部分在:数据被修改(set)时重新计算结果
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式计算器</title>
<style>
.btnGroup > button.active{
background-color: orange;
color: #fff;
}
</style>
</head>
<body>
<div id="computed">
<div class="result">0</div>
<div class="inputGroup">
<input type="number" class="ipt1" value="0" />
<br/>
<input type="number" class="ipt2" value="0" />
</div>
<div class="btnGroup">
<button data-field="add" class="active">+</button>
<button data-field="sub">-</button>
<button data-field="mul">*</button>
<button data-field="div">/</button>
</div>
</div>
<!-- 引入js文件 -->
<script src="./js/index.js"></script>
</body>
</html>
js部分
class Arithmetic { //定义算法类
add(a, b) {
return a + b
}
sub(a, b) {
return a - b
}
mul(a, b) {
return a * b
}
div(a, b) {
return a / b
}
}
class Computed extends Arithmetic {
constructor() {
super();
this.result = document.getElementsByClassName('result')[0]; //获取结果dom
this.ipt1 = document.getElementsByClassName('ipt1')[0]; //获取输入框
this.ipt2 = document.getElementsByClassName('ipt2')[0];
this.btnGroup = document.getElementsByClassName('btnGroup')[0];
this.btnItems = this.btnGroup.getElementsByTagName('button');
this.data = this.defineData();
this.btnIndex = 0; //记录选中btn索引
}
// 1.进行初始化
init() {
this.bindEvent();
}
// 2.绑定事件
bindEvent() {
this.btnGroup.addEventListener('click', this.onFieldBtnClick.bind(this), false)
this.ipt1.addEventListener('input', this.onNumberIpt.bind(this), false)
this.ipt2.addEventListener('input', this.onNumberIpt.bind(this), false)
}
// 6.使用Object.defineProperty进行数据劫持
defineData() {
let _obj = {},
field = 'add', //保存计算方法字段
fNumber = 0, //inp1的value
sNumber = 0; //inp2的value
let that = this;
// 进行数据劫持
Object.defineProperties(_obj, {
fNumber: {
get() {
return fNumber
},
set(newVal) {
fNumber = newVal
// 重点:在fNumber改变之后计算结果,此处vue使用的是Dom diff算法
that.computedResult(fNumber, sNumber, field);
}
},
sNumber: {
get() {
return sNumber
},
set(newVal) {
sNumber = newVal
that.computedResult(fNumber, sNumber, field);
}
},
field: {
get() {
return field
},
set(newVal) {
field = newVal
that.computedResult(fNumber, sNumber, field);
}
}
})
return _obj
}
// 3.btn点击事件
onFieldBtnClick(ev) {
let e = ev || window.event,
tar = e.target || e.srcElement,
tarName = tar.tagName.toLowerCase();
tarName === 'button' && this.update(tar);
}
// 4.btn进行选项卡切换并更改field
update(target) {
this.btnItems[this.btnIndex].className = '';
this.btnIndex = [].indexOf.call(this.btnItems, target);
target.className += ' active';
this.data.field = target.getAttribute('data-field');
}
// 5.ipt改变(input)事件
onNumberIpt(ev) {
let e = ev || window.event,
tar = e.target || e.srcElement,
className = tar.className,
val = Number(tar.value.replace(/\s+/g, '')) || 0; //去除input的空格
switch (className) {
case 'ipt1':
// 改变fNumber触发set()将重新计算结果
this.data.fNumber = val;
break;
case 'ipt2':
// 改变sNumber触发set()将重新计算结果
this.data.sNumber = val;
break;
default:
break;
}
}
// 7.使用继承算法类的方法进行结果计算
computedResult(fNumber, sNumber, field) {
this.result.innerHTML = this[field](fNumber, sNumber)
}
}
new Computed().init();