Object.defineProperty()、Proxy

Object.defineProperty()

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

Object.defineProperty(obj, prop, descriptor)有三个参数。

obj 是定义属性的对象
prop 用来定义或修改属性的名称
descriptor 是将被定义或修改的属性描述符

下面通过案例来看一下Object.defineProperty()具体的用法。

基本使用

var obj = {};

Object.defineProperty(obj, 'a', {
  value: 1,
});

console.log(obj.a); 

// 1

定义一个空对象,使用Object.defineProperty()来定义一个a属性。

属性描述符

Object.defineProperty()方法目前存在的属性描述符主要有两种形式。
数据描述符和存取描述符。
数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
存取描述符是使用getter-setter函数来实现描述的功能。
属性描述符可以是数据描述符,也可以是存取描述符,不能两种同时使用。

Object.defineProperty()方法定义的属性,默认不可修改、不可删除。
可以通过定义configurable、enumerable、writable来更改Object.defineProperty()定义对象的默认状态。

value:定义属性的值,可以是任何有效的值,默认为undefined。
configurable:当该属性为true时,属性值才能被改变或者删除,默认为false。
enumerable:当该属性为true时,属性值才能够出现在对象的枚举属性中,默认为false。
writable:当该属性为true时,value属性才能被修改,默认为false。

1. writable

属性值默认是不可修改的

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1,
  },
  b: {
    value: 2
  }
});

console.log(obj.a); 
obj.a = 2;
console.log(obj.a); 

// 1
// 1

配置writable属性后,属性值可修改。

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1,
    writable: true
  },
  b: {
    value: 2
  }
});

console.log(obj.a);
obj.a = 2;
console.log(obj.a);

// 1
// 2
2. enumerable

默认是不可枚举的。

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1
  },
  b: {
    value: 2
  }
});

for (const key in obj) {
  console.log(key + ':' + obj[key]);
}

配置enumerable属性后,属性值可枚举。

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1,
    enumerable: true
  },
  b: {
    value: 2
  }
});

for (const key in obj) {
  console.log(key + ':' + obj[key]); 
}

// a:1
3. configurable

默认属性是不可删除的。

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1,
    enumerable: true
  },
  b: {
    value: 2,
    enumerable: true
  }
});

delete obj.a;

for (const key in obj) {
  console.log(key + ':' + obj[key]);
}

// a:1
// b:2

配置configurable属性后,属性值可删除。

var obj = {};

Object.defineProperties(obj, {
  a: {
    value: 1,
    enumerable: true,
    configurable: true
  },
  b: {
    value: 2,
    enumerable: true
  }
});

delete obj.a;

for (const key in obj) {
  console.log(key + ':' + obj[key]);
}

// b:2
4. get、set

对于一个对象,它的取值和赋值有一系列的配置和阻止的方法,用以阻拦数据的输出和输入。
get、set方法是存取描述符,它和value、writable、enumerable、configurable互斥。
可以使用get和set方法实现数据劫持的效果。

每一个属性被定义时,都存在getter、setter机制。

var obj = {},
    a = 1;

Object.defineProperties(obj, {
  a: {
    get () {
      return '"a"\'s value is ' + a + '.';
    },
    set (newVal) {
      console.log('The value "a" has been designed a new value "' + newVal + '".');
    }
  }
});

console.log(obj.a);
obj.a = 1;

// "a"'s value is 1.
// The value "a" has been designed a new value "1".

Object.defineProperty() 操作数组

function DataArr () {
  var _val = null,
      _arr = [];

  Object.defineProperty(this, 'val', {
    get: function () {
      return _val;
    },
    set: function (newVal) {
      _val = newVal;
      _arr.push({val: _val});
      console.log('A new value ' + _val + ' has been pushed to _arr');
    }
  })

  this.getArr = function () {
    return _arr;
  }
}

var dataArr = new DataArr();

dataArr.val = 123;
dataArr.val = 234;

console.log(dataArr.getArr());

// A new value 123 has been pushed to _arr
// A new value 234 has been pushed to _arr
// [ { val: 123 }, { val: 234 } ]

Proxy

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

Object.defineProperty() 劫持数据,给对象进行扩展,对属性进行设置;
Proxy 用来给对象进行扩展;

Proxy对象有两个参数。

target:用Proxy包装的目标对象(可以是任何类型的对象)。
handler:一个对象,属性是当执行一个操作时定义代理的行为的函数。

对象使用proxy

var target = {
  a: 1,
  b: 2
}

let proxy = new Proxy(target, {
  get (target, prop) {
    return `This is property value ${target[prop]}`;
  },
  set (target, prop, value) {
    console.log(`The property ${prop} set value ${value}`);
    target[prop] = value;
  }
});

console.log(proxy.a);
proxy.b = 3;

// This is property value 1
// The property b set value 3

数组使用proxy

