Absolute cleanest way to assign sub-methods to a method in JS? - javascript

I'm creating a JS library object that has a primary function, but I also want to assign sub-methods to that primary function to extend it.
So right now what I have looks like this:
Parser.prototype = {
init: function(){
},
primaryFunction:function(){
}
}
I really like this notation for assigning methods to Parser.prototype, but how can I use similar notation for assigning methods to primaryFunction? Also, the submethods are characterized by a string. Because eventually, my goal is to be able call like this: primaryFunction["*"]();
It seems I can't do this:
primaryFunction:function(char){
"*":function(){
}
}
Sort of makes sense why, but am I forced to do this?
primaryFunction:function(char){
this["*"] = function(){};
}

There's a big difference between adding properties to Parser.prototype and adding properties to primaryFunction. When you add to Parser.prototype, you're adding properties to a non-function object that is used as the underlying prototype of any object (instance) created via the new Parser expression. But if you add properties to primaryFunction, you're adding those properties to the function object of primaryFunction, directly.
The result is that properties added to Parser.prototype become, in effect, methods of instances created via new Parser. Within calls to those "methods," provided they're made normally (instance.init() and similar), this will refer to the instance.
But properties added to primaryFunction are methods of the function primaryFunction, not instances created with it. If you call them in the normal way (instance.primaryFunction["*"]()), within the call this is primaryFunction (the function object), not instance.
If that's really want you want, then you simply assign them after the fact (you can't do it within the object initializer):
Parser.prototype.primaryFunction["*"] = function() { /* ... */ };
But there are relatively few use cases for doing that, not least because if you're using Parser as a constructor function (e.g., with new Parser), you probably want to do things with the instance created, and you won't have access to it in primaryFunction["*"] unless you do some funny stuff.
The funny stuff, FWIW, looks like this — but I'm not recommending it (nor recommending against it), just being complete:
/* ...inside the `Parser` function... */
this.primaryFunction["*"] = this.primaryFunction["*"].bind(this);
That creates a new function that, when called, will call primaryFunction but setting this to the instance created by new Parser, and saves the resulting function as an "own" property on the instance being created.
From your comment below:
So my use case is just a parsing object that performs different
actions based on what characters it reads. The Parser loops through a
string, and my goal is to call a different function based on what the
current character at the index is. There will only be a set of unique
functions for each special character that may be in the string. For
instance, those characters could be: *, /, \, <, >,
essentially non alphanumerics. For anything alphanumeric, I want a
default function to handle those. So essentially, in a loop I'm
getting the currentChar, and trying to call a sub-method based on
which char it is. So the loop is essentially doing:
this.handleChar[currentChar]().
The bind solution above would work for that. But I think I'd probably go another way: Have a private, shared map of handler functions that you call (either passing in the parser instance as an argument, or as this) with the characters:
var Parser = function() {
var handlers = {
"*": function(char) {
// handle *...
},
"/": function(char) {
// handle /...
},
// ...and so on...
"default": function(char) {
// default handling...
}
};
function Parser() {
// ...
}
// (I always advocate *augmenting*, not replacing, `FuncName.prototype` properties)
Parser.prototype.handleChar = function() {
var currentChar;
while (!!(currentChar = /*...get the character...*/)) {
(handlers[currentChar] || handlers.default).call(this, currentChar);
// On some older JavaScript engines, you'd have to write the above like this:
//
// (handlers[currentChar] || handlers["default"]).call(this, currentChar);
//
// ...because ES3 didn't let you use a keyword as a property name literal
// (ES5 does, and some engines always did).
}
};
return Parser;
}();
That example passes the parser instance as this by using Function#call (the first argument is what ends up being this in the function call). It also uses JavaScript's curiously-powerful || operator to pick the function on handlers to call.
(If you don't care that handlers be private, then you don't need the wrapper.)

Related

Static method instead of Prototype method Javascript

