VUE2.x实现双向数据绑定的核心API
作者: 郭政鸿 2020/8/1
前言: 前端有三大框架Vue、React、Angular, 本文知识由Vue引出, Vue的一个十分重要的特性就是
双向数据绑定
, 在Vue的1.x 和 2.x版本中, 双向数据绑定依赖的是Object.defineProperty
API, 此API中双向数据绑定最主要就是依赖get和set
下面我们就用原生javascript来动手实现一个简版的双向数据绑定demo
<input type="text" id="ih" />
<h1 id="vh">h1>
<script>let $ = el => document.querySelector(el);// 定义响应式数据const data = {value: "hello",
};// 事件驱动数据更新
$('#ih').addEventListener("input", e => {
data.value = e.target.value;
});// 模拟模板数据渲染const setValue = () => {
$('#ih').value = data.value;
$('#vh').innerText = data.value;
};// 初始化模板数据function init(value, value2) {
setValue();
}// 定义响应式数据函数function defineReactiveValue(data) {for (const key in data) {let _value = data[key];Object.defineProperty(data, key, {get() {if (this._key === undefined) {this._key = _value;
}return this._key;
},set(newValue) {this._key = newValue;// 更新模板数据
setValue();
}
})
}
}// 使data数据响应式
defineReactiveValue(data);// 初始化模板数据
init();script>
知道了双向数据绑定的基本原理我们在来看看get和set的定义方法吧.
顺便说下, 目前处于RC版本的vue实现双向数据绑定采用的是ES6的Proxy API而不再是ES5的Object.defineProperty
1) 字面量声明时定义
// 死循环代码
let obj = {
num: 665,
get num() {return this.num + 1},
set num(v) {this.num = v + 1}
}
// 解决办法
let obj = {
_num: 665,
get num() {return this._num + 1},
set num(v) {this._num = v + 1}
}
2) Object.create创建时定义
let obj = Object.create(Object.prototype, {
num: {
set(v) {
this._num = v + 10;
},
get() {
this._num || (this._num = 0);
return this._num + 1;
}
}
})
3) Object.defineProperty/Reflect.defineProperty
// 使用数据描述符
let obj = {};
Reflect.defineProperty(obj, "num", {
writable: false,
enumrable: false,
configurable: false,
value: 999
});
// 直接字面量定义的对象: writable, enumrable, configurable值都是true
// 使用存储描述符
let obj = {};
Reflect.defineProperty(obj, "num", {
writable: false,
configurable: false,
get() {
return this.value + 1;
},
set(v) {
this.value += 1;
}
})
4) Object.defineProperties
let obj = {};
Object.defineProperties(obj, {
num: {
writable: true,
enumrable: true,
configurable: true,
value: 0
},
val: {
configurable: true,
get() {
return this.val;
},
set(v) {
this.val = v;
}
}
})
// 若想冻结属性用Object.freeze(即使configurable, writable为true也不可修改)
(Object.freeze || Object)(obj);
// 若想保护prototype
// (Object.freeze || Object)(Fn.prototype);
5) Object.prototype.__defineGetter__/__defineSetter__
let obj = {x: 123}
Object.prototype.__defineGetter__("getX", function() {
return this.x;
});
Object.prototype.__defineSetter__("setX", function(v) {
this.x = v;
});
6) ES6的Proxy实现
let obj = new Proxy({}, {
get(target, key, receiver) {
console.log("get: ");
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log("set: ");
Reflect.set(target, key, value, receiver);
}
});
7) 总结
查看对象的属性描述符
使用Object.getOwnPropertyDescriptor(obj, prop)方法
defineProperty&defineProperties的属性描述符
数据描述符
- configurable: 默认值
false
, 设置为true
该属性描述符才能修改,value也能delete
- enumerable: 默认值
false
,设置为true
对象才能被for in
遍历 - writable: 默认值
false
,设置为true
时value
才能被赋值运算符改变 - value: 默认值
undefined
- configurable: 默认值
存储描述符
function Obj() {
this.xx = "666";
}
// 实例化对象
let obj = new Obj();
// 默认writable, enumerable, configurable的值全为false, value为undefined
Object.defineProperty(obj, "yy", {
writable: false,
enumerable: false,
configurable: false,
value: "999"
})
// 查看配置选项
console.log(Object.getOwnPropertyDescriptor(obj, "yy"));
/* configurable默认值false */
// 设置为false, 不能delete, 不能重新定义属性, 如:
Object.defineProperty(obj, "yy", {}); // 报错
delete obj.yy; // 忽略操作, 无报错
/* configurable默认值false */
/* writable默认值为false */
// 设置为false, 不能通过通过赋值符号赋值
obj.yy = "123"; // 忽略操作, 无报错
/* writable默认值为false */
/* enumerable默认值为false */
// 设置为false, 不能通过for in遍历
for (const key in obj) {
console.log(key); // 只输出xx, 无yy
}
/* enumerable默认值为false */属性描述符可同时设置的键值
configurable enumerable value writable get set 数据描述符 Yes Yes Yes Yes No No 存取描述符 Yes Yes No No Yes Yes 如果一个描述符不具有
value
、writable
、get
和set
中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有value
或writable
和get
或set
键,则会产生一个异常。也就是有
get
或者set
就不能有writable
和value
- get: 默认值
undefined
- set: 默认值
undefined
接收唯一参数作为新值
- get: 默认值
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty