• Metaprogramming in ES6: Symbols and Why they’re awesome
  • By Keith Cirkel
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: yoyoyohamapi
  • Proofreader: Usey95 IridescentMia

You’ve heard of the ES6, right? This is a new version of JavaScript that excels in many ways. Every time I find an amazing new feature in ES6, I start gushing about it to my colleagues (not everyone likes taking lunch breaks).

A number of excellent new ES6 features come from new metaprogramming tools that inject low-level hooks into the code mechanism. There are very few articles on ES6 metaprogramming so far, so I think I’ll write three blog posts about them (incidentally, I’m too lazy, this 90% complete post has been sitting in my draft box for three months, and more has been written here since I said I was going to write) :

In this paper, Proxies are played around with Proxies. The Proxies are played around with Proxies

metaprogramming

First, let’s take a quick look at metaprogramming and explore the wonderful world of metaprogramming. Metaprogramming (broadly speaking) is all about the underlying mechanisms of a language, not the high-level abstractions of data modeling or business logic. If programs can be described as “making programs,” metaprogramming can be described as “letting programs make programs.” You may already be using metaprogramming unconsciously in your daily programming.

Well, meta-programming has some “subgenres” — one of them is Code Generation, Also called Eval — JavaScript has had the ability to generate code since its inception (JavaScript had eval in ES1, even before try/catch and switch). Other popular programming languages today have code generation features.

Another aspect of metaprogramming is Reflection — it’s used to discover and tweak the structure and semantics of your application. JavaScript has several tools to accomplish reflection. Functions are Function#name, Function#length, and Function#bind, Function#call, and funcin #apply. All methods available on Object are also reflections, such as Object.getownProperties. JavaScript also has reflection/introspection operators such as Typeof, Instancesof, and DELETE.

Reflection is a really cool part of metaprogramming because it allows you to change the inner workings of your application. In Ruby, for example, you can declare an operator as a method to override how it works on the class (this is often called “operator overloading”) :

class BoringClass
end
class CoolClass
  def = =(other_object)
   other_object.is_a? CoolClass
  end
end
BoringClass.new == BoringClass.new #=> false
CoolClass.new == CoolClass.new #=> true!Copy the code

JavaScript’s metaprogramming features lag behind those of other languages like Ruby or Python — especially given its lack of nice tools like operator overloading — but ES6 is starting to help JavaScript catch up in metaprogramming.

Metaprogramming in ES6

ES6 brings three new apis: Symbol, Reflect, and Proxy. It’s a little confusing when you first look at them — do all three apis serve metaprogramming? If you look at these apis separately, you can see that they make a lot of sense:

  • Symbols is Reflection Within Implementation — you apply Symbols to your existing classes and objects to change their behavior.
  • Reflect is Reflection through introspection — often used to explore very low-level code information.
  • A Proxy implements Reflection through intercession — wraps an object and intercepts its behavior through an interception.

So, how do they work? How do they become useful? This article will cover Symbols, and the next two articles will cover reflection and proxy, respectively.

Symbols — implemented reflection

Symbols is the new primitive type. Like Number, String, and Boolean. Symbols has a Symbol function used to create Symbols. Unlike the other primitive types, Symbols does not have a literal syntax (for example, String has “) — the only way to create a Symbol is to use a Symbol function that resembles a constructor but is not a constructor:

Symbol(a);// symbol
console.log(Symbol()); // Prints "Symbol()" to the console
assert(typeof Symbol() = = ='symbol')
new Symbol(a);// TypeError: Symbol is not a constructorCopy the code

Symbols has built-in debugging capabilities

Symbols can specify a description, which is useful for debugging, and the programming experience will be more user-friendly when we can output more useful information to the console:

console.log(Symbol('foo')); // Prints "Symbol(foo)" to the console
assert(Symbol('foo').toString() === 'Symbol(foo)');Copy the code

Symbols can be used as objects’ keys

This is where Symbols gets really interesting. They are tightly intertwined with the object. Symbols can be used as object keys (similar to string keys). This means you can assign an infinite number of unique Symbols to an object. These Symbols are guaranteed not to conflict with existing string keys, or with other Symbol keys:

var myObj = {};
var fooSym = Symbol('foo');
var otherSym = Symbol('bar');
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(myObj.foo === 'bar');
assert(myObj[fooSym] === 'baz');
assert(myObj[otherSym] === 'bing');Copy the code

