最近准备系统性的学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>

  • 如果可访问的属性是继承的,则在后代对象上访问和修改该属性时,将调用其getset方法。如果这些方法使用变量存储值,则所有对象将共享该值。

描述符的混合使用

缺陷

不能监听数组的变化

数组的这些方法是无法触发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 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

image-20210120141816958

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {
  get: function(target, propKey) {         //设置getter 读取数据时执行的函数
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

如果没有proxy的代理介入,点操作符是直接访问这个对象,第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作