Objects are JavaScript’s most important data structure, and ES6 has given it a major upgrade.

A concise representation of the property

ES6 allows variables and functions to be written directly inside curly braces as object properties and methods. Make writing more concise.

const foo = 'bar'; const baz = {foo}; Baz // {foo: "bar"} // equals const baz = {foo: foo};Copy the code

In the code above, the variable foo is written directly inside curly braces. In this case, the attribute name is the variable name, and the attribute value is the variable value.

In addition to attributes, methods can also be abbreviated

const o = { method() { return "Hello!" ; }}; Const o = {method: function() {return "Hello!" ; }};Copy the code

Attribute name expression

JavaScript defines attributes of objects in two ways.

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;
Copy the code

The method always uses an identifier as its property name, and the second method uses an expression as its property name, enclosing the expression in square brackets.

However, if you define objects in a literal fashion (using curly braces), you can only use method one in ES5.

var obj = {
  foo: true,
  abc: 123
};
Copy the code

In ES6, expressions can be used.

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
Copy the code

In particular, attribute name expressions and indirect representations should not be used at the same time, otherwise an error will be reported.

// error const foo = 'bar'; const bar = 'abc'; const baz = { [foo] }; // Correct const foo = 'bar'; const baz = { [foo]: 'abc'};Copy the code

Note that the attribute name expression, if it is an object, automatically converts the object to a string by default.

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}
Copy the code

In the above code, both [keyA] and [keyB] get [object object], so [keyB] overwrites [keyA], and myObject has only one [object object] attribute.

Method’s name property

Function name property, returns the function name. Object methods are also functions, so they also have a name attribute.

const person = { sayName() { console.log('hello! '); }}; person.sayName.name // "sayName"Copy the code

In the code above, the name attribute of the method returns the function name (that is, the method name).

If an object’s methods use getters and setters, the name attribute is not on the method, but on the property description of the object’s GET and set properties. The return value is the method name preceded by get and set.

const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
Copy the code

There are two special cases: for functions created by bind, the name attribute returns bound plus the name of the original function; The Function constructor creates a Function whose name attribute returns anonymous.

