前言
今天和大家聊一聊ref,响应式数据的底层实现原理。
vue中template是我们的视图层也就是views,每一个views都对应一个组件,此时我们的数据模型Model的url就应该从/去到我们的/about(例如切换了首页到关于页),此时显示的组件就发生了改变
vue中就是这种开发思想,把视图views和model结合起来(VM)总的结合来说就是MVVM开发框架
回顾DOM编程
没有vue的时候我们的dom编程,操作dom元素,获取到一个dom然后做...事情,是这样的
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue MVVM开发框架</title>
</head>
<body>
<span id="container">1</span>
<button id="btn">点击加1</button>
<script>
const oSpan = document.querySelector("#container")
const oBtn = document.querySelector("#btn")
oBtn.addEventListener("click", function () {
oSpan.innerHTML = parseInt(oSpan.innerHTML) + 1
})
</script>
</body>
</html>
获取到dom元素,添加绑定事件,点击做加1。很明显的发现,在这种开发模式下,完全涉及不到model的概念,现在的数据源纯粹来自于页面中的1,但是页面中到底显示的数据是多少他应该是一个响应式
的,需要有一个model
来承接
接下来我们想转换一下思想,从原生的dom编程走向vue,用vue的MVVM思想来完成这个事情应该如何完成呢?
MVVM开发思想
首先我们就需要准备一个数据源,定义一个obj对象(json对象),接下来我们的业务就是,当obj中的value发生改变时,页面就应该更新
es5中有一个监控对象
,它可以让我们定义一个对象上的属性defineProperty
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 最受欢迎的MVVM开发框架</title>
</head>
<body>
<span id="container">1</span>
<button id="btn">点击加1</button>
<script>
const oSpan = document.querySelector("#container")
const oBtn = document.querySelector("#btn")
var obj = {
value: 1
}
var value = 1
oBtn.addEventListener("click", function () {
obj.value++
})
// 监控对象 es5
Object.defineProperty(obj, 'value', {
get: function () {
return value
},
set: function (newValue) {
value = newValue
console.log('点击了按钮...');
oSpan.innerHTML = newValue
}
})
</script>
</body>
</html>
可以看见,当我们点击按钮,obj对象上的value属性值就会发生改变,并且通过es5中的监控对象监控obj,当其值发生改变,我们就打印点击了按钮...
- 既如此,我们就可以把这个newValue给到页面重新渲染
- 这样我们就做到了每次我们的操作,都是操作模型层数据,现在的dom编程就直接被封装到了defineproperty的内部。
理解了这些,接下来看看我们的代码,你就能够理解vue中任何一个响应式对象底层是怎么打造的了
html
<!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>
<span id="container">1</span>
<button id="btn">点击加1</button>
<script>
// 不用dom操作,针对数据状态做业务
var obj = {
value: 1,
}
; (function () {
function watch(obj, key, func) {
console.log('监听数据');
var value = obj[key]
// 数据拦截
Object.defineProperty(obj, key, {
get: function () {
return value
},
set: function (newValue) {
value = newValue
func(newValue)
}
})
}
this.watch = watch
})()
// 数据可以被监听
watch(obj, 'value', function (newValue) {
document.getElementById('container').innerHTML = newValue
})
document.getElementById('btn').onclick = function () {
obj.value++
}
</script>
</body>
</html>
-
HTML 结构 : 页面包含一个
span
元素和一个button
元素。span
元素用于显示数据,而button
元素用于触发数据更新。 -
JavaScript 实现:
- 定义了一个对象
obj
,它有一个属性value
,初始值为1
。 - 使用立即执行函数表达式(IIFE)定义了一个
watch
函数,该函数接受三个参数:要观察的对象、要观察的键以及当值改变时调用的回调函数。 - 在
watch
函数内部,使用了Object.defineProperty
方法来对obj
对象的key
属性进行 getter 和 setter 拦截。getter 返回当前值,setter 更新值并调用回调函数。 watch
函数被调用来观察obj.value
的变化,并在值改变时更新 DOM。button
的点击事件处理器调用了obj.value++
,这将触发watch
函数中的 setter,从而更新 DOM。
- 定义了一个对象
-
响应式原理 : 当你点击按钮时,
obj.value
的值会增加,这触发了watch
函数中set
方法的执行,set
方法不仅更新了obj.value
的值,还调用了回调函数,这个回调函数负责更新 DOM,使span
元素的内容与新的obj.value
值同步。 -
与 Vue.js 的对比 : Vue.js 的响应式系统更复杂,它使用了依赖收集和调度机制。Vue 使用了
Observer
类来递归地遍历对象,将其所有属性转换为 getter/setter。同时,Vue 使用Watcher
类来连接数据和视图,它会在数据变化时触发视图更新。此外,Vue 还有组件系统、模板编译器等高级特性
小结
目前我们就做了一个简单的响应式数据的打造原理,更加深层的东西将在以后逐步更新,创作不易,望多支持,如有错误,欢迎指正。