Symbols, moreover, the key to pass for in, for, or Object. GetOwnPropertyNames – the only way to get them is Object. GetOwnPropertySymbols:

var fooSym = Symbol('foo');
var myObj = {};
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
Object.keys(myObj); // -> [ 'foo' ]
Object.getOwnPropertyNames(myObj); // -> [ 'foo' ]
Object.getOwnPropertySymbols(myObj); // -> [ Symbol(foo) ]
assert(Object.getOwnPropertySymbols(myObj)[0] === fooSym);Copy the code

This means that Symbols can provide a hidden layer to objects, enabling them to achieve a new purpose. Properties cannot be iterated, cannot be obtained by existing reflection tools, and can be guaranteed not to conflict with any existing properties of the object.

Symbols is completely unique……

By default, each newly created Symbol has a completely unique value. If you create a Symbol(var mysym = Symbol()), a new value will be created inside the JavaScript engine. If you do not keep a reference to the Symbol object, you cannot use it. This also means that two symbols will never be equivalent to the same value, even if they have the same description:

assert.notEqual(Symbol(), Symbol());
assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.notEqual(Symbol('foo'), Symbol('bar'));

var foo1 = Symbol('foo');
var foo2 = Symbol('foo');
var object = {
    [foo1]: 1,
    [foo2]: 2}; assert(object[foo1] ===1);
assert(object[foo2] === 2);Copy the code

. Wait, there are exceptions

Hang on, here’s a small caveat — JavaScript also has another way of creating symbols to make it easy to obtain and reuse them: symbol.for (). This method creates a Symbol in the Global Symbol Registry. One extra note: This registry is also cross-domain, meaning that the Symbol in the iframe or service worker will be equal to the current frame Symbol:

assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.equal(Symbol.for('foo'), Symbol.for('foo'));

// Not the only one:
var myObj = {};
var fooSym = Symbol.for('foo');
var otherSym = Symbol.for('foo');
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(fooSym === otherSym);
assert(myObj[fooSym] === 'bing');
assert(myObj[otherSym] === 'bing');

/ / across domains
iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
assert.notEqual(iframe.contentWindow.Symbol, Symbol);
assert(iframe.contentWindow.Symbol.for('foo') = = =Symbol.for('foo')); // true!Copy the code

The global Symbol will make things more complicated, but we are reluctant to give up its good aspects. Now, some of you may say, “How do I know which symbols are unique and which are not?” To which I would say “Don’t worry, we also have symbol.keyfor ()” :

var localFooSymbol = Symbol('foo');
var globalFooSymbol = Symbol.for('foo');

assert(Symbol.keyFor(localFooSymbol) === undefined);
assert(Symbol.keyFor(globalFooSymbol) === 'foo');
assert(Symbol.for(Symbol.keyFor(globalFooSymbol)) === Symbol.for('foo'));Copy the code

What are Symbols, and what are they not?

Above we have had an overview of what symbols are and how they work, but more importantly, we need to know what situations symbols are and are not suitable for.

  • Symbols never conflicts with the string key of an object. This feature makes Symbol outstanding when extending an existing object (for example, Symbol as a function parameter) without explicitly affecting the object:

  • Symbols cannot be read by existing reflection tools. You need a new method for Object. GetOwnPropertySymbols () to access the Object Symbols, which makes the Symbol suitable for storing those you don’t want to let others direct access to information. Use Object. GetOwnPropertySymbols () is a very special case, ordinary people don’t know.

  • Symbols is not private. As a double-edged sword, on the other side of the Object in all Symbols can be directly through the Object. The getOwnPropertySymbols () – which we use the Symbol of privatisation store some really value. Don’t try to use Symbols to store values in objects that need to be truly privatized — Symbols are always available.

  • Enumerable Symbols can be copied to other objects, using new methods like object. assign. If you try to call Object.assign(newObject, objectWithSymbols) and all of the iterable Symbols are passed as the second argument (objectWithSymbols), These Symbols are copied to the first argument (newObject). If you don’t want this to happen, use obejct.defineProperty to make these Symbols non-iterable.

  • Symbols cannot cast a type to a primitive object. If you try to cast a Symbol to the original value object (+Symbol(), -symbol (), Symbol() + ‘foo’), an error will be thrown. This prevents you from accidentally stringifying symbols when you set them to object property names. Symbol can be converted to bool (typeof!! Symbol(”) === ‘Boolean ‘)

  • Symbols are not always unique. As mentioned above, symbol.for () will return you a Symbol that is not unique. Do not always assume that Symbol is unique unless you are sure of its uniqueness.

  • Symbols is not the same thing as Ruby’s Symbols. Both have some commonalities, such as a Symbol registry, but only that. JavaScript symbols cannot be used as Ruby symbols.

