在Vue中我们都知道是数据去驱动视图的变化。那么数据是如何驱动视图的更新的,在vue中主要使用了Object.definedProperty()这个方法。
一、1.Object.definedProperty(obj,prop,desc,)是在一个对象上定义一个新的属性或者修改现有的属性。
它接收3个参数,第一个为对象,第二个为属性,第三个为属性的描述对象。
一般情况下我们给对象定义属性是直接进行赋值操作,那么定义的属性可以修改也可以删除,那么如果我们相对其进行更加精准的操作,就需要使用这个方法。
2.在js中一共有3种类型的属性:
- 命名数据属性:拥有一个确定的值的属性。这也是最常见的属性
2.命名访问器属性:通过getter和setter进行读取和赋值的属性
3.内部属性:由JavaScript引擎内部使用的属性,不能通过JavaScript代码直接访问到,不过可以通过一 些方法间接的读取和设置。比如,每个对象都有一个内部属性[[Prototype]],你不能直接访问这个属性,但可以通过Object.getPrototypeOf()方法间接的读取到它的值。虽然内部属性通常用一个双吕括号包围的名称来表示,但实际上这并不是它们的名字,它们是一种抽象操作,是不可见的,根本没有上面两种属性有的那种字符串类型的属性。
var obj = {};
obj.a = "a";
obj["b"]="b"
console.log(obj);
//直接通过字面量进行赋值操作
var obj = {};
Object.defineProperty(obj, "a", {
value: "5",
writable:false,//writable代表属性值是否可以改变
enumerable:true//属性是否可以枚举
});
obj.a="6666" //上面为false所以它是变不了。
console.log(obj); //这里打印依旧为5
//用 for in 和 Object.keys语法去枚举属性
for( x in obj){
console.log('-------',x) //上面enumerable为true所以可以枚举。此处打印为属性a
}
let prop= Object.keys(obj)
console.log(prop) //上面enumerable为true所以可以枚举。此处打印为属性a
属性描述对象的属性有6个:
1. value:“属性值”,
2. writable:“属性值是否可变”,
3. enumerable:“属性是否可以枚举”
4. configurable:“是否可配置” :configurable: false 时,不能删除当前属性,且不能重新配置当 前属性的描述符(有一个小小的意外:可以把writable的状态由true改为false,但是无法由false改为true),但是在writable: true的情况下,可以改变value的值,configurable: true时,可以删除当前属性,可以配置当前属性所有描述符。
5. get()函数:“取值函数”,一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。
6. set()函数:“存值函数”,一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined。
1.使用get,set方法
var obj = {};
let temp = null;
Object.defineProperty(obj, "a", {
get: function () {
return temp;
},
set: function (val) {
temp = val;
},
});
obj.temp="4111" //调用了set函数
console.log(obj.tmep); //调用了get函数 //此处打印undefinded
console.log(obj); //打印 {tmep:"4111"}
2. 使用 get set方法
var obj = { _b: "yz" };
let temp = null;
Object.defineProperty(obj, "_b", {
get: function () {
return this._b; //这里直接返回obj对象的属性b
},
set: function (val) {
this._b = val; //我们传递的新值赋值给b属性
},
});
obj.b = "新值";
console.log(obj); //{b:"新值"}
var obj = {};
obj.a = "a";
//1. 直接通过字面量进行赋值操作等于下面
Object.defineProperty(obj, "a", {
value: "a",
writable: true,
enumerable: true,
configurable: true,
});
//2. 我们直接定义新的属性通过这个方法
Object.defineProperty(obj,"b",{
value:"b"
})
//属性的配置描述符,如果我们不写,其他3个默认值为false
二 、大体对这个方法有了了解,那么我们继续介绍Vue中如何使用这个方法进行数据驱动视图的渲染的。
1.给对象类型数据添加新的属性
function defindObj(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, //可以枚举
configurable: true, //可配置
get: function () {
return val;
},
set: function (newVal) {
console.log("set",val); //打印set yz
val = val + newVal;
},
});
}
let obj = { name: "yz", age: 18 };
Object.keys(obj).forEach(key => {
defindObj(obj,key,obj[key])
});
obj.name="重新设置name" //触发set函数
console.log(obj); //{name:"yz 重新设置name" ,age:18}
obj.unknow="5555" //添加一个新的属性不会触发set函数,
//这个也就验证了vue中定义对象的时候我们如果给定义好的对象再添加属性,它就监听不到属性值得变化。
2.给数组类型添加新的属性
function defineArr(arr, key, val) {
Object.defineProperty(arr, key, {
enumerable: true,
configurable: true,
get: function () {
console.log("get");
return val;
},
set: function (newVal) {
console.log("set", val); //此处打印 "set 1"
val = val + newVal;
},
});
}
//1.
let arr = [1, 2, 3, 4, 5];
arr.forEach((value, index) => {
defineArr(arr, index, value);
});
arr[0] = "给第一个元素值为一"; //触发set函数
arr.push(6); //此处不会触发set函数
console.log(arr); //[1,2,3,4,5,6]
//2.
let arr2 = [{ name: "yz" }, { age: 18 }];
arr2.forEach((value, index) => {
defineArr(arr2, index, value);
});
arr2.forEach((v, index) => {
v.name = "6666666666"; //不会触发set
});
arr2[0]={name:"我直接赋值会触发set"} //{name:6666666666666666666}
arr2.push[{name:"99999"}] //不会触发set
arr2.splice(1,1,{name:"88888"}) //触发set
//从上面可以看出各种情况对于数组的操作会不会触发set函数
1.在vue中数据初始化时候会对每个属性进行调用Object.definedProperty方法,,当给变量赋值时就会触发变量对应的set方法,从而调用视图更新的函数。
2.Object.definedProperty是可以通过数组下标修改值之后触发对应的set方法的,通过push、pop等方法修改数组的值之后是不会触发set方法。
3.Vue中考虑到性能问题,对于数组类型的数据改变自己定义了一套触发响应的规则,通过数组下标修改数组的值是不会触发视图更新的,但是通过push、pop、shift、unshift等方法修改数组的值是可以触发视图更新的。
//此处由于原生jSd的限制Vue不能检测对象属性的添加和删除
<template>
<div @click="changeName">
{{name}}
</div>
</template>
<script>
export default {
data () {
return {
name: { a: 'yz' }
}
},
methods: {
changeName () {
this.name.b = '666' // 我添加一个新的属性Vue检测不到
console.log(this.name) // 这里可以打印出{a:"yz",b:"666"}
}
}
}
</script>
2.默认的Object.definedProperty中,对于操作数组的方法,默认只有利用数组中的splice修改数组值时可以触发set(splice(1, 1, value), 相当于通过数组下标修改属性值)。
但因为Vue做了处理,以下变异方法修改数组的值都可以触发视图的更新。
push,pop, shifit,unshift, sort,splice,reverse, 下图为Vue源码:
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
/**
* Define a property.
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
从上面可以看出vue中基于原生Array的原型对象创建了一个新对象,重新定义了数组中的一些方法。
将以上方法定义通过defineProperty定义成了响应式, 对于push、unshift、splice方法给数组新增值时还做了特别的处理。在vue中直接操作以上方法修改数组值,视图都能响应。
Object.defineProperty作为vue2.0响应式的核心将在vue3.0 被Proxy取代。因为前者存在一些局限,前者不能监听到对象的属性的增加和删除,以及不能监听到数组通过原生的方法对数组做的修改等问题,后者提供了对更多种类数据的拦截和对对象更好的支持。