vue2响应式原理
Vue2
是借助Object.defineProperty()
实现的,而Vue3
是借助Proxy
实现的- 想深入学习
Vue2
的响应式原理, 需要先学习Object.defineProperty()
方法- 为了称呼方便, 后续说
Vue
响应式原理统一指Vue2
的响应式原理
1.Object.defineProperty 方法 - 简介
- 定义一个对象
obj
, 需要具备name
/age
等属性 - 当
age
变化时, 在控制台打印最新的值 - 当访问
obj.age
时, 控制台会显示xxx
读取了age
属性
演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
<p></p>
<button id="btn">修改年龄</button>
</div>
<script>
const obj = {
name: 'zs'
}
Object.defineProperty(obj, 'age', {
// 访问 obj.age 时这个方法会自动执行
get() {
console.log('有人访问了 age !', temp)
return 18
},
// 给 obj.age 赋值时这个方法会自动执行, 我们也称之为拦截
set(newVal) {
console.log(newVal)
}
})
document.querySelector('p').innerHTML = `${ obj.name } --- ${ obj.age }`
document.querySelector('#btn').onclick = function() {
obj.age = 20
}
</script>
</body>
</html>
总结
- 使用
Object.defineProperty()
可以无痕的监视到对象的属性什么时候发生变化, 什么时候被访问
2.Object.defineProperty 方法 - 利用临时变量来中转
- 使用了
Object.defineProperty()
后发现get
/set
并不好用 - 无法设置数据, 同时也不能获取到变化的属性
解决方法
-
定义一个临时变量来中转
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="box"> <p></p> <button id="btn">修改年龄</button> </div> <script> const obj = { name: 'zs' } let temp = 18 Object.defineProperty(obj, 'age', { // 访问 obj.age 时这个方法会自动执行 get() { // 一般会在这里收集依赖, 例如: 网页中 p / h1 / div 等标签要渲染 age 数据 // 注意: 这里是收集这些依赖, 下面会在 set 中自动通知这些依赖来更新 DOM console.log('有人访问了 age !', temp) return temp }, // 给 obj.age 赋值时这个方法会自动执行, 我们也称之为拦截 set(newVal) { // 一般当数据变更时, 通知依赖项更新 DOM console.log(newVal) temp = newVal } }) document.querySelector('p').innerHTML = `${ obj.name } --- ${ obj.age }` document.querySelector('#btn').onclick = function() { obj.age ++ } </script> </body> </html>
-
通过定义一个临时变量作为中转, 可以让
get
/set
方法用起来就像普通属性去获取 / 修改
3.Object.defineProperty 方法 - 使用闭包进行封装 defineReactive 函数
- 利用闭包, 来封装一个方法
val
局部变量被内层函数get
/set
使用, 形成了闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
<p></p>
<button id="btn">修改年龄</button>
</div>
<script>
const obj = {
name: 'zs'
}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
// 访问 obj.age 时这个方法会自动执行
get() {
// 一般会在这里收集依赖, 例如: 网页中 p / h1 / div 等标签要渲染 age 数据
// 注意: 这里是收集这些依赖, 下面会在 set 中自动通知这些依赖来更新 DOM
console.log(`有人访问了 ${ key } !`, val)
return val
},
// 给 obj.age 赋值时这个方法会自动执行, 我们也称之为拦截
set(newVal) {
// 一般当数据变更时, 通知依赖项更新 DOM
console.log(newVal)
val = newVal
}
})
}
defineReactive(obj, 'age', 18)
document.querySelector('p').innerHTML = `${ obj.name } --- ${ obj.age }`
document.querySelector('#btn').onclick = function() {
obj.age ++
}
</script>
</body>
</html>
总结
- 通过利用闭包的特性, 封装一个函数, 可以很好的定义对象的「响应式属性」
- 将来
Vue
就是利用这一特性, 来实现的响应式原理
4. 响应式原理 - 简介
讲解
- Vue 响应式核心就是利用了两个类,
Dep
类和Watcher
类 - 每个定义在
data
中的数组、对象中都会有一个dep
属性,访问属性的时候get
方法会收集对应的Watcher
总结
- 初始化
data
的时候, 调用observe()
方法给data
里的属性定义get
方法和set
方法 - 渲染真实 DOM 的时候,
Watcher
会访问页面上使用的属性变量 - 由于
Object.defineProperty()
的特点, 会自动执行get
方法, 给数据的Dep
都加上渲染函数,每次修改数据时通知渲染Watcher
更新视图