最近准备系统性的学Vue,但是在学习响应式原理的时候被一个知识点卡住了,那就是Object.defineProperty
什么是Object.defineProperty?
是一种用来设定修改对象上属性的“设定”,然后返回对象的静态方法
语法
Object.defineProperty(obj, prop, descriptor)
obj:修改的对象
prop:要修改的对象的属性(string)
descriptor:定义或修改的属性的描述符。(obj)
return:传递给函数的对象。
作用?
此方法可以精确地添加或修改对象上的属性。这些属性的值可以更改,并且可以删除。此方法允许将这些额外的详细信息从其默认值更改。默认情况下,使用添加的值Object.defineProperty()
是不可变的且不可枚举。
可以控制属性是否可枚举(for-in的时候是否被遍历到),可以控制属性时候可修改,是否可删除(delete
),还可以给赋值和取值时调用函数…
描述符
configurable
true
是否可以更改此属性描述符的类型,以及是否可以从相应对象中删除该属性。
默认为false
。
这个描述符得和delete
一起理解
var person = {age : 28, title : 'fe'};
delete person.age; // true
delete person['title']; // true
person.age; // undefined
delete person.age; // true 即使删除了也会返回true,delete的返回值true只是说明这个对象已经没有这个属性了
delete Object.prototype; // false 不能删除原型上的属性
//Object.getOwnPropertyDescriptor返回该属性描述符的描述信息
var descriptor = Object.getOwnPropertyDescriptor(Object,'prototype');
descriptor.configurable; // false 对象的原型不可删除
Object.defineProperty(person,"sex",{configurable: false,value:"male"}) //{sex: "male"}
delete person.sex //false(由于设定了不可修改,所以不可删除,delete时会返回false)
writable
true
如果与属性相关的值可以用赋值运算符(obj.XX = XXX 这种叫赋值运算)改变。
默认为false
。
const person = {age : 18, sex : "male"}
Object.defineProperty(person,"height",{
writable : false,
value : 180
})
person.height = 183; //183,看似成功修改,但是由于writable为false,所以无法修改
person.height; //180
//严格模式下
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // throws TypeError: "b" is read-only
return o.b; // 2
}());
enumerable
true
当且仅当在枚举相应对象的属性时显示此属性,如果为false
,在for-in等枚举时会跳过此属性
默认为false
。
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
o.c = 3; // enumerable默认为true
for (var i in o) {
console.log(i);
} //a c
//obj.propertyIsEnumerable 用来判断属性在obj中是否是可枚举属性。
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // true
value
与属性关联的值。可以是任何有效的JavaScript值(数字,对象,函数等)。
默认为undefined
get
当访问该属性时,将调用该函数(一个充当属性getter的函数,如果没有getter的函数就返回undefined
)而无需使用参数,并且将其this
设置为访问该属性所通过的对象(由于继承,它可能不是在其上定义该属性的对象)。返回值将用作属性的值。
默认为undefined
set
赋值属性后,将使用一个参数(将值分配给属性)并this
设置为分配该属性的对象来调用此函数(一个充当属性setter的函数,如果没有setter就返回undefined
)。
默认为undefined
const person = {
name : "zhouxuyu",
age : 18
}
Object.defineProperty(person,"sex",{
get:()=>{
console.log(`我是${sex}的`)
},
set:(newSex)=>{
sex = newSex
console.log(`我现在是${sex}的`)
},
})
person.sex = "male"; //我现在是male的 "male"
person.sex; //我是male的
注意!!get和value不能一起用,不然会报错:Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #\<Object>
- 如果可访问的属性是继承的,则在后代对象上访问和修改该属性时,将调用其
get
和set
方法。如果这些方法使用变量存储值,则所有对象将共享该值。
描述符的混合使用
缺陷
不能监听数组的变化
数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.
Vue 把会修改原来数组的方法定义为变异方法 (mutation method)
非变异方法 (non-mutating method):例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。
必须遍历对象的每个属性
使用 Object.defineProperty() 多数要配合 Object.keys() 和遍历,,于是多了一层嵌套
必须深层遍历嵌套的对象
当一个对象为深层嵌套的时候,必须进行逐层遍历,直到把每个对象的每个属性都调用 Object.defineProperty() 为止。
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
下面是另一个拦截读取属性行为的例子。
var proxy = new Proxy({}, {
get: function(target, propKey) { //设置getter 读取数据时执行的函数
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
如果没有proxy
的代理介入,点操作符是直接访问这个对象,第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作