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

An overview,

Object attribute names in ES5 are all strings, which can easily cause attribute name conflicts. For example, if you use an object provided by someone else and want to add a new method to the object (the mixin pattern), the name of the new method may conflict with the existing method.

It would be nice if there were a mechanism to ensure that each property name was unique, preventing property name conflicts at all.

ES6 introduces a new primitive data type, Symbol, that represents unique values. It is the seventh data type of the JavaScript language, the first six being:

  • undefined
  • null
  • Boolean
  • String
  • Number
  • Object

The Symbol value is generated by the Symbol function. This means that object property names can now be of two types: the original string and the new Symbol type. Attribute names that belong to the Symbol type are unique and are guaranteed not to conflict with other attribute names.

let s = Symbol(a);typeof s
// "symbol"
Copy the code

In the code above, the variable S is a unique value. The result of the Typeof operator, indicating that the variable S is a Symbol data type and not some other type, such as a string.

Note that the new command cannot be used before the Symbol function, otherwise an error will be reported. This is because the generated Symbol is a primitive type value, not an object. That is, because the Symbol value is not an object, attributes cannot be added. Basically, it’s a data type similar to a string.

The Symbol function can take a string as an argument representing a description of the Symbol instance, mainly to make it easier to distinguish when displayed on the console or converted to a string.

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
Copy the code

In the code above, s1 and s2 are two symbols. Without arguments, they are both printed as Symbol() on the console, making it hard to tell apart. So when you have parameters, you have descriptions for them, and when you print them out, you can tell which values they are.

If Symbol’s argument is an object, the object’s toString method is called and converted to a string before a Symbol value is generated.

const obj = {
  toString() {
    return 'abc'; }};const sym = Symbol(obj);
sym // Symbol(abc)
Copy the code

Note that the parameter to the Symbol function only represents a description of the current Symbol value, so the return value of the Symbol function with the same parameter is not equal.

// No arguments
let s1 = Symbol(a);let s2 = Symbol(a); s1 === s2// false

// With parameters
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false
Copy the code

In the code above, s1 and s2 are both return values of the Symbol function and have the same parameters, but they are not equal.

The Symbol value cannot be evaluated with other types of values and will report an error.

let sym = Symbol('My symbol');

"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
Copy the code

However, the Symbol value can be explicitly converted to a string.

let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
Copy the code

In addition, the Symbol value can be converted to a Boolean value, but not to a value.

let sym = Symbol(a);Boolean(sym) // true! sym// false

if (sym) {
  // ...
}

Number(sym) // TypeError
sym + 2 // TypeError
Copy the code

Second, the Symbol. The prototype. The description

When creating a Symbol, you can add a description.

const sym = Symbol('foo');
Copy the code

In the code above, sym is described as the string foo.

However, reading this description requires converting Symbol explicitly to a string, as follows.

const sym = Symbol('foo');

String(sym) // "Symbol(foo)"
sym.toString() // "Symbol(foo)"
Copy the code

The above usage is not very convenient. ES2019 provides an instance attribute, Description, that returns the description of Symbol directly.

const sym = Symbol('foo');

sym.description // "foo"
Copy the code

Symbol as attribute name

Since each Symbol value is not equal, this means that the Symbol value can be used as an identifier for the property name of the object, ensuring that no property with the same name will appear. This is useful in cases where an object is made up of multiple modules, preventing a key from being accidentally overwritten or overwritten.

let mySymbol = Symbol(a);// The first way
let a = {};
a[mySymbol] = 'Hello! ';

// The second way
let a = {
  [mySymbol]: 'Hello! '
};

// The third way
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello! ' });

// All the above methods give the same result
a[mySymbol] // "Hello!"
Copy the code

The above code specifies the attribute name of the Object as a Symbol value using square brackets and Object.defineProperty.

Note that the dot operator cannot be used when the Symbol value is the name of the object property.

const mySymbol = Symbol(a);const a = {};

a.mySymbol = 'Hello! ';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
Copy the code

In the above code, because the dot operator is always followed by a string, the value mySymbol refers to as the identifier name is not read, resulting in the attribute name of A being actually a string rather than a Symbol value.

Similarly, inside an object, when defining an attribute using the Symbol value, the Symbol value must be placed in square brackets.

let s = Symbol(a);let obj = {
  [s]: function (arg) {... }}; obj[s](123);
Copy the code

In the above code, if s is not enclosed in square brackets, the property’s key name is the string S, not the Symbol value that s represents.

With enhanced object writing, the obJ object of the above code can be written more succinctly.

letobj = { [s](arg) { ... }};Copy the code

The Symbol type can also be used to define a set of constants that are guaranteed to have unequal values.

