# 15. Object.defineProperty和Proxy区别

# Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。IE8不兼容。

Object.defineProperty(obj, prop, descriptor)

参数
obj: 要定义属性的对象。
prop: 要定义或修改的属性的名称或 Symbol 。
descriptor: 要定义或修改的属性描述符。

返回值
被传递给函数的对象。

Object.defineProperty(obj, 'name', {
  // configurable: false,//默认为false
  // enumerable: false,//默认为false
  value: 'kongzhi',
  // writable: false,/默认为false
  // get(){},
  // set(v){}
});
1
2
3
4
5
6
7
8

定义了 valuewritable , 一定不能有 getset, 反之亦然, 否则报错

对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符
数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
存取描述符是由 getter 函数和 setter 函数所描述的属性。
一个描述符只能是这两者其中之一;不能同时是两者

这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):

# 默认值:

configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false。

enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false。

# 数据描述符还具有以下可选键值:

value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined。

writable
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。 默认为 false。

# 存取描述符还具有以下可选键值:

get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined。

set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined。

# 描述符默认值汇总

拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined。

# 描述符可拥有的键值

- configurable enumerable value writable get set
数据描述符 可以 可以 可以 可以 不可以 不可以
存取描述符 可以 可以 不可以 不可以 可以 可以
let obj={a:2,b:{c:3}};
Object.defineProperty(obj,'a',{
  configurable:true,
  enumerable:true,
  get(){
    return obj['a']
  },
  set(value){
    return obj['a']=value;
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
let obj={a:2,b:{c:3}};
Object.defineProperty(obj,'b',{
  writable:false,
  configurable:false
})
obj.b=3;
obj.b.c=4;
console.log(obj)//{a:2,b:{c:4}}
1
2
3
4
5
6
7
8
const obj = {};
Object.defineProperty(obj, 'name', {
  // configurable: false,//默认为false
  // enumerable: false,//默认为false
  value: 'kongzhi',
  // writable: false,/默认为false
  // get(){},
  // set(v){}
});
console.log(obj.name); // 输出 kongzhi

// 改写obj.name 的值
obj.name = 111;
console.log(obj.name); // 还是打印出 kongzhi,因为writable默认为false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var o = {};

o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});


// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 全局变量Date不可修改,为了安全性考虑

Object.defineProperty(window,'Date',{
  writable:false,
  configurable:false
})
1
2
3
4

# Object.defineProperties

Object.defineProperties本质上定义了obj 对象上props的可枚举属性相对应的所有属性。

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // etc. etc.
});
1
2
3
4
5
6
7
8
9
10
11
12

# Proxy

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。IE不兼容

const p = new Proxy(target, handler)

参数
target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

返回值
一个Proxy代理的对象,操作这个对象会触发handler对应操作。改变原始对象不会触发。

# handler 对象的方法

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

handler.getPrototypeOf()
handler.setPrototypeOf()
handler.isExtensible()
handler.preventExtensions()
handler.getOwnPropertyDescriptor()
handler.defineProperty()
handler.has()//in 操作符的捕捉器。
handler.get(target, property)
handler.set(target, property, value)
handler.deleteProperty()//delete 操作符的捕捉器。
handler.ownKeys()
handler.apply()
handler.construct()//new 操作符的捕捉器。
1
2
3
4
5
6
7
8
9
10
11
12
13

# 基本操作

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37
1
2
3
4
5
6
7
8
9
10
11
12

# Proxy只代理对象外层属性

let obj={a:1,b:{c:2}};
let handler={
  get:function(obj,prop){
    const v = Reflect.get(obj,prop);
    return v; // 返回obj[prop]
  },
  set(obj,prop,value){
    return Reflect.set(obj,prop,value);//设置成功返回true
  }
};
let p=new Proxy(obj,handler);

p.a//会触发get方法
p.b.c//会触发get方法获取p.b,不会触发.c的set,因为c没被代理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 递归代理对象内部对象

let obj={a:1,b:{c:2}};
let handler={
  get:function(obj,prop){
    const v = Reflect.get(obj,prop);
    if(v !== null && typeof v === 'object'){
      return new Proxy(v,handler);//代理内层
    }else{
      return v; // 返回obj[prop]
    }
  },
  set(obj,prop,value){
    return Reflect.set(obj,prop,value);//设置成功返回true
  }
};
let p=new Proxy(obj,handler);

p.a//会触发get方法
p.b.c//会先触发get方法获取p.b,然后触发返回的新代理对象的.c的set。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

可以看出Proxy代理对象时只会在调用时递归,不会一开始就全部递归,优化了性能。

# Proxy对象和原始对象

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发

target.a=4;
console.log(p.a)//4

console.log(target==p)//false
1
2
3
4
5
6
7
8
9
10
11

# 总结

  1. Proxy使用上比Object.defineProperty方便的多。
  2. Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。
  3. 如果对象内部要全部递归代理或者定义,则Proxy只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty。
  4. 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
  5. 数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
  6. Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。