What does Symbols really fit into?

In reality, Symbols is just a slightly different way of binding object properties — you can easily provide some well-known Symbols (such as Symbols.iterator) as standard methods, As the Object. The prototype. The hasOwnProperty method appears in the all inherited from the Object of the Object (inherited from Object, basically means that all objects have the hasOwnProperty method). In fact, languages such as Python provide standard methods in this way — in Python, the equivalent of symbol. iterator is __iter__, The equivalent of Symbole. HasInstance is __instancecheck__, and I guess __cmp__ is similar to Symbole. ToPrimitive. While This may be a poor approach from Python, JavaScript’s Symbols can provide standard methods without relying on any weird syntax, and in no case will users encounter any conflicts with these standard methods.

In my opinion, Symbols can be used in the following two scenarios:

1. The unique value used as a replaceable string or integer

Suppose you have a log library that contains multiple log levels, such as Logger.levels.debug, Logger.levels.info, Logger.levels.warn, and so on. In ES5, you set or determine levels by string or integer: Logger.levels. DEBUG === ‘DEBUG’, logger.levels.DEBUG === 10. None of these approaches is ideal because they don’t guarantee unique levels, but the uniqueness of Symbols does the job well! Logger. levels now become:

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

2. As a place to place metadata in an object

You can also use symbols to store meta information properties that are less important to real objects. Think of this as another layer of non-iterability (after all, non-iterable keys still appear in Object.getownproperties). Let’s create a solid collection class and add a size reference to it to get unary information about the size of the collection. This information is not exposed externally by Symbol (just remember, Symbols are not private — and only if you don’t care if other parts of your application modify the Symbols property). Use Symbol again:

var size = Symbol('size');
class Collection {
    constructor() {
        this[size] = 0;
    }

    add(item) {
        this[this[size]] = item;
        this[size]++;
    }

    static sizeOf(instance) {
        returninstance[size]; }}var x = new Collection();
assert(Collection.sizeOf(x) === 0);
x.add('foo');
assert(Collection.sizeOf(x) === 1);
assert.deepEqual(Object.keys(x), ['0']);
assert.deepEqual(Object.getOwnPropertyNames(x), ['0']);
assert.deepEqual(Object.getOwnPropertySymbols(x), [size]);Copy the code

3. Give developers the ability to add hooks to objects in the API

This is going to sound a little strange, but be patient and listen to me. Suppose we have a console.log style utility function — this function can take any object and output it to the console. It has its own mechanism for determining how objects are displayed on the console — but as a developer using the API, thanks to a hook in the Inspect Symbol implementation, you can provide a way to override the display mechanism:

// Get the magic Inspect Symbol from the API's Symbols constant
var inspect = console.Symbols.INSPECT;

var myVeryOwnObject = {};
console.log(myVeryOwnObject); / / log ` ` {}

myVeryOwnObject[inspect] = function () { return 'DUUUDE'; };
console.log(myVeryOwnObject); // Log output 'DUUUDE'Copy the code

The inspect hook is roughly implemented as follows:

console.log = function (... items) {
    var output = ' ';
    for(const item of items) {
        if (typeof item[console.Symbols.INSPECT] === 'function') {
            output += item[console.Symbols.INSPECT](item);
        } else {
            output += console.inspect[typeof item](item);
        }
        output += ' ';
    }
    process.stdout.write(output + '\n');
}Copy the code

To be clear, this doesn’t mean you should write code that changes a given object. This is never allowed (for this, take a look at WeakMaps, which provides you with secondary objects to collect meta-information that you define on your own objects).

If you have doubts about WeakMap, see StackOverflow — What are the actual uses of ES6 WeakMap? .

Node.js already has a similar implementation in its console.log. It uses a string (‘inspect’) instead of Symbol, which means you can set x.inspect = function(){} — not smart, as this may conflict with your class methods at some point. Using Symbol is a very proactive way to prevent this from happening.

The use of Symbols in this way is profound and has become a part of the language, which allows us to delve into some of the famous Symbols.

