Vue2是借助Object.defineProperty()实现的,而Vue3是借助Proxy实现的- 想深入学习
Vue2的响应式原理, 需要先学习Object.defineProperty()方法- 为了称呼方便, 后续说
Vue响应式原理统一指Vue2的响应式原理
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>Documenttitle>
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() 可以无痕的监视到对象的属性什么时候发生变化, 什么时候被访问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>Documenttitle>
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 方法用起来就像普通属性去获取 / 修改
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>Documenttitle>
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 就是利用这一特性, 来实现的响应式原理
Dep 类和 Watcher 类data 中的数组、对象中都会有一个 dep 属性,访问属性的时候 get 方法会收集对应的 Watcher
data 的时候, 调用 observe() 方法给 data 里的属性定义 get 方法和 set 方法Watcher 会访问页面上使用的属性变量Object.defineProperty() 的特点, 会自动执行 get 方法, 给数据的 Dep 都加上渲染函数,每次修改数据时通知渲染 Watcher 更新视图