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:
- This property cannot be deleted, that is
delete obj.xxx
Invalid, 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
- When Enumerable or Writable is
false
When, turn them intotrue
An error; But when they aretrue
But you can turn them intofalse
(Note that it must be inconfigurable
Under 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
- Modify it again anyway
get
andset
Both 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
- As long as
writable
Is true, you canArbitrary redefinition
The value, but whenwritable
False 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