To my knowledge, the Symbol primitive in JavaScript ES6 is particularly useful for two things:
Create unique keys for Object properties
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.
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.
Related
I was reading the source code for pallet.js and came across this.
var ret = (function(proto) {
return {
slice: function(arr, opt_begin, opt_end) {
return proto.slice.apply(arr, proto.slice.call(arguments, 1));
},
extend: function(arr, arr2) {
proto.push.apply(arr, arr2);
}
};
})(Array.prototype);
var slice = ret.slice;
var extend = ret.extend;
Why is this necessary? Why could they not simply write this:
var slice = function(arr,opt_begin,opt_end) {
return Array.prototype.slice.apply(arr,[opt_begin,opt_end]));
}
var extend = function(arr,arr2) {
return Array.prototype.push.apply(arr,arr2);
}
EDIT 1:
In response to the duplicate question. I don't think it is a duplicate, but that question definitely does address my question. So it is an optimization. But won't each one only be evaluated once? So is there really a significant improvement here for two function calls?
Also if we are worried about performance why are we calling proto.slice.call(arguments,1) instead of constructing the array of two elements by hand [opt_begin,opt_end], is slice faster?
Because the syntax is just so much cooler. Plus you can rationalize it's use by telling yourself that it's more DRY. You didn't have to type Array.prototype twice.
I can't be sure what was the original rationale behind that code (only the author knows) but I can see a few differences:
proto is a closed-over local variable, while instead Array is a global. It's possible for a smart enough Javascript engine to optimize access because proto is never changed and thus it could even be captured by value, not reference. proto.slice can be faster than Array.prototype.slice because one lookup less is needed.
passing opt_begin and opt_end as undefined is not the same as not passing them in general. The called function can know if a parameter was passed and happens to be undefined or if instead it wasn't passed. Using proto.slice.call(arguments, 1) ensures that the parameters are passed to slice only if they were actually passed to the closure.
Before I begin, I want to confess that I am a JavaScript novice and I have very little understanding/knowledge of JavaScript patterns and terminologies so please feel free to explain basic concepts to me like I'm 5!
I have previously used the JavaScript prototype pattern to great effect in my work.
Here is a sample of my previous work with the prototype pattern
var SomeNameSpace = SomeNameSpace || {};
SomeNameSpace.SomeClass = function(oSomeParameter){
this.SomeProperty = oSomeParameter
...
}
SomeNameSpace.SomeClass.prototype = {
SomeClassMethod: function (oSomeOtherParameter) {//code here}
}
var someClassInstance = new SomeNameSpace.SomeClass("some string");
var result = someClassInstance.SomeClassMethod("some other string");
That snippet is an example of how I have always worked with javascript
I have been put in charge of supporting some new javascript code. I would like to introduce the same sort of prototype pattern to this new library. However, the namespace is written in a way which is foreign to me and I do not know how to modify it to suit my needs.
An example
if (typeof SomeNamespace == "undefined") {
SomeNamespace = { __namespace: true };
}
SomeNamespace.SomeOtherNamespace = {
SomeClass: function(oSomeParameter){
this.SomeProperty = oSomeParameter
...
}
}
I don't know how to add prototype functions to this code....
(Sorry if I'm vague on details, I'm not even sure why the namespace is declared like that in my 2nd example so if someone could explain that to me, that'd be great!)
*Edit*
Corrected syntax in 2nd example
*Edit*
Left out the "new" keyword in my example
Defining methods
This piece of code is not syntactically correct:
SomeNamespace.SomeOtherNamespace = {
SomeClass = function(oSomeParameter){ // you probably have : instead of =
this.SomeProperty = oSomeParameter
...
}
}
To add an instance method in the second example, you can simply do after the definition of SomeClass:
SomeNamespace.SomeotherNamespace.SomeClass.prototype.SomeClassMethod = function() {
};
In both the first and the second way you mentioned, your code wants to show that these functions (instance methods in first example, classes in second example) all belong to the same object (prototype in first example, namespace in second example). That is all nice and good for a few properties, but i find this gets more in the way when you're dealing with classes with many methods or even worse, namespaces with many classes.
I would recomend you separate your code using different files and minify them together. A folder represents a namespace and a file represents a class. Follow the pattern in your first example, but instead of saying "this is the prototype object with these methods", simply add them one at a time using the example line above.
Declaring namespaces
First of all, we need to be on the same page. In JavaScript a namespace is simply an object (that contains as properties whatever interests you, constructors, static functions - ex factory methods, other namespaces, etc).
The first example a = a || {} makes sure that namespace a is defined but makes sure not to overwrite it if it was defined elsewhere. For most use cases it is enough and it has the advantage of being very concise and clear to most people reading your code.
The second example does something similar to what the first does, but with two differences:
Specifically checks that a was undefined before defining it (ex1 only checked for falsyness which is usually enough)
Adds the _namespace property to a
Regarding the check for undefined, i doubt you need it. If your code has collisions with something that uses 'a' as something else than an object, then there's a high chance something will break regardless of the method used.
The _namespace property is something purely conventional to that code i think. It may help with various tools (perhaps during debugging or for automatic documentation generation), but that's about all i can think of. Obviously you're in a much better position to see if it is actually used for something, so if you encounter an interesting usage, perhaps you could leave a comment.
To sum it up, i prefer the first variant because it is more concise and even more frequent (so easier to recognize by someone reading the code).
Full example:
// class definition
a = a || {}; // global namespace, all good
a.b = a.b || {}; // both lines are needed
a.b.Class = function() {
this.myProp = 'hello';
};
a.b.Class.prototype.myMethod = function() {
};
// usage
var myInstance = new a.b.Class();
instance.myMethod();
var x = instance.myProp;
Question is self explanatory. I know it is possible to extend primitive data types such as string but is it possible to overwrite it?
This is a question that has been asked in an interview.
No, you cannot overwrite anything. EcmaScript defines the primitive types Undefined, Null, Boolean, Number, and String; these are internal and will be used regardless of what you are doing (for example overwriting the global String constructor). Type conversion and evaluation of literals does not rely on any public functions but uses only these internal types and the algorithms specified for them.
Of course, if someone does string coercion with String(myval) instead of ""+myval assigning to the global String variable will have an effect on that code. Any internal use would still point to the "old" function.
If you were talking about prototype objects for the primitive types (when used as objects), those are not overwritable as well. You may extend those objects, but as soon as you assign to e.g. Number.prototype you just have lost a reference to the actual, original number protype object. Example spec for The Number constructor:
The [prototype] of the newly constructed object is set to the original Number prototype object, the one that is the initial value of Number.prototype (15.7.3.1)
Yes (edit: almost). Open up a Javascript console (F12 if you're using Chrome) and type
String = function(){alert('bang!')};
You can overwrite (edit: almost) everything in Javascript — even the window global context! evil.js is a library that uses this trick to rewrite many native objects as possible.
Needless to say, this is extremely dangerous. I performed the String remapping code above, and since writing it down I've caused over 520 Javascript errors (and I've seen 'bang' alerted quite a few times). Native objects are used everywhere, and you shouldn't modify these in case 3rd party code relies on them in ways you don't know about. This is one of the reasons Prototype.js lost popularity — because its extension of native objects would often work against the expectations of other code.
Edit: Factually incorrect assertion that absolutely everything could be overwritten, as pointed out in Bergi's answer. Edits made inline.
You can extend prototypes of native types.
String.prototype.moo = function() {
console.log( 'Moo!' )
};
'Cow says'.moo();
>> "Moo!"
However you cannot directly overwrite constructors of built-in types unless you overwrite the reference to the entire object:
String = function() {
console.log( 'Custom function.' )
};
new String( 'Hello!' );
>> "Custom function."
>> String {} // now you've broken your website ;)
...but still:
'Wat?!'
>> "Wat?!" // you can still create strings by typing letters in quotes
So... the answer is "yes but no". You can mess with native types (Number, Date, String...) but you cannot re-define them entirely from scratch. They're a part of JS engine that you're using (most likely native C++ code) and this brings some limitations.
Possible like this but you must always succeed without side effects.Not good practice.
function Array() {
var obj = this;
var ind = 0;
var getNext = function(x) {
obj[ind++] setter = getNext;
if (x) alert("Data stolen from array: " + x.toString());
};
this[ind++] setter = getNext;
}
var a = ["private stuff"];
// alert("Data stolen from array: private stuff");
I saw these 2 basic ways of namespacing in JavaScript.
Using object:
var Namespace = { };
Namespace.Class1 = function() { ... };
Using function:
function Namespace() { };
Namespace.Class1 = function() { ... };
How do they differ? Thank you.
As others have pointed out, a function is an object so the two forms can be interchangeable. As a side note, jQuery utilizes the function-as-namespace approach in order to support invocation and namespacing (in case you're wondering who else does that sort of thing or why).
However with the function-as-namespace approach, there are reserved properties that should not be touched or are otherwise immutable:
function Namespace(){}
Namespace.name = "foo"; // does nothing, "name" is immutable
Namespace.length = 3; // does nothing, "length" is immutable
Namespace.caller = "me"; // does nothing, "caller" is immutable
Namespace.call = "1-800-555-5555" // prob not a good idea, because...
// some user of your library tries to invoke the
// standard "call()" method available on functions...
Namespace.call(this, arg); // Boom, TypeError
These properties do not intersect with Object so the object-as-namespace approach will not have these behaviours.
The first one declares a simple object while the second one declares a function. In JavaScript, functions are also objects, so there is almost no difference between the two except that in the second example you can call Namespace() as a function.
Well, if all you're doing us using that "Namespace" thing as a way to "contain" other names, then those two approaches are pretty much exactly the same. A function instance is just an object, after all.
Now, generally one would use a function like that if the function itself were to be used as a constructor, or as a "focal point" for a library (as is the case with jQuery).
They don't. Functions are "first class objects". All this means is that conceptually and internally they are stored and used in the same ways. Casablanca's point of one difference you can call it as a function is a good one though. You can also test for whether or not the class was defined through a function with the typeof operator.
typeof {}
returns "object"
typeof (function())
returns "function"
Here's a ugly bit of Javascript it would be nice to find a workaround.
Javascript has no classes, and that is a good thing. But it implements fallback between objects in a rather ugly way. The foundational construct should be to have one object that, when a property fails to be found, it falls back to another object.
So if we want a to fall back to b we would want to do something like:
a = {sun:1};
b = {dock:2};
a.__fallback__ = b;
then
a.dock == 2;
But, Javascript instead provides a new operator and prototypes. So we do the far less elegant:
function A(sun) {
this.sun = sun;
};
A.prototype.dock = 2;
a = new A(1);
a.dock == 2;
But aside from elegance, this is also strictly less powerful, because it means that anything created with A gets the same fallback object.
What I would like to do is liberate Javascript from this artificial limitation and have the ability to give any individual object any other individual object as its fallback. That way I could keep the current behavior when it makes sense, but use object-level inheritance when that makes sense.
My initial approach is to create a dummy constructor function:
function setFallback(from_obj, to_obj) {
from_obj.constructor = function () {};
from_obj.constructor.prototype = to_obj;
}
a = {sun:1};
b = {dock:2};
setFallback(a, b);
But unfortunately:
a.dock == undefined;
Any ideas why this doesn't work, or any solutions for an implementation of setFallback?
(I'm running on V8, via node.js, in case this is platform dependent)
Edit:
I've posted a partial solution to this below, that works in the case of V8, but isn't general. I'd still appreciate a more general solution.
You could just use Object.create. It's part of ES5 so it's already available natively in some browsers. I believe it does exactly what you want.
Okay, some more research and cross-platform checking and there's some more information (though not a general solution).
Some implementations have basically what I did for my __fallback__. It is called __proto__ and is about perfect:
a = {sun:1};
b = {dock:2};
a.__proto__ = b;
a.dock == 2;
It seems that, what happens in when a new object is constructed is roughly this:
a = new Constructor(...args...);
produces behavior roughly equivalent to:
object.constructor = constructor;
object.__proto__ = constructor.prototype;
constructor.call(this, ...args...);
So it is no wonder that coming along later and adjusting an object's constructor or constructor.prototype has no effect, because the __proto__ setting is already set.
Now for my v8 application, I can just use __proto__, but I understand it that this isn't exposed on the IE VM (I don't run windows, so I can't tell). So it is not a general solution to the problem.