When instance function are called in a class each instance of object get own copy of function but in prototype method and static method no copy is created , they belong to class, so if they both doesn't create a copy of their function, so why do we have static function if we can simply use prototype method if we don't want to copy??
I am a little bit confuse, if anyone can explain it will be a great help
In order to use a prototype/instance method, you need to either have an instance of an object or specifically access the type's .prototype. For methods that don't require an instance, a static method provides simpler syntax. Think of the String.fromCharCode() method as an example. It wouldn't make sense to say:
let str = "dummy string".fromCharCode(127);
The extra string instance there is just a distraction from what you're really trying to do:
let str = String.fromCharCode(127);
This applies good programming practices of reduced coupling (not requiring an instance in order to invoke a method that doesn't need it) and information hiding (by not exposing a method on instances of objects which doesn't pertain to those specific objects).
A static method does not exist on instances. Prototype methods do. So, if you want to call someArr.filter(x => x > 5) that would be an instance method that works on the given array.
An example of astatic method is Array.isArray(someArr). It makes very little sense to make the static method an instance method because you'd need an instance before calling it. That would lead to code like someArr.isArray(someArr) which is illogical - you need an array to check if something is an array. And that can very easily be fail spectacularly if someArr is not in fact an array:
const someArr = {
isArray() { return true; },
filter() { return "I am not an array"; },
};
console.log(someArr.isArray(someArr));
console.log(someArr.filter(x => x > 5));
Yes, that example is indeed highly illogical in order to highlight why it is weird. Assuming .isArray() was an instance method, you could create a new array in order to use it to call [].isArray(someArr). But that method does not require any instance data. The object created exists only to give you access to the method and is discarded immediately afterwards. That design is still not sensible.
Both static methods and prototype methods exist independent from any instances. The difference is that a prototype method expects to be called on an instance, i.e. to have an instance passed as the this argument, whereas the static method does not require an instance and expects none.
Even without placing them anywhere on the class, we can see this distinction in the following example:
function myStaticMethod(arg) {
console.log('Doing something with '+arg);
}
function myMethod(arg) {
console.log('Doing something with '+this+' and '+arg);
}
const myInstance = new MyClass();
myStaticMethod('value');
myMethod.call(myInstance, 'value');
Now the .call() syntax is not very ergonomic, we prefer myInstance.myMethod('value'), so that is why we place the method on the prototype object of the class, having it inherited by all instances.
For static methods, this is not necessary. They don't need an instance, we don't want to call them on an instance, we want to call them as MyClass.myStaticMethod('value') so that is where we place them. We could put them on the prototype as well, but that would lead to confusion (myInstance.myStaticMethod()), name collisions, and unncessarily long invocations (MyClass.prototype.myStaticMethod()). It's imaginable to write new MyClass().myStaticMethod(), but there you would unnecessarily create an instance that is not required (and it might not even be possible to create).

Assign DOM element to object literal member

