数据代理
分析准备
Object.defineProperty(obj, prop, descriptor)
该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- configurable :属性的描述符是否够被改变
- enumerable :是否可以枚举
- value :该属性对应的值
- writable :属性的值是否可以修改
- get :回调函数,根据其他相关的属性动态计算得到当前属性值
- set:回调函数,监视当前属性值的变化,更新其他相关的属性值
const obj = {
firstName: 'A',
lastName: 'B'
}
Object.defineProperty(obj, 'fullName', {
get() {
return this.firstName + '-' + this.lastName
},
set(value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
})
console.log(obj.fullName) //A-B
obj.firstName = 'C'
obj.lastName = 'D'
console.log(obj.fullName) //C-D
obj.fullName = 'E-F'
console.log(obj.firstName, obj.lastName) //E F
Object.defineProperty(obj, 'fullName2', {
configurable: false,
enumerable: true,
value: 'G-H',
writable: false
})
console.log(obj.fullName2) //G-H
obj.fullName2 = 'I-J'
console.log(obj.fullName2) //G-H
Object.defineProperty(obj, 'fullName2', {
configurable: false,
enumerable: true,
value: 'G-H',
writable: true
}) //Cannot redefine property: fullName2(不能重新定义)
Object.keys(obj)
该方法会返回一个由一个给定对象的自身可枚举属性组成的数组
数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 。
const names = Object.keys(obj)
console.log(names) //["firstName", "lastName", "fullName2"]
通过分析结果可知,fullName中没有定义enumerable时默认值为false,而fullName2定义了enumerable的值为true,输出的数字由可枚举属性组成。
Vue实例
什么是数据代理?
通过一个对象代理对另一个对象中属性的操作(读/写)
vue数据代理:
通过vm对象来代理data对象中所有属性的操作,更方便的操作data中的数据。
vm是对象代理
<script>
const vm = new Vue({
el: "#app",
data: {
name: 'kk'
}
})
console.log(vm.name) //kk
vm.name = 'xx'
console.log(vm.name) //xx
</script>
源码分析
以下代码用的是:https://github.com/DMQ/mvvm.git,此版进行简化改造主要说明原理与实现
const vm = new MVVM({
el: "#app",
data: {
name: 'kk'
}
})
console.log(vm.name, vm) //kk MVVM {$options: {…}, _data: {…}, $compile: Compile}
vm.name = 'xx'
console.log(vm.name) //xx
实现:
- 通过Object.defineProperty(vm,key,{})给vm添加与data对象的属性对应的属性
- 所有添加的属性都包含get/set方法
- 在get/set方法中去操作data中对应的属性
/*
相当于Vue的构造函数
*/
function MVVM(options) {
// 将配置对象保存到vm
this.$options = options || {};
// 将data对象保存到vm和变量data中
var data = this._data = this.$options.data;
// 保存vm到变量me
var me = this;
// 数据代理:实现 vm.name -> vm._data.name
// 遍历data中所有属性
Object.keys(data).forEach(function (key) {
//对指定属性实现代理
me._proxyData(key);
});
this._initComputed();
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
constructor: MVVM,
$watch: function (key, cb, options) {
new Watcher(this, key, cb);
},
/*
实现指定属性代理的方法
*/
_proxyData: function (key, setter, getter) {
var me = this;
setter = setter ||
// 给vm添加指定属性名的属性
Object.defineProperty(me, key, {
configurable: false, //不能重新定义
enumerable: true, //可以枚举遍历
// 从data中获取对应的属性值返回
get: function proxyGetter() {
return me._data[key];
},
// value被保存到data中对应的属性上
set: function proxySetter(newVal) {
me._data[key] = newVal;
}
});
},
_initComputed: function () {
var me = this;
var computed = this.$options.computed;
if (typeof computed === 'object') {
Object.keys(computed).forEach(function (key) {
Object.defineProperty(me, key, {
get: typeof computed[key] === 'function' ?
computed[key] : computed[key].get,
set: function () {}
});
});
}
}
};
简单的理解:首先如红框所示将配置对象保存到vm,其次如浅蓝框所示将data对象保存到vm和变量data中,完成初始一些配置。遍历data中所有属性,依次对指定属性实现代理。如绿框所示给vm添加指定属性名的属性,在get/set方法中去操作data中对应的属性。重点还是在对代理概念,以及Object.defineProperty和Object.keys的理解上。
若有错误,欢迎指正!!