(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"
Copy the code

If the object’s method is a Symbol value, the name property returns a description of the Symbol value.

const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
Copy the code

In the code above, key1 corresponds to the Symbol value is described, key2 does not.

Enumerability and traversal of properties

The enumeration of

Each property of an object has a Descriptor that controls the behavior of the property. The description of the Object. GetOwnPropertyDescriptor method can obtain the attribute Object. (Object. GetOwnPropertyDescriptor method returns the specified Object on a has its own corresponding attribute descriptor. (Own properties refer to properties that are directly assigned to the object and do not need to be looked up on the stereotype chain)

let obj = { foo: 123 }; Object. GetOwnPropertyDescriptor (obj, 'foo') / / {/ / value: 123, / / the value of the attribute (valid only for data attributes descriptor) / / writable: True, // True if and only if the value of an attribute can be changed. // Enumerable: True, // If and only if the specified object attributes can be enumerated, it is true. // Different: True // True if and only if the attribute description of the specified object can be changed or the attribute can be deleted. / /}Copy the code

An enumerable property that describes an object is called “enumerable.” If it is false, it means that some operations ignore the current property.

Currently, there are four operations that ignore enumerable as false.

  • for... inLoop: Only the enumerable properties of the object itself and its inheritance are iterated over.
  • Object.keys(): returns the key names of all the enumerable properties of the object itself.
  • JSON.stringify(): Serializes only enumerable properties of the object itself.
  • Object.assign(): ignoreenumerableforfalseOnly enumerable properties of the object itself are copied.

Of the four operations, the first three are available in ES5, and the last object.assign () is new in ES6. Among them, only… In returns the inherited properties, and the other three methods ignore the inherited properties and process only the properties of the object itself. In fact, the original purpose of enumerable was to make it possible for certain properties to bypass for… In operation, otherwise all internal properties and methods will be traversed. For example, the toString method of an object prototype and the length property of an array are “enumerable” to avoid being used for… Go through to.

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false

Object.getOwnPropertyDescriptor([], 'length').enumerable
Copy the code

In the code above, both toString and Length are Enumerable false, so for… In does not iterate over these two attributes inherited from the stereotype.

In addition, ES6 states that all Class stereotype methods are non-enumerable.

Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
Copy the code

In general, the introduction of inherited properties into operations can complicate matters, and most of the time, we only care about the properties of the object itself. So, try not to use for… In loop and use object.keys () instead.

Property traversal

ES6 has five methods for traversing an object’s properties.

(1) for… in

for… The in loop iterates over the object’s own and inherited enumerable properties (excluding the Symbol property).

(2) the Object. The keys (obj)

Keys returns an array containing the key names of all of the Object’s own (not inherited) enumerable properties (not including the Symbol property).

(3) Object. GetOwnPropertyNames (obj)

Object. GetOwnPropertyNames returns an array containing all attributes of the Object itself (excluding Symbol attribute, but cannot be enumerated attribute) of keys.

(4) the Object. GetOwnPropertySymbols (obj)

Object. GetOwnPropertySymbols returns an array containing all the Symbol attribute of the Object itself the key name.

(5) Reflect. OwnKeys (obj)

Reflect.ownKeys returns an array containing all of the object’s own (not inherited) key names, whether they are symbols or strings or enumerable.

All five methods follow the same sequence rules for traversal of attributes.

  • First, all numeric keys are iterated, sorted in ascending order.

  • Next, all the string keys are iterated in ascending order by the time they were added.

  • Finally, all Symbol keys are iterated in ascending order of time they were added.

    Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) // [‘2′, ’10’, ‘b’, ‘a’, Symbol()]

In the above code, the reflect. ownKeys method returns an array containing all the properties of the argument object. The array’s properties are in this order, starting with the numeric properties 2 and 10, followed by the string properties B and A, and finally the Symbol property.

The super keyword

We know that the this keyword always refers to the current object of the function, but ES6 has added another similar keyword super to refer to the prototype object of the current object.

const proto = { foo: 'hello' }; const obj = { foo: 'world', find() { return super.foo; }}; Object.setPrototypeOf(obj, proto); obj.find() // "hello"Copy the code

The object.setProtoTypeof () method sets the Prototype of a specified Object (that is, the internal [[Prototype]] property) to another Object or null.

In the code above, the object obj.find() method references the foo property of the prototype object proto via super.foo.

Note that when the super keyword represents a prototype object, it can only be used in the object’s methods, and will return an error if used elsewhere.

Const obj = {foo: super.foo} const obj = {foo: super.foo} const obj = {foo: () => super.foo} function () { return super.foo } }Copy the code

All three of the above uses of super give an error because, to the JavaScript engine, it doesn’t use super in object methods. The first way is that super is used in properties, and the second and third ways are that super is used in a function and then assigned to foo. Currently, only the shorthand for object methods allows the JavaScript engine to validate that it defines object methods.

Inside the JavaScript engine, super.foo is equivalent to Object.getProtoTypeof (this).foo (property) or Object.getProtoTypeof (this).foo.call(this) (method).

const proto = { x: 'hello', foo() { console.log(this.x); }}; const obj = { x: 'world', foo() { super.foo(); } } Object.setPrototypeOf(obj, proto); obj.foo() // "world"Copy the code

In the code above, super.foo refers to the foo method of the prototype object proto, but the bound this is the current object obj, so the output is world.

Object extension operator


Deconstruction assignment

Destructible assignment of an object is used to take values from an object, which is equivalent to assigning all of the target object’s Enumerable properties that have not yet been read to the specified object. All the keys and their values are copied to the new object.

let { x, y, ... z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }Copy the code

In the code above, the variable Z is the object to which the deconstructed assignment is located. It takes all unread keys (a and B) to the right of the equals sign and copies them along with their values.

Since destructuring assignment requires an object to the right of the equals sign, an error is reported if the equals sign is undefined or null, because they cannot be converted to objects.

let { ... z } = null; // Runtime error let {... z } = undefined; // Runtime errorCopy the code

The destruct assignment must be the last argument, or an error will be reported.

let { ... x, y, z } = someObject; // let {x,... y, ... z } = someObject; // Syntax errorCopy the code

In the code above, the destruct assignment is not the last argument, so an error is reported.

Note that a copy of a deconstructed assignment is a shallow copy, meaning that if the value of a key is a value of a compound type (array, object, function), the deconstructed assignment copies a reference to that value, not a copy of that value.

let obj = { a: { b: 1 } }; let { ... x } = obj; obj.a.b = 2; x.a.b // 2Copy the code

In the above code, x is the object to which the destruct assignment is to be made, and the a property of object obj is copied. The a attribute refers to an object, and changing the value of the object will affect the destruction-assignment reference to it.

In addition, the destructive assignment of the extension operator cannot copy the attributes inherited from the prototype object.

let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; let { ... o3 } = o2; o3 // { b: 2 } o3.a // undefinedCopy the code

In the code above, object O3 copies O2, but only the properties of O2 itself, not the properties of its prototype o1.

Here’s another example.

const o = Object.create({ x: 1, y: 2 }); o.z = 3; let { x, ... newObj } = o; let { y, z } = newObj; x // 1 y // undefined z // 3Copy the code

The ** object.create ()** method creates a new Object, using an existing Object to provide the __proto__ of the newly created Object.

In the above code, variable X is a pure deconstructed assignment, so it can read the properties inherited from object O; Variables y and z are deconstructed assignments of the extended operators. They can only read the attributes of the object O itself, so the assignment of variable Z can be successful, and variable Y cannot take a value. ES6 specifies that the extension operator must be followed by a variable name, not a deconstructed assignment expression, so the above code introduces the intermediate variable newObj, which will cause an error if written as follows.

let { x, ... { y, z } } = o; // SyntaxError: ... must be followed by an identifier in declaration contextsCopy the code

One use of deconstructing assignment is to extend the arguments of a function to introduce other operations.

function baseFunction({ a, b }) { // ... } function wrapperFunction({ x, y, ... RestConfig}) {// Use x and y parameters to operate // Pass the rest of the parameters to the original function return baseFunction(restConfig); }Copy the code

In the above code, the original function baseFunction takes a and bas arguments, and the function wrapperFunction extends baseFunction to accept extra arguments and retain the behavior of the original function.

Extended operator

Object extension operator (…) Retrieves all traversable properties of the parameter object and copies them to the current object.

let z = { a: 3, b: 4 }; let n = { ... z }; n // { a: 3, b: 4 }Copy the code

Since arrays are special objects, the extension operators for objects can also be used for arrays.

let foo = { ... ['a', 'b', 'c'] }; foo // {0: "a", 1: "b", 2: "c"}Copy the code

If the extension operator is followed by an empty object, it has no effect.

{... {}, a: 1} // { a: 1 }Copy the code

If the extension operator is not followed by an object, it is automatically converted to an object.

// the same as {... Object(1)} {... 1} / / {}Copy the code

In the code above, the extension operator is followed by the integer 1, which is automatically converted to the numeric wrapper object Number{1}. Since the object has no properties of its own, an empty object is returned.

The following examples are similar.

// the same as {... Object(true)} {... True} // {} // equivalent to {... Object(undefined)} {... Undefined} // {} // equivalent to {... Object(null)} {... null} // {}Copy the code

However, if the extension operator is followed by a string, it is automatically converted to an array-like object, so it does not return an empty object.

{... 'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}Copy the code

Object extension operators are equivalent to using the object.assign () method.

The ** object.assign ()** method is used to assign the values of all enumerable properties from one or more source objects to target objects. It will return the target object.

let aClone = { ... a }; // equivalent to let aClone = object. assign({}, a);Copy the code

The above example only copies the properties of the object instance. If you want to clone an object completely, and also copy the properties of the object prototype, you can write as follows.

Const clone1 = {__proto__: object.getProtoTypeof (obj),... obj }; Const clone2 = object. assign(object.create (object.getProtoTypeof (obj)), obj); // Const clone2 = object. assign(object.create (object.getProtoTypeof (obj)), obj); / / write three const clone3 = Object. The create (Object. GetPrototypeOf (obj), Object, getOwnPropertyDescriptors (obj))Copy the code

In the code above, the __proto__ attribute of script 1 is not necessarily deployed in a non-browser environment, so script 2 and script 3 are recommended.

The extension operator can be used to merge two objects.

let ab = { ... a, ... b }; // same as let ab = object. assign({}, a, b);Copy the code

If a user-defined attribute is placed after an extension operator, the attributes of the same name inside the extension operator will be overwritten.

let aWithOverrides = { ... a, x: 1, y: 2 }; // same as let aWithOverrides = {... a, ... { x: 1, y: 2 } }; Let x = 1, y = 2, aWithOverrides = {... a, x, y }; // equivalent to let aWithOverrides = object. assign({}, a, {x: 1, y: 2});Copy the code

In the above code, the x and y attributes of object A will be overwritten when copied to the new object.

This is handy for modifying properties of existing object parts.

let newVersion = { ... previousVersion, name: 'New Name' // Override the name property };Copy the code

In the above code, the newVersion object has a custom name attribute, and all other attributes are copied from the previousVersion object.

If you place a custom property in front of the extension operator, you are setting the default property value for the new object.

let aWithDefaults = { x: 1, y: 2, ... a }; // let aWithDefaults = object. assign({}, {x: 1, y: 2}, a); // let aWithDefaults = object. assign({x: 1, y: 2}, a);Copy the code

Like the extension operators of arrays, the extension operators of objects can be followed by expressions.

const obj = { ... (x > 1 ? {a: 1} : {}), b: 2, };Copy the code

The extension operator’s argument object will be executed if it has the value get.

let a = { get x() { throw new Error('not throw yet'); } } let aWithXGetter = { ... a }; / / an errorCopy the code

In the example above, the get function is automatically executed when extending object A, resulting in an error.

Chain judgment operator

In programming practice, if you read a property inside an object, you often need to check whether the object exists. . For example, to read the message body. User. FirstName, the security method is as follows.

/ / error writing const firstName = message. The body. The user. The firstName; / / the correct writing const firstName = (message && message. The body & message. The body. The user && message. The body. The user. The firstName) | | 'default';Copy the code

In the example above, the firstName attribute is at the fourth level of the object, so you need to check four times to see if each level has a value.

Ternary operators? : is also used to determine whether an object exists.

const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
Copy the code

In the example above, you must check whether fooInput exists before reading fooinput. value.

Such layers of determination are cumbersome, so ES2020 introduces the optional chaining operator? To simplify the above.

const firstName = message? .body? .user? .firstName || 'default'; const fooValue = myForm.querySelector('input[name=foo]')? .valueCopy the code

Does the above code use? The. Operator directly determines whether the object on the left is null or undefined during the chain call. If so, it does not proceed further and returns undefined.

Here is an example of determining whether an object method exists and executing it immediately if it does.

iterator.return? . ()Copy the code

Iterator. return is called if it has a definition, otherwise iterator.return returns undefined. .The latter part.

This operator is especially useful for methods that may not be implemented.

if (myForm.checkValidity? .() === false) {// Form verification failed return; }Copy the code

In the above code, old browser forms may not have checkValidity methods, in which case? The. Operator returns undefined, and the judgment statement becomes undefined === false, so the following code is skipped.

The chain judgment operator has three uses.

  • obj? .prop// Object properties
  • obj? .[expr]/ / same as above
  • func? . (... args)// Call a function or object method

Obj is here? .[expr] an example of usage.

let hex = "#C0FFEE".match(/#([A-Z]+)/i)? . [1];Copy the code

In the above example, the string’s match() method returns null if no match is found, or an array if a match is found,? The. Operator does the judging.

The following is? The common form of the. Operator and its equivalent form when the operator is not used.

a? .b // equivalent to a == null? undefined : a.b a? .[x] // equivalent to a == null? undefined : a[x] a? .b() // equivalent to a == null? undefined : a.b() a? .() // Equivalent to a == null? undefined : a()Copy the code

In the code above, pay particular attention to the last two forms, if a? The a.b in.b() is not a function. .b() is an error. a? If a is not null or undefined, but is not a function, then a? .() will report an error.

There are several caveats to using this operator.

(1) Short circuit mechanism

? The. Operator acts as a short-circuit mechanism that stops executing if the condition is not met.

a? .[++x] // Equivalent to a == null? undefined : a[++x]Copy the code

In the above code, if a is undefined or null, x is not incremented. That is, once the chain judgment operator is true, the expression on the right is no longer evaluated.

(2) Delete operator

delete a? .b // equivalent to a == null? undefined : delete a.bCopy the code

In the above code, if a is undefined or null, undefined is returned instead of delete.

(3) The influence of parentheses

If the attribute chain has parentheses, the chain judgment operator has no effect outside the parentheses, only inside the parentheses.

(a? .b).c // equivalent to (a == null? undefined : a.b).cCopy the code

In the code above,? .c after the parentheses is always executed, regardless of whether the a object exists.

In general, use? The. Operator should not use parentheses.

(4) Error reporting occasions

The following syntax is forbidden and an error may be reported.

// constructor new a? .() new a? .b() // There is template string a to the right of the chain judgment operator. .`{b}` a? .b '{c}' // The chain judgment operator on the left is super super? .() super? The.foo // chain operator is used to assign to the left of the operator a? .b = cCopy the code

(5) The right side shall not be a decimal value

To ensure compatibility with previous code, allow foo? .3:0 is resolved to foo? .3:0, so if? .followed by a decimal number, then? Instead of being treated as a complete operator, it is treated as a ternary operator, that is, the decimal point is assigned to the following decimal number, forming a decimal number.

The Null judgment operator


When reading object properties, you sometimes need to specify a default value for an attribute if its value is null or undefined. Common practice is to pass | | operators specify a default value.

const headerText = response.settings.headerText || 'Hello, world! '; const animationDuration = response.settings.animationDuration || 300; const showSplashScreen = response.settings.showSplashScreen || true;Copy the code

The three lines of code above all through the | | operator to specify a default value, but it is wrong. The developer’s original intention is that the default value will work as long as the property value is null or undefined, but the default value will also work if the property value is an empty string or false or 0.

To avoid this, ES2020 introduces a new Null judgment operator?? . Its behavior similar | |, but only the operator to the left of the value is null or undefined, will return to the right of the value.

const headerText = response.settings.headerText ?? 'Hello, world! '; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true;Copy the code

In the above code, the default value is valid only if the left attribute value is null or undefined.

One of the purposes of this operator is to check the chain operator, okay? . Set the default value for null or undefined.

const animationDuration = response.settings? .animationDuration ?? 300;Copy the code

The above code, if the response Settings is null or undefined, or response. Settings. AnimationDuration is null or undefined, will return to the default value of 300. In other words, this line of code contains two levels of attribute judgment.

This operator is good for determining whether a function parameter is assigned.

function Component(props) { const enable = props.enabled ?? true; / /... }Copy the code

This code checks whether the enabled attribute of the props parameter is assigned.

function Component(props) { const { enabled: enable = true, } = props; / /... }Copy the code

?? Have an operation priority issues, it with && and | | priority is tall what is low. The rule now is that if multiple logical operators are used together, parentheses must indicate precedence or an error will be reported.

LHS && middle?? rhs lhs ?? middle && rhs lhs || middle ?? rhs lhs ?? middle || rhsCopy the code

All four expressions above report errors and must be parentheses indicating precedence.

(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);

(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);

(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);

(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
Copy the code