Recently, I am writing a series of “JavaScript API full parsing” (just finished writing String, now I am writing Object, JavaScript API full parsing), I want to use all the API recommended by MDN, also can be used to prepare a material for myself. Object.defineproperty () is a very complicated Object, so I’ll put it here separately.

grammar

defineProperty(o: any.p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>) :any;
Copy the code

describe

Used to define new properties or modify existing properties on an object and return the object.

parameter

  • O Target Object

  • P Name of property or method to define (existing property or method can be modified or new property or method can be added)

  • Attributes Indicates the attribute descriptor. The attributes are as follows:

interfacePropertyDescriptor { configurable? :boolean; enumerable? :boolean; value? :any; writable? :boolean; get? () :any; set? (v:any) :void;
}
Copy the code

Attribute descriptor

There are two types of properties in ECMAScript: data properties and accessor properties.

Data attributes include: [[Configurable]], [[Enumerable]], [[Writable]], [[Value]]

Accessor properties include: [[Configurable]], [[Enumerable]], [[Get]], [[Set]]

The key values that the property descriptor can have simultaneously

configurable enumerable value writable get set
Data attributes Yes Yes Yes Yes No No
Accessor properties Yes Yes No No Yes Yes

In short, if a value or writable is defined, there must be no get or set, or vice versa.

Configurable

If a different property is false, then:

  1. This property cannot be deleted, that isdelete obj.xxxInvalid, error reported in strict mode.
// Removing an "unconfigurable" property in non-strict mode returns false
const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'yancey'.configurable: false});delete obj.name; // false

// obj.name is not deleted
obj.name; // yancey
Copy the code
// In strict mode, an error is reported when deleting a "unconfigurable" attribute
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2.configurable: false});delete o.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
  returno.b; }) ();Copy the code
  1. When Enumerable or Writable isfalseWhen, turn them intotrueAn error; But when they aretrueBut you can turn them intofalse(Note that it must be inconfigurableUnder the premise of if attributeconfigurable, Enumerable and Writable toggle true and false)
const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'yancey'.configurable: false.writable: false});// If both "writable" and "64x" are false, an error will be reported if you attempt to change "writable" to true
// Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(obj, 'name', { writable: true });
Copy the code
const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'yancey'.configurable: false.writable: true});// But "writable" can be successfully switched from true to false
Object.defineProperty(obj, 'name', { writable: false });
Copy the code
  1. Modify it again anywaygetandsetBoth will return an error because the attribute value of the two is a function, and the same function cannot exist in JS.

Complex data types store data names and the address of a heap in the stack, and properties and values in the heap. The address is fetched from the stack and the corresponding value is fetched from the heap.

const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'yancey'.configurable: false});// Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(obj, 'name', { get: function() {}});// Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(obj, 'name', { set: function() {}});Copy the code
  1. As long aswritableIs true, you canArbitrary redefinitionThe value, but whenwritableFalse depends on the data type. In the first example, although different isfalseBut as long as writable istrue, you can redefine value; In the second example, value isBasic data types, so when defining value again, just overwrite the original value; The third example value is a complex data type, again becauseThe stackProblem and cannot reassign.
const obj = {};

Object.defineProperty(obj, 'name', {
  value: [].configurable: false.writable: true});// Any redefinition of value is not reported
Object.defineProperty(obj, 'name', { value: 123 }); // {name: 123}

// Any redefinition of value is not reported
Object.defineProperty(obj, 'name', { value: {}); // {name: {}}
Copy the code
const obj = {};

Object.defineProperty(obj, 'name', {
  value: 123.configurable: false.writable: false});// If value is a basic data type, overwriting it with the original value is not an error
Object.defineProperty(obj, 'name', { value: 123 }); // {name: 123}

// An error must be reported if another value is used
Object.defineProperty(obj, 'name', { value: {}); // Uncaught TypeError: Cannot redefine property: name
Copy the code
const obj = {};

Object.defineProperty(obj, 'name', {
  value: [].configurable: false.writable: false});// When value is a complex data type, modifying value is always an error, again due to stack reasons
Object.defineProperty(obj, 'name', { value: []});// {name: 123}
Copy the code

Writable

If a property’s writable is set to false, the property cannot be changed by the assignment operator. However, if the value is an array, it will not be affected by push, splice and other methods.

const obj = {};

Object.defineProperty(obj, 'hobby', {
  value: ['girl'.'music'.'sleep'].writable: false.configurable: true.enumerable: true});// "writable: false" does not apply to methods like push, shift, etc
obj.hobby.push('drink');
obj.hobby; // ['girl', 'music', 'sleep', 'drink']

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// When Hobby is "assigned" to an empty array, the property value of this property does not change
obj.hobby = [];
obj.hobby; // ['girl', 'music', 'sleep', 'drink']

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

When strict mode is used, assigning a value to an "unwritable" attribute will result in an error
(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} ());Copy the code

Enumerable

Defines whether an object’s properties can be used in a for… Are enumerated in the in loop and object.keys ()

const obj = {
  name: 'yancey'.age: 18.say() {
    return 'say something... '; }};Object.defineProperty(obj, 'hobby', {
  value: ['girl'.'music'.'sleep'].enumerable: true});Object.defineProperty(obj, 'income', {
  value: '100,00,000'.enumerable: false});// None of the following iterators can output "non-enumerable attributes ", i.e., income information
for (const i in obj) {
  console.log(obj[i]);
}
Object.keys(obj);
Object.values(obj);
Object.entries(obj);
Copy the code

Getter & Setter

Getter is the function that is called to read the property. The Setter is the function that is called to set the property, and the Setter will take one parameter, the value that is set.

The following code creates an obj object that defines two attributes name and _time. Note that the underscore of _time is a common notation for attributes that can only be accessed through object methods. The accessor property time contains a getter function and a setter function. The getter function returns the modified _time value, and the setter modifies name based on the set value. So when obj.time = 2, name becomes +2. This is a common way to use accessor properties, where setting the value of one property causes the other properties to change.

const obj = {
  name: ' '._time: 1};Object.defineProperty(obj, 'time', {
  configurable: true.get() {
    return `default: The ${this._time}s`;
  },
  set(newValue) {
    if (Number(newValue)) {
      this._time = newValue;
      this.name = I for `The ${this.name}+${newValue}s`; }}}); obj.time;// 'default: 1'
obj.time = 2; / / 2
obj.name; // 'I am +2'
Copy the code

Let’s look at another example where we hijack obj.input via Object.defineProperty to set the input value into a tag with id name. Here’s a taste of Vue. Js, and I recommend an article that looks at how Vue is implemented – how to implement bidirectional binding MVVM.

<p>Hello, <span id='name'></span></p>
<input type='text' id='input'>const obj = { input: '', }; const inputDOM = document.getElementById('input'); const nameDOM = document.getElementById('name'); inputDOM.addEventListener('input', function (e) { obj.input = e.target.value; }) Object.defineProperty(obj, 'input', { set: function (newValue) { nameDOM.innerHTML = newValue.trim().toUpperCase(); }})Copy the code

As a final example of inheritance, we create a Person constructor that takes two arguments, firstName and lastName. This constructor exposes four attributes: FirstName, lastName, fullName, species, we want the first three properties to change dynamically, and the last one to be a constant and not allowed to change.

The following code clearly does not achieve the desired effect: when trying to change firstName or lastName, fullName is not updated in real time; The Species attribute can be changed at will.

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = this.firstName + ' ' + this.lastName;
  this.species = 'human';
}

const person = new Person('Yancey'.'Leo');

// Although firstName and lastName have been changed, fullName is still "Yancey Leo"
person.firstName = 'Sayaka';
person.lastName = 'Yamamoto';

// We define a constructor for "person", so we don't want species to be changed to fish
person.species = 'fish';

// When we change fullName, we also want firstName and lastName to be updated
person.fullName = 'Kasumi Arimura';
Copy the code

So let’s rewrite the example using Object.defineProperty(). Note that the hijacked properties should be in the stereotype. This way, even if you create multiple instances, there is no conflict, so you can use it safely.

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Object.defineProperty(Person.prototype, 'species', {
  value: 'human'.writable: false});Object.defineProperty(Person.prototype, 'fullName', {
  get() {
    return this.firstName + ' ' + this.lastName;
  },
  set(newValue) {
    const newValueArr = newValue.trim().split(' ');
    if (newValueArr.length === 2) {
      this.firstName = newValueArr[0];
      this.lastName = newValueArr[1]; }}});const person = new Person('Yancey'.'Leo');

person.firstName = 'Sakaya';
person.lastName = 'Yamamoto';
person.fullName; // 'Sayaka Yamamoto'

person.fullName = 'Kasumi Arimura';
person.firstName; // 'Kasumi'
person.lastName; // 'Arimura'

person.species = 'fish';
person.species; // 'human'
Copy the code

extension

In addition to getters and setters in Object.defineProperty(), there are two similar methods.

__defineGetter__ and __defineSetter__ ()

The __defineGetter__ method sets (creates or modifies) accessor attributes for an existing object, and the __defineSetter__ method binds a function to a specified property of the current object. When that property is assigned, the function you bound to will be called.

var o = {};
o.__defineGetter__('gimmeFive'.function() {
  return 5;
});
o.gimmeFive; / / 5
Copy the code

::: DANGER This feature is non-standard, please try not to use it in production environment!

This feature has been removed from the Web standards, and while some browsers still support it, it may be discontinued at some point in the future. Please try not to use this feature.

Get syntax in object literals

The GET syntax in object literals can only be used when creating a new object.

var o = {
  get gimmeFive() {
    return 5; }}; o.gimmeFive;/ / 5
Copy the code

reference

Vue core data hijacking

If you don’t Object. DefineProperty, you’re out

Application principle of vue.js about Object.defineProperty

Interviewer: How better is implementing a two-way binding Proxy than defineProperty?

JAVASCRIPT ES5: MEET THE OBJECT.DEFINEPROPERTY() METHOD