vue响应式原理
数据驱动
数据响应式:据模型仅仅是普通的JavaScript
对象,而当我们修改数据时,视图会进行更新,避免了频繁的DOM
操作,提高开发效率,这与Jquery
不一样,Jquery
是频繁的操作Dom
双向绑定:
数据改变,视图改变,视图改变,数据也随之改变( 双向绑定中是包含了数据响应式的内容)
我们可以使用`v-model` 在表单元素上创建双向数据绑定
数据驱动是Vue
最独特的特性之一
开发过程中仅仅需要关注数据本身,不需要关心数据是如何渲染到视图中的。主流的`MVVM`框架都已经实现了数据响应式与双向绑定,所以可以将数据绑定到`DOM`上。
响应式核心原理
vue2原理
官方文档:https://cn.vuejs.org/v2/guide/reactivity.html
在Vue2.x
中响应式的实现是通过Object.defineProperty
来完成的,注意该属性无法降级(shim)处理,所以Vue
不支持IE8
以及更低版本的浏览器的原因。
数据变化反应到导视图
声明式改变视图:
将data中name属性的值作为文本渲染到标记了v-text的p标签内部,在vue中,我们把这种标记式的声明式渲染叫做指令
<!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="app">
<p v-text='name'></p>
<p v-text='age'></p>
</div>
<script>
let data = { name: '李白', age: 12 }
Object.keys(data).forEach(key => {
define(data, key, data[key])
});
function define(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value = newValue
compile()
}
})
}
function compile() {
let app = document.querySelector('#app')
let nodes = app.childNodes
// console.log(nodes);
nodes.forEach(item => {
if (item.nodeType === 1) {
let arr = item.attributes
console.log(arr);
Array.from(arr).forEach(node => {
let name = node.nodeName
let value = node.nodeValue
if (name === 'v-text') {
item.innerHTML = data[value]
}
})
}
})
}
compile()
</script>
</body>
</html>
命令式改变视图:
通过原始的操作dom的方式让每一次的name的最新值都能显示到p元素内部
<!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="app">
<p></p>
</div>
<script>
let p = document.querySelector("#app p")
let data = { name: '李白', age: 12 }
// console.log(Object.keys(data));
Object.keys(data).forEach(key => {
console.log(data);
console.log(key);
define(data, key, data[key])
});
function define(data, key, value) {
// console.log(data, key, value);
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value = newValue
p.innerHTML = value
}
})
}
p.innerHTML = data.name
</script>
</body>
</html>
视图变化反应到数据
<!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="app">
<input type="text" v-model="name">
</div>
<script>
let data = {
name: '李白',
age: 18
}
Object.keys(data).forEach(key => {
defineReactiveProperty(data, key, data[key])
})
function defineReactiveProperty(data, key, value) {
console.log();
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
if (newValue === value) {
return
}
value = newValue
// 使用声明式渲染,就要便利所有节点,看哪个节点有我们的自定义绑定属性
compile()
}
})
}
function compile() {
let app = document.querySelector('#app')
// 拿到 app 下所有子元素
const nodes = app.childNodes
nodes.forEach(node => {
if (node.nodeType === 1) {
const attrs = node.attributes
Array.from(attrs).forEach(attr => {
const nodeName = attr.nodeName
const nodeValue = attr.nodeValue
if (nodeName === 'v-model') {
// 当data中数据发生变化时,改变文本框的value属性,以更新文本框的值
node.value = data[nodeValue]
// 当文本框的内容发生变化时,更新 data 中属性的值
node.addEventListener('input', e => {
data[nodeValue] = e.target.value
})
}
})
}
})
}
compile()
</script>
</html>
Vue3响应式原理
Vue3
的响应式原理是通过Proxy
来完成的。
Proxy
直接监听对象,而非属性,所以将多个属性转换成getter/setter
的时候,不需要使用循环。
Proxy
是ES6
课程中新增的,IE
不支持
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Proxy</title>
</head>
<body>
<div id="app">hello</div>
<script>
//模拟Vue中的data选项
let data = {
msg: "hello",
count: 0,
};
//模拟Vue实例
//为data创建一个代理对象vm,这样就可以通过vm.msg来获取data中的msg属性的值,而这时候会执行get方法
let vm = new Proxy(data, {
// 当访问vm的成员时会执行
//target表示代理的对象(这里为data对象),key表示所代理的对象中的属性
get(target, key) {
console.log("get key:", key, target[key]);
return target[key];
},
//当设置vm的成员时会执行
set(target, key, newValue) {
console.log("set key:", key, newValue);
if (target[key] === newValue) {
return;
}
target[key] = newValue;
document.querySelector("#app").textContent = target[key];
},
});
//测试
vm.msg = "aaaa";
console.log(vm.msg);
</script>
</body>
</html>
双向数据绑定
双向数据绑定包含两部分内容,数据变化更新视图,视图变化更新数据。
实现双向数据绑定
基本的思路就是,我们可以给文本框(第一个文本框)添加一个input
事件,在输入完数据后触发该事件,同时将用户在文本框中输入的数据赋值给data
中的属性(视图变化,更新数据,而当数据变化后,会执行行observer.js
中的set
方法,更新视图,也就是触发了响应式的机制)。
//处理v-model
modelUpdater(node, value, key) {
//v-model是文本框的属性,给文本框赋值需要通过value属性
node.value = value;
new Watcher(this.vm, key, (newValue) => {
node.value = newValue;
});
//实现双向绑定
node.addEventListener("input", () => {
this.vm[key] = node.value;
});
}
为当前的文本框节点添加了input
事件,当在文本框中输入内容的时候会触发该事件,同时,将用户在文本框节点中输入的值重新赋值给了data
中对应的属性。
在文本框中输入值,对应的差值表达式和v-text
中的内容都会发生改变。同时在控制台中输出vm.msg
的值会发现数据也发生了变化。
当给data
中的属性赋值后,会执行observer.js
中的set
方法,更新视图,也就是触发了响应式的机制。