赞
踩
MVVM
原生的Javascript代码Model和View没有分离,如果数据发生任意的改动, 接下来我们需要编写大篇幅的JS代码操作DOM元素更新视图
MVVM
是目前前端开发领域当中倡导Model和View进行分离的开发思想或者架构模式,大部分主流框架如Vue和React都借鉴了这个MVVM思想
核心VM
负责更新,当Model发生改变之后VM自动去更新View,同理当View发生改动之后VM自动去更新ModelVue框架中对应MVVM中的角色
M(Model)
: 对应data配置项中的数据
V(View)
: 对应容器中的模板语句, Vue实例的所有属性及Vue原型上的所有属性在模板语句中都可以直接使用
VM(ViewModel)
: 对应Vue的实例对象也是MVVM中核心部分
<!-- 准备容器 -->
<!-- View V-->
<div id="app">
姓名:<input type="text" v-model="name">
</div>
<!-- vue程序 -->
<script>
// ViewModel vm表示Vue实例
const vm = new Vue({
el : '#app',
// Model M
data : {
name : 'zhangsan'
}
})
</script>
代理机制的原理
Vue实例可以访问的属性有三种: 以$
或以_
开始的属性用来和数据代理的属性区分开, Vue实例对象的原型对象的属性,Vue实例对象代理的目标对象data上的属性
以$开始的属性
: 可以看做是公开的属性,这些属性是供程序员使用的以_开始的属性
: 可以看做是私有的属性,这些属性是Vue框架底层使用的, 程序员很少使用对象新增属性的方法: Object.defineProperty(新增属性的对象, '新增的属性名', {新增属性的相关配置项key:value})
属性配置项 | 作用 |
---|---|
value | 设置新增属性的值 |
writable | 设置新增属性的值是否可以被修改, true表示可以修改 , 默认是false表示不能修改 |
enumerable | 设置新增属性是否可以遍历,true表示可以遍历的,默认是false表示不可遍历,Object.keys(对象) 可以遍历对象的属性 |
configurable | 设置新增属性是否可以被删除,true表示可以被删除, ,默认是false表示不可删除,delete 对象.属性 可以删除对象的属性 |
getter方法 | 当读取新增属性值的时候,getter()方法被自动调用, 方法返回的值就是新增属性的值 |
setter方法 | 当给新增属性赋值的时候,setter(val)方法被自动调用,val参数可以接收修改后的值 |
<script>
// 这是一个普通的对象
let phone = {}
// 临时变量
let temp
// 给phone对象新增一个color属性并设置setter和getter方法
Object.defineProperty(phone, 'color', {
//value : '太空灰',
//writable : true,
enumerable : false,
configurable : false
// getter方法配置项
get : function(){
console.log('getter方法执行');
return temp
// 以下这种写法会造成死循环,一直读取新增属性值一直调用get方法
//return this.color
},
// setter方法配置项
set : function(val){
console.log('setter方法执行',val);
temp = val
// 以下这种写法会造成死循环,一直给新增属性赋值一直调用set方法
//this.color = val
}
// 在ES6对象中的函数/方法:function是可以省略的
get(){
console.log('getter方法执行');
return temp
},
// setter方法配置项
set(val){
console.log('setter方法执行',val);
temp = val
}
})
</script>
代理机制的实现
数据代理机制就是代理对象新增一个和目标对象相同属性名的属性,通过访问代理对象的属性
来间接访问目标对象的属性
,本质上是不同对象的属性
<script>
// 目标对象
let target = {
name : 'zhangsan'
}
// 代理目标对象的name属性加给代理对象新增一个name属性(属性名要一致)
let proxy = {}
Object.defineProperty(proxy, 'name', {
get(){
// 间接访问目标对象的属性
return target.name
},
set(val){
target.name = val
}
})
</script>
在Vue框架中代理对象是Vue的实例对象vm
,目标对象是构造函数的options
参数中的data对象
以_和$开始
的属性名做数据代理(vm就没有该属性),所以我们就无法通过vm间接访问data对象上以_和$开始
的属性_和$
这两个符号开始,防止和Vue框架自身的属性名冲突发生属性值覆盖<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Hello Vue!',
// 以下这两个属性就是data对象的属性,但vm不会做数据代理
_name : 'zhangsan',
$age : 20
}
})
</script>
手动实现Vue框架的数据代理机制
需求: 代理对象vm负责代理目标对象data的msg属性
<script>
const vm = new Vue({
data : {
msg : 'Hello Vue!',
name : 'zhangsan',
age : 20
}
})
</script>
<script>
// 定义一个Vue类
class Vue {
// 定义构造函数
constructor(options){// options是一个简单的纯粹的JS对象{},有一个data配置项对象
// 获取data对象的所有的属性名
Object.keys(options.data).forEach((propertyName, index) => {
// 如string msg 0
console.log(typeof propertyName, propertyName, index)
// 如果是以_和$开始的属性名就不做数据代理
let firstChar = propertyName.charAt(0)
if(firstChar != '_' && firstChar != '$'){
// this就是Vue的实例对象vm
Object.defineProperty(this, propertyName, {
get(){
// propertyName是个字符串,通过对象["属性名"]的方式读取属性值
return options.data[propertyName]
},
set(val){
options.data[propertyName] = val
}
})
}
})
}
}
</script>
_data和$data
<script>
// 调用
function Vue(options){
this._init(options);
Vue.prototype._init = function (options){
// 调用方法将options合并到$options,即$options含有options的所有属性
// 程序执行到这里的时候vm上还没有_data属性
var data = vm.$options.data;
// 程序执行完这个代码之后,vm对象上多了一个_data这样的属性
// 如果data是函数,则调用getData(data, vm)来获取data对象
// 如果data不是函数,则直接将data对象返回给data变量, 并且同时将data对象赋值给vm._data属性
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
// 对于Vue实例vm来说,_data和$data都直接指向了底层真实的data对象,_data是私有的用于框架内部使用的, $data是公开的是给程序员使用
// 如果我们程序员不想走代理的方式读取data(不走getter和setter方法),可以通过_data和$data属性直接读取data当中的数据
// 判断字符串是否以_和$开始,true表示是, false表示否
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
// 数据代理: 给vm新增属性,代理_data即data对象的所有属性
while (i--) {
var keys = Object.keys(data);
// key是data对象的一个属性名
var key = keys[i];
// sharedPropertyDefinition是新增属性的配置项
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
proxy(vm, "_data", key)
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(vm, key, sharedPropertyDefinition);
}
}
}
</script>
当修改data配置项中的数据后,页面实现自动改变/刷新的响应式效果
数据劫持
Vue的响应式是通过数据劫持机制实现的: 底层使用了Object.defineProperty方法
给新增的属性都配置了setter方法对数据进行劫持
修改属性值和重新渲染页面
的业务代码后期通过vm.$data.属性名
的方式追加的属性默认没有添加响应式处理,需要通过调用Vue.set/$set(目标对象,‘属性名’, 值)
给追加的属性添加响应式处理
vm或vm.$data
,所以vm和data中的属性只能在声明时提前定义好通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的(数组本身或者数组元素的内部属性都是有响应式处理的)
vm.$set/set(数组对象, 下标, 值)
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}岁</div>
<div>数字:{{a.b.c.e}}</div>
<div>邮箱:{{a.email}}</div>
<!--数组中的元素没有响应式处理-->
<ul>
<li v-for="user in users">{{user}}</li>
</ul>
<!--如果数组元素是个对象,对象的属性是有响应式处理的-->
<ul>
<li v-for="vip in vips" :key="vip.id">{{vip.name}}</li>
</ul>
</div>
<script>
const vm = new Vue({
el : '#app',
// Vue会给创建Vue实例时data中所有的属性(包括属性中的属性)都添加响应式
data : {
msg : '响应式与数据劫持',
name : 'jackson',
age : 20,
a : {
b : {
c : {
e : 1
}
}
}
users : ['jack', 'lucy', 'james'],
vips : [
{id:'111', name:'zhangsan'},
{id:'222', name:'lisi'}
]
}
})
// vm后期追加的属性并没有添加响应式处理
vm.$data.a.email = 'jack@126.com'
// 调用以下的两个方法给后期追加的属性添加响应式处理
//Vue.set(vm.a, 'email', 'jack@123.com')
vm.$set(vm.a, 'email', 'jack@456.com')
// 不能直接给vm/vm.$data追加响应式属性,只能在声明时提前定义好
//Vue.set(vm, 'x', '1')
//Vue.set(vm.$data, 'x', '1')
// 直接通过数组下标修改数组中的没有响应式效果
vm.users[0] = "李四"
vm.vips[0] = {id:'333',name:'wangwu'}
// 数组中元素的属性有响应式效果
vm.vips[0].name = "张三"
// 操作数组元素并具有响应式效果
Vue.$set(vm.users,0,"李四")
Vue.$set(vm.vips,2,{id:'333',name:'wangwu'})
vm.users.push('王五')
// 修改数组从0位置开始的一个元素
vm.users.splice(0,1'杰克')
</script>
</body>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。