2

To my knowledge, the Symbol primitive in JavaScript ES6 is particularly useful for two things:

  1. Create unique keys for Object properties
  2. Override standard built-in JavaScript Object methods, properties and operators
    • For example: Symbol.hasInstance is called when (before) instanceof runs
    • Thus, if we create a custom version of Symbol.hasInstance, we could override the behavior of instanceof

My somewhat basic question is: Why use a Symbol to override these functions? Can't we just override them directly?

For example: Override String.prototype.match() instead of Symbol.match

Edit: Agree with the commenter that overriding instanceof directly would not work, thus using match() as an example instead.

Magnus
  • 6,791
  • 8
  • 53
  • 84
  • 1
    How to override `instanceof` directly? – guest271314 Jan 19 '18 at 18:26
  • Agreed, instanceof can't be overridden directly. Thus I used match() as an example instead. – Magnus Jan 19 '18 at 18:34
  • fwiw, was not able to achieve the setting of aribtrary `File` objects at `` element `.files` property without using `Symbol.iterator` property of the `FileList` object, even though that particular implementation did not fully meet requirement [How to set File objects and length property at FileList object where the files are also reflected at FormData object?](https://stackoverflow.com/questions/47119426/how-to-set-file-objects-and-length-property-at-filelist-object-where-the-files-a) – guest271314 Jan 19 '18 at 18:40
  • Why would you ever want to override an existing prototype method? If the objective is to provide a general override mechanism, requiring every library to override a global function would extremely easy to screw up. – loganfsmyth Jan 19 '18 at 19:00
  • Another approach to provide a different functionality for a particular globally defined function is to use `class`, which will not override the global native function having the same name. The specific approach depends on what you are trying to achieve – guest271314 Jan 19 '18 at 19:03
  • @loganfsmyth Not sure if I understood you correctly. I would like to change the behavior of say match() or replace() in my own script. Thus, I override it in the script. Due to JavaScript being prototype linked, changing one prototype method in an Object higher up in the prototype chain would affect all the objects that inherit from that changed Object. – Magnus Jan 19 '18 at 19:08
  • Right. Generally it's considered extremely bad practice to modify objects that you don't conceptually own unless you're explicitly adding a new guaranteed-unique property like a Symbol. It seems like you're promoting that as an alternative to the `Symbol.match` usage. Am I understanding that right? By allowing `Symbol.match` to be added to an object you control, you don't risk affecting anything on a global scale. – loganfsmyth Jan 19 '18 at 19:27
  • @loganfsmyth Thanks. I guess I am confused about how overriding the behavior of `Symbol.match() {...}` differ from overriding `String.prototype.match() {...}`. If every internal and external JS call of the latter string prototype method first runs `Symbol.match()`, and `Symbol.match()` is prototype linked like any other object in JS, then why would altering the two not have the same outcome? – Magnus Jan 20 '18 at 10:29
  • Oh, are you thinking that the `Symbol.match` case is overridden like `Symbol.match = function(){...}`? It's the name of a property, so you do `"".match({ [Symbol.match](){ ... } });` e.g. you own the object that the property lives on, where you don't own the string prototype. – loganfsmyth Jan 20 '18 at 19:31
  • @loganfsmyth I see now that I do not understand how `well-known symbols` work. In the below MDN article, it seems `Symbol.match` is a property on the `String` object. If we change it to `Symbol.match=false`, it forces `String.prototype.match()` to return `false` on our strings (i.e. a string will never be identified as a regular expression). Will this not apply to the global scope (i.e all places I use `myString.match()`)? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match – Magnus Jan 21 '18 at 01:24
  • @loganfsmyth A lot of comments here now, perhaps instead, do you have a link where I could read a bit more about what you did here: `"".match({ [Symbol.match](){ ... } });`? Or even better (if you have time), add a full answer clearing up how those well-known symbols can be used. Thanks a lot for all your help. – Magnus Jan 21 '18 at 02:08

1 Answers1

3

You didn't quite elaborate enough in your question, but from a bit of inference and the comments, it seems like you are be misunderstanding how well-known symbols are used to interact with existing types. That is causing you to misunderstand how they improve over possible ES5 global overwriting solutions.

It's important to understand that the value of String.match is a symbol, not a function for matching. It is almost as if someone had done

Symbol.match = Symbol("match");

at the top of your program to create a new Symbol, and set it as a global property so anyone can access it from anywhere.

This is in contrast with the value of String.prototype.match which is the actual function that is used when developers call "foo".match(...).

You seem to be envisioning String.prototype.match like

String.prototype.match = function(obj) {
  return Symbol.match(obj);
};

which is not the case. Looking at a simplified example of the actual implementation may help you understand:

String.prototype.match = function(obj) {
  // Any object that has a `Symbol.match` property 
  // will just call that property. This includes every
  // existing RegExp object.
  if (obj != null && obj[Symbol.match] !== undefined) {
    return obj[Symbol.match](this);
  }

  // Otherwise create a new regex to match against
  // match match using that. This is to handle strings, e.g
  // "foo".match("f") 
  const reg = new RegExp(obj);
  return reg[Symbol.match](this);
};

and remember, obj[Symbol.match](this); is not calling Symbol.match(), it is reading a property from obj with the name Symbol.match, and then calling the resulting function.

Why use a Symbol to override these functions?

Hopefully that example makes the reasoning behind this clearer.

var regexp = new RegExp("little");
var result = "a little pattern".match(regexp);

is essentially identical to doing

var regexp = new RegExp("little");
var result = regexp[Symbol.match]("a little pattern");

So why does this matter? Because now when you're designing an API to process text, you aren't limited to doing so with regular expressions. I could make my own whole library as

class MyOwnMatcher {
  constructor(word) {
    this.word = word;
  }

  [Symbol.match](haystack) {
    return haystack.indexOf(this.word);
  }
}

var index = "a little pattern".match(new MyOwnMatcher("little"));
// index === 2

and most importantly, I'm able to do this without having to change any globals. In JS code generally it's considered bad practice to modify globals unless you're polyfilling an officially specced and adopted API. You could implement the above like

var original = String.prototype.match;
String.prototype.match = function(arg) {
  if (arg instanceof MyOwnMatcher) return this.indexOf(arg);

  return original.apply(this, arguments);
};

but it's extremely ugly, easy to get wrong, and modifies a global object that isn't one that you've defined in your own code.

You can essentially think of well-known symbols as a way to implement an interface defined by a separate piece of code.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 1
    Logan, that is truly helpful, thank you. So, in summary, well-known symbols allow you to create a custom function for one specific object, which will be used by a global built-in object (in place of its own method). The key point is: Only the one object you use the Symbol with, will have the changed behavior. All other uses of say .match() will remain unaffected. Is that the right understanding of its purpose? (PS: Sorry for the late reply, I have been traveling) – Magnus Jan 24 '18 at 12:21