I'm learning how one can achieve OOP patterns in JavaScript. I'd like to know, which one is a correct way to assign a DOM element to a object literal member in pure JavaScript and what are the differences between those examples.
I am doing this so that I could reuse that DOM element in object literal functions and if I change any id, name or class names, I only have to update in one place.
MyObject = {
// Version 1
member: document.getElementByName('elementName'),
// Version 2
member2: function() {
return document.getElementByName('elementName');
},
// Version 3
member3: function() {
document.getElementByName('elementName');
}
};
MyObject2 = {
// Is this member in a different namespace
member: document.getElementByName('element2Name'),
};
// Is this member in a different namespace
member: document.getElementByName('element2Name'),
MyObject2.member and MyObject1.member are different. Javascript doesn't natively support namespace like in other languages but the same can be achieved using the Object literals.
Regarding the 3 different versions for the member assignment, the deciding factor is what kind of access do you need for your variable and how you want to consume that.
// Version 1
member: document.getElementsByName('elementName'),
This assigns the member property the result of the method getElementsByName, The result is a NodeList Collection. To access the property member you need to write it like MyObject.member.
// Version 2
member2: function() {
return document.getElementByName('elementName');
},
The member2 is a function, whereas member wasn't a function and hence how the invocation is done is different. In this case you can get the same result as the version1 by calling MyObject.member2().
// Version 3
member3: function() {
document.getElementByName('elementName');
}
Version 3 doesn't return anything and is useless if you need to consume the result. Basically, this version never stores the result of the function call document.getElementByName('elementName'); and hence the return value is undefined. Like member2, member3 is also a function. But, invoking the function MyObject.member3() returns undefined.
Which version to choose
version 3 is useless as it doesn't return anything.
I do not have enough information of your application to suggest you best match. Based on limited information available, I would prefer version 2 because of the following reasons
Since the value of the result which is defined by document.getElementByName('elementName'); changes and is dependent on DOM. So, i will go ahead with the member2 or version2. i generally prefer function whenever it's not simple and involve some computation. Also, the result of the function call gives the caller information that the result of function call can change. Properties are good when you can define simple data attribute.
Note: A property's value can be a function, in which case the property is known as a method.

Make a javascript "function object" inherit from another

