Object.defineProperty() вы можете не знать

JavaScript

Недавно пишу цикл "Полный анализ JavaScript API" (только что закончил писать String, а сейчас пишу Object, который можно штамповатьПолный анализ JavaScript API), если вы хотите пройти через все API, рекомендованные MDN, вы также можете подготовить документ для себя. Поскольку очки знаний, связанные с Object.defineProperty(), более сложны, они вынесены отдельно и размещены здесь, и вы можете делать кирпичики.

грамматика

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

описывать

Используется для определения новых свойств или изменения существующих свойств объекта и возврата объекта.

параметр

  • oцель

  • pИмя свойства или метода, которое необходимо определить (вы можете изменить существующие или добавить новые свойства или методы).

  • attributesДескриптор атрибута, конкретные атрибуты следующие:

interface PropertyDescriptor {
  configurable?: boolean;
  enumerable?: boolean;
  value?: any;
  writable?: boolean;
  get?(): any;
  set?(v: any): void;
}

дескриптор свойства

В ECMAScript есть два типа свойств:数据属性а также访问器属性.

атрибут данныхвключать:[[Configurable]], [[Enumerable]], [[Writable]], [[Value]]

свойство доступавключать:[[Configurable]], [[Enumerable]], [[Get]], [[Set]]

Ключевые значения, которые дескрипторы атрибутов могут иметь одновременно

configurable enumerable value writable get set
атрибут данных Yes Yes Yes Yes No No
свойство доступа Yes Yes No No Yes Yes

Короче говоря, определитьvalueилиwritable, 一定不能有getилиset, и наоборот, в противном случае сообщается об ошибке.

描述符可同时具有的键值

Configurable

Если параметр configurable свойстваfalse, Так:

  1. не сможет удалить этот атрибут, т.е.delete obj.xxxНедействительно, об ошибке будет сообщено непосредственно в строгом режиме.
// 非严格模式下删除一个"不可配置"的属性会返回false
const obj = {};

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

delete obj.name; // false

// obj.name并没有被删除
obj.name; // yancey
// 严格模式下删除一个"不可配置"的属性直接报错
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2,
    configurable: false,
  });
  delete o.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
  return o.b;
})();
  1. когда перечисляемый или записываемыйfalse, превратить их вtrueто сообщается об ошибке, но когда ониtrue, но может превратить их вfalse( Обратите внимание, что должно быть不可配置По предположению, если имущество可配置, enumerable и writable можно переключать между true и false)
const obj = {};

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

// 当"writable"和"configurable"均为false时, 尝试将"writable"变为true会报错
// Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(obj, 'name', { writable: true });
const obj = {};

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

// 但"writable"可成功从true切换到false
Object.defineProperty(obj, 'name', { writable: false });
  1. Все равно изменить еще разgetа такжеsetОба сообщат об ошибке, потому что значение свойства обоих является функцией, а в JS невозможно иметь одну и ту же функцию.

:::совет ОБЗОР сложные типы данных всредний накопительимя данныхс однимадрес кучи, существуетсредний накопительАтрибутыа такжестоимостьПри обращении сначала получить адрес из стека, а потом взять соответствующее значение из кучи. :::

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() {} });
  1. если толькоwritableправда, да任意重新定义значение, но когдаwritableКогда оно ложно, ему нужно увидеть конкретный тип данных.В первом примере, хотя настраиваемыйfalse, но пока доступен для записиtrue, вы можете переопределить значение; во втором примере значение равно基本数据类型, поэтому, когда вы снова определяете значение, вам просто нужно перезаписать исходное значение; третье значение в примере является сложным типом данных, также потому, чтокучапроблема и не может быть переназначен.
const obj = {};

Object.defineProperty(obj, 'name', {
  value: [],
  configurable: false,
  writable: true,
});

// 任意重定义value不报错
Object.defineProperty(obj, 'name', { value: 123 }); // {name: 123}

// 任意重定义value不报错
Object.defineProperty(obj, 'name', { value: {}); // {name: {}}
const obj = {};

Object.defineProperty(obj, 'name', {
  value: 123,
  configurable: false,
  writable: false,
});

// 当value是基本数据类型, 用原值覆盖不会报错
Object.defineProperty(obj, 'name', { value: 123 }); // {name: 123}

// 用其他值代替必然报错
Object.defineProperty(obj, 'name', { value: {}); // Uncaught TypeError: Cannot redefine property: name
const obj = {};

Object.defineProperty(obj, 'name', {
  value: [],
  configurable: false,
  writable: false,
});

// 当value是复杂数据类型, 修改value必定报错, 同样是堆栈的原因
Object.defineProperty(obj, 'name', { value: [] }); // {name: 123}

Writable

если собственностьwritableустановить какfalse, то свойство нельзя использовать赋值运算符Изменено. Но если значение свойства является массивом, оно не будет затронутоpush, spliceэффекты других методов.

const obj = {};

Object.defineProperty(obj, 'hobby', {
  value: ['girl', 'music', 'sleep'],
  writable: false,
  configurable: true,
  enumerable: true,
});

// "writable: false"并不对push、shift等方法起作用
obj.hobby.push('drink');
obj.hobby; // ['girl', 'music', 'sleep', 'drink']

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

// 当 hobby 被"赋值"给一个空数组时, 此属性的属性值不会被改变
obj.hobby = [];
obj.hobby; // ['girl', 'music', 'sleep', 'drink']

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

// 而当使用"严格模式"时, 给一个"不可写"属性赋值将直接报错
(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
}());

Enumerable

Определяет, можно ли перечислить свойства объекта в циклах for...in и 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,
});

