深入浅出-Object.defineProperty、Proxy

139 阅读4分钟

前言

Object.defineProperty对于前端的同学来说熟悉不过,Vue2.X实现响应式原理核心依赖这个api。通过对data里边的对象属性进行数据劫持,访问和修改数据的时候通知订阅器更新的界面,达到数据驱动界面的理念。在最新Vue3.X,尤大大通过Proxy代理的方式实现数据监听,性能要比前者更加优秀。

Object.defineProperty

1.定义以及语法

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

Object.defineProperty(obj, prop, descriptor)

  • obj => 目标对象
  • prop => 要定义或修改的属性的名称或 Symbol
  • descriptor => 要定义或修改的属性描述符。
descriptor: {
    value: ‘’,
    configurable: true
    enumerable: truewritable:  true,
    get: function() {
    },
    set: function() {
    }
}

value => 属性对应值

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

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

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

get/set => getter ,setter 属性被方法或修改会调用该函数

2.知识扩展

  • Object.defineProperties(obj, props)

----方法跟Object.defineProperty差不多,跟字面意思一样,代表支持多个新增或修改属性

var obj = {};
 Object.defineProperties(obj, {  
   'property1': {     value: true,     writable: true   },  
   'property2': {     value: 'Hello',     writable: false   } 
});
  • Object.getOwnPropertyDescriptor(obj, prop)

----方法返回指定对象上一个自有属性对应的属性描述符

  • Object.isExtensible(obj)

----方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)

Object.preventExtensionsObject.seal 或 Object.freeze方法都可以标记一个对象为不可扩展

  • Object.create(proto, propertiesObject)

proto---新创建对象的原型对象

propertiesObject---新创建的对象添加指定的属性值和对应的属性描述符

Proxy

概念

Proxy是ES6新增的api。本质是代理,包装的意思。是一个构造函数,需要通过new Proxy()来实现实例。但跟普通的构造函数又有区别,因为我们知道只要是函数都会有prototype。但是Proxy却是没有的。这是为什么呢?

typeof Proxy    // 'function'
Proxy.prototype // undefined

const p = new Proxy(target, handler)

原因在于首先构造方法的 prototype 不一定是为对象的,可以置为 null 的。由于 Proxy 构造出来的实例对象是对 target 的一个代理,所以 Proxy 在构造过程中是不需要 prototype 进行初始化的。

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

除了get和set来拦截读取和赋值操作之外,Proxy还支持对其他多种行为的拦截。下面是一个简单介绍,想要深入了解的可以去MDN上看看。

handler.getPrototypeOf()

handler.setPrototypeOf()

handler.isExtensible()

handler.preventExtensions()

handler.getOwnPropertyDescriptor()

handler.defineProperty()

handler.has() //in 操作符的捕捉器。

handler.get(target, propKey, receiver)

handler.set(target, propKey, value, receiver)

handler.deleteProperty() //delete 操作符的捕捉器。

handler.ownKeys()

handler.apply()

handler.construct() //new 操作符的捕捉器。

handler对象中优先重点关注一下标红的几个方法。

get(target, key, receiver)

set(target, key, value, receiver)

  • target => 目标对象
  • key => 目标属性
  • receiver => 可选。proxy 实例本身(严格地说,是操作行为所针对的对象)

基本操作

实例1 无操作转发代理

const person = {
    name: '黄忠'
}
const p = new Proxy(person, {})  //纯粹代理没有做任何操作
console.log(p.name)  //黄忠
console.log(person.name) //黄忠
p.name = '赵云';
console.log(p.name) //赵云
console.log(person.name) //赵云

实例2

const person = {
    name: '黄忠'
}
const p = new Proxy(person, {
    get(target, key){
        return target[key] 
    },
    set(target, key, value) {
        target[key] = value
        return true
    }
})

实例3 Proxy也可以作为其他对象的原型对象使用

const obj = Object.create(p);
console.log(obj.name)  //黄忠

两者区别

虽然这样能够实现数组的监听,但是相比于proxy来说,还是比较麻烦的。

  1. 实现对象的深度监听,需要一次性递归到底。对于层级比较深的数据来说,计算量比较大。
  2. 无法监听新增属性/删除属性(但是vue2.0提供了另外的api,分别是Vue.set和Vue.delete)