In Javascript, it's possible to have some kind of inheritance, by using function contructors and their "prototype" attribute (i.e the "parent of future instances"), or more recently Object.create(), to get new objects.
However recently I needed to "override" the Jquery object ("$") for one of my projects, that is to say, provide a similar function, which also has all the fn/extend/... [inherited] attributes of the original "jQuery" factory function.
The goal was to have a "$()" function which always received "window.parent" as the second parameter by default, so that the whole program modified the parent frame and not the iframe in which the program was loaded.
Typically I expected to be able to do something like this:
var oldJQ = $;
$ = function (selector) {
return oldJQ(selector, window.parent);
}
$.__proto__ = oldJQ .__proto__;
$.extend({...}); // must work
Despite numerous attempts, I couldn't get it to work, I always got simple objects instead of real functions when using the "new" keyword, or the inheritance didn't work. I found alternative ways (for example, by copy-pasting all attributes from $ into my new function object), but it seems to me like a horrible way of doing stuffs.
Is there any way to have a new function object which has, as a parent/prototype, the original $ object ?
While functions are objects in javascript, they aren't regular objects. Specifically, they're objects for which inheritance is broken. Functions are not the only such objects in the language, DOM elements are similarly handicapped. That's one reason jQuery is implemented as wrapper around DOM rather than extending the DOM's features via inheritance.
Fortunately, while OOP doesn't work in this case, we can take inspiration from jQuery itself and use FP to do what we need. In other words, while you can't inherit functions, you can wrap it in another function.
For example, if you need a special console.log function for your app that sends logs back to your server you can override it by redefining it:
var log_orig = console.log;
console.log = function () {
ajaxlog([].slice.call(arguments,0).map(function(x){
return JSON.parse(x);
}).join(',');
log_orig.apply(console,arguments);
}
Or if you want a special version of $ that does something extra:
function $$ () {
/*
* do extra stuff
*/
return $.apply(null,arguments);
}
Or if you want to override $ instead if wrapping:
var old$ = $;
function $ () {
/*
* do extra stuff
*/
return old$.apply(null,arguments);
}

Extending core types without modifying prototype

How does one extend core JavaScript types (String, Date, etc.) without modifying their prototypes? For example, suppose I wanted to make a derived string class with some convenience methods:
function MyString() { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
return this.split('').reverse().join('');
};
var s = new MyString("Foobar"); // Hmm, where do we use the argument?
s.reverse();
// Chrome - TypeError: String.prototype.toString is not generic
// Firefox - TypeError: String.prototype.toString called on incompatible Object
The error seems to originate from String base methods, probably "split" in this case, since its methods are being applied to some non-string object. But if we can't apply the to non-string objects then can we really reuse them automatically?
[Edit]
Obviously my attempt is flawed in many ways but I think it demonstrates my intent. After some thinking, it seems that we can't reuse any of the String prototype object's functions without explicitly calling them on a String.
Is it possible to extend core types as such?
2 years later: mutating anything in global scope is a terrible idea
Original:
There being something "wrong" with extending native prototypes is FUD in ES5 browsers.
Object.defineProperty(String.prototype, "my_method", {
value: function _my_method() { ... },
configurable: true,
enumerable: false,
writeable: true
});
However if you have to support ES3 browsers then there are problems with people using for ... in loops on strings.
My opinion is that you can change native prototypes and should stop using any poorly written code that breaks
Update: Even this code does not fully extend the native String type (the length property does not work).
Imo it's probably not worth it to follow this approach. There are too many things to consider and you have to invest too much time to ensure that it fully works (if it does at all). #Raynos provides another interesting approach.
Nevertheless here is the idea:
It seems that you cannot call String.prototype.toString on anything else than a real string. You could override this method:
// constructor
function MyString(s) {
String.call(this, s); // call the "parent" constructor
this.s_ = s;
}
// create a new empty prototype to *not* override the original one
tmp = function(){};
tmp.prototype = String.prototype;
MyString.prototype = new tmp();
MyString.prototype.constructor = MyString;
// new method
MyString.prototype.reverse = function() {
return this.split('').reverse().join('');
};
// override
MyString.prototype.toString = function() {
return this.s_;
};
MyString.prototype.valueOf = function() {
return this.s_;
};
var s = new MyString("Foobar");
alert(s.reverse());
As you see, I also had to override valueOf to make it work.
But: I don't know whether these are the only methods you have to override and for other built-in types you might have to override other methods. A good start would be to take the ECMAScript specification and have a look at the specification of the methods.
E.g. the second step in the String.prototype.split algorithm is:
Let S be the result of calling ToString, giving it the this value as its argument.
If an object is passed to ToString, then it basically calls the toString method of this object. And that is why it works when we override toString.
Update: What does not work is s.length. So although you might be able to make the methods work, other properties seem to be more tricky.
First of all, in this code:
MyString.prototype = String.prototype;
MyString.prototype.reverse = function() {
this.split('').reverse().join('');
};
the variables MyString.prototype and String.prototype are both referencing the same object! Assigning to one is assigning to the other. When you dropped a reverse method into MyString.prototype you were also writing it to String.prototype. So try this:
MyString.prototype = String.prototype;
MyString.prototype.charAt = function () {alert("Haha");}
var s = new MyString();
s.charAt(4);
"dog".charAt(3);
The last two lines both alert because their prototypes are the same object. You really did extend String.prototype.
Now about your error. You called reverse on your MyString object. Where is this method defined? In the prototype, which is the same as String.prototype. You overwrote reverse. What is the first thing it does? It calls split on the target object. Now the thing is, in order for String.prototype.split to work it has to call String.prototype.toString. For example:
var s = new MyString();
if (s.split("")) {alert("Hi");}
This code generates an error:
TypeError: String.prototype.toString is not generic
What this means is that String.prototype.toString uses the internal representation of a string to do its thing (namely returning its internal primitive string), and cannot be applied to arbitrary target objects that share the string prototype. So when you called split, the implementation of split said "oh my target is not a string, let me call toString," but then toString said "my target is not a string and I'm not generic" so it threw the TypeError.
If you want to learn more about generics in JavaScript, you can see this MDN section on Array and String generics.
As for getting this to work without the error, see Alxandr's answer.
As for extending the exact built-in types like String and Date and so on without changing their prototypes, you really don't, without creating wrappers or delegates or subclasses. But then this won't allow the syntax like
d1.itervalTo(d2)
where d1 and d2 are instances of the built-in Date class whose prototype you did not extend. :-) JavaScript uses prototype chains for this kind of method call syntax. It just does. Excellent question though... but is this what you had in mind?
You got only one part wrong here. MyString.prototype shouldn't be String.prototype, it should be like this:
function MyString(s) { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
this.split('').reverse().join('');
};
var s = new MyString("Foobar");
s.reverse();
[Edit]
To answer your question in a better way, no it should not be possible.
If you take a look at this: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor it explains that you can't change the type on bools, ints and strings, thus they cannot be "subclassed".
I think the basic answer is you probably can't. What you can do is what Sugar.js does - create an object-like object and extend from that:
http://sugarjs.com/
Sugar.js is all about native object extensions, and they do not extend Object.prototype.

