Object.defineproperty (), which allows you to directly define a new property on an Object, or modify an existing property on an Object, and return the Object. This API is heavily used within the Vue framework to define properties for objects, and the principle of responsiveness is accomplished by customizing setters and getters through this API.

Object.defineproperty () is rarely used in normal business development because defining a new property on an Object is straightforward. Operator, for example, obj.a = 123. But the API allows you to do more with an object property, such as intercepting data, and customizing setters and getters.

concept

Object.defineProperty(obj, prop, descriptor)

  • Obj: The object on which properties are to be defined
  • Prop: The name of the property to define or modify
  • Descriptor: Attribute descriptor to be defined or modified

The first two arguments are straightforward, indicating which property to define or modify on which object. What matters is the attribute descriptor when we pass. When the operator defines or modifies a property, it is equivalent to calling object.defineProperty (). As follows:

const man = {}
man.name = 'lihaoze'
Object.getOwnPropertyDescriptor(man, 'name')
// {value: "lihaoze", writable: true, enumerable: true, configurable: true}
Copy the code
const man = {}
Object.defineProperty(man, 'name', {
  value: 'lihaoze'.writable: true.configurable: true.enumerable: true
})
Object.getOwnPropertyDescriptor(man, 'name')
// {value: "lihaoze", writable: true, enumerable: true, configurable: true}
Copy the code

Value is, as the name suggests, the corresponding value of the attribute, but it does not have to exist. It is mutually exclusive with the setter and getter. We’ll talk about that later. The writable, enumerable, configurable and exactly what is useful, we then introduce respectively.

writable

If and only if the attribute’s writable is true, value can be changed by the assignment operator. The default is false.

When we define a property via object.defineProperty () do not set the writable property, or set it to false. Then we won’t be able to pass. To modify the value of the property.

const man = { name: 'lihaoze' }
Object.defineProperty(man, 'age', {
  value: 18.writable: false.configurable: true.enumerable: true
})
man.age = 22
alert(man.age) / / 18
Copy the code

You can see that I’ve been 18 forever 😜, but someone will try to debunk me, so they can change it just by redefining value using the API. The following

Object.defineProperty(man, 'age', {
  value: 22.writable: false.configurable: true.enumerable: true
})
alert(man.age) / / 22
Copy the code

configurable

The property descriptor can be changed and removed from the corresponding object only if and only if the information of the property is set to true. The default is false. Therefore, when the information is set to false, the descriptor of this property cannot be modified or deleted. This is an irreversible operation.

const man = { name: 'lihaoze' }
Object.defineProperty(man, 'age', {
  value: 18.writable: true.configurable: false.enumerable: true
})
man.age = 22
console.log(man.age) / / 22
delete man.age
// This attribute cannot be deleted
console.log(man.age) / / 22
Enumerable Cannot re-define property: age
Object.defineProperty(man, 'age', {
  value: 18.writable: true.configurable: false.enumerable: false 
})
Copy the code

There is an exception: when the writable property is true, it can be changed to false. Look at the following code:

// In this case, only writable can be changed to false, but after false, it cannot be changed to true
Object.defineProperty(man, 'age', {
  value: 18.writable: false.configurable: false.enumerable: true
})
man.age = 22
console.log(man.age) / / 18
Copy the code

enumerable

An enumerable property can appear in an object’s enumeration property if and only if its Enumerable is true. The default is false. This descriptor controls whether an attribute appears in an object’s attribute enumeration, such as for.. In loop, if Enumerable is set to false, this property does not appear in the enumeration, although it is still accessible.

const man = { name: 'lihaoze'.age: 18 }
Object.defineProperty(man, 'job', {
  value: 'Web Engineer'.writable: false.configurable: true.enumerable: false 
})
console.log(man) // {name: "lihaoze", age: 18, job: "Web Engineer"}
console.log(Object.keys(man)) // ["name", "age"]
for (let key in man) {
  console.log(key) // name age
}
Copy the code

The set and get

Set: a method that provides a setter for a property, or undefined if there is no setter. The method is triggered when the property value is modified. The method takes a unique parameter, the new parameter value for the property.

Get: a method that provides a getter for a property, or undefined if there is no getter. When the property is accessed, the method is executed. No arguments are passed, but the this object is passed

Note: When a set or get descriptor is defined for an attribute, JavaScript ignores the value and writable properties of that attribute. In other words, these two pairs are mutually exclusive

const man = { name: 'lihaoze'.birthYear: 1996 }
// Define the setter and getter,
// The get and set methods cannot use their properties, which will cause the stack exceeded. Maximum call stack size exceeded
Object.defineProperty(man, 'age', {
  configurable: true.enumerable: true,
  get() {
    const date = new Date(a)return date.getFullYear() - this.birthYear
  },
  set(val) {
    // Uncaught Error: The real age cannot be changed
    throw new Error('Can't change real age')}})Copy the code

The example above calculates my age by year of birth. When setting the age, we throw an error to prevent it from being changed.

// Attempts to define value and writable descriptors will throw an error
// Uncaught TypeError: Invalid property descriptor. 
// Cannot both specify accessors and a value or writable attribute
Object.defineProperty(man, 'age', {
  value: 15.configurable: true.enumerable: true,
  get() {
    const date = new Date(a)return date.getFullYear() - this.birthYear
  },
  set(val) {
    // Uncaught Error: The real age cannot be changed
    throw new Error('Can't change real age')}})Copy the code

application

  • Set the set descriptor to stop others from changing the value and give a friendly hint to the code above. Vue also uses this method internally to give us developer-friendly hints. A few examples:

    // We don't need to define ereactive, just know that the fourth argument of the function is the function defined get,
    // In a non-production environment, Vue defines get for $attrs, props, etc.
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { ! isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(props, key, value, () => {
      if(! isRoot && ! isUpdatingChildComponent) { warn(`Avoid mutating a prop directly since the value will be ` +
          `overwritten whenever the parent component re-renders. ` +
          `Instead, use a data or computed property based on the prop's ` +
          `value. Prop being mutated: "${key}"`,
          vm
        )
      }
    })
    Copy the code
  • The reason we can use this.xxx to access data, methods, and props in Vue is because Vue sets them to other private objects. Look at the following code:

    const vm = new Vue({
      el: '#app'
      data: {
        msg: 'hello world! '
      }
    })
    vm.msg = vm._data.msg // true
    Copy the code

    Where, Vues is through the proxy function to achieve data proxy, Vue part of the source code is as follows:

    // Define a generic descriptor
    const sharedPropertyDefinition = {
      enumerable: true.configurable: true.get: noop,
      set: noop
    }
    This [sourceKey] = this[sourceKey] = this[sourceKey] = this[sourceKey]
    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    / /... Ignore irrelevant code
    proxy(vm, `_data`, key)
    Copy the code