2.3.1 MVVM分层思想
1. MVVM是什么?
M:Model(模型/数据)
V:View(视图)
VM:ViewModel(视图模型):VM是MVVM中的核心部分。(它起到一个核心的非常重要的作用。)
MVVM是目前前端开发领域当中非常流行的开发思想。(一种架构模式。)
目前前端的大部分主流框架都实现了这个MVVM思想,例如Vue,React等。
2. Vue框架遵循MVVM吗?
虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。
Vue框架基本上也是符合MVVM思想的。
3. MVVM模型当中倡导了Model和View进行了分离,为什么要分离?
假如Model和View不分离,使用最原始的原生的javascript代码写项目:
如果数据发生任意的改动,接下来我们需要编写大篇幅的操作DOM元素的JS代码。
将Model和View分离之后,出现了一个VM核心,这个VM把所有的脏活累活给做了,
也就是说,当Model发生改变之后,VM自动去更新View。当View发生改动之后,
VM自动去更新Model。我们再也不需要编写操作DOM的JS代码了。开发效率提高了很多。
<body>
<!-- 准备容器 -->
<!-- View V-->
<div id="app">
姓名:<input type="text" v-model="name">
</div>
<!-- vue程序 -->
<script>
// ViewModel VM
const vm = new Vue({
el : '#app',
// Model M
data : {
name : 'zhangsan'
}
})
</script>
</body>
2.3.2、vm可以访问哪些属性
通过Vue实例都可以访问哪些属性?(通过vm都可以vm. 什么。)
- Vue实例中的属性很多,有的以 $ 开始,有的以 _ 开始。
-
- 所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。
- 所有以 _ 开始的属性,可以看做是私有的属性,这些属性是Vue框架底层使用的。一般我们程序员很少使用。
- 通过vm也可以访问Vue实例对象的原型对象上的属性,例如:vm.$delete...
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
let dataObj = {
msg: "Hello Vue!",
};
const vm = new Vue({
el: "#app",
data: dataObj,
});
// 按说msg是dataObj对象的属性。
console.log("dataObj的msg", dataObj.msg);
// msg属性也可以通过vm来访问呢?
// 这是因为Vue框架底层使用了数据代理机制。
// 要想搞明白数据代理机制,必须有一个基础知识点要学会:Object.defineProperty()。
// console.log("vm的msg", vm.msg);
// 模拟Vue构造函数,无法直接用newVm.msg打印出
// function myVue(obj) {
// return obj
// }
// let newVm = new myVue({
// el: "#app",
// data: dataObj,
// });
// console.log(newVm.data.msg);
</script>
</body>
2.3.3、object.defineProperty()
1. 这个方法是ES5新增的。
2. 这个方法的作用是:给对象新增属性,或者设置对象原有的属性。
3. 怎么用?
Object.defineProperty(给哪个对象新增属性, '新增的这个属性名叫啥', {给新增的属性设置相关的配置项key:value对})
4. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
- value 配置项:给属性指定值
- writable 配置项:设置该属性的值是否可以被修改。true表示可以修改。false表示不能修改。
- enumerable 配置项:设置该属性是否可以被遍历。
-
- true表示该属性是可以遍历的。(可枚举的,可迭代的。)
- false表示该属性是不可遍历的。
- configurable 配置项:设置该属性是否被删除。
-
- true表示该属性是可以被删除的。
- false表示该属性不可以被删除
- getter方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter方法被自动调用。
getter方法的返回值非常重要,这个返回值就代表读取的这个属性它的值。
- setter方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter方法被自动调用。
setter方法上是有一个参数的,这个参数可以接收传过来的值。
注意:当配置项当中有setter和getter的时候,value和writable配置项都不能存在。
<body>
<script>
// 这是一个普通的对象
let person = {};
// 临时变量
let temp;
// 给上面的phone对象新增一个color属性
Object.defineProperty(person, "name", {
//value : '章三',
//writable : true,
enumerable: false,
// true表示该属性是可以遍历的。(可枚举的,可迭代的。)
// false表示该属性是不可遍历的。
configurable: false,
// true表示该属性是可以被删除的。
// false表示该属性是不可以被删除的。
// getter方法配置项
get: function () {
console.log("getter方法执行了@@@");
//return '动态'
//return this.name //递归,死循环
return temp;
},
// setter方法配置项
set: function (val) {
console.log("setter方法执行了@@@", val);
//this.name = val //递归,死循环
temp = val;
},
});
</script>
</body>
2.3.4、数据代理机制
通过访问 代理对象的属性 来间接访问 目标对象的属性。数据代理机制的实现需要依靠:Object.defineProperty()方法。
注意:代理对象新增的这个属性的名字 和 目标对象的属性名要一致,这样我们访问代理对象属性,就像在访问目标对象的属性一样
<script>
// 目标对象
let target = {
name: "zhangsan",
};
// 代理对象
let proxy = {};
// 如果要实现数据代理机制的话,就需要给proxy新增一个name属性。
Object.defineProperty(proxy, "name", {
get() {
console.log("getter方法执行了@@@@");
// 间接访问目标对象的属性
return target.name;
},
set(val) {
target.name = val;
},
});
</script>
2.3.5、数据代理时对属性名的要求
1. Vue实例不会给以_和$开始的属性名做数据代理。
2. 为什么?
如果允许给_或$开始的属性名做数据代理的话。 vm这个Vue实例上可能会出现_xxx或$xxx属性, 而这个属性名可能会和Vue框架自身的属性名冲突。
3. 在Vue当中,给data对象的属性名命名的时候,不能以_或$开始。
<body>
<!-- 容器 -->
<div id="app">
<h1>{{msg}}</h1>
</div>
<!-- vue程序 -->
<script>
const vm = new Vue({
el: "#app",
data: {
msg: "Hello Vue!",
_name: "zhangsan", //不会做数据代码,vm上看不到
$age: 20,//不会做数据代码,vm上看不到
},
});
</script>
</body>
2.3.6、模拟实现数据代理
简单实现myvm.name==options.data.name
<script>
const myvm = new MyVue({
data: {
msg: "Hello Vue!",
name: "jackson",
age: 30,
},
});
</script>
数据代理js
// 实现数据代理,目的是读取 myvm.name == options.data.name
// 定义一个Vue类
class MyVue {
// 定义构造函数
// options 是一个对象{}
// options对象中有一个data配置项
constructor(options) {
Object.keys(options.data).forEach((propertyName, index) => {
Object.defineProperty(this, propertyName, {
get() {
// 读取对象的属性值 对象[变量]
return options.data[propertyName];
},
set(val) {
options.data[propertyName] = val;
},
});
});
}
}
2.3.7、Vue中的数据代理与数据劫持
在vm身上,有两个属性$data,_data,这两个属性都指向Vue底层的真实的data对象,
通过$data,_data获取各属性值,是不会走数据代理机制的
其中:_data是框架内部使用的,可以看做是私有的
$data,这是Vue框架对外公开的一个属性
也就是说Vue框架会将data中的数据实时通过Object.defineProperty拷贝一份放在$data以及_data身上,供vm去使用
1、Vue的数据代理
(1)、基本阐述
vue将_data中的所有数据属性通过Object.defineProperty添加到vm实例上
,并且提供了getter和setter方法
,于是通过vm直接获取数据的时候就调用getter,获取_data中的值
,当修改的时候调用setter修改_data中的值
(2)数据代理有什么用呢?
既然vm上挂的属性就是_data中的数据代理,那么{{vm._data.name}}和{{name}}是等价的,{{vm_data.name='szk2'}}和{{name='szk2'}}也是等价的
所以就是为了写代码的方便,在{{}}直接写数据,或者直接修改就能操作到_data中
2、数据劫持
数据劫持就是将vue代码里我们写的data加工了一下,变成_data,让每个属性有了getter和setter
vue通过监听者observer来监听data中的数据,这个getter和setter就是监听者里面的方法,getter就是监听者获取data中数据的,setter则是监听当数据发生变化的时候执行操作的,当修改属性的时候,setter被调用,在setter方法中就会让订阅者执行重新解析模板的操作,从而改变了页面
先看看我们写的data和加工之后的data有什么区别
代码实现
劫持一层数据结构的数据
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
//下面的方法只能解析一层解构,多层数据结构需要用到递归
function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
obj[k] = val
}
})
})
}
劫持多层数据结构的数据
observer(data)
function observer(target) {
if (typeof target !== 'object' || !target) {
return target
}
for (const key in target) {
if (target.hasOwnProperty(key)) {
const value = target[key]
observerObject(target, key, value)
}
}
}
function observerObject(target, name, value) {
if (typeof value === 'object' || Array.isArray(target)) {
observer(value);
}
Object.defineProperty(target, name, {
get() {
return value
},
set(newVal) {
if (newVal !== value) {
if (typeof value === 'object' || Array.isArray(value)) {
observer(value)
}
value = newVal
}
renderView() //模拟视图渲染操作
}
})
}
3、总结
数据劫持:
创建Vue实例vm,vm身上会有_data属性,_data通过劫持data配置项,再通过defineProperty的getter和setter,得到的响应式的数据。把vue中的data数据拦截改写成具有getter和setter形式的_data,就是数据劫持。
数据代理
vm中_data中的数据又通过数据代理(也是通过defineProperty的getter和setter实现),放置到vm身上,vm可以通过getter方法,setter方法直接使用_data中的数据,方便书写数据
步骤:
1-把vue实例中的data,通过Object.defineProperty的setter和getter进行数据劫持,使得data改写为到_data(使得数据改写为响应式的数据,具有getter和setter);
2-通过vm进行数据代理,代理_data