Argument question javascript

One more question regarding arguments in javascript. I was reading a tutorial about the javascript framework Prototype and came over the following text, I will quote below:
"If you have some DOM methods of your own that you'd like to add to those of Prototype, no problem! Prototype provides a mechanism for this, too. Suppose you have a bunch of functions encapsulated in an object, just pass the object over to Element.addMethods():
var MyUtils = {
truncate: function(element, length){
element = $(element);
return element.update(element.innerHTML.truncate(length));
},
updateAndMark: function(element, html){
return $(element).update(html).addClassName('updated');
}
}
Element.addMethods(MyUtils);
// now you can:
$('explanation').truncate(100);
The only thing to watch out here is to make sure the first argument of these methods is the element itself. In your methods, you can also return the element in the end to allow for chainability (or, as practiced in the example, any method which itself returns the element)."
Everything seems pretty straightforward except one thing that I seem to have problem understanding. It says that you need to make sure the first argument of these methods is the element itself. And that is the case in the functions but when you call the function you only provide 1 argument? So for example:
$('explanation').truncate(100);
There is only 1 argument here that is sent to the function namely 100, but the function signature looks like this:
truncate: function(element, length){
So element comes from where? And is it logic that you must make sure that the first argument is the element itself but you do not provide it when you call the function?
Prototype provides two ways to do things:
Methods on the object itself, for example:
Element.toggleClassName('selector', 'something');
Via the prototype:
$('selector').toggleClassName('something');
IIRC, the second method simply calls the first, passing the Element selected to it as well as the original argument. That's what happens in your case as well.
Javascript functions can test their arguments and decide what to do based on what was passed. jQuery is famous for this and it's fairly common in a lot of libraries. It allows one named function to perform many different (but related) operations and often allows shorthand notation that can omit optional or default parameters. You can often pass one, two or three arguments and the code inside the function will examine which arguments are present and, in some cases, the type of the arguments to decide how to treat them.
When functions do this, you have to read their documentation carefully to see which parameters are required and which are optional and what order things can be passed in.
If a function optionally takes three parameters and all three are different types, it's possible for the function to examine the type of each passed parameter and accept them in any order. It can tell which is which by the type. I don't personally think this is good programming style, but it is possible. More likely, it allows you to just leave out some parameters which are not needed and the function will either adapt it's behavior when some things are missing or will set a default for that value.
In your specific example of truncate, I'd have to see a documentation page for that function to explain what it does when no element is passed vs. when one is.
Here's a made up example of a string truncate function that can be used a bunch of different ways:
var str = "This is a long string that I want to be truncated to something shorter";
str.truncate(10); // truncate to max length of 10
str.truncate("string", "end"); // truncate after the first occurrence of "string"
str.truncate(/\bbe\b/, "begin"); // truncate before the first occurrence of a regular expression
str.truncate("string", 10); // truncate 10 chars after the first occurrence of "string"
And the outline of this function would look something like this:
string.prototype.truncate = function(lookFor, pos, cnt) {
if (lookFor == undefined) {
return(this); // no parameters passed, nothing to do
}
if (typeof lookFor == "Number") {
// truncate to a certain length
} else if (typeof lookFor == "String") {
// look for a plain string
} else if (lookFor.exec) {
// look for a regular expression
} else {
return(this); // nothing to do
}
}

Categories