let arr = [
  { name: '小明', age: 18 },
  { name: '小宏', age: 23 },
  { name: '小青', age: 34 },
  { name: '小黄', age: 21 },
  { name: '小王', age: 37 },
  { name: '小李', age: 23 }
];

let persons = new Proxy(arr, {
  get (arr, prop) {
    return arr[prop];
  },
  set (arr, prop, value) {
    arr[prop] = value;
  }
})

console.log(persons[0]);
persons[0] = { name: '小杨', age: 21 }
console.log(persons[0]);

// { name: '小明', age: 18 }
// { name: '小杨', age: 21 }

函数使用proxy

const fn = function () {
  console.log('i am a function.');
}

fn.a = 123;

let newFn = new Proxy(fn, {
  get (fn, prop) {
    return fn[prop]  + ' This is a Proxy return.';
  }
});
console.log(newFn.a); 

// 123 This is a Proxy return.

总结

const target = { a: 1, b: 2 };

const proxy = new Proxy(target, {
  get (target, prop) {
    return 'GET:' +  prop + ' = ' + target[prop];
  },
  set (target, prop, value) {
    target[prop] = value;
    console.log('SET:' + prop + ' = ' + value);
  },
  has (target, prop) {
    console.log(target[prop]);
    return target[prop];
  },
  deleteProperty (target, prop) {
    console.log('DELETE:' +  target[prop]);
    delete target[prop];
  }
});

Proxy中代理的方法有下面描述的14种对象操作的方法。

Object.defineProperty()与Proxy

1. defineProperty和Proxy都可以实现拦截的功能,但是本质是不同的;
2. defineProperty原则上是给对象增加属性使用的,它在操作数组的长度,
   操作数组的值等,无法触发defineProperty的setter方法。
   vue中使用的数组的操作不是原生的,是自己实现的。	
3. Proxy对于操作数组,会触发setter方法,数组元素的添加、移除等。

对象操作的14种方法

ECMAScript 委员会,对于对象操作,定义了14种方法。

const obj = { a: 1, b: 2 };

1. 获取原型 [[GetPrototypeOf]]

var proto = Object.getPrototypeOf(obj);

console.log(proto);
console.log(obj.__proto__);
console.log(Object.prototype);

2. 设置原型 [[SetProtypeOf]]

Object.setPrototypeOf(obj, { c: 3, d : 4 });

Object.prototype.e = 5;
obj.__proto__.f = 6;

console.log(obj); 

3. 获取对象的可扩展性 [[Is Extensible]]

var extensible = Object.isExtensible(obj);

console.log(extensible); // true

Object.freeze(obj); // 冻结对象

var extensible2 = Object.isExtensible(obj);

console.log(extensible2); // false
seal 封闭对象、不可修改、不可删除、可写、可读
Object.seal(obj);

obj.c = 3;
console.log(obj);

delete obj.a;
console.log(obj);

obj.b = 4;
console.log(obj);

for (var key in obj) {
  console.log(obj[key]);
}

// { a: 1, b: 2 }
// { a: 1, b: 2 }
// { a: 1, b: 2 }
// 1
// 4
freeze 冻结对象、不可修改、不可删除、不可写、可读
Object.freeze(obj);

obj.c = 3;
console.log(obj);

delete obj.a;
console.log(obj);

obj.b = 4;
console.log(obj);

for (var key in obj) {
  console.log(obj[key]);
}

// { a: 1, b: 2 }
// { a: 1, b: 2 }
// { a: 1, b: 2 }
// 1
// 2

4. 获取自由属性 [[GetOwnProperty]]

Object.setPrototypeOf(obj, { c: 3, d: 4});

console.log(Object.getOwnPropertyNames(obj));

// [ 'a', 'b' ]

5. 禁止扩展对象 [[PreventExtension]]

Object.preventExtensions(obj);

obj.c = 3; // 禁止增加属性
console.log(obj);

delete obj.a; // 可删除属性
console.log(obj);

// { a: 1, b: 2 }
// { b: 2 }

6. 拦截对象操作 [[DefineOwnProperty]]

Object.defineProperty()

7. 判断是否是自己属性 [[HasProperty]]

console.log(obj.hasOwnProperty('a')); // true

8. [[GET]]

console.log('c' in obj); // false
console.log('a' in obj); // true
console.log(obj.a); // 1

9. [[SET]]

obj.a = 3;
obj['b'] = 4;

console.log(obj); //  a: 3, b: 4 }

10. [[Delete]]

console.log(obj); // { a: 1, b: 2 }
delete obj.a;
console.log(obj); // { b: 2 }

11. [[Enumerate]]

for (var k in obj) {
  console.log(obj[k]);
}

// 1
// 2

12. 获取键集合 [[OwnPropertyKeys]]

console.log(Object.keys(obj)); // [ 'a', 'b' ]

13.

function test () {}

obj.test = function () {}
obj.test();

14.

function Test () {}
new Test();