This is the 13th day of my participation in the August More Text Challenge.More challenges in August

A concise representation of attributes

ES6 allows variables and functions to be written directly inside curly braces as object properties and methods. It’s much more concise.

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

/ / is equivalent to
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. Here’s another example.

function f(x, y) {
  return {x, y};
}

/ / is equivalent to

function f(x, y) {
  return {x: x, y: y};
}

f(1.2) // Object {x: 1, y: 2}
Copy the code

In addition to property abbreviations, methods can also be abbreviated.

const o = {
  method() {
    return "Hello!"; }};/ / is equivalent to

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

Here is a practical example.

let birth = '2000/01/01';

const Person = {

  name: 'Joe'.// Is the same as birth
  birth,

  // equivalent to hello: function ()...
  hello() { console.log('My name is.'.this.name); }};Copy the code

This notation is handy for the return value of a function.

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()
// {x:1, y:10}
Copy the code

The CommonJS module outputs a set of variables and is a good place to write succinct.

let ms = {};

function getItem (key) {
  return key in ms ? ms[key] : null;
}

function setItem (key, value) {
  ms[key] = value;
}

function clear () {
  ms = {};
}

module.exports = { getItem, setItem, clear };
/ / is equivalent to
module.exports = {
  getItem: getItem,
  setItem: setItem,
  clear: clear
};
Copy the code

Setters and getters for properties are actually written this way.

const cart = {
  _wheels: 4,

  get wheels () {
    return this._wheels;
  },

  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('Too small! ');
    }
    this._wheels = value; }}Copy the code

Brevity is also useful when printing objects.

let user = {
  name: 'test'
};

let foo = {
  bar: 'baz'
};

console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}
Copy the code

In the code above, console.log directly outputs user and foo as two key/value pairs, which can be confused. Enclosing them in braces becomes a concise representation of the object, with the name of the object printed before each pair of keys and values, so that it is clearer.

Note that the shorthand object method cannot be used as a constructor and will report an error.

const obj = {
  f() {
    this.foo = 'bar'; }};new obj.f() / / an error
Copy the code

In the above code, f is a shorthand object method, so obj. F cannot be used as a constructor.

Attribute name expression

JavaScript defines attributes of objects in two ways.

/ / method
obj.foo = true;

/ / method 2
obj['a' + 'bc'] = 123;
Copy the code

The first method of the above code is to use the identifier as the name of the property directly, and the second method is to use the expression as the name of the property, enclosing the expression in square brackets.

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

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

ES6 allows literals to define objects using method two (expressions) as the object’s attribute name, that is, by placing the expression inside square brackets.

let propKey = 'foo';

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

Here’s another example.

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
Copy the code

Expressions can also be used to define method names.

let obj = {
  ['h' + 'ello'] () {return 'hi'; }}; obj.hello()// hi
Copy the code

Note that property name expressions and compact representations cannot be used at the same time and will cause an error.

/ / an error
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

/ / right
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, with special care.

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.

The name attribute of the method

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(a);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 attributes

1. Enumeration

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.

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
/ /}
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(): ignoreenumerablefalseOnly 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
// false
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.

2. Traversal of attributes

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.
  • Let’s go through everything one last timeSymbolKeys, in ascending order of accession time.
Reflect.ownKeys({ [Symbol()] :0.b:0.10:0.2:0.a:0 })
// ['2', '10', 'b', 'a', Symbol()]
Copy the code

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.

Five, 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

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.

/ / an error
const obj = {
  foo: super.foo
}

/ / an error
const obj = {
  foo: () = > super.foo
}

/ / an error
const obj = {
  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 operators

The extension operator (…) was introduced in the chapter “Extending arrays”. . ES2018 introduces this operator to objects.

1. Deconstruct 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 error
Copy the code

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

let { ...x, y, z } = someObject; // Syntax error
let{ x, ... y, ... z } = someObject;// Syntax error
Copy 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 / / 2
Copy 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 // undefined
Copy 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 / / 3
Copy the code

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 contexts
Copy 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 }) {
  // Operate with x and y arguments
  // The remaining arguments are passed 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.

2. Extend operators

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

let z = { a: 3.b: 4 };
letn = { ... 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.

letfoo = { ... ['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}/ / {}

// the same as {... Object(undefined)}{... undefined}/ / {}

// the same as {... 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.

letaClone = { ... a };/ / is 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.

/ / write one
const clone1 = {
  __proto__: Object.getPrototypeOf(obj), ... obj };/ / write two
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

/ / writing three
const clone3 = Object.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.

letab = { ... a, ... b };/ / is equivalent to
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.

letaWithOverrides = { ... a,x: 1.y: 2 };
/ / is equivalent to
letaWithOverrides = { ... a, ... {x: 1.y: 2}};/ / is equivalent to
let x = 1, y = 2, aWithOverrides = { ... a, x, y };/ / is equivalent to
let aWithOverrides = Object.assign({}, a, { x: 1.y: 2 });
Copy the code

In the code above, 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.

letnewVersion = { ... 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 };/ / is equivalent to
let aWithDefaults = Object.assign({}, { x: 1.y: 2 }, a);
/ / is equivalent to
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.

constobj = { ... (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'); }}Copy the code
letaWithXGetter = { ... a };/ / an error
Copy 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.

// This is not the case
const  firstName = message.body.user.firstName;

// The correct way to write
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.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.

constfirstName = 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 propertiesobj? .[expr]/ / same as abovefunc? . (... args)// Call a function or object method
Copy the code

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/ / is equivalent to
a == null ? undefined: a.b a? .[x]/ / is equivalent to
a == null ? undefined: a[x] a? .b()/ / is equivalent to
a == null ? undefined: a.b() a? . ()/ / is 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]/ / is 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

deletea? .b/ / is equivalent to
a == null ? undefined : delete a.b
Copy 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 the
(a == null ? undefined : a.b).c
Copy 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
newa? . ()newa? .b()// There is a template string to the right of the chain judgment operatora? .`{b}`a? .b`{c}`

// To the left of the chain judgment operator is super
super? . ()super? .foo// The chain operator is used to the left of the assignment operatora? .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.

constanimationDuration = 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.

/ / an error
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
Copy 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