The built-in Symbols

A key part of what makes Symbols useful is the set of Symbol constants, called the “built-in Symbols”. These constants are actually a bunch of static methods on the Symbol class that are implemented by other native objects such as arrays, strings, and JavaScript engines internally. This is where part of the real “Reflection within Implementation” happens, because these built-in symbols change JavaScript internal behavior. Next, I’ll detail what each Symbol does and why these Symbols are so great.

Symbol.hasInstance: instanceof

Symbol.hasInstance is a Symbol that implements the behavior of Instanceof. When an ES6-compatible engine sees the instanceof operator in an expression, it calls symbol.hasinstance. For example, the expression lho instanceof rho will call rho[symbol.hasinstance](lho) (rho is the right-hand operand of the operator and lho is the left-hand operand). This method can then determine whether an object inherits from a particular instance. You can implement this method as follows:

class MyClass {
    static [Symbol.hasInstance](lho) {
        return Array.isArray(lho);
    }
}
assert([] instanceof MyClass);Copy the code

Symbol.iterator

If you’ve more or less heard of Symbols, you’ve probably heard of symbol.iterator. ES6 brings a new pattern — the for of loop, which iterates over the current value by calling symbol. iterator as the right-hand operand. In other words, the following code is equivalent:

var myArray = [1.2.3];

// An implementation using 'for of'
for(var value of myArray) {
    console.log(value);
}

// There is no 'for of' implementation
var _myArray = myArray[Symbol.iterator]();
while(var _iteration = _myArray.next()) {
    if (_iteration.done) {
        break;
    }
    var value = _iteration.value;
    console.log(value);
}Copy the code

Symbol.ierator will allow you to override the of operator — which means developers love you if you use it to create a library:

class Collection {
  *[Symbol.iterator]() {
    var i = 0;
    while(this[i] ! = =undefined) {
      yield this[i]; ++i; }}}var myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(var value of myCollection) {
    console.log(value); // 1, then 2
}Copy the code

Symbol.isConcatSpreadable

Symbol. IsConcatSpreadable is a very special Symbol – driven Array# concat behavior. As you can see, Array#concat can take multiple arguments, and if you pass in multiple arrays as arguments, the arrays will be flattened and merged later. Consider the following code:

x = [1.2].concat([3.4], [5.6].7.8);
assert.deepEqual(x, [1.2.3.4.5.6.7.8]);Copy the code

Under the ES6 Array# concat will use the Symbol. IsConcatSepreadable to decide whether its parameters can be carried out. For this, it’s fair to say that your classes that inherit from Array are not particularly suited for Array#concat, rather than for any other reason:

class ArrayIsh extends Array {
    get [Symbol.isConcatSpreadable]() {
        return true; }}class Collection extends Array {
    get [Symbol.isConcatSpreadable]() {
        return false;
    }
}
arrayIshInstance = new ArrayIsh();
arrayIshInstance[0] = 3;
arrayIshInstance[1] = 4;
collectionInstance = new Collection();
collectionInstance[0] = 5;
collectionInstance[1] = 6;
spreadableTest = [1.2].concat(arrayInstance).concat(collectionInstance);
assert.deepEqual(spreadableTest, [1.2.3.4, <Collection>]);Copy the code

Symbol.unscopables

This Symbol has some interesting history. In fact, while developing ES6, Technical Committees (TC) found some old code in popular JavaScript libraries:

var keys = [];
with(Array.prototype) {
    keys.push('foo');
}Copy the code

This code works fine in ES5 and earlier versions of javascript, but ES6 now has an Array#keys — which means that when you execute with(array.prototype), Keys refers to the keys method on the Array prototype, Array#keys, not the keys you defined outside of with. There are three ways to solve this problem:

  1. Retrieve all sites that use the code and upgrade the corresponding code base. (It’s almost impossible.)
  2. deleteArray#keysAnd pray that such bugs do not appear. (This doesn’t really solve the problem either.)
  3. Write a hack wrapped around all such code to preventkeysAppear in thewithStatement scope.

The technical committee chose the third option, hence symbol.unscopables, which defines a set of “unscopable” values for objects that are not set to values on the object when used in the with statement. You almost never use this Symbol — you won’t see this in everyday JavaScript, either — but it still shows how Symbols are used and preserves the integrity of the Symbol:

Object.keys(Array.prototype[Symbol.unscopables]); // -> ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']

// Do not use unscopables:
class MyClass {
    foo() { return 1; }}var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); / / 1!!!!!
}

/ / use unscopables:
class MyClass {
    foo() { return 1; }
    get [Symbol.unscopables]() {
        return { foo: true}; }}var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); / / 2!!!!!
}Copy the code

Symbol.match

This is another Symbol for a function. The String#match function will be able to customize the macth rule stream to determine whether a given value matches. Now you can implement your own matching strategy instead of using regular expressions:

class MyMatcher {
    constructor(value) {
        this.value = value;
    }
    [Symbol.match](string) {
        var index = string.indexOf(this.value);
        if (index === - 1) {
            return null;
        }
        return [this.value]; }}var fooMatcher = 'foobar'.match(new MyMatcher('foo'));
var barMatcher = 'foobar'.match(new MyMatcher('bar'));
assert.deepEqual(fooMatcher, ['foo']);
assert.deepEqual(barMatcher, ['bar']);Copy the code

Symbol.replace

Similar to symbol.match, symbol.replace allows you to pass custom classes to accomplish string substitution, rather than just using regular expressions:

class MyReplacer {
    constructor(value) {
        this.value = value;
    }
    [Symbol.replace](string, replacer) {
        var index = string.indexOf(this.value);
        if (index === - 1) {
            return string;
        }
        if (typeof replacer === 'function') {
            replacer = replacer.call(undefined.this.value, string);
        }
        return `${string.slice(0, index)}${replacer}${string.slice(index + this.value.length)}`; }}var fooReplaced = 'foobar'.replace(new MyReplacer('foo'), 'baz');
var barMatcher = 'foobar'.replace(new MyReplacer('bar'), function () { return 'baz' });
assert.equal(fooReplaced, 'bazbar');
assert.equal(barReplaced, 'foobaz');Copy the code

Symbol.search

Similar to symbol. match and symbol.replace, symbol.search enhances String#search — allowing custom classes to be passed in place of regular expressions:

class MySearch {
    constructor(value) {
        this.value = value;
    }
    [Symbol.search](string) {
        return string.indexOf(this.value); }}var fooSearch = 'foobar'.search(new MySearch('foo'));
var barSearch = 'foobar'.search(new MySearch('bar'));
var bazSearch = 'foobar'.search(new MySearch('baz'));
assert.equal(fooSearch, 0);
assert.equal(barSearch, 3);
assert.equal(bazSearch, - 1);Copy the code

Symbol.split

Now it’s time for the last string-related Symbol — symbol. split corresponds to String#split. Usage:

class MySplitter {
    constructor(value) {
        this.value = value;
    }
    [Symbol.split](string) {
        var index = string.indexOf(this.value);
        if (index === - 1) {
            return string;
        }
        return [string.substr(0, index), string.substr(index + this.value.length)]; }}var fooSplitter = 'foobar'.split(new MySplitter('foo'));
var barSplitter = 'foobar'.split(new MySplitter('bar'));
assert.deepEqual(fooSplitter, [' '.'bar']);
assert.deepEqual(barSplitter, ['foo'.' ']);Copy the code

Symbol.species

Symbol.species is a very clever Symbol that points to a constructor of a class, which allows the class to create its own new version of a method. For example, Array#map can create a new array with the values returned each time the callback function is passed — an implementation of Array#map in ES5 might look something like this:

Array.prototype.map = function (callback) {
    var returnValue = new Array(this.length);
    this.forEach(function (item, index, array) {
        returnValue[index] = callback(item, index, array);
    });
    return returnValue;
}Copy the code

Array#map in ES6, as well as all other immutable Array methods (Array#filter, etc.), have been updated to create objects using the symbol. species property, so an Array#map implementation in ES6 might look like this:

Array.prototype.map = function (callback) {
    var Species = this.constructor[Symbol.species];
    var returnValue = new Species(this.length);
    this.forEach(function (item, index, array) {
        returnValue[index] = callback(item, index, array);
    });
    return returnValue;
}Copy the code

Now, if you write class Foo extends Array — every time you call Foo#map, before it returns an Array of type Array (which is not what we want), You should have written your own Map implementation to create an Array of Foo types instead of Array classes, but now, with sympbol. species, Foo# Map returns an Array of Foo types directly:

class Foo extends Array {
    static get [Symbol.species]() {
        return this; }}class Bar extends Array {
    static get [Symbol.species]() {
        return Array;
    }
}

assert(new Foo().map(function(){}) instanceof Foo);
assert(new Bar().map(function(){}) instanceof Bar);
assert(new Bar().map(function(){}) instanceof Array);Copy the code

You may ask, why use this.constructor instead of this.constructor[symbol.species]? Symbol.species provides customizable access to the types that need to be created — you may not always want to subclass and how to create them, take this code for example:

class TimeoutPromise extends Promise {
    static get [Symbol.species]() {
        return Promise; }}Copy the code

This timeout promise can create a delayed action — of course, you don’t want a promise to delay subsequent promises along the entire Prmoise chain, So symbol. species can be used to tell a TimeoutPromise to return a Promise from its prototype chain method. If a TimeoutPromise is returned, then each Promise in the chain of promises concatenated by Promise#then is a TimeoutPromise. It’s so convenient.

Symbol.toPrimitive

This Symbol provides us with the overloaded Abstract Equality Operator (short ==). Basically, Symbol. ToPrimitive is used when the JavaScript engine needs to convert your object to its original value — for example, if you do +object, JavaScript calls object[symbol.toprimitive](‘number’); If you execute “+object, JavaScript calls object[symbol.toprimive](‘string’), and if you execute if(object), JavaScript calls object[symbol.toprimitive](‘default’). Previously, we had valueOf and toString to handle these cases, but both were somewhat crude and you probably never got the desired behavior from them. The implementation of symbol.toprimitive is as follows:

class AnswerToLifeAndUniverseAndEverything {[Symbol.toPrimitive](hint) {
        if (hint === 'string') {
            return 'Like, 42, man';
        } else if (hint === 'number') {
            return 42;
        } else {
            // Most classes (except Date) return a numeric raw value by default
            return 42; }}}var answer = new AnswerToLifeAndUniverseAndEverything();
+answer === 42;
Number(answer) === 42;
' '+answer === 'Like, 42, man';
String(answer) === 'Like, 42, man';Copy the code

Symbol.toStringTag

This is the last built-in Symbol. Symbol.toStringTag is a really cool Symbol — if you haven’t tried to implement your own type judgment to replace the Typeof operator, You might use object #toString() — it returns weird strings like ‘[object object]’ or ‘[object Array]’. Before ES6, the method’s behavior was hidden in the implementation details, but today, in ES6 paradise, we have a Symbol that controls its behavior! Any object passed to Object#toString() will be checked to see if it has a [symbol.tostringtag] property, which is a string. If so, the string will be used as the result of Object#toString(), as shown in the following example:

class Collection {

  get [Symbol.toStringTag]() {
    return 'Collection'; }}var x = new Collection();
Object.prototype.toString.call(x) === '[object Collection]'Copy the code

The other thing about this — if you’re using Chai for testing, it’s now using Symbol underneath for type checking, so, You can write expect(x).to.be.a(‘Collection’) in your tests (x has a property like symbol.tostringTag above, and this code needs to run on browsers that support that Symbol).

Missing Symbol: symbol. isAbstractEqual

You probably already know the meaning and usage of Symbol in ES6, but I really like the idea of reflection in Symbol, so I want to say a few more words. For me, this is missing a Symbol that I would be excited about: Symbol. IsAbstractEqual. This Symbol reglorifies the abstract equality operator (==). Like Ruby, Python, etc., we can use it in our own way, for our own classes. When you see code such as lho == rho, JavaScript can convert to rho[symbol.isAbstractequal](lHO), allowing the class to override the meaning of the operator ==. This can be done in a backward-compatible way — by defining default values for all current primitive value prototypes (such as number.prototype), the Symbol will make much of the specification clearer and give developers a reason to revert to ==.

conclusion

What do you think about Symbols? Still confused? Want to yell at someone? I’m @Keithamus from Titterverse — harass me all you want and ONE day I’ll spend my lunch hour telling you about my favorite new ES6 features.

Now that you’ve read all about Symbols, it’s time to read part 2 — Reflect.

Finally, I would like to thank the excellent developers @Focusaurus, @MTTSHw, @colby_Russell, @MDMazzola, and @Webreflection for checking and improving this article.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, React, front-end, back-end, product, design and other fields. If you want to see more high-quality translation, please continue to pay attention to the Project, official Weibo, Zhihu column.