const log = {};

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn')};console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');
Copy the code

It is also important to note that when the Symbol value is used as the property name, the property is still public, not private.

Example: Eliminate magic string

A magic string is a specific string or number that occurs multiple times in the code and is strongly coupled to the code.

Well-styled code should eliminate magic strings as much as possible and replace them with well-defined variables.

function getArea(shape, options) {
  let area = 0;

  switch (shape) {
    case 'Triangle': // Magic string
      area = . 5 * options.width * options.height;
      break;
    / *... more code ... * /
  }

  return area;
}

getArea('Triangle', { width: 100.height: 100 }); // Magic string
Copy the code

In the code above, the string Triangle is a magic string. It occurs multiple times, creating a “strong coupling” with the code that is not conducive to future modifications and maintenance.

A common way to eliminate a magic string is to write it as a variable.

const shapeType = {
  triangle: 'Triangle'
};

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = . 5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100.height: 100 });
Copy the code

In the code above, we wrote Triangle as the Triangle property of the shapeType object, eliminating strong coupling.

If you look closely, you can see that it doesn’t matter which value shapeType.triangle equals, just make sure it doesn’t conflict with the values of other shapeType attributes. Therefore, this is a good place to use the Symbol value.

const shapeType = {
  triangle: Symbol()
};
Copy the code

In the above code, nothing needs to be changed except to set the shapetype. triangle value to a Symbol.

Traversal of attribute names

Symbol is the attribute name. When traversing the object, the attribute does not appear in the for… In, for… Of loop, will not be the Object. The keys (), Object, getOwnPropertyNames (), JSON. The stringify () returns.

However, it is not private property, with an Object. The getOwnPropertySymbols () method, which can get all the Symbol of specified Object attribute names. This method returns an array of all the Symbol values used as attribute names for the current object.

const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]
Copy the code

Code above is Object. GetOwnPropertySymbols () method of sample, you can get all the Symbol attribute names.

Here is another example of the Object. GetOwnPropertySymbols () method with the for… In circulation, the Object. Comparing getOwnPropertyNames method example.

const obj = {};
const foo = Symbol('foo');

obj[foo] = 'bar';

for (let i in obj) {
  console.log(i); / / no output
}

Object.getOwnPropertyNames(obj) / / []
Object.getOwnPropertySymbols(obj) // [Symbol(foo)]
Copy the code

In the code above, use for… In circulation and the Object. GetOwnPropertyNames () methods are not Symbol key name, you need to use the Object. The getOwnPropertySymbols () method.

Another new API, the reflecti.ownkeys () method, returns all types of key names, including regular and Symbol.

let obj = {
  [Symbol('my_key')]: 1.enum: 2.nonEnum: 3
};

Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
Copy the code

Symbol. For(), Symbol. KeyFor ()

Sometimes, we want to reuse the same Symbol value, and the symbol.for () method can do this.

Symbol.for() takes a string as an argument and searches for a Symbol value named for that argument. If so, return the Symbol value, otherwise create a new Symbol with the name of the string and register it globally.

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true
Copy the code

In the code above, s1 and s2 are both Symbol values, but they are both generated by the symbol. for method with the same argument, so they are actually the same value.

Symbol. For () and Symbol() both generate a new Symbol. The difference is that the former will be registered in the global environment for search, while the latter will not.

Symbol.for() does not return a new Symbol value each time it is called. Instead, it checks to see if the given key already exists and creates a new value if it does not. For example, if you call symbol. for(“cat”) 30 times, the same Symbol will be returned each time, but calling Symbol(“cat”) 30 times will return 30 different symbols.

Symbol.for("bar") = = =Symbol.for("bar")
// true

Symbol("bar") = = =Symbol("bar")
// false
Copy the code

In the code above, since the Symbol() notation has no registration mechanism, each call returns a different value.

The symbol.keyfor () method returns the key of a registered Symbol type value.

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
Copy the code

In the code above, the variable s2 belongs to an unregistered Symbol value, so undefined is returned.

Note that symbol.for () registers the name of the Symbol value for the global environment, whether or not it is running in the global environment.

function foo() {
  return Symbol.for('bar');
}

const x = foo();
const y = Symbol.for('bar');
console.log(x === y); // true
Copy the code

In the code above, symbol.for (‘bar’) is run inside the function, but the generated Symbol value is registered globally. So, a second run of symbol. for(‘bar’) fetches the Symbol value.

The global registration feature of symbol.for () can be used to fetch the same value from different iframes or service workers.

iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);

iframe.contentWindow.Symbol.for('foo') = = =Symbol.for('foo')
// true
Copy the code

In the code above, the Symbol value generated by the iframe window is available on the main page.