我们都知道Vue是一个构建数据驱动的渐进式框架,渐进式的意思就是在开发过程中根据需要引入三方包,不断完善项目。数据驱动就是数据发生改变,页面自动刷新,也就是数据的双向绑定,响应式。这也是Vue最大的特性和优点之一
那么实现响应式的原理到底是什么呢?本文主要就是概述一下Vue响应式(数据双向绑定)的原理
主要是利用了MVVM架构的思想,使用Object.property()数据劫持结合订阅者发布者模式,给Vue中所有的数据添加了get和set方法,视图想要拿数据必须走get方法,拿的是get中的返回值。当数据更新后会自动触发set方法,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
上述原理已经说到,Vue响应式原理是基于MVVM架构的思想实现的,但是MVVM又是由MVC衍生而来,所以在此先简述一下MVC的架构思想。
核心思想:代码层次分明,能够清晰明了分离 数据定义层Model ,渲染视图层View,业务逻辑控制层controller
特点:前端的MVC架构并不能简化代码,只是让代码看起来更具有规范性。
<body>
<div class="box">div>
<script>
// model:数据模型层,提供数据
const data = {
uname: '亚洲舞王赵四'
}
// view视图层,渲染页面
function render(){
document.querySelector('.box').innerHTML = `${data.uname}`
}
render();
// controller:控制器层,负责交互行为,修改数据,通知视图层重新渲染页面
function changeUname(){
data.uname = '象牙山小诸葛刘能';
render();
}
script>
body>
模型层(Model): 负责提供数据
视图层(View): 负责渲染页面
视图模型层(ViewModel):连接模型层和视图层的桥梁
特点: 视图更新 通知 defineProperty 更新数据;数据更新 defineProperty 重新渲染页面
js在 Object类提供了一个数据挟持的静态方法 Object.defineProperty()
被挟持的数据 永远不能直接赋值,必须走set 方法
被挟持的数据 获取不到自己的值,获取的是get方法 return 的数据
var data = {
name:"象牙山小诸葛"
}
//数据挟持,前两个参数分别是要劫持的对象和劫持对象中的属性
Object.defineProperty(data,'name',{
//获取挟持数据的值时自动触发
get(){
console.log('get....');
return '亚洲舞王';
},
//设置 挟持数据的值时自动触发
set(newVal){
console.log('set....',newVal);
}
})
data.name = '铁锹战神' //直接赋值失效,把值传给了set方法的newVal
console.log(data.name); //获取值 拿不到对象中的数据,拿到的是 get方法 return的数据 ====输出'亚洲舞王'
// 模型层
let data = {
uname:'象牙山小诸葛'
}
// 视图模型层
let tempUname = data.uname;
Object.defineProperty(data, 'uname', {
get(){
// 访问'uname'时触发的方法
console.log('get');
return tempUname;
},
set(newVal){
// 修改'uname'时触发的方法
console.log('set');
tempUname = newVal;
render();
}
})
// 视图层
function render(){
document.querySelector('#app').innerHTML = `${data.uname}
${data.uname} οnchange='changeUname(this)' />`
}
render();
function changeUname(inp) {
console.log(inp.value);
data.uname = inp.value;
}
上述响应式原理是Vue2.x版本的,这个响应式会有不完美的地方,那就是监听不到对象的新增属性、数组长度以及通过数组下标添加的新元素
vue底层对以下7个方法单独做了新的封装,让通过以下方法修改的数组也可以是响应式的:
push() pop() shift() unshift() splice() sort() reverse()
`解决`:
# 对象的新增属性: 1. this.$set(对象,'属性','值'); 2. 合并对象:Object.assign({}, 对象, 新增的对象); 3. 浅拷贝: {...对象, 属性:值}
# 数组的操作: 1. this.$set(数组, 索引 ,'值'), 2. 七种方法之一
Vue3.x底层利用了Es6的新特性Proxy构造函数来实现数据的响应式,通过Proxy来代理整个数据对象,解决了Vue2.x版本响应式的缺陷
Proxy简易代码实现:Proxy是可以监听到对象的新属性和数组新元素的,下面代码没有写,只是让大家知道Proxy也可以实现数据的响应式
// M: 模型层
let data = {
uname: '刘能'
}
// VM: 视图模型
let vm = new Proxy(data, {
get(target, key) {
// target: 监听的对象
// key: 对象的属性名
// 访问了属性时触发的方法
return target[key]
},
set(target, key, newVal) {
// 修改了属性时触发的方法
console.log('set');
target[key] = newVal;
render();
}
})
// V: 视图层
function render() {
let app = document.querySelector('#app');
app.innerHTML = `${vm.uname}
${vm.uname} />`
}
render();
function changeUname(input) {
vm.uname = input.value;
}