vue2实现数据双向绑定的数据挟持
vue2数据双向绑定介绍
双向数据绑定:数据劫持 + 发布-订阅模式
本篇主要讲vue2的数据挟持
- 数据挟持
监听数据的变化 数据劫持是实现双向绑定的底层技术,其核心在于拦截对数据的读写操作,从而触发更新机制。
vue2数据挟持的原理
主要利用的是Object.Object.defineProperty (ES5)
通过定义属性的
getter
和setter
,在数据被访问或修改时执行回调函数。
Object.defineProperty
一、核心功能
定义/修改属性 通过该方法可以直接在对象上添加新属性,或调整已有属性的特性(如是否可写、可枚举等)。与普通属性赋值(如
obj.prop = value
)不同,它允许开发者对属性行为进行细粒度控制控制属性特性 通过属性描述符(
descriptor
)参数,可以设置以下特性:数据描述符
数据描述符(直接操作值)
value
属性的默认值(如数值、对象等)
writable
:若为false
,属性值不可被赋值运算符修改(但可通过defineProperty
修改)configurable
:若为false
,属性无法被删除,且除value
和writable
外的特性不可修// 定义只读属性 const obj = {}; Object.defineProperty(obj, 'id', { value: 123, writable: false, configurable: false }); obj.id = 456; // 修改无效(严格模式下报错) console.log(obj.id); // 123 // 使用存取器实现响应式 let _value = 0; Object.defineProperty(obj, 'count', { get() { return _value; }, set(newVal) { _value = newVal; console.log('值已更新:', newVal); }, enumerable: true }); obj.count = 10; // 输出 "值已更新: 10"
此方法需要配合深度监听实现
接下来看代码
Object.defineProperty('挟持数据','key','相关操作')
具体代码示例
const obj = {name:'张三'}
Object.defineProperty(obj,key,{
get() { // 当obj.name是调用
// 可以执行访问是的相关操作
return obj[key]
},
set(newValue) { // obj.name = '李四' // 赋值是执行
// 执行赋值操作
if(obj[name] !== newValue) {
// 此处如果不是单纯的对象,则需要深度遍历每一个来监听
observer(newVal);
obj[name] = newValue
// 此处
upadtaView() //视图跟新函数
}
}
})
数组需要单独监听
劫持数组的原有方法进行改造
// 绑定数组监听 const arrProto = Object.create(Array.prototype); // 获取Array原型里面的方法 // 将数组方法复写挂载到新对象中 ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { // 改造数组 arrProto[methodName] = function() { // 更新视图 updateView(); // 执行原型链的方法 oldArrayProperty[methodName].call(this, ...arguments); } })
接下来看完整事例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class="but">改变</button>
<div class="content"></div>
<script>
const data = {
name: 'Tom',
age: 18,
children: [
{
name: 'Jerry',
age: 16
}
]
}
// 视图更新函数
const updateView = () => {
// 这里面就可以利用diff进行比对dom
console.log('视图更新了')
}
// 数组单独监听
const oldFn = Array.prototype
// 劫持数组的方法
const arr = Object.create(oldFn)
// 增加数组方法
const fnArr = ['push','pop','shift','unshift','splice']
fnArr.forEach(fnName => {
arr[fnName] = function() {
updateView()// 更新视图
oldFn[fnName].call(this,...arguments)
}
})
// 深度监视
const observer = (obj) => {
if(typeof obj !== 'object')return obj;
if(Array.isArray(obj)) {
obj.__proto__ = arr
}
for(let key in obj) {
defineReactive(obj,key,obj[key])
}
}
// 执行对象监听
const defineReactive = (obj,key, value) => {
observer(value)
Object.defineProperty(obj,key,{
get() {
return value
},
set(newValue) {
if(newValue !== value) {
// 深度监听,避免重新设置对象,无法监听
observer(newValue)
value = newValue
updateView()
}
}
})
}
observer(data)
const content = document.querySelector('.content')
content.innerHTML = data.children[0].name
const but = document.querySelector('.but')
but.addEventListener('click', () => {
// data.children[0].name = '张三'
data.or = '123'
})
</script>
</body>
</html>
优缺点
- 可以兼容其他浏览器
- 深度监听需要一次性递归
- 无法监听 新增 \ 删除 属性
- 无法原生监听数组,需要特殊处理