// 以下迭代器均不能输出"不可枚举属性", 即 income 的相关信息
for (const i in obj) {
  console.log(obj[i]);
}
Object.keys(obj);
Object.values(obj);
Object.entries(obj);

Getter & Setter

Getter — это функция, вызываемая при чтении свойства, Setter — это функция, вызываемая при установке свойства, и Setter будет иметь один параметр — установленное значение.

Следующий код создает объект obj, который определяет два атрибута name и _time, обратите внимание, что символ подчеркивания _time является общепринятой нотацией, используемой для обозначения只能通过对象方法访问的属性. Свойство метода доступа time содержит функцию получения и функцию установки.被修饰Значение _time , на котором основан установщик被设置的值Изменить имя. Поэтому, когдаobj.time = 2, имя станет+2Это распространенный способ использования свойств средства доступа, т. е. установка значения одного свойства приводит к изменению других свойств.

const obj = {
  name: '',
  _time: 1,
};

Object.defineProperty(obj, 'time', {
  configurable: true,
  get() {
    return `default: ${this._time}s`;
  },
  set(newValue) {
    if (Number(newValue)) {
      this._time = newValue;
      this.name = `我为${this.name}+${newValue}s`;
    }
  },
});

obj.time; // 'default: 1'
obj.time = 2; // 2
obj.name; // '我为+2'

Посмотрите на другой пример, угон через Object.definePropertyobj.input, введет значениеsetИдентифицировать какnameв теге.Здесь есть вкус Vue.js,рекомендую статьюРазбор принципа реализации Vue — как реализовать двустороннюю привязку 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();
  }
})

MVVM?

Наконец, взглянув на пример наследования, мы создаемPersonКонструктор, который включает в себя два параметра:firstNameа такжеlastName, этот конструктор предоставляет четыре свойства:firstName, lastName, fullName, species, мы хотим, чтобы первые три свойства изменялись динамически, а последнее свойство было постоянным и не могло изменяться.

Следующий код явно не дает желаемого эффекта:firstNameилиlastNameВремя,fullNameне обновляется в режиме реального времени;speciesСвойства могут быть изменены по желанию.

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');

// 虽然 firstName 和 lastName 被修改了, 但 fullName 仍然是 "Yancey Leo"
person.firstName = 'Sayaka';
person.lastName = 'Yamamoto';

// 我们定义了一个关于“人”的构造函数, 所以并不希望 species 被修改成 fish
person.species = 'fish';

// 当我们修改了 fullName, 也同样希望 firstName 和 lastName 被更新
person.fullName = 'Kasumi Arimura';

Поэтому мы перепишем этот пример, используя Object.defineProperty() Обратите внимание, что:Захваченные свойства должны быть помещены в прототип, Таким образом, даже если будет создано несколько экземпляров, не будет конфликтов, Таким образом, вы можете использовать его с уверенностью.

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'

расширять

Помимо Getter и Setter в Object.defineProperty() есть два похожих способа.

__defineGetter__ и __defineSetter__()

Метод __defineGetter__ может быть已经存在Объект устанавливает (создает или изменяет) свойства средства доступа, метод __defineSetter__ может связать функцию с указанным свойством текущего объекта, и когда это свойство будет назначено, будет вызвана функция, которую вы связываете.

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

:::Опасность Эта функция нестандартна, старайтесь не использовать ее в продакшене!

Эта функция была удалена из веб-стандартов, хотя некоторые браузеры все еще поддерживают ее в настоящее время, но могут перестать поддерживать ее в будущем, пожалуйста, постарайтесь не использовать эту функцию. :::

получить синтаксис в литералах объектов

Синтаксис get в литерале объекта может использоваться только в新建一个对象при использовании.

var o = {
  get gimmeFive() {
    return 5;
  },
};
o.gimmeFive; // 5

Ссылаться на

Перехват данных ядра Vue

Нет Object.defineProperty вы вышли

Vue.js использует принцип Object.defineProperty.

Интервьюер: Каковы преимущества и недостатки реализации прокси с двусторонней привязкой по сравнению с defineproperty?

JAVASCRIPT ES5: MEET THE OBJECT.DEFINEPROPERTY() METHOD

img { margin: